Bläddra i källkod

优化微信支付处理逻辑

woody 7 månader sedan
förälder
incheckning
3260dec866

+ 0 - 83
framework-wechat/src/main/java/com/chelvc/framework/wechat/DefaultWechatPaymentHandler.java

@@ -1,83 +0,0 @@
-package com.chelvc.framework.wechat;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import com.chelvc.framework.base.context.ApplicationContextHolder;
-import com.chelvc.framework.common.util.ObjectUtils;
-import com.chelvc.framework.common.util.StringUtils;
-import com.google.common.collect.Lists;
-import lombok.NonNull;
-
-/**
- * 微信支付操作默认实现
- *
- * @author Woody
- * @date 2024/6/19
- */
-public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
-    private final List<WechatPaymentProcessor> processors;
-
-    public DefaultWechatPaymentHandler(@NonNull List<WechatPaymentProcessor> processors) {
-        this.processors = Lists.newArrayList(processors);
-    }
-
-    /**
-     * 查找支付处理器
-     *
-     * @param name 处理器名称
-     * @return 支付处理器实例
-     */
-    private WechatPaymentProcessor lookupPaymentProcessor(String name) {
-        if (ObjectUtils.notEmpty(this.processors)) {
-            WechatPaymentProcessor optional = null;
-            String merchant = ApplicationContextHolder.getProperty("wechat.payment." + name + ".merchant");
-            for (WechatPaymentProcessor processor : this.processors) {
-                if (Objects.equals(processor.getName(), name)) {
-                    if (optional == null) {
-                        optional = processor;
-                    }
-
-                    // 优先使用指定商户号支付配置
-                    if (StringUtils.isEmpty(merchant) || Objects.equals(processor.getMchid(), merchant)) {
-                        return processor;
-                    }
-                }
-            }
-
-            // 使用同应用类型的其他支付配置
-            if (Objects.nonNull(optional)) {
-                return optional;
-            }
-        }
-        throw new UnsupportedOperationException("Wechat payment processor not found: " + name);
-    }
-
-    @Override
-    public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
-        return this.lookupPaymentProcessor(name).sign(parameters);
-    }
-
-    @Override
-    public boolean validate(@NonNull String name, @NonNull Map<String, String> parameters) {
-        if (ObjectUtils.isEmpty(this.processors)) {
-            return false;
-        }
-        String sign = parameters.get("sign");
-        if (StringUtils.isEmpty(sign)) {
-            return false;
-        }
-        for (WechatPaymentProcessor processor : this.processors) {
-            if (Objects.equals(processor.getName(), name) && Objects.equals(processor.sign(parameters), sign)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public WechatUnifiedOrder unifiedorder(@NonNull String name, @NonNull WechatPayRequest request) {
-        return this.lookupPaymentProcessor(name).unifiedorder(request);
-    }
-}

+ 41 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatPaymentCallback.java

@@ -0,0 +1,41 @@
+package com.chelvc.framework.wechat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * 微信支付回调信息
+ *
+ * @author Woody
+ * @date 2024/8/30
+ */
+@Data
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WechatPaymentCallback implements Serializable {
+    /**
+     * 订单号
+     */
+    private String order;
+
+    /**
+     * 交易单号
+     */
+    private String transaction;
+
+    /**
+     * 支付时间
+     */
+    private Date datetime;
+
+    /**
+     * 回调响应内容
+     */
+    private String response;
+}

+ 13 - 5
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatPaymentHandler.java

@@ -9,6 +9,14 @@ import java.util.Map;
  * @date 2024/6/19
  */
 public interface WechatPaymentHandler {
+    /**
+     * 判断是否支持指定应用
+     *
+     * @param name 应用名称
+     * @return true/false
+     */
+    boolean supports(String name);
+
     /**
      * 参数签名
      *
@@ -19,13 +27,13 @@ public interface WechatPaymentHandler {
     String sign(String name, Map<String, String> parameters);
 
     /**
-     * 校验参数有效性
+     * 支付回调参数解析
      *
-     * @param name       应用名称
-     * @param parameters 参数键/值对
-     * @return true/false
+     * @param name    应用名称
+     * @param content 支付回调内容
+     * @return 支付回调信息
      */
-    boolean validate(String name, Map<String, String> parameters);
+    WechatPaymentCallback callback(String name, String content);
 
     /**
      * 微信统一下单

+ 0 - 69
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatPaymentProcessor.java

@@ -1,69 +0,0 @@
-package com.chelvc.framework.wechat;
-
-import java.util.Map;
-
-/**
- * 微信支付处理器接口
- *
- * @author Woody
- * @date 2024/8/27
- */
-public interface WechatPaymentProcessor {
-    /**
-     * 获取处理器名称
-     *
-     * @return 处理器名称
-     */
-    String getName();
-
-    /**
-     * 获取支付方式
-     *
-     * @return 支付方式
-     */
-    PayMode getMode();
-
-    /**
-     * 获取应用标识
-     *
-     * @return 应用标识
-     */
-    String getAppid();
-
-    /**
-     * 获取商户号标识
-     *
-     * @return 商户号标识
-     */
-    String getMchid();
-
-    /**
-     * 获取支付回调地址
-     *
-     * @return 支付回调地址
-     */
-    String getCallback();
-
-    /**
-     * 获取支付渠道
-     *
-     * @return 支付渠道
-     */
-    PayChannel getChannel();
-
-    /**
-     * 参数签名
-     *
-     * @param parameters 签名参数
-     * @return 签名信息
-     */
-    String sign(Map<String, String> parameters);
-
-    /**
-     * 微信统一下单
-     *
-     * @param request 支付请求参数
-     * @return 微信统一下单信息
-     */
-    WechatUnifiedOrder unifiedorder(WechatPayRequest request);
-}

+ 20 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatUnifiedOrder.java

@@ -33,6 +33,16 @@ public class WechatUnifiedOrder implements Serializable {
      */
     private String mchid;
 
+    /**
+     * 数字签名
+     */
+    private String sign;
+
+    /**
+     * 支付package
+     */
+    private String pack;
+
     /**
      * 随机串
      */
@@ -43,6 +53,11 @@ public class WechatUnifiedOrder implements Serializable {
      */
     private String prepayid;
 
+    /**
+     * 签名算法
+     */
+    private String algorithm;
+
     /**
      * 二维码地址
      */
@@ -52,4 +67,9 @@ public class WechatUnifiedOrder implements Serializable {
      * 跳转链接
      */
     private String redirect;
+
+    /**
+     * 时间戳
+     */
+    private String timestamp;
 }

+ 9 - 9
framework-wechat/src/main/java/com/chelvc/framework/wechat/config/WechatConfigurer.java

@@ -5,14 +5,14 @@ import java.util.stream.Collectors;
 
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.wechat.DefaultWechatAppletHandler;
-import com.chelvc.framework.wechat.DefaultWechatPaymentHandler;
-import com.chelvc.framework.wechat.DefaultWechatPublicHandler;
 import com.chelvc.framework.wechat.PayChannel;
 import com.chelvc.framework.wechat.WechatAppletHandler;
-import com.chelvc.framework.wechat.WechatPaymentProcessor;
+import com.chelvc.framework.wechat.WechatPaymentHandler;
 import com.chelvc.framework.wechat.WechatPublicHandler;
-import com.chelvc.framework.wechat.support.DefaultWechatPaymentProcessor;
+import com.chelvc.framework.wechat.support.DefaultWechatAppletHandler;
+import com.chelvc.framework.wechat.support.DefaultWechatPaymentHandler;
+import com.chelvc.framework.wechat.support.DefaultWechatPublicHandler;
+import com.chelvc.framework.wechat.support.DelegatingWechatPaymentHandler;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -60,13 +60,13 @@ public class WechatConfigurer implements BeanPostProcessor {
 
         // 注册微信支付操作Bean
         if (ObjectUtils.notEmpty(this.properties.getPayments())) {
-            List<WechatPaymentProcessor> processors = this.properties.getPayments().stream().map(payment -> {
+            List<WechatPaymentHandler> handlers = this.properties.getPayments().stream().map(payment -> {
                 if (payment.getChannel() == null || payment.getChannel() == PayChannel.WECHAT) {
-                    return new DefaultWechatPaymentProcessor(payment);
+                    return new DefaultWechatPaymentHandler(payment);
                 }
-                throw new UnsupportedOperationException("Not support payment channel: " + payment.getChannel());
+                throw new IllegalArgumentException("Not support payment channel: " + payment.getChannel());
             }).collect(Collectors.toList());
-            this.applicationContext.registerBean(DefaultWechatPaymentHandler.class, processors);
+            this.applicationContext.registerBean(DelegatingWechatPaymentHandler.class, handlers);
         }
     }
 

+ 6 - 1
framework-wechat/src/main/java/com/chelvc/framework/wechat/DefaultWechatAppletHandler.java → framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatAppletHandler.java

@@ -1,8 +1,13 @@
-package com.chelvc.framework.wechat;
+package com.chelvc.framework.wechat.support;
 
 import java.util.Map;
 
 import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.wechat.WechatAppletHandler;
+import com.chelvc.framework.wechat.WechatMobile;
+import com.chelvc.framework.wechat.WechatScheme;
+import com.chelvc.framework.wechat.WechatSession;
+import com.chelvc.framework.wechat.WechatUser;
 import com.chelvc.framework.wechat.config.WechatProperties;
 import com.chelvc.framework.wechat.context.WechatContextHolder;
 import com.google.common.collect.ImmutableMap;

+ 116 - 28
framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatPaymentProcessor.java → framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatPaymentHandler.java

@@ -1,10 +1,14 @@
 package com.chelvc.framework.wechat.support;
 
+import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
+import java.util.Date;
 import java.util.Map;
 import java.util.Objects;
 
 import com.chelvc.framework.base.context.RestContextHolder;
+import com.chelvc.framework.common.util.AssertUtils;
+import com.chelvc.framework.common.util.DateUtils;
 import com.chelvc.framework.common.util.HostUtils;
 import com.chelvc.framework.common.util.NumberUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
@@ -12,7 +16,8 @@ import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.wechat.PayChannel;
 import com.chelvc.framework.wechat.PayMode;
 import com.chelvc.framework.wechat.WechatPayRequest;
-import com.chelvc.framework.wechat.WechatPaymentProcessor;
+import com.chelvc.framework.wechat.WechatPaymentCallback;
+import com.chelvc.framework.wechat.WechatPaymentHandler;
 import com.chelvc.framework.wechat.WechatUnifiedOrder;
 import com.chelvc.framework.wechat.config.WechatProperties;
 import com.chelvc.framework.wechat.context.WechatContextHolder;
@@ -20,67 +25,142 @@ import com.github.wxpay.sdk.WXPayUtil;
 import com.google.common.collect.Maps;
 import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.MessageDigestAlgorithms;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 
 /**
- * 微信支付处理器默认实现
+ * 微信支付操作默认实现
  *
  * @author Woody
- * @date 2024/8/27
+ * @date 2024/6/19
  */
 @Slf4j
-public class DefaultWechatPaymentProcessor implements WechatPaymentProcessor {
+public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
     /**
      * 微信下单接口地址
      */
     private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
 
+    /**
+     * 支付回调响应内容
+     */
+    private static final String CALLBACK_RESPONSE =
+            "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
+
     private final WechatProperties.Payment properties;
 
-    public DefaultWechatPaymentProcessor(@NonNull WechatProperties.Payment properties) {
+    public DefaultWechatPaymentHandler(@NonNull WechatProperties.Payment properties) {
         this.properties = properties;
     }
 
-    @Override
-    public String getName() {
+    /**
+     * 获取处理器名称
+     *
+     * @return 处理器名称
+     */
+    protected String getName() {
         return this.properties.getName();
     }
 
-    @Override
-    public PayMode getMode() {
-        return this.properties.getMode();
-    }
-
-    @Override
-    public String getAppid() {
+    /**
+     * 获取应用标识
+     *
+     * @return 应用标识
+     */
+    protected String getAppid() {
         return this.properties.getAppid();
     }
 
-    @Override
-    public String getMchid() {
+    /**
+     * 获取商户号标识
+     *
+     * @return 商户号标识
+     */
+    protected String getMchid() {
         return this.properties.getMchid();
     }
 
-    @Override
-    public String getCallback() {
+    /**
+     * 获取支付方式
+     *
+     * @return 支付方式
+     */
+    protected PayMode getMode() {
+        return this.properties.getMode();
+    }
+
+    /**
+     * 获取支付渠道
+     *
+     * @return 支付渠道
+     */
+    protected PayChannel getChannel() {
+        return this.properties.getChannel();
+    }
+
+    /**
+     * 获取支付回调地址
+     *
+     * @return 支付回调地址
+     */
+    protected String getCallback() {
         return this.properties.getCallback();
     }
 
+    /**
+     * 将字符串转换成日期对象
+     *
+     * @param string 日期字符串形式
+     * @return 日期对象
+     */
+    protected Date string2datetime(String string) {
+        return StringUtils.isEmpty(string) ? null : DateUtils.parse(string, DateUtils.DATETIME_NUMBER_FORMATTER);
+    }
+
+    /**
+     * 将金额转换成字符串
+     *
+     * @param amount 金额
+     * @return 金额字符串形式
+     */
+    protected String amount2string(BigDecimal amount) {
+        return NumberUtils.format(NumberUtils.multiply100(amount), true);
+    }
+
     @Override
-    public PayChannel getChannel() {
-        return this.properties.getChannel();
+    public boolean supports(@NonNull String name) {
+        return Objects.equals(name, this.getName());
     }
 
     @Override
-    public String sign(@NonNull Map<String, String> parameters) {
+    public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
+        AssertUtils.check(this.supports(name), name);
+
         return WechatContextHolder.sign(this.properties.getMchkey(), parameters);
     }
 
     @Override
-    public WechatUnifiedOrder unifiedorder(@NonNull WechatPayRequest request) {
+    public WechatPaymentCallback callback(@NonNull String name, @NonNull String content) {
+        AssertUtils.check(this.supports(name), name);
+
+        Map<String, String> notification = WechatContextHolder.xml2map(content);
+        if (Objects.equals(notification.get("return_code"), "SUCCESS")
+                && Objects.equals(this.sign(name, notification), notification.get("sign"))) {
+            return WechatPaymentCallback.builder().order(notification.get("out_trade_no"))
+                    .transaction(notification.get("transaction_id")).response(CALLBACK_RESPONSE)
+                    .datetime(this.string2datetime(notification.get("time_end"))).build();
+        }
+        log.error("Wechat payment callback failure: {}", content);
+        return null;
+    }
+
+    @Override
+    public WechatUnifiedOrder unifiedorder(@NonNull String name, @NonNull WechatPayRequest request) {
+        AssertUtils.check(this.supports(name), name);
+
         // 构建请求参数
         Map<String, String> parameters = Maps.newHashMap();
         parameters.put("appid", this.getAppid());
@@ -91,7 +171,7 @@ public class DefaultWechatPaymentProcessor implements WechatPaymentProcessor {
             parameters.put("attach", request.getContext());
         }
         parameters.put("fee_type", "CNY");
-        parameters.put("total_fee", NumberUtils.format(NumberUtils.multiply100(request.getAmount()), true));
+        parameters.put("total_fee", this.amount2string(request.getAmount()));
         parameters.put("spbill_create_ip", HostUtils.LOCAL_ADDRESS);
         parameters.put("notify_url", this.getCallback());
         parameters.put("trade_type", this.getMode().name());
@@ -105,7 +185,7 @@ public class DefaultWechatPaymentProcessor implements WechatPaymentProcessor {
         }
 
         // 请求参数签名
-        parameters.put("sign", this.sign(parameters));
+        parameters.put("sign", this.sign(name, parameters));
         boolean debug = log.isDebugEnabled();
         if (debug) {
             log.debug("Wechat unifiedorder request: {}", parameters);
@@ -124,15 +204,23 @@ public class DefaultWechatPaymentProcessor implements WechatPaymentProcessor {
             log.debug("Wechat unifiedorder response: {}", result);
         }
         if (Objects.isNull(result) || !Objects.equals(result.get("return_code"), "SUCCESS")
-                || !Objects.equals(result.get("sign"), this.sign(result))) {
+                || !Objects.equals(result.get("sign"), this.sign(name, result))) {
             String message = result == null ? null : result.get("return_msg");
             if (StringUtils.notEmpty(message)) {
                 message = new String(message.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
             }
             throw new RuntimeException("Wechat unifiedorder failed: " + message);
         }
-        return WechatUnifiedOrder.builder().mode(this.getMode()).appid(this.getAppid()).mchid(this.getMchid())
-                .nonce(result.get("nonce_str")).prepayid(result.get("prepay_id")).qrcode(result.get("code_url"))
-                .redirect(result.get("mweb_url")).build();
+        WechatUnifiedOrder order = WechatUnifiedOrder.builder().mode(this.getMode()).appid(this.getAppid())
+                .mchid(this.getMchid()).nonce(WXPayUtil.generateNonceStr()).prepayid(result.get("prepay_id"))
+                .qrcode(result.get("code_url")).redirect(result.get("mweb_url"))
+                .timestamp(String.valueOf(System.currentTimeMillis() / 1000).substring(0, 10)).build();
+        if (this.getMode() == PayMode.MWEB || this.getMode() == PayMode.JSAPI) {
+            order.setAlgorithm(MessageDigestAlgorithms.MD5);
+            order.setPack("prepay_id=" + order.getPrepayid());
+        } else {
+            order.setPack("Sign=WXPay");
+        }
+        return order;
     }
 }

+ 8 - 1
framework-wechat/src/main/java/com/chelvc/framework/wechat/DefaultWechatPublicHandler.java → framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatPublicHandler.java

@@ -1,4 +1,4 @@
-package com.chelvc.framework.wechat;
+package com.chelvc.framework.wechat.support;
 
 import java.security.MessageDigest;
 import java.util.Collection;
@@ -13,6 +13,13 @@ import java.util.stream.Stream;
 import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.wechat.WechatPublicHandler;
+import com.chelvc.framework.wechat.WechatPublicMessage;
+import com.chelvc.framework.wechat.WechatPublicOpenids;
+import com.chelvc.framework.wechat.WechatPublicSubscriber;
+import com.chelvc.framework.wechat.WechatPublicSubscribers;
+import com.chelvc.framework.wechat.WechatUser;
+import com.chelvc.framework.wechat.WechatWebToken;
 import com.chelvc.framework.wechat.config.WechatProperties;
 import com.chelvc.framework.wechat.context.WechatContextHolder;
 import com.google.common.collect.ImmutableMap;

+ 77 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DelegatingWechatPaymentHandler.java

@@ -0,0 +1,77 @@
+package com.chelvc.framework.wechat.support;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import com.chelvc.framework.common.util.AssertUtils;
+import com.chelvc.framework.wechat.WechatPayRequest;
+import com.chelvc.framework.wechat.WechatPaymentCallback;
+import com.chelvc.framework.wechat.WechatPaymentHandler;
+import com.chelvc.framework.wechat.WechatUnifiedOrder;
+import com.google.common.collect.Lists;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 微信支付处理器委派实现
+ *
+ * @author Woody
+ * @date 2024/6/19
+ */
+@Slf4j
+public class DelegatingWechatPaymentHandler implements WechatPaymentHandler {
+    private final List<WechatPaymentHandler> handlers;
+
+    public DelegatingWechatPaymentHandler(@NonNull List<WechatPaymentHandler> handlers) {
+        AssertUtils.nonempty(handlers, () -> "Delegating handlers must not be empty");
+        this.handlers = Lists.newArrayList(handlers);
+    }
+
+    @Override
+    public boolean supports(@NonNull String name) {
+        for (WechatPaymentHandler handler : this.handlers) {
+            if (handler.supports(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
+        for (WechatPaymentHandler handler : this.handlers) {
+            if (handler.supports(name)) {
+                return handler.sign(name, parameters);
+            }
+        }
+        throw new IllegalArgumentException("Wechat payment handler not found: " + name);
+    }
+
+    @Override
+    public WechatPaymentCallback callback(@NonNull String name, @NonNull String content) {
+        for (WechatPaymentHandler handler : this.handlers) {
+            if (handler.supports(name)) {
+                try {
+                    WechatPaymentCallback callback = handler.callback(name, content);
+                    if (Objects.nonNull(callback)) {
+                        return callback;
+                    }
+                } catch (Exception e) {
+                    log.error("Wechat payment callback handle failed: {}", content, e);
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public WechatUnifiedOrder unifiedorder(@NonNull String name, @NonNull WechatPayRequest request) {
+        for (WechatPaymentHandler handler : this.handlers) {
+            if (handler.supports(name)) {
+                return handler.unifiedorder(name, request);
+            }
+        }
+        throw new IllegalArgumentException("Wechat payment handler not found: " + name);
+    }
+}