|
@@ -37,6 +37,7 @@ import java.util.Base64;
|
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
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;
|
|
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
|
|
@Override
|
|
|
public R handleNotify(String notifyData, HttpServletRequest request) throws Exception {
|
|
public R handleNotify(String notifyData, HttpServletRequest request) throws Exception {
|
|
|
|
|
+ log.info("[微信支付回调] 进入 handleNotify, notifyData 长度={}", notifyData != null ? notifyData.length() : 0);
|
|
|
/*{
|
|
/*{
|
|
|
"id": "EV-2018022511223320873",
|
|
"id": "EV-2018022511223320873",
|
|
|
"create_time": "2015-05-20T13:29:35+08:00",
|
|
"create_time": "2015-05-20T13:29:35+08:00",
|
|
@@ -253,6 +255,12 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
|
|
|
return R.fail("验签参数缺失");
|
|
return R.fail("验签参数缺失");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 2.1 签名探测:微信会发 WECHATPAY/SIGNTEST/ 开头的 Signature 检测商户是否正确验签,需直接返回成功
|
|
|
|
|
+ if (signature.startsWith("WECHATPAY/SIGNTEST/")) {
|
|
|
|
|
+ log.info("[微信支付回调] 收到签名探测请求,直接应答成功");
|
|
|
|
|
+ return R.success("OK");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
StringBuilder signStr = new StringBuilder();
|
|
StringBuilder signStr = new StringBuilder();
|
|
|
signStr.append(timestamp).append("\n"); // 第一行:应答时间戳
|
|
signStr.append(timestamp).append("\n"); // 第一行:应答时间戳
|
|
|
signStr.append(nonce).append("\n"); // 第二行:应答随机串
|
|
signStr.append(nonce).append("\n"); // 第二行:应答随机串
|
|
@@ -269,46 +277,49 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
|
|
|
|
|
|
|
|
// 步骤5:执行验签(对应命令行-verify逻辑)
|
|
// 步骤5:执行验签(对应命令行-verify逻辑)
|
|
|
if (sign.verify(signatureBytes)) {
|
|
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 rootNode = objectMapper.readTree(notifyData);
|
|
|
JsonNode resourceNode = rootNode.get("resource");
|
|
JsonNode resourceNode = rootNode.get("resource");
|
|
|
if (resourceNode == null) {
|
|
if (resourceNode == null) {
|
|
|
log.warn("微信支付回调报文无resource字段");
|
|
log.warn("微信支付回调报文无resource字段");
|
|
|
- return R.fail("回调数据格式异常");
|
|
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 提取resource下的加密相关参数
|
|
|
|
|
String encryptAlgorithm = resourceNode.get("algorithm").asText();
|
|
String encryptAlgorithm = resourceNode.get("algorithm").asText();
|
|
|
String resourceNonce = resourceNode.get("nonce").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();
|
|
String ciphertext = resourceNode.get("ciphertext").asText();
|
|
|
-
|
|
|
|
|
- // 校验加密算法(目前仅支持AEAD_AES_256_GCM)
|
|
|
|
|
if (!"AEAD_AES_256_GCM".equals(encryptAlgorithm)) {
|
|
if (!"AEAD_AES_256_GCM".equals(encryptAlgorithm)) {
|
|
|
log.warn("不支持的加密算法:{}", encryptAlgorithm);
|
|
log.warn("不支持的加密算法:{}", encryptAlgorithm);
|
|
|
- return R.fail("不支持的加密算法");
|
|
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- // ========== 第三步:AES-256-GCM解密,获取明文业务信息 ==========
|
|
|
|
|
String plainBusinessData = WeChatPayUtil.decrypt(
|
|
String plainBusinessData = WeChatPayUtil.decrypt(
|
|
|
- apiV3key,
|
|
|
|
|
- resourceNonce,
|
|
|
|
|
- associatedData,
|
|
|
|
|
- ciphertext
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ apiV3key, resourceNonce, associatedData, ciphertext);
|
|
|
log.info("微信支付回调解密后的业务信息:{}", plainBusinessData);
|
|
log.info("微信支付回调解密后的业务信息:{}", plainBusinessData);
|
|
|
JSONObject jsonObject = JSONObject.parseObject(plainBusinessData);
|
|
JSONObject jsonObject = JSONObject.parseObject(plainBusinessData);
|
|
|
String tradeState = jsonObject.getString("trade_state");
|
|
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.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 {
|
|
try {
|
|
|
storeOrderService.resetTableAfterPayment(storeOrder.getTableId());
|
|
storeOrderService.resetTableAfterPayment(storeOrder.getTableId());
|
|
|
log.info("支付完成后重置餐桌成功, tableId={}", 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
|
|
@Override
|