94、支付集成:支付宝支付、微信支付、银联支付、服务端签名
支付集成,说白了就是让用户能在你的App里花钱。嗯,这件事做得好,用户觉得顺滑;做得不好,用户可能直接卸载App。我这些年接过的支付方案,少说也有几十个了,踩过的坑能写满一本小册子。
今天咱们就聊聊支付宝、微信、银联这三家主流支付,以及那个绕不开的——服务端签名。
支付流程的核心逻辑
不管哪家支付,流程都差不多。我画了张图,你看一眼就明白了:
流程其实就这几步:客户端请求下单 → 服务端去支付平台拿参数 → 返回签名 → 客户端调起支付 → 结果回调。嗯,这里有个关键点——签名必须在服务端做。为什么?往下看。
服务端签名:为什么不能放在客户端
我见过不少新手直接把App Key和Secret写在客户端代码里。结果呢?被反编译后,别人拿着你的Key去刷单,一夜之间亏了几十万。这不是段子,是我真实见过的案例。
服务端签名的核心原因就两个:
- 安全:密钥放在服务端,客户端拿不到,无法被逆向
- 可控:服务端可以做金额校验、订单校验,防止篡改
支付宝支付集成
支付宝的集成相对简单。我个人习惯用「App支付」方案,也就是客户端调起支付宝App进行支付。
服务端返回的支付参数是一个签名字符串,客户端拿到后直接调起:
// 支付宝支付 - 客户端代码
public void startAlipay(String orderInfo) {
final AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
APP_ID,
APP_PRIVATE_KEY,
"json",
"UTF-8",
ALIPAY_PUBLIC_KEY,
"RSA2"
);
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setBizContent("{" +
"\"out_trade_no\":\"20231001001\"," +
"\"total_amount\":\"0.01\"," +
"\"subject\":\"测试商品\"," +
"\"product_code\":\"QUICK_MSECURITY_PAY\"" +
"}");
// 这里返回的是签名字符串
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
String orderString = response.getBody();
// 调起支付宝
final PayTask alipay = new PayTask(activity);
alipay.payV2(orderString, true);
}
微信支付集成
微信支付和支付宝不太一样。微信需要先调起「预付订单」接口,拿到prepay_id,然后再调起支付。
我记得第一次接微信支付时,被那个「调起支付」的参数搞晕了——为什么要有sign?后来才明白,微信的签名是客户端自己算的,但参数是服务端给的。
// 微信支付 - 客户端代码
public void startWechatPay(WechatPayParams params) {
IWXAPI api = WXAPIFactory.createWXAPI(context, params.getAppId());
api.registerApp(params.getAppId());
PayReq request = new PayReq();
request.appId = params.getAppId();
request.partnerId = params.getPartnerId();
request.prepayId = params.getPrepayId();
request.nonceStr = params.getNonceStr();
request.timeStamp = params.getTimeStamp();
request.packageValue = "Sign=WXPay";
request.sign = params.getSign(); // 服务端生成的签名
api.sendReq(request);
}
银联支付集成
银联支付在国内用得相对少一些,但在某些行业(比如金融、政务)是刚需。银联的集成方式比较传统,用的是「控件模式」。
银联的流程是这样的:
- 服务端调用银联接口获取tn(交易流水号)
- 客户端拿到tn后调起银联支付控件
- 用户在控件内完成支付
- 银联回调结果给客户端
// 银联支付 - 客户端代码
public void startUnionPay(String tn, String mode) {
// mode: "00" 正式环境, "01" 测试环境
UPPayAssistEx.startPayByJAR(context,
new PayActivity(),
null,
null,
tn,
mode);
}
// 在Activity中处理结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data == null) return;
String msg = "";
String str = data.getExtras().getString("pay_result");
if (str.equalsIgnoreCase("success")) {
msg = "支付成功";
} else if (str.equalsIgnoreCase("fail")) {
msg = "支付失败";
} else if (str.equalsIgnoreCase("cancel")) {
msg = "用户取消";
}
}
支付结果处理:同步 vs 异步
支付结果的处理,是很多新手容易搞混的地方。我简单说一下:
| 方式 | 说明 | 可靠性 |
|---|---|---|
| 客户端同步回调 | 支付完成后SDK直接返回结果 | 低(可被伪造) |
| 服务端异步通知 | 支付平台主动通知服务端 | 高(带签名验证) |
我的建议是:永远以服务端异步通知为准。 客户端回调只能用来刷新UI,不能作为订单完成的依据。为什么?因为客户端回调可以被Hook、被篡改。你想想看,如果有人写了个Xposed模块,把支付失败改成支付成功,你的App不就白给了吗?
避坑指南
这些年我踩过的坑,整理一下分享给你:
- 签名算法不一致:支付宝用RSA2,微信用MD5/HMAC-SHA256,银联用SHA1withRSA。别搞混了。
- 金额单位:支付宝和微信都是「元」,银联是「分」。我曾经因为单位问题,让用户付了100倍的金额……还好及时发现。
- 订单号唯一性:每个订单号只能支付一次。如果重复使用,支付平台会直接返回失败。
- 网络超时:支付过程中如果网络断了,客户端可能收不到回调。这时候需要主动去服务端查询订单状态。
总结
支付集成说白了就是三件事:服务端签名、客户端调起、结果验证。签名必须在服务端做,结果必须以服务端异步通知为准。这三家支付虽然接口不同,但核心逻辑是一样的。
嗯,最后说一句:支付相关的代码,一定要做充分的异常处理。用户的钱不是小事,咱们做开发的,得对得起这份信任。
公众号:蓝海资料掘金营,微信deep3321