Pārlūkot izejas kodu

feat:小程序支付完成

刘云鑫 1 mēnesi atpakaļ
vecāks
revīzija
41c2552222

+ 27 - 6
alien-dining/src/main/java/shop/alien/dining/controller/PaymentController.java

@@ -7,6 +7,8 @@ import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.CrossOrigin;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -16,6 +18,7 @@ import shop.alien.entity.result.R;
 import shop.alien.util.common.constant.PaymentEnum;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -53,16 +56,34 @@ public class PaymentController {
     }
 
     /**
-     * 通知接口 之后可能会用
-     * @param notifyData
-     * @return
+     * 微信支付回调通知接口(符合微信支付 V3 规范)
+     * 验签通过:返回 204 无包体;验签失败:返回 5XX + {"code":"FAIL","message":"失败"}
+     * @param notifyData 回调 JSON 报文
+     * @return 204 无 body 或 5XX + 失败报文
      */
     @RequestMapping("/weChatMininNotify")
-    public R notify(@RequestBody String notifyData, HttpServletRequest request) throws Exception {
+    public ResponseEntity<?> notify(@RequestBody String notifyData, HttpServletRequest request) throws Exception {
+        log.info("[微信支付回调] 收到回调请求, Content-Length={}, Wechatpay-Serial={}", request.getContentLength(), request.getHeader("Wechatpay-Serial"));
         try {
-            return paymentStrategyFactory.getStrategy(PaymentEnum.WECHAT_PAY_MININ_PROGRAM.getType()).handleNotify(notifyData,request);
+            R result = paymentStrategyFactory.getStrategy(PaymentEnum.WECHAT_PAY_MININ_PROGRAM.getType()).handleNotify(notifyData, request);
+            if (R.isSuccess(result)) {
+                // 文档要求:验签通过时 HTTP 200 或 204,无需返回应答报文
+                return ResponseEntity.noContent().build();
+            } else {
+                // 文档要求:验签不通过时 4XX/5XX,并返回 {"code":"FAIL","message":"失败"}
+                String message = result.getMsg() != null ? result.getMsg() : "失败";
+                Map<String, String> failBody = new HashMap<>(2);
+                failBody.put("code", "FAIL");
+                failBody.put("message", message);
+                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(failBody);
+            }
         } catch (Exception e) {
-            return R.fail(e.getMessage());
+            log.error("[微信支付回调] 处理异常", e);
+            String msg = e.getMessage() != null ? e.getMessage() : "失败";
+            Map<String, String> failBody = new HashMap<>(2);
+            failBody.put("code", "FAIL");
+            failBody.put("message", msg);
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(failBody);
         }
     }
 

+ 40 - 32
alien-dining/src/main/java/shop/alien/dining/strategy/payment/impl/WeChatPaymentMininProgramStrategyImpl.java

@@ -37,6 +37,7 @@ import java.util.Base64;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 
 
 /**
@@ -103,13 +104,13 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
     /**
      * 微信支付预支付路径
      */
-    @Value("${payment.wechatPay.miniProgram.prePayPath:/v3/pay/transactions/jsapi}")
+    @Value("${payment.wechatPay.business.miniProgram.prePayPath:/v3/pay/transactions/jsapi}")
     private String prePayPath;
     /**
      * 微信支付预支付通知路径
      */
-    @Value("${payment.wechatPay.miniProgram.prePayNotifyUrl:https://www.weixin.qq.com/wxpay/pay.php}")
-    private String prePayNotifyUrl;
+//    @Value("${payment.wechatPay.business.miniProgram.prePayNotifyUrl:https://www.weixin.qq.com/wxpay/pay.php}")
+    private String prePayNotifyUrl = "https://frp-way.com:12473/aliendining/payment/weChatMininNotify";
     /**
      * 微信支付退款通知路径
      */
@@ -226,6 +227,7 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
 
     @Override
     public R handleNotify(String notifyData, HttpServletRequest request) throws Exception {
+        log.info("[微信支付回调] 进入 handleNotify, notifyData 长度={}", notifyData != null ? notifyData.length() : 0);
         /*{
     "id": "EV-2018022511223320873",
     "create_time": "2015-05-20T13:29:35+08:00",
@@ -253,6 +255,12 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
             return R.fail("验签参数缺失");
         }
 
+        // 2.1 签名探测:微信会发 WECHATPAY/SIGNTEST/ 开头的 Signature 检测商户是否正确验签,需直接返回成功
+        if (signature.startsWith("WECHATPAY/SIGNTEST/")) {
+            log.info("[微信支付回调] 收到签名探测请求,直接应答成功");
+            return R.success("OK");
+        }
+
         StringBuilder signStr = new StringBuilder();
         signStr.append(timestamp).append("\n"); // 第一行:应答时间戳
         signStr.append(nonce).append("\n");     // 第二行:应答随机串
@@ -269,46 +277,49 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
 
         // 步骤5:执行验签(对应命令行-verify逻辑)
         if (sign.verify(signatureBytes)) {
-            // 更新订单状态 TODO 改为异步
-            // ========== 第二步:解析回调报文,提取加密的resource字段 ==========
+            // 文档要求:验签通过后先应答(5 秒内),再异步处理业务,避免超时导致微信重复回调
+            final String notifyDataCopy = notifyData;
+            CompletableFuture.runAsync(() -> processNotifyBusiness(notifyDataCopy));
+            return R.success("OK");
+        } else {
+            return R.fail("Verified error");
+        }
+    }
+
+    /**
+     * 异步处理回调业务:解密并更新订单状态(文档建议应答后再处理业务,避免超时)
+     */
+    private void processNotifyBusiness(String notifyData) {
+        try {
             JsonNode rootNode = objectMapper.readTree(notifyData);
             JsonNode resourceNode = rootNode.get("resource");
             if (resourceNode == null) {
                 log.warn("微信支付回调报文无resource字段");
-                return R.fail("回调数据格式异常");
+                return;
             }
-
-            // 提取resource下的加密相关参数
             String encryptAlgorithm = resourceNode.get("algorithm").asText();
             String resourceNonce = resourceNode.get("nonce").asText();
-            String associatedData = resourceNode.get("associated_data").asText();
+            // associated_data 选填,可能为空(文档:该字段可能为空)
+            String associatedData = resourceNode.has("associated_data") && !resourceNode.get("associated_data").isNull()
+                    ? resourceNode.get("associated_data").asText() : "";
             String ciphertext = resourceNode.get("ciphertext").asText();
-
-            // 校验加密算法(目前仅支持AEAD_AES_256_GCM)
             if (!"AEAD_AES_256_GCM".equals(encryptAlgorithm)) {
                 log.warn("不支持的加密算法:{}", encryptAlgorithm);
-                return R.fail("不支持的加密算法");
+                return;
             }
-            // ========== 第三步:AES-256-GCM解密,获取明文业务信息 ==========
             String plainBusinessData = WeChatPayUtil.decrypt(
-                    apiV3key,
-                    resourceNonce,
-                    associatedData,
-                    ciphertext
-            );
+                    apiV3key, resourceNonce, associatedData, ciphertext);
             log.info("微信支付回调解密后的业务信息:{}", plainBusinessData);
             JSONObject jsonObject = JSONObject.parseObject(plainBusinessData);
             String tradeState = jsonObject.getString("trade_state");
-            // 如果支付成功
-            if(tradeState.equals("SUCCESS")){
-                String transactionId = jsonObject.getString("transaction_id");
-                StoreOrder storeOrder = storeOrderService.getOne(new QueryWrapper<StoreOrder>().eq("transaction_id", transactionId));
-                if(storeOrder != null && storeOrder.getPayStatus() != 1){
+            if ("SUCCESS".equals(tradeState)) {
+                String outTradeNo = jsonObject.getString("out_trade_no");
+                StoreOrder storeOrder = storeOrderService.getOne(new QueryWrapper<StoreOrder>().eq("order_no", outTradeNo));
+                if (storeOrder != null && storeOrder.getPayStatus() != 1) {
                     storeOrder.setPayStatus(1);
-                    storeOrder.setOrderStatus(1); // 已支付
-                    if(storeOrderService.updateById(storeOrder)){
-                        log.info("小程序更新订单成功,订单号transactionId:{}", transactionId);
-                        // 支付成功后,清空购物车并重置餐桌
+                    storeOrder.setOrderStatus(1);
+                    if (storeOrderService.updateById(storeOrder)) {
+                        log.info("小程序更新订单成功,订单号outTradeNo:{}", outTradeNo);
                         try {
                             storeOrderService.resetTableAfterPayment(storeOrder.getTableId());
                             log.info("支付完成后重置餐桌成功, tableId={}", storeOrder.getTableId());
@@ -318,12 +329,9 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
                     }
                 }
             }
-            return R.success("Verified OK");
-        } else {
-            return R.fail("Verified error");
+        } catch (Exception e) {
+            log.error("[微信支付回调] 异步处理业务异常", e);
         }
-
-
     }
 
     @Override