实现代码
// pages/pay/pay.js —— 点餐页「去支付」
Page({
data: { total: 0, paying: false },
onPay() {
if (this.data.paying) return;
this.setData({ paying: true });
// ① 每次支付都重新拿 code(有效期 5 分钟,一次性)
wx.login({
success: async ({ code }) => {
try {
// ② 后端换 openid + 调 miniAppPay.do,返回 6 个支付参数
const res = await wx.request({
url: 'https://your-api.com/api/wxyf/miniapp/prepay',
method: 'POST',
data: { code, tableNo: this.data.tableNo }
});
const r = res.data || {};
if (!r.ok) {
wx.showToast({ title: r.msg || '下单失败', icon: 'none' });
return;
}
// ③ 拉起微信支付
wx.requestPayment({
timeStamp: r.pay.timeStamp,
nonceStr: r.pay.nonceStr,
package: r.pay.package,
signType: r.pay.signType, // 文档示例是 RSA,务必按平台返回传
paySign: r.pay.paySign,
success: () => {
// ④ 前端只做引导,最终以服务端回调/查单为准
wx.redirectTo({ url: '/pages/result/result?orderId=' + r.orderId });
},
fail: (e) => {
if (e.errMsg.indexOf('cancel') === -1) {
wx.showToast({ title: '支付未完成', icon: 'none' });
}
}
});
} finally {
this.setData({ paying: false });
}
}
});
}
});
// 后端:把 code 换成 openid,再调 miniAppPay.do
// 注意:金额必须服务端按菜品重算,不能信前端传的 total
@PostMapping("/api/wxyf/miniapp/prepay")
public Map<String,Object> prepay(@RequestBody PrepayReq req) {
// ① code -> openid(微信标准接口,用小程序自己的 appid+secret)
String openid = wechatService.code2Session(APPID, SECRET, req.getCode());
// ② 服务端重算金额(防篡改)
BigDecimal money = orderService.calcTotal(req.getTableNo());
String orderId = orderService.newOrderId(); // 全局唯一
// ③ 调平台:miniAppPay.do
JSONObject p = new JSONObject();
p.put("program_id", PROGRAM_ID);
p.put("shop_id", SHOP_ID);
p.put("device", DEVICE_SN);
p.put("money", money.setScale(2, RoundingMode.HALF_UP).toString());
p.put("order_id", orderId);
p.put("order_type", "weixin");
p.put("appid", APPID); // 必须已与商户号绑定
p.put("openid", openid);
p.put("notify_url", NOTIFY_URL);
p.put("ver", "3.0");
p.put("sign", SignUtil.sign(p, ACCESS_KEY)); // 参数排序+&key= → MD5大写
JSONObject r = Http.postJson(BASE + "/api/miniAppPay.do", p);
if (!"success".equals(r.getString("result_code"))) {
return Map.of("ok", false, "msg", r.getString("error_msg"));
}
// ④ 平台字段 -> wx.requestPayment 六参数
Map<String,Object> pay = new HashMap<>();
pay.put("timeStamp", r.getString("time_end")); // 时间戳
pay.put("nonceStr", r.getString("nonce_str"));
pay.put("package", r.getString("package"));
pay.put("signType", r.getString("sign_type")); // RSA
pay.put("paySign", r.getString("pay_sign"));
return Map.of("ok", true, "orderId", orderId, "pay", pay);
}
// 回调验签:规则与请求不同!只算 4 个字段
@GetMapping("/api/wxyf/notify")
public String notify(@RequestParam Map<String,String> q) {
String raw = "money=" + q.getOrDefault("money", "")
+ "&orderId=" + q.getOrDefault("orderId", "")
+ "&result_code=" + q.getOrDefault("result_code", "")
+ "&shopId=" + q.getOrDefault("shopId", "")
+ "&key=" + ACCESS_KEY;
String calc = DigestUtils.md5Hex(raw).toUpperCase();
if (!calc.equalsIgnoreCase(q.get("sign"))) {
log.warn("验签失败 orderId={}", q.get("orderId"));
return "FAIL";
}
// 幂等:同一订单重复回调只处理一次
if (orderService.isHandled(q.get("orderId"))) return "SUCCESS";
orderService.markPaid(q.get("orderId"), q.get("result_code"));
return "SUCCESS"; // 必须回 SUCCESS,否则平台会重试
}