微信/支付宝:两大巨头的对接差异

同样的"扫码支付",背后却是两套完全不同的世界观

写在前面

做过支付对接的同学都知道,微信和支付宝虽然都是"扫一扫就付钱",但当你真正开始对接时,会发现这两个平台的设计理念差异巨大。

就像同样是"盖房子",一个是精装修交付(微信),一个是毛坯房自己装(支付宝)。各有优劣,也各有坑点。

今天我们来聊聊这两大支付平台的核心差异,帮你在对接时少踩几个坑 🐛


一、接入流程:两种风格的开局体验

微信支付:严格的"入职流程"

微信支付的接入,就像大厂入职——审核严格、流程规范、资料要全。

审核周期通常 1-5 个工作日,期间还可能被打回补充材料。常见被拒原因:

支付宝:灵活的"快车道"

支付宝的接入相对灵活,支持多种商户类型。

  1. 即时到账 - T+0 结算,适合高频小额
  2. 担保交易 - 有确认收货流程,适合电商
  3. 双功能支付 - 用户可选即时到账或担保
维度 微信支付 支付宝
审核严格度 高,资料要求齐全 中等,根据场景调整
审核周期 1-5 个工作日 1-3 个工作日
个人接入 不支持 支持(功能受限)
资质要求 营业执照必须 部分场景可灵活
开通速度 慢但规范 快但需自己把握

[配图建议:对比图 - 左边是微信的"严格审核"流程图,右边是支付宝的"灵活接入"流程图]


二、文档风格:说明书 vs 教科书

微信文档:精简的说明书

微信的文档像产品说明书——够用,但不多。

// 微信的错误码说明
错误码:SYSTEMERROR
描述:系统错误
解决方案:请使用相同参数稍后重新调用

// 程序员:...所以具体是什么错误?

支付宝文档:详尽的教科书

支付宝的文档更像教科书——全面、系统、有理论深度。

// 支付宝的错误码说明
错误码:ACQ.TRADE_NOT_EXIST
描述:交易不存在
解决方案:
1. 检查商户订单号是否正确
2. 确认该订单是否已经发起支付
3. 如果使用外部商户号,请确认映射关系
4. 联系技术支持提供完整的请求参数和响应
维度 微信支付 支付宝
文档完整度 中等
代码示例 基础 丰富
错误码说明 简略 详细
搜索体验 一般 较好
最佳实践
更新及时性 较好 一般

三、微信支付的"四大金刚"

微信支付提供了多种支付方式,每种都有特定的使用场景:

JSAPI 支付:公众号里的"原住民"

  1. 用户在微信内打开 H5 页面
  2. 通过 OAuth2.0 获取用户的 openid
  3. 调用微信 JS-SDK 的 chooseWXPay 接口
  4. 唤起微信支付界面
  5. 用户完成支付后,回到原页面
// 前端调用微信支付
function onBridgeReady() {
  WeixinJSBridge.invoke('getBrandWCPayRequest', {
    "appId": "wx2421b700c0",     // 公众号名称
    "timeStamp": "1395712654",    // 时间戳
    "nonceStr": "e61463f8efa",    // 随机串
    "package": "prepay_id=wx212", // 预支付交易会话标识
    "signType": "MD5",            // 签名方式
    "paySign": "70EA570631E4"     // 签名
  }, function(res) {
    if (res.err_msg == "get_brand_wcpay_request:ok") {
      // 支付成功
    }
  });
}

H5 支付:跳出微信的"自由人"

  1. 商户后台调用统一下单接口,获取支付链接
  2. 前端跳转到微信提供的中间页
  3. 中间页唤起微信 App
  4. 用户在微信内完成支付
  5. 支付完成后跳回商户页面(需设置 referer)
{
  "type": "WAP",
  "wap_url": "https://www.example.com",  // WAP网站URL
  "wap_name": "某某游戏"                   // WAP网站名称
}

