根据微信官网的介绍,微信支付共有6种方式,本文介绍是APP支付。APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。
支付流程
1、用户在APP内选中商品或服务,点击支付。
2、APP将支付订单信息发送给APP后台进行处理。
3、APP后台将签名后的订单信息返回。
4、APP内调用微信支付接口发送支付请求。
5、请求成功,则唤起微信支付界面;如果失败,则在回调函数内进行错误信息处理和提示。
6、微信支付系统将支付结果发送APP后台。
7、APP查询后台,获取支付结果。
下面是官网给出的详细流程图:
运行官网APP Demo
下载官网示例,压缩包是SDKSample_Android_v3_pay.zip。由于版本的问题,官网给的deomo并不能直接运行,需要很多修改。跑起来界面如下,本文只研究了微信支付。
1、解压后发现是Eclipse工程,将其导入到AndroidStudio中,这时会默认复制一份新文件夹。需要手动将debug.keystore
文件拷贝到新项目文件夹中。
2、导入后修改两个build.gradle如下:
buildscript {
repositories {
google()
jcenter()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'//替换你自己的as版本号
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "net.sourceforge.simcpux"
minSdkVersion 16
targetSdkVersion 28
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
signingConfigs {
debug {
storeFile file("../debug.keystore")
}
}
}
repositories {
flatDir {
dirs 'libs' //this way we can find the .aar file in libs folder
}
}
dependencies {
api 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:5.3.1'//新版本的api使用方式,不需要再手动引入libammsdk.jar和wechat-sdk-android-with-mta-1.0.2.jar两个文件
api 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'junit:junit:4.12'
}
android {
useLibrary 'org.apache.http.legacy'
}
3、将所有命名空间com.tencent.mm.sdk
改为com.tencent.mm.opensdk
4、编译一下。如果出现下面提示:
failed linking file resources
generated id ‘android:id/*******’ for external package ‘android’.
有两个办法:
1)定位到错误文件位置:将android:id="@+android:id/title"
改为android:id="@android:id/title"
2)忽略这些错误。因为如果没有其他错误,这些错误最后也会消失(是不是有点绕?)
5、WXImageObject的属性imgUrl
替换为imagePath
(这个错误跟微信支付无关,根据错误提示修改就好了)
6、还有最关键一步要改.demo里的支付代码主要在PayActivity的appayBtn里,这里模拟请求了一个后台订单信息,用到了HttpClient类,但是as开发要求所有耗时操作必须放到异步里执行。因此需要对原代码进行改造:
@Override
public void onClick(View v) {
String url = "https://wxpay.wxutil.com/pub_v2/app/app_pay.php";
Button payBtn = (Button) findViewById(R.id.appay_btn);
payBtn.setEnabled(false);
Toast.makeText(PayActivity.this, "获取订单中...", Toast.LENGTH_SHORT).show();
@SuppressLint("StaticFieldLeak") AsyncTask<String, Integer, Void> atask = new AsyncTask<String, Integer, Void>() {
@Override
protected Void doInBackground(String... strings) {
//将请求操作放到异步中执行
try{
byte[] buf = Util.httpGet(strings[0]);
if (buf != null && buf.length > 0) {
String content = new String(buf);
Log.e("get server pay params:",content);
JSONObject json = new JSONObject(content);
if(null != json && !json.has("retcode") ){
PayReq req = new PayReq();
//req.appId = "wxf8b4f85f3a794e77"; // 测试用appId
req.appId = json.getString("appid");
req.partnerId = json.getString("partnerid");
req.prepayId = json.getString("prepayid");
req.nonceStr = json.getString("noncestr");
req.timeStamp = json.getString("timestamp");
req.packageValue = json.getString("package");
req.sign = json.getString("sign");
req.extData = "app data"; // optional
//Toast.makeText(PayActivity.this, "正常调起支付", Toast.LENGTH_SHORT).show();
System.out.println("正常调起支付...");
System.out.println("检查支付参数checkArgs:" +req.checkArgs() );
// 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
api.sendReq(req);
}else{
Log.d("PAY_GET", "返回错误"+json.getString("retmsg"));
//Toast.makeText(PayActivity.this, "返回错误"+json.getString("retmsg"), Toast.LENGTH_SHORT).show();
}
}else{
Log.d("PAY_GET", "服务器请求错误");
//Toast.makeText(PayActivity.this, "服务器请求错误", Toast.LENGTH_SHORT).show();
}
}catch(Exception e){
Log.e("PAY_GET", "异常:"+e.getMessage());
Toast.makeText(PayActivity.this, "异常:"+e.getMessage(), Toast.LENGTH_SHORT).show();
}
return null;
}
};
atask.execute(url);
payBtn.setEnabled(true);
}
7、运行后,点击支付,返回结果如下。(注意:这里返回结果-1,是因为appId没有正确设置。但是如果是在一部手机上第一次运行demo且第一次点击微信支付,是能成功一次的,再点击就一直返回 -1 了)。
ps:正在申请自己的appid,然后跑自己的demo试试。
app后台都干了啥
APP后台主要做了两件事:
- 从微信支付系统那获取预付单信息。
- 接收并保存支付结果。
首先看第一件事:
1、APP后台生成支付订单。
2、调用统一下单API
(接口详请) 。这里有两个安全控制参数:随机字符串nonce_str和签名sign。nonce_str是由随机数算法生成的不长于32位的随机字符串,用于防范重放攻击;sign是由签名生成算法生成的签名信息,防止信息窃取篡改。
3、获取生成的预付单。如果下单API调用成功,则能获取到prepay_id
。这是微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时
4、将参数再次签名传输给APP发起支付。注意:这里sign的签名方式一定要与统一下单接口使用的一致。
再看第二件事:
支付完成后,App 后台需要接收处理支付通知。注意:
1、要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失),并按文档规范返回应答。
2、当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
微信提供的其它API
- 查询订单:该接口提供所有微信支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。
- 关闭订单:以下情况需要调用关单接口:商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
- 申请退款 :当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
- 查询退款:提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
- 下载对账单 :商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致,通过对账单核对后可校正支付状态。
- 下载资金账单 :商户可以通过该接口下载自2017年6月1日起 的历史资金流水账单。
- 退款结果通知:当商户申请的退款有结果后,微信会把相关结果发送给商户,商户需要接收处理,并返回应答。