支付对接:微信/支付宝支付、回调解析、订单核销

📅 2026/8/3 3:20:21
支付对接:微信/支付宝支付、回调解析、订单核销
支付对接微信/支付宝支付、回调解析、订单核销钱货两讫天经地义——但在无人售货柜里钱这一步可比你想的复杂多了。一、支付在无人售货柜中的完整流程先说清楚整个支付链路搞不懂全貌写出来的代码一定是漏的。无人售货柜的支付流程和普通电商不同它不是先下单再付款而是先拿货再付款。完整链路如下关门 → 视觉识别商品 → 生成订单 → 用户支付 → 支付平台回调 → 核销订单 → 交易完成 ↓ (失败)→ 超时关单/退款核心区别在于订单是在用户拿完商品关门后才生成的用户扫码开门时不需要支付关门瞬间由视觉识别系统判断拿了什么生成订单后调起支付。二、微信支付对接2.1 JSAPI支付小程序场景无人售货柜多配套微信小程序所以微信支付主要走JSAPI支付。流程是后端调用微信「统一下单」接口拿到预支付会话标识prepay_id后端用prepay_id生成前端调起支付所需参数前端调用wx.requestPayment拉起支付界面用户输入密码/指纹完成支付微信服务器异步通知后端回调接口2.2 统一下单核心代码ServiceSlf4jpublicclassWechatPayService{AutowiredprivateWxPayConfigwxPayConfig;/** * 统一下单生成小程序调起支付参数 */publicWxPayJsapiResultunifiedOrder(Orderorder){try{// 1. 构建统一下单请求WxPayUnifiedOrderRequestrequestnewWxPayUnifiedOrderRequest();request.setAppid(wxPayConfig.getAppId());request.setMchId(wxPayConfig.getMchId());request.setNonceStr(WxPayKit.generateNonceStr());request.setBody(无人售货柜-order.getOrderNo());request.setOutTradeNo(order.getOrderNo());// 金额单位分request.setTotalFee(order.getPayAmount().multiply(BigDecimal.valueOf(100)).intValue());request.setSpbillCreateIp(IpUtil.getClientIp());request.setNotifyUrl(wxPayConfig.getNotifyUrl());request.setTradeType(JSAPI);// 用户openid小程序登录后获取request.setOpenid(order.getOpenid());// 2. 签名request.setSign(WxPayKit.createSign(BeanUtil.beanToTreeMap(request),wxPayConfig.getApiKey()));// 3. 调用微信统一下单接口StringxmlWxPayKit.toXml(request);StringresultHttpUtil.post(wxPayConfig.getUnifiedOrderUrl(),xml);MapString,StringresultMapWxPayKit.xmlToMap(result);// 4. 校验返回结果StringreturnCoderesultMap.get(return_code);StringresultCoderesultMap.get(result_code);if(!SUCCESS.equals(returnCode)||!SUCCESS.equals(resultCode)){log.error(微信统一下单失败: {},resultMap);thrownewBusinessException(微信支付下单失败);}// 5. 生成前端调起支付参数StringprepayIdresultMap.get(prepay_id);WxPayJsapiResultjsapiResultnewWxPayJsapiResult();jsapiResult.setAppId(wxPayConfig.getAppId());jsapiResult.setTimeStamp(String.valueOf(System.currentTimeMillis()/1000));jsapiResult.setNonceStr(WxPayKit.generateNonceStr());jsapiResult.setPackage(prepay_idprepayId);jsapiResult.setSignType(RSA);// 支付签名jsapiResult.setPaySign(WxPayKit.createPaySign(jsapiResult.getAppId(),jsapiResult.getTimeStamp(),jsapiResult.getNonceStr(),jsapiResult.getPackage(),wxPayConfig.getApiKey()));returnjsapiResult;}catch(Exceptione){log.error(微信统一下单异常, orderNo{},order.getOrderNo(),e);thrownewBusinessException(支付服务异常);}}}新手提醒微信支付金额单位是分不是元。BigDecimal转换时一定要乘以100这个坑踩过的人都知道。三、支付宝对接3.1 当面付扫码场景支付宝这边我们用「当面付」F2FPay适用于用户扫码直接支付的流程。支付宝的SDK封装得比较友好用alipay-sdk-java即可。ServiceSlf4jpublicclassAlipayService{AutowiredprivateAlipayConfigalipayConfig;/** * 当面付预创建交易生成二维码 */publicAlipayTradePrecreateResultprecreate(Orderorder){try{AlipayClientclientnewDefaultAlipayClient(alipayConfig.getGateway(),alipayConfig.getAppId(),alipayConfig.getPrivateKey(),json,UTF-8,alipayConfig.getAlipayPublicKey(),RSA2);AlipayTradePrecreateRequestrequestnewAlipayTradePrecreateRequest();request.setNotifyUrl(alipayConfig.getNotifyUrl());MapString,ObjectbizContentnewHashMap();bizContent.put(out_trade_no,order.getOrderNo());bizContent.put(total_amount,order.getPayAmount().toString());bizContent.put(subject,无人售货柜-order.getOrderNo());request.setBizContent(JSON.toJSONString(bizContent));AlipayTradePrecreateResponseresponseclient.execute(request);if(!response.isSuccess()){log.error(支付宝预下单失败: {},response.getSubMsg());thrownewBusinessException(支付宝支付下单失败);}AlipayTradePrecreateResultresultnewAlipayTradePrecreateResult();result.setQrCode(response.getQrCode());result.setTradeNo(response.getTradeNo());returnresult;}catch(AlipayApiExceptione){log.error(支付宝预下单异常,e);thrownewBusinessException(支付服务异常);}}}四、支付回调处理核心中的核心4.1 回调处理流程支付回调是整个支付链路最关键的一环处理不好就是资损。流程如下支付平台异步通知 → 验签 → 金额校验 → 幂等判断 → 更新订单状态 → 扣减库存 → 记录流水 → 返回SUCCESS4.2 回调处理Service完整实现ServiceSlf4jpublicclassPayCallbackService{AutowiredprivateOrderServiceorderService;AutowiredprivatePaymentServicepaymentService;AutowiredprivateInventoryServiceinventoryService;AutowiredprivateRedissonClientredissonClient;/** * 微信支付回调处理 */publicStringhandleWechatCallback(StringxmlData){try{MapString,StringparamsWxPayKit.xmlToMap(xmlData);// 1. 验签 —— 防止伪造回调if(!WxPayKit.verifySign(params,wxPayConfig.getApiKey())){log.error(微信回调验签失败);returnxmlreturn_codeFAIL/return_code/xml;}// 2. 解析回调数据StringorderNoparams.get(out_trade_no);StringtransactionIdparams.get(transaction_id);StringtotalFeeparams.get(total_fee);StringresultCodeparams.get(result_code);if(!SUCCESS.equals(resultCode)){log.error(微信回调支付失败, orderNo{},orderNo);returnxmlreturn_codeSUCCESS/return_code/xml;}// 3. 金额校验 —— 防止金额被篡改OrderorderorderService.getByOrderNo(orderNo);if(ordernull){log.error(订单不存在, orderNo{},orderNo);returnxmlreturn_codeSUCCESS/return_code/xml;}intexpectedFeeorder.getPayAmount().multiply(BigDecimal.valueOf(100)).intValue();if(expectedFee!Integer.parseInt(totalFee)){log.error(金额不一致, 期望{}分, 实际{}分,expectedFee,totalFee);// 记录异常人工介入paymentService.recordAmountMismatch(orderNo,expectedFee,totalFee);returnxmlreturn_codeSUCCESS/return_code/xml;}// 4. 幂等处理 —— 分布式锁防重复处理RLocklockredissonClient.getLock(pay:callback:orderNo);if(!lock.tryLock(0,30,TimeUnit.SECONDS)){log.warn(回调重复到达, 已有处理中, orderNo{},orderNo);returnxmlreturn_codeSUCCESS/return_code/xml;}try{// 再次检查订单状态双重幂等if(order.getStatus()!OrderStatus.UNPAID){log.info(订单已处理, 跳过, orderNo{}, status{},orderNo,order.getStatus());returnxmlreturn_codeSUCCESS/return_code/xml;}// 5. 更新订单状态orderService.paySuccess(orderNo,transactionId);// 6. 确认扣减库存预占 → 确认inventoryService.confirmDeduct(order);// 7. 记录支付流水PaymentpaymentnewPayment();payment.setOrderNo(orderNo);payment.setTransactionId(transactionId);payment.setPayType(PayType.WECHAT);payment.setAmount(order.getPayAmount());payment.setStatus(PaymentStatus.SUCCESS);payment.setCallbackTime(LocalDateTime.now());paymentService.save(payment);log.info(微信支付回调处理成功, orderNo{},orderNo);returnxmlreturn_codeSUCCESS/return_code/xml;}finally{lock.unlock();}}catch(Exceptione){log.error(微信回调处理异常,e);returnxmlreturn_codeFAIL/return_code/xml;}}}4.3 幂等性设计详解支付平台会重试回调微信最多通知15次所以幂等性是生命线。实现方式层级方案说明第一层分布式锁Redisson锁同一订单号防止并发处理第二层状态判断订单非UNPAID状态直接返回SUCCESS第三层流水号去重transaction_id唯一索引防止重复入账三层防护缺一不可。五、支付状态查询兜底回调可能因网络问题丢失所以必须有主动查询兜底机制/** * 定时扫描未确认支付的订单主动查询支付状态 */Scheduled(cron0 */5 * * * ?)publicvoidcheckUnpaidOrders(){// 查询创建超过5分钟但仍未支付的订单ListOrderordersorderService.findUnpaidOrders(5);for(Orderorder:orders){try{// 主动查询微信/支付宝支付状态PayStatusResultresultpayQueryService.queryPayStatus(order);if(result.isPaid()){// 主动补偿走和回调一样的逻辑payCallbackService.processPaySuccess(order,result.getTransactionId());}elseif(result.isExpired()){// 支付已过期关闭订单orderService.closeOrder(order.getOrderNo(),支付超时关闭);inventoryService.releasePreempt(order);}}catch(Exceptione){log.error(查询支付状态异常, orderNo{},order.getOrderNo(),e);}}}六、退款流程退款不是简单的把钱还回去涉及多步状态流转发起退款 → 第三方处理 → 退款回调 → 更新订单(已退款) → 恢复库存 → 通知用户/** * 发起微信退款 */publicvoidrefund(Orderorder,Stringreason){// 1. 创建退款记录RefundRecordrefundnewRefundRecord();refund.setOrderNo(order.getOrderNo());refund.setRefundNo(IdUtil.generateRefundNo());refund.setAmount(order.getPayAmount());refund.setReason(reason);refund.setStatus(RefundStatus.PROCESSING);refundRecordService.save(refund);// 2. 调用微信退款接口WxPayRefundRequestrequestnewWxPayRefundRequest();request.setOutTradeNo(order.getOrderNo());request.setOutRefundNo(refund.getRefundNo());request.setTotalFee(order.getPayAmount().multiply(BigDecimal.valueOf(100)).intValue());request.setRefundFee(refund.getAmount().multiply(BigDecimal.valueOf(100)).intValue());request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl());WxPayRefundResponseresponsewxPayService.refund(request);if(!response.isSuccess()){thrownewBusinessException(退款发起失败: response.getErrMsg());}// 退款结果由异步回调通知}七、支付流水表设计CREATETABLEpayment(idBIGINTPRIMARYKEYAUTO_INCREMENT,order_noVARCHAR(64)NOTNULLCOMMENT订单号,transaction_idVARCHAR(64)COMMENT第三方流水号,refund_noVARCHAR(64)COMMENT退款单号,pay_typeTINYINTNOTNULLCOMMENT支付方式:1微信2支付宝,amountDECIMAL(10,2)NOTNULLCOMMENT支付金额,statusTINYINTNOTNULLCOMMENT状态:0待支付1成功2失败3已退款,callback_timeDATETIMECOMMENT回调时间,create_timeDATETIMEDEFAULTCURRENT_TIMESTAMP,INDEXidx_order_no(order_no),INDEXidx_transaction_id(transaction_id),UNIQUEINDEXuk_transaction_id(transaction_id))COMMENT支付流水表;transaction_id加唯一索引既是查询索引又是去重保障一箭双雕。八、安全设计要点安全风险防护方案伪造回调回调验签微信MD5/HMAC-SHA256支付宝RSA2金额篡改回调金额与订单金额比对校验重放攻击校验回调时间戳超时拒绝重复入账分布式锁 状态判断 流水号唯一索引退款超退退款金额校验不超过原支付金额九、常见问题排查回调网络超时微信要求5秒内返回响应回调处理逻辑中不要做耗时操作。库存扣减走异步MQ回调只做核心状态更新。金额不一致最危险的情况。回调金额与订单金额不符时不要自动处理记录异常流水转人工审核。重复回调幂等设计兜底重复回调直接返回SUCCESS即可微信收到SUCCESS就不再重试。但如果你返回FAIL微信会疯狂重试15次系统压力陡增。支付了但订单没更新这就是支付状态查询兜底机制存在的意义——定时任务主动查不依赖回调。支付对接看着简单坑全在细节里。验签、幂等、金额校验、兜底查询每一环都是血的教训换来的。把这几块设计扎实了后面不管是加新的支付渠道还是处理退款都是照猫画虎的事。