小程序支付:生态内的"特权阶级"

  1. 小程序调用 wx.login() 获取 code
  2. 后端用 code 换取 openid 和 session_key
  3. 后端调用统一下单接口,获取 prepay_id
  4. 前端调用 wx.requestPayment() 唤起支付
// 小程序端
wx.requestPayment({
  timeStamp: '',     // 时间戳
  nonceStr: '',      // 随机字符串
  package: '',       // 统一下单接口返回的 prepay_id 参数值
  signType: 'MD5',   // 签名算法
  paySign: '',       // 签名
  success(res) {
    // 支付成功
  },
  fail(res) {
    // 支付失败
  }
})

APP 支付:独立的"正规军"

  1. 后端调用统一下单接口,获取预支付交易会话标识
  2. App 调用微信 SDK,传入参数
  3. SDK 唤起微信 App
  4. 用户完成支付后,返回商户 App
// iOS 调用微信支付
PayReq *request = [[[PayReq alloc] init] autorelease];
request.partnerId = @"10000100";
request.prepayId = @"wx2016051";
request.nonceStr = @"a32ef42";
request.timeStamp = 1493852427;
request.package = @"Sign=WXPay";
request.sign = @"C380BEC2BFD727A4B6845133519F3AD6";

[WXApi sendReq:request];
// Android 调用微信支付
IWXAPI api = WXAPIFactory.createWXAPI(this, null);
api.registerApp(Constants.APP_ID);

PayReq request = new PayReq();
request.appId = Constants.APP_ID;
request.partnerId = "10000100";
request.prepayId = "wx2016051";
request.nonceStr = "a32ef42";
request.timeStamp = "1493852427";
request.packageValue = "Sign=WXPay";
request.sign = "C380BEC2BFD727A4B6845133519F3AD6";

api.sendReq(request);
支付方式 使用场景 openid要求 跳转 体验评分
JSAPI 微信内H5 必须 ⭐⭐⭐⭐⭐
H5 微信外 不需要 ⭐⭐⭐
小程序 小程序内 必须 ⭐⭐⭐⭐⭐
APP 独立App 不需要 ⭐⭐⭐⭐

[配图建议:四种支付方式的场景对比图,标注各自的使用环境]


四、支付宝的"三剑客"

支付宝的支付方式也有自己的特色:

手机网站支付:Web 时代的经典

  1. 商户后端调用 alipay.trade.wap.pay 接口
  2. 获取支付表单(HTML form)
  3. 前端自动提交表单,跳转到支付宝
  4. 用户完成支付后,跳回商户页面
// Java 示例
AlipayClient alipayClient = new DefaultAlipayClient(
  "https://openapi.alipay.com/gateway.do",
  APP_ID,
  APP_PRIVATE_KEY,
  "json",
  "UTF-8",
  ALIPAY_PUBLIC_KEY,
  "RSA2"
);

AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://www.example.com/return");
request.setNotifyUrl("https://www.example.com/notify");
request.setBizContent("{" +
  "\"out_trade_no\":\"20231001001\"," +
  "\"total_amount\":88.88," +
  "\"subject\":\"游戏充值\"," +
  "\"product_code\":\"QUICK_WAP_WAY\"" +
"}");

String form = alipayClient.pageExecute(request).getBody();
// 返回 form 给前端,前端自动提交

APP 支付:移动优先的设计

  1. 后端调用 alipay.trade.app.pay 接口
  2. 获取支付订单字符串
  3. App 调用支付宝 SDK,传入订单字符串
  4. SDK 唤起支付宝 App 或内嵌 H5
// iOS 调用支付宝支付
NSString *orderString = @"app_id=2021001&biz_content=...";
[[AlipaySDK defaultService] payOrder:orderString 
                      fromScheme:@"yourappscheme" 
                        callback:^(NSDictionary *resultDict) {
    // 处理支付结果
}];
// Android 调用支付宝支付
String orderString = "app_id=2021001&biz_content=...";
PayTask alipay = new PayTask(MainActivity.this);
Map<String, String> result = alipay.payV2(orderString, true);
// 处理支付结果

当面付:线下场景的利器

  1. 付款码支付: 用户出示付款码,商家扫码收款
  2. 扫码支付: 商家生成二维码,用户扫码付款
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl("https://www.example.com/notify");
request.setBizContent("{" +
  "\"out_trade_no\":\"20231001001\"," +
  "\"total_amount\":88.88," +
  "\"subject\":\"游戏充值\"" +
"}");

AlipayTradePrecreateResponse response = alipayClient.execute(request);
String qrCode = response.getQrCode();
// 将 qrCode 生成二维码图片

生活号支付:生态内的"私域流量"

支付方式 使用场景 SDK要求 跳转 体验评分
手机网站支付 H5/PC 不需要 ⭐⭐⭐⭐
APP支付 独立App 需要 ⭐⭐⭐⭐
当面付 线下/PC 不需要 ⭐⭐⭐⭐
生活号支付 生活号内 不需要 ⭐⭐⭐⭐⭐

五、签名机制:MD5 vs RSA 的哲学差异

这是两个平台最核心的技术差异之一,也是新手最容易踩坑的地方。

微信:MD5/HMAC-SHA256 签名——简单直接

1. 将所有非空参数按字典序排序
2. 用 & 连接成 key=value 格式
3. 最后拼接 &key=商户密钥
4. 对整个字符串做 MD5 运算
5. 转换为大写
原始参数:
appid=wxd930ea5705
mch_id=10000100
device_info=1000
body=test
nonce_str=ibuaiVcKdpRxkhJA

步骤1:字典序排序后拼接
stringA = "appid=wxd930ea5705&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"

步骤2:拼接密钥
stringSignTemp = stringA + "&key=192006250b4c09247ec02e" // key 为商户密钥

步骤3:MD5 运算并转大写
sign = MD5(stringSignTemp).toUpperCase() = "9A0A8659F005D6984697E2CA0A9CF3B7"
function MakeSign($params, $key) {
    // 1. 按字典序排序
    ksort($params);
    
    // 2. 拼接字符串
    $string = '';
    foreach ($params as $k => $v) {
        if ($k != 'sign' && $v != '' && !is_array($v)) {
            $string .= $k . '=' . $v . '&';
        }
    }
    $string = trim($string, '&');
    
    // 3. 拼接密钥
    $string = $string . '&key=' . $key;
    
    // 4. MD5 运算
    $string = md5($string);
    
    // 5. 转大写
    $result = strtoupper($string);
    
    return $result;
}
// 步骤3 改为
$string = hash_hmac('sha256', $string, $key);

支付宝:RSA2 签名——非对称的艺术

1. 商户生成 RSA 密钥对(公钥+私钥)
2. 把公钥上传到支付宝
3. 支付宝生成支付宝公钥(不同于商户公钥)
4. 商户用私钥对请求参数签名
5. 支付宝用商户公钥验证签名
6. 支付宝用支付宝私钥对响应签名
7. 商户用支付宝公钥验证响应
# 使用 OpenSSL 生成 RSA 密钥对
openssl genrsa -out app_private_key.pem 2048
openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem

# 查看私钥
cat app_private_key.pem

# 查看公钥
cat app_public_key.pem
// 构造待签名字符串
String content = getSignContent(params);

// 使用私钥签名
PrivateKey privateKey = getPrivateKeyFromPem(privateKeyStr);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes("UTF-8"));
byte[] signBytes = signature.sign();

// Base64 编码
String sign = Base64.getEncoder().encodeToString(signBytes);
// 使用支付宝公钥验证签名
PublicKey publicKey = getPublicKeyFromPem(alipayPublicKeyStr);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes("UTF-8"));
boolean verified = signature.verify(Base64.getDecoder().decode(sign));

两种签名机制对比

维度 微信 MD5/HMAC-SHA256 支付宝 RSA2
加密类型 对称加密 非对称加密
密钥管理 双方共享密钥 公私钥分离
实现复杂度 简单 复杂
安全级别 中等
性能 较慢
密钥泄露风险 低(私钥不传输)
适用场景 内部系统、可信环境 开放平台、高安全要求
  1. 微信:密钥要严格保密,定期更换
  2. 支付宝:私钥绝对不能泄露,公钥可以公开
  3. 两者都需要验证回调签名,防止伪造请求
  4. 建议使用官方 SDK,避免自己实现签名算法

[配图建议:签名机制对比图,左边是 MD5 的对称加密流程,右边是 RSA 的非对称加密流程]


六、回调处理:通知的"性格差异"

支付完成后,平台会通知你的服务器。这块的设计也很有意思。

微信回调:话痨型通知

15s, 15s, 30s, 3m, 10m, 20m, 30m, 30m, 30m, 60m, 3h, 3h, 3h, 6h, 6h

总共通知 15 次,持续约 24 小时。

<xml>
  <appid>wx2421b700c0</appid>
  <attach>支付测试</attach>
  <bank_type>CFT</bank_type>
  <fee_type>CNY</fee_type>
  <is_subscribe>N</is_subscribe>
  <mch_id>10000100</mch_id>
  <nonce_str>5d2b6c2a8db53831f7eda20af46e531c</nonce_str>
  <openid>oUpF8uMEb4qRXf22hE3X68TekukE</openid>
  <out_trade_no>1409811653</out_trade_no>
  <result_code>SUCCESS</result_code>
  <return_code>SUCCESS</return_code>
  <sign>594B6D97F089D24D683F600E71E9</sign>
  <time_end>20140903131540</time_end>
  <total_fee>1</total_fee>
  <trade_type>JSAPI</trade_type>
  <transaction_id>1004400740201409030005092168</transaction_id>
</xml>
<!-- 成功响应 -->
<xml>
  <return_code>SUCCESS</return_code>
  <return_msg>OK</return_msg>
</xml>

<!-- 失败响应(会继续重试) -->
<xml>
  <return_code>FAIL</return_code>
  <return_msg>签名失败</return_msg>
</xml>

支付宝回调:稳重派通知

0s, 2m, 10m, 10m, 1h, 2h, 6h, 15h

最多通知 8 次,持续约 25 小时。

gmt_create=2023-10-01+12%3A00%3A00
&charset=UTF-8
&gmt_payment=2023-10-01+12%3A00%3A05
&notify_time=2023-10-01+12%3A00%3A10
&subject=游戏充值
&sign_type=RSA2
&buyer_id=2088101117955611
&out_trade_no=20231001001
&total_amount=88.88
&trade_status=TRADE_SUCCESS
&trade_no=202310012200140895...
&...
&sign=xxxxx
// 成功响应
success

// 失败响应(会继续重试)
failure

回调处理最佳实践

// 微信验签
String sign = params.get("sign");
params.remove("sign");
String calculatedSign = MD5Sign(params, apiKey);
if (!sign.equals(calculatedSign)) {
    log.error("微信回调签名验证失败");
    return "FAIL";
}

// 支付宝验签
boolean verified = AlipaySignature.rsaCheckV1(
    params, alipayPublicKey, charset, signType
);
if (!verified) {
    log.error("支付宝回调签名验证失败");
    return "failure";
}
// 使用数据库唯一索引或分布式锁
String orderId = params.get("out_trade_no");
String lockKey = "pay:notify:" + orderId;

boolean locked = redisLock.tryLock(lockKey, 30);
if (!locked) {
    log.info("订单正在处理中: {}", orderId);
    return "success"; // 返回成功,避免重复通知
}

try {
    // 检查订单状态
    Order order = orderDao.findByOrderId(orderId);
    if (order.getStatus() == PAID) {
        log.info("订单已处理: {}", orderId);
        return "success";
    }
    
    // 处理支付成功逻辑
    orderService.paySuccess(orderId, ...);
    
} finally {
    redisLock.unlock(lockKey);
}

return "success";
// 记录原始回调数据
PayNotifyLog log = new PayNotifyLog();
log.setOrderId(orderId);
log.setNotifyData(JSON.toJSONString(params));
log.setNotifyTime(new Date());
log.setChannel("wechat"); // 或 "alipay"
payNotifyLogDao.save(log);
维度 微信支付 支付宝
通知次数 15 次 8 次
持续时间 ~24 小时 ~25 小时
数据格式 XML 表单
成功响应 XML 格式 纯文本 "success"
重试策略 指数退避 相对固定间隔

七、API 详细对比

统一下单接口

参数 必填 说明
appid 公众号/小程序/AppID
mch_id 商户号
nonce_str 随机字符串
sign 签名
body 商品描述
out_trade_no 商户订单号
total_fee 金额(分)
spbill_create_ip 终端IP
notify_url 回调地址
trade_type JSAPI/H5/NATIVE/APP
openid 用户标识(JSAPI必填)
参数 必填 说明
app_id 应用ID
method 接口名称
charset 编码格式
sign_type 签名类型
sign 签名
timestamp 时间戳
version 版本
biz_content 业务参数(JSON)
{
  "out_trade_no": "20231001001",
  "total_amount": "88.88",
  "subject": "游戏充值",
  "product_code": "QUICK_WAP_WAY",
  "body": "游戏道具-钻石100"
}

订单查询接口

参数 必填 说明
appid 公众号ID
mch_id 商户号
nonce_str 随机字符串
sign 签名
out_trade_no 二选一 商户订单号
transaction_id 二选一 微信订单号
参数 必填 说明
app_id 应用ID
method alipay.trade.query
... ... 公共参数
biz_content {"out_trade_no":"xxx"}

退款接口

参数 必填 说明
appid 公众号ID
mch_id 商户号
nonce_str 随机字符串
sign 签名
out_trade_no 二选一 商户订单号
transaction_id 二选一 微信订单号
out_refund_no 商户退款单号
total_fee 原订单金额(分)
refund_fee 退款金额(分)
refund_desc 退款原因
参数 必填 说明
app_id 应用ID
method alipay.trade.refund
... ... 公共参数
biz_content 见下表
{
  "out_trade_no": "20231001001",
  "refund_amount": "88.88",
  "refund_reason": "用户申请退款",
  "out_request_no": "REF20231001001"
}

API 对比总结

维度 微信支付 支付宝
数据格式 XML JSON(新)/ 表单(旧)
签名算法 MD5/HMAC-SHA256 RSA2
证书要求 部分接口需要 不需要
沙箱环境
API 版本 V2/V3 无版本区分
错误码 数字 字符串

八、实际对接案例

案例1:小游戏充值

  1. 使用微信小程序支付
  2. 通过 wx.login() 获取 code,后端换取 openid
  3. 后端调用统一下单接口,获取 prepay_id
  4. 前端调用 wx.requestPayment() 完成支付
问题:调用 wx.login() 后,后端换取 openid 时报错
原因:小程序的 appid 和 secret 配置错误
解决:检查小程序后台配置,确保 appid 和 secret 正确
问题:前端调用支付时提示"签名错误"
原因:后端生成签名时参数名错误(appId 应该是 appId 不是 appid)
解决:严格按照文档参数名,注意大小写
问题:用户支付成功,但道具发放了两次
原因:回调处理没有做幂等
解决:在发放道具前检查订单状态,使用数据库唯一索引
// 小程序端
const pay = async (productId) => {
  // 1. 登录获取 code
  const { code } = await wx.login();
  
  // 2. 请求后端创建订单
  const res = await request({
    url: '/api/pay/create',
    method: 'POST',
    data: { code, productId }
  });
  
  // 3. 调用支付
  await wx.requestPayment({
    timeStamp: res.timeStamp,
    nonceStr: res.nonceStr,
    package: res.package,
    signType: res.signType,
    paySign: res.paySign
  });
  
  // 4. 支付成功
  wx.showToast({ title: '支付成功' });
};

案例2:独立游戏App

  1. 同时接入微信支付和支付宝
  2. 后端统一封装支付接口,屏蔽差异
  3. 根据用户选择调用对应支付方式
┌─────────────────────────────────────┐
│           游戏客户端                 │
│  ┌──────────┐    ┌──────────┐       │
│  │ 微信支付SDK │    │支付宝SDK │       │
│  └─────┬────┘    └────┬─────┘       │
└────────┼──────────────┼─────────────┘
         │              │
         ▼              ▼
┌─────────────────────────────────────┐
│         统一支付网关                 │
│  ┌─────────────────────────────┐    │
│  │ PayService                  │    │
│  │ - createOrder()             │    │
│  │ - queryOrder()              │    │
│  │ - refund()                  │    │
│  │ - handleNotify()            │    │
│  └─────────────────────────────┘    │
│  ┌─────────┐    ┌───────────┐       │
│  │WechatPay│    │AlipayService│     │
│  └─────────┘    └───────────┘       │
└─────────────────────────────────────┘
// 支付服务接口
public interface PayService {
    // 创建订单
    PayResult createOrder(PayRequest request);
    
    // 查询订单
    OrderResult queryOrder(String orderId);
    
    // 退款
    RefundResult refund(RefundRequest request);
    
    // 处理回调
    String handleNotify(String channel, Map<String, String> params);
}

// 支付请求(统一格式)
public class PayRequest {
    private String channel;      // wechat/alipay
    private String orderId;      // 商户订单号
    private BigDecimal amount;   // 金额
    private String subject;      // 商品名称
    private String body;         // 商品描述
    private String userId;       // 用户ID
    private String extra;        // 扩展信息
}
问题:微信支付时提示"商户号未绑定AppID"
原因:AppID 需要在商户平台单独绑定
解决:登录商户平台 → 产品中心 → AppID账号管理 → 关联AppID
问题:签名验证一直失败
原因:使用了错误的公钥(应该用支付宝公钥,不是应用公钥)
解决:在支付宝开放平台获取"支付宝公钥",不是自己生成的应用公钥
问题:App 审核被拒,因为虚拟商品必须用 IAP
原因:iOS 规定虚拟商品必须走苹果内购
解决:虚拟商品(钻石、道具)用 IAP,实体商品可以用第三方支付

案例3:PC 网页游戏

  1. 微信:使用 Native 支付(扫码支付)
  2. 支付宝:使用手机网站支付(扫码或账号登录)
// 生成支付二维码
public String createQRCode(String orderId, BigDecimal amount) {
    // 1. 调用统一下单接口
    WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
        .outTradeNo(orderId)
        .totalFee(amount.multiply(new BigDecimal(100)).intValue()) // 元转分
        .body("游戏充值")
        .tradeType(TradeType.NATIVE) // 扫码支付
        .notifyUrl(notifyUrl)
        .build();
    
    WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request);
    
    // 2. 返回二维码链接
    return result.getCodeURL();
}

// 前端生成二维码
// 使用 qrcode.js 或类似库将 codeURL 转成二维码图片
// 生成支付二维码
public String createQRCode(String orderId, BigDecimal amount) {
    AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
    request.setNotifyUrl(notifyUrl);
    request.setBizContent("{" +
        "\"out_trade_no\":\"" + orderId + "\"," +
        "\"total_amount\":\"" + amount + "\"," +
        "\"subject\":\"游戏充值\"" +
    "}");
    
    AlipayTradePrecreateResponse response = alipayClient.execute(request);
    return response.getQrCode();
}
// 前端轮询
let pollTimer = null;
let pollCount = 0;
const MAX_POLL = 60; // 最多轮询60次(5分钟)

function startPolling(orderId) {
    pollTimer = setInterval(async () => {
        pollCount++;
        
        const res = await fetch(`/api/pay/query?orderId=${orderId}`);
        const data = await res.json();
        
        if (data.status === 'PAID') {
            clearInterval(pollTimer);
            showSuccess();
        } else if (pollCount >= MAX_POLL) {
            clearInterval(pollTimer);
            showTimeout();
        }
    }, 5000); // 每5秒查询一次
}

// 用户扫码后,前端轮询检测支付状态
问题:用户扫码时提示"二维码已过期"
原因:微信扫码支付二维码有效期 2 小时
解决:设置定时器,1.5 小时后刷新二维码,或提示用户刷新页面
问题:1.10 元的订单,实际扣款 1.1 元
原因:后端使用了 float 类型,精度丢失
解决:使用 BigDecimal,微信支付用整数分,支付宝用字符串
问题:回调通知无法接收
原因:测试环境和生产环境域名不同
解决:使用内网穿透工具(如 ngrok)测试,或部署到测试服务器

九、游戏行业的选型建议

说了这么多差异,作为游戏平台,该怎么选?

选择微信支付的场景

社交属性强的游戏

小游戏

H5 游戏,主要流量来自微信

国内用户为主的游戏

选择支付宝的场景

独立 App 游戏

PC 端游戏/网页游戏

大额充值场景

需要国际化的游戏

电商+游戏混合场景

实际建议:两个都要接

  1. 根据用户画像,决定优先展示哪个
- 如果是微信小游戏,默认微信支付

- 如果是独立 App,根据统计数据选择

  1. 两个都支持,让用户自己选
- 提供明显的切换入口

- 记住用户上次的选择

  1. 根据数据反馈,调整优先级
- 监控各渠道的支付成功率

- 动态调整推荐顺序

决策流程图

                    开始
                      │
                      ▼
              ┌───────────────┐
              │ 是微信小游戏? │
              └───────┬───────┘
                      │
           ┌──────────┴──────────┐
           │Yes                  │No
           ▼                     ▼
    ┌─────────────┐      ┌─────────────┐
    │ 只接微信支付 │      │ 用户主要在?│
    └─────────────┘      └──────┬──────┘
                                 │
                    ┌────────────┼────────────┐
                    │            │            │
                    ▼            ▼            ▼
                ┌───────┐   ┌───────┐   ┌───────┐
                │微信内 │   │独立App│   │ PC端  │
                └───┬───┘   └───┬───┘   └───┬───┘
                    │           │           │
                    ▼           ▼           ▼
              ┌──────────┐┌──────────┐┌──────────┐
              │微信优先  ││两个都接  ││支付宝优先│
              └──────────┘└──────────┘└──────────┘

[配图建议:决策树图,帮助选择支付方式的流程图]


十、对接时的"坑点"提醒

最后,分享几个最常见的坑:

微信的坑

⚠️ 坑1:openid 获取

问题描述:
- 不同公众号的 openid 不一样
- 同一用户在不同应用中 openid 不同

解决方案:
- 使用开放平台的 unionid 统一用户身份
- 提前规划账号体系
- 多个应用绑定到同一个开放平台账号

⚠️ 坑2:证书配置

问题描述:
- 退款接口需要双向证书
- 证书有有效期(通常1年)
- 不同环境需要不同证书

解决方案:
- 建立证书更新提醒机制
- 提前1个月申请更新
- 使用配置中心管理证书路径

⚠️ 坑3:签名类型

问题描述:
- V2 接口支持 MD5 和 HMAC-SHA256
- V3 接口只支持 RSA
- 混用会导致签名失败

解决方案:
- 明确每个接口的签名类型
- 不要混用不同版本的 SDK
- 查看错误信息中的签名类型提示

支付宝的坑

⚠️ 坑1:密钥管理

问题描述:
- 有应用私钥、应用公钥、支付宝公钥三种
- 容易混淆用错

解决方案:
- 应用私钥:自己生成,妥善保管,绝不上传
- 应用公钥:上传到支付宝开放平台
- 支付宝公钥:从开放平台获取,用于验签
- 使用 SDK 时注意区分

⚠️ 坑2:网关选择

问题描述:
- 有正式环境和沙箱环境
- 网关地址不同
- 沙箱密钥和正式密钥不同

解决方案:
- 测试时使用沙箱网关:https://openapi.alipaydev.com/gateway.do
- 上线时切换到正式网关:https://openapi.alipay.com/gateway.do
- 使用配置文件管理,避免硬编码

⚠️ 坑3:编码问题

问题描述:
- UTF-8 和 GBK 编码混用
- 中文参数签名失败

解决方案:
- 统一使用 UTF-8 编码
- 签名前检查编码格式
- 使用官方 SDK 避免编码问题

两者共同的坑

⚠️ 坑1:金额单位

问题描述:
- 微信:单位是"分",100 表示 1 元
- 支付宝:单位是"元",1.00 表示 1 元
- 这是最容易踩的坑!

解决方案:
// 微信支付
int totalFee = new BigDecimal("1.00")
    .multiply(new BigDecimal(100))
    .intValue(); // 100 分

// 支付宝支付
String totalAmount = "1.00"; // 直接用字符串

⚠️ 坑2:时间格式

问题描述:
- 微信:时间戳(秒)
- 支付宝:格式化字符串(yyyy-MM-dd HH:mm:ss)
- 时区问题

解决方案:
// 微信
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);

// 支付宝
String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    .format(new Date());

⚠️ 坑3:幂等处理

问题描述:
- 同一个订单可能收到多次回调
- 网络重试导致重复通知
- 用户重复点击

解决方案:
1. 使用唯一订单号
2. 回调处理前检查订单状态
3. 使用数据库唯一索引
4. 使用分布式锁

代码示例:
// 伪代码
if (order.status == PAID) {
    return "success"; // 已处理,直接返回成功
}

// 加锁处理
if (lock.tryLock(orderId, 30)) {
    try {
        // 双重检查
        if (order.status == PAID) {
            return "success";
        }
        // 处理支付成功逻辑
        processPaySuccess(order);
    } finally {
        lock.unlock(orderId);
    }
}

return "success";

⚠️ 坑4:回调地址

问题描述:
- 回调地址必须是外网可访问的
- 不能是 localhost 或内网 IP
- 必须支持 HTTPS(生产环境)

解决方案:
- 开发环境使用内网穿透工具(ngrok、frp)
- 测试环境部署到测试服务器
- 生产环境确保 HTTPS 证书有效

⚠️ 坑5:错误码处理

问题描述:
- 错误码定义不统一
- 同样的错误可能返回不同的错误码
- 错误信息不够详细

解决方案:
// 建立错误码映射表
Map<String, String> errorCodeMap = new HashMap<>();
errorCodeMap.put("SYSTEMERROR", "系统繁忙,请稍后重试");
errorCodeMap.put("PARAM_ERROR", "参数错误,请检查请求参数");
errorCodeMap.put("OUT_TRADE_NO_USED", "商户订单号重复");

// 统一错误处理
try {
    // 调用支付接口
} catch (PayException e) {
    String userMsg = errorCodeMap.getOrDefault(
        e.getErrorCode(), 
        "支付失败,请联系客服"
    );
    throw new BusinessException(userMsg);
}

十一、总结:核心要点速记

📌 接入流程

📌 支付方式

📌 签名机制

📌 回调处理

📌 API 差异

📌 选型建议

📌 必须记住的三件事

  1. 金额单位 - 微信是分,支付宝是元!
  2. 幂等处理 - 回调可能重复,必须检查状态!
  3. 签名验证 - 回调必须验签,防止伪造!

支付对接没有银弹,理解差异、选对场景、做好容错,才能让用户的充值体验流畅无感 💰✨


💬 评论 (0)

0/500
排序: