فهرست منبع

优化微信支付逻辑

woody 9 ماه پیش
والد
کامیت
6d9bd50d7f

+ 9 - 8
framework-common/src/main/java/com/chelvc/framework/common/crypto/CipherFactory.java

@@ -7,6 +7,7 @@ import javax.crypto.Cipher;
 
 import com.chelvc.framework.common.util.CodecUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
 import lombok.NonNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,7 +68,7 @@ public interface CipherFactory {
      * @return 密文
      */
     default String encrypt(String plaintext, boolean force) {
-        return plaintext == null ? null : this.encrypt(this.getEncryptor(), plaintext, force);
+        return StringUtils.isEmpty(plaintext) ? plaintext : this.encrypt(this.getEncryptor(), plaintext, force);
     }
 
     /**
@@ -78,7 +79,7 @@ public interface CipherFactory {
      * @return 密文
      */
     default String encrypt(@NonNull Cipher cipher, String plaintext) {
-        return CodecUtils.encrypt(cipher, plaintext);
+        return StringUtils.isEmpty(plaintext) ? plaintext : CodecUtils.encrypt(cipher, plaintext);
     }
 
     /**
@@ -90,8 +91,8 @@ public interface CipherFactory {
      * @return 密文
      */
     default String encrypt(@NonNull Cipher cipher, String plaintext, boolean force) {
-        if (plaintext == null) {
-            return null;
+        if (StringUtils.isEmpty(plaintext)) {
+            return plaintext;
         }
         try {
             return this.encrypt(cipher, plaintext);
@@ -202,7 +203,7 @@ public interface CipherFactory {
      * @return 明文
      */
     default String decrypt(String ciphertext, boolean force) {
-        return ciphertext == null ? null : this.decrypt(this.getDecrypter(), ciphertext, force);
+        return StringUtils.isEmpty(ciphertext) ? ciphertext : this.decrypt(this.getDecrypter(), ciphertext, force);
     }
 
     /**
@@ -213,7 +214,7 @@ public interface CipherFactory {
      * @return 明文
      */
     default String decrypt(@NonNull Cipher cipher, String ciphertext) {
-        return CodecUtils.decrypt(cipher, ciphertext);
+        return StringUtils.isEmpty(ciphertext) ? ciphertext : CodecUtils.decrypt(cipher, ciphertext);
     }
 
     /**
@@ -225,8 +226,8 @@ public interface CipherFactory {
      * @return 明文
      */
     default String decrypt(@NonNull Cipher cipher, String ciphertext, boolean force) {
-        if (ciphertext == null) {
-            return null;
+        if (StringUtils.isEmpty(ciphertext)) {
+            return ciphertext;
         }
         try {
             return this.decrypt(cipher, ciphertext);

+ 20 - 119
framework-wechat/src/main/java/com/chelvc/framework/wechat/DefaultWechatPaymentHandler.java

@@ -1,28 +1,14 @@
 package com.chelvc.framework.wechat;
 
-import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
 import com.chelvc.framework.base.context.ApplicationContextHolder;
-import com.chelvc.framework.base.context.RestContextHolder;
-import com.chelvc.framework.common.util.AssertUtils;
-import com.chelvc.framework.common.util.HostUtils;
-import com.chelvc.framework.common.util.NumberUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.wechat.config.WechatProperties;
-import com.chelvc.framework.wechat.context.WechatContextHolder;
-import com.github.wxpay.sdk.WXPayUtil;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import lombok.NonNull;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.MediaType;
 
 /**
  * 微信支付操作默认实现
@@ -30,38 +16,32 @@ import org.springframework.http.MediaType;
  * @author Woody
  * @date 2024/6/19
  */
-@Slf4j
 public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
-    /**
-     * 微信下单接口地址
-     */
-    private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
-
-    private final List<WechatProperties.Payment> payments;
+    private final List<WechatPaymentProcessor> processors;
 
-    public DefaultWechatPaymentHandler(@NonNull List<WechatProperties.Payment> payments) {
-        this.payments = Lists.newArrayList(payments);
+    public DefaultWechatPaymentHandler(@NonNull List<WechatPaymentProcessor> processors) {
+        this.processors = Lists.newArrayList(processors);
     }
 
     /**
-     * 查找支付配置
+     * 查找支付处理器
      *
-     * @param name 应用名称
-     * @return 支付配置
+     * @param name 处理器名称
+     * @return 支付处理器实例
      */
-    private WechatProperties.Payment lookupPayment(String name) {
-        if (ObjectUtils.notEmpty(this.payments)) {
-            WechatProperties.Payment optional = null;
+    private WechatPaymentProcessor lookupPaymentProcessor(String name) {
+        if (ObjectUtils.notEmpty(this.processors)) {
+            WechatPaymentProcessor optional = null;
             String merchant = ApplicationContextHolder.getProperty("wechat.payment." + name + ".merchant");
-            for (WechatProperties.Payment payment : this.payments) {
-                if (Objects.equals(payment.getName(), name)) {
+            for (WechatPaymentProcessor processor : this.processors) {
+                if (Objects.equals(processor.getName(), name)) {
                     if (optional == null) {
-                        optional = payment;
+                        optional = processor;
                     }
 
                     // 优先使用指定商户号支付配置
-                    if (StringUtils.isEmpty(merchant) || Objects.equals(payment.getMchid(), merchant)) {
-                        return payment;
+                    if (StringUtils.isEmpty(merchant) || Objects.equals(processor.getMchid(), merchant)) {
+                        return processor;
                     }
                 }
             }
@@ -71,54 +51,25 @@ public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
                 return optional;
             }
         }
-        throw new IllegalArgumentException("Not support payment: " + name);
-    }
-
-    /**
-     * 构建支付参数
-     *
-     * @param payment 支付配置
-     * @param request 支付请求参数
-     * @return 支付参数键/值对
-     */
-    private Map<String, String> buildPaymentParameter(WechatProperties.Payment payment, WechatPayRequest request) {
-        String mchid = AssertUtils.nonempty(payment.getMchid(), () -> "Payment mchid is missing");
-        String callback = AssertUtils.nonempty(payment.getCallback(), () -> "Payment callback is missing");
-        Map<String, String> parameters = Maps.newHashMap();
-        parameters.put("appid", payment.getAppid());
-        parameters.put("mch_id", mchid);
-        parameters.put("body", request.getComment());
-        parameters.put("out_trade_no", request.getOrder());
-        if (StringUtils.notEmpty(request.getContext())) {
-            parameters.put("attach", request.getContext());
-        }
-        parameters.put("fee_type", "CNY");
-        parameters.put("total_fee", NumberUtils.format(NumberUtils.multiply100(request.getAmount()), true));
-        parameters.put("spbill_create_ip", HostUtils.LOCAL_ADDRESS);
-        parameters.put("notify_url", callback);
-        parameters.put("trade_type", payment.getMode().name());
-        parameters.put("nonce_str", WXPayUtil.generateNonceStr());
-        return parameters;
+        throw new UnsupportedOperationException("Wechat payment processor not found: " + name);
     }
 
     @Override
     public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
-        WechatProperties.Payment payment = this.lookupPayment(name);
-        return WechatContextHolder.sign(payment.getMchkey(), parameters);
+        return this.lookupPaymentProcessor(name).sign(parameters);
     }
 
     @Override
     public boolean validate(@NonNull String name, @NonNull Map<String, String> parameters) {
-        if (ObjectUtils.isEmpty(this.payments)) {
+        if (ObjectUtils.isEmpty(this.processors)) {
             return false;
         }
         String sign = parameters.get("sign");
         if (StringUtils.isEmpty(sign)) {
             return false;
         }
-        for (WechatProperties.Payment payment : this.payments) {
-            if (Objects.equals(payment.getName(), name)
-                    && Objects.equals(WechatContextHolder.sign(payment.getMchkey(), parameters), sign)) {
+        for (WechatPaymentProcessor processor : this.processors) {
+            if (Objects.equals(processor.getName(), name) && Objects.equals(processor.sign(parameters), sign)) {
                 return true;
             }
         }
@@ -127,56 +78,6 @@ public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
 
     @Override
     public WechatUnifiedOrder unifiedorder(@NonNull String name, @NonNull WechatPayRequest request) {
-        // 获取支付配置
-        WechatProperties.Payment payment = this.lookupPayment(name);
-
-        // 构建请求参数
-        Map<String, String> parameters = this.buildPaymentParameter(payment, request);
-
-        if (payment.getMode() == PayMode.MWEB) {
-            // 如果是H5支付则需要指定scene_info,场景信息
-            parameters.put("scene_info", AssertUtils.nonempty(request.getScene(), () -> "Payment scene is missing"));
-        } else if (payment.getMode() == PayMode.JSAPI) {
-            // 如果是小程序支付则需要指定openid
-            parameters.put("openid", AssertUtils.nonempty(request.getOpenid(), () -> "Payment openid is missing"));
-        }
-
-        // 请求参数签名
-        String mchkey = payment.getMchkey();
-        parameters.put("sign", WechatContextHolder.sign(mchkey, parameters));
-        boolean debug = log.isDebugEnabled();
-        if (debug) {
-            log.debug("Wechat unifiedorder request: {}", parameters);
-        }
-
-        // 将请求参数转换成xml请求体
-        String body = WechatContextHolder.map2xml(parameters);
-
-        // 发起下单请求
-        HttpHeaders headers = new HttpHeaders();
-        headers.setContentType(MediaType.TEXT_XML);
-        HttpEntity<?> entity = new HttpEntity<>(body, headers);
-        String response = RestContextHolder.execute(rest -> rest.exchange(
-                UNIFIEDORDER_URL, HttpMethod.POST, entity, String.class
-        )).getBody();
-
-        // 验证相应结果
-        Map<String, String> result = ObjectUtils.ifNull(response, WechatContextHolder::xml2map);
-        if (debug) {
-            log.debug("Wechat unifiedorder response: {}", result);
-        }
-        if (Objects.isNull(result) || !Objects.equals(result.get("return_code"), "SUCCESS")
-                || !Objects.equals(result.get("sign"), WechatContextHolder.sign(mchkey, 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(payment.getMode()).appid(result.get("appid"))
-                .mchid(result.get("mch_id")).nonce(result.get("nonce_str")).prepayid(result.get("prepay_id"))
-                .qrcode(result.get("code_url")).redirect(result.get("mweb_url")).build();
+        return this.lookupPaymentProcessor(name).unifiedorder(request);
     }
 }

+ 14 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/PayChannel.java

@@ -0,0 +1,14 @@
+package com.chelvc.framework.wechat;
+
+/**
+ * 支付渠道枚举
+ *
+ * @author Woody
+ * @date 2024/8/28
+ */
+public enum PayChannel {
+    /**
+     * 微信支付
+     */
+    WECHAT;
+}

+ 5 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatPayRequest.java

@@ -44,6 +44,11 @@ public class WechatPayRequest implements Serializable {
      */
     private String openid;
 
+    /**
+     * 微信unionid
+     */
+    private String unionid;
+
     /**
      * 上下文参数
      */

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

@@ -0,0 +1,69 @@
+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);
+}

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

@@ -1,12 +1,18 @@
 package com.chelvc.framework.wechat.config;
 
+import java.util.List;
+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.WechatPublicHandler;
+import com.chelvc.framework.wechat.support.DefaultWechatPaymentProcessor;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -54,7 +60,13 @@ public class WechatConfigurer implements BeanPostProcessor {
 
         // 注册微信支付操作Bean
         if (ObjectUtils.notEmpty(this.properties.getPayments())) {
-            this.applicationContext.registerBean(DefaultWechatPaymentHandler.class, this.properties.getPayments());
+            List<WechatPaymentProcessor> processors = this.properties.getPayments().stream().map(payment -> {
+                if (payment.getChannel() == null || payment.getChannel() == PayChannel.WECHAT) {
+                    return new DefaultWechatPaymentProcessor(payment);
+                }
+                throw new UnsupportedOperationException("Not support payment channel: " + payment.getChannel());
+            }).collect(Collectors.toList());
+            this.applicationContext.registerBean(DefaultWechatPaymentHandler.class, processors);
         }
     }
 

+ 6 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/config/WechatProperties.java

@@ -3,6 +3,7 @@ package com.chelvc.framework.wechat.config;
 import java.util.Collections;
 import java.util.List;
 
+import com.chelvc.framework.wechat.PayChannel;
 import com.chelvc.framework.wechat.PayMode;
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -129,6 +130,11 @@ public class WechatProperties {
          * 支付回调地址
          */
         private String callback;
+
+        /**
+         * 支付渠道
+         */
+        private PayChannel channel;
     }
 
     /**

+ 138 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatPaymentProcessor.java

@@ -0,0 +1,138 @@
+package com.chelvc.framework.wechat.support;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Objects;
+
+import com.chelvc.framework.base.context.RestContextHolder;
+import com.chelvc.framework.common.util.HostUtils;
+import com.chelvc.framework.common.util.NumberUtils;
+import com.chelvc.framework.common.util.ObjectUtils;
+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.WechatUnifiedOrder;
+import com.chelvc.framework.wechat.config.WechatProperties;
+import com.chelvc.framework.wechat.context.WechatContextHolder;
+import com.github.wxpay.sdk.WXPayUtil;
+import com.google.common.collect.Maps;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+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
+ */
+@Slf4j
+public class DefaultWechatPaymentProcessor implements WechatPaymentProcessor {
+    /**
+     * 微信下单接口地址
+     */
+    private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+
+    private final WechatProperties.Payment properties;
+
+    public DefaultWechatPaymentProcessor(@NonNull WechatProperties.Payment properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public String getName() {
+        return this.properties.getName();
+    }
+
+    @Override
+    public PayMode getMode() {
+        return this.properties.getMode();
+    }
+
+    @Override
+    public String getAppid() {
+        return this.properties.getAppid();
+    }
+
+    @Override
+    public String getMchid() {
+        return this.properties.getMchid();
+    }
+
+    @Override
+    public String getCallback() {
+        return this.properties.getCallback();
+    }
+
+    @Override
+    public PayChannel getChannel() {
+        return this.properties.getChannel();
+    }
+
+    @Override
+    public String sign(@NonNull Map<String, String> parameters) {
+        return WechatContextHolder.sign(this.properties.getMchkey(), parameters);
+    }
+
+    @Override
+    public WechatUnifiedOrder unifiedorder(@NonNull WechatPayRequest request) {
+        // 构建请求参数
+        Map<String, String> parameters = Maps.newHashMap();
+        parameters.put("appid", this.getAppid());
+        parameters.put("mch_id", this.getMchid());
+        parameters.put("body", request.getComment());
+        parameters.put("out_trade_no", request.getOrder());
+        if (StringUtils.notEmpty(request.getContext())) {
+            parameters.put("attach", request.getContext());
+        }
+        parameters.put("fee_type", "CNY");
+        parameters.put("total_fee", NumberUtils.format(NumberUtils.multiply100(request.getAmount()), true));
+        parameters.put("spbill_create_ip", HostUtils.LOCAL_ADDRESS);
+        parameters.put("notify_url", this.getCallback());
+        parameters.put("trade_type", this.getMode().name());
+        parameters.put("nonce_str", WXPayUtil.generateNonceStr());
+        if (this.getMode() == PayMode.MWEB) {
+            // 如果是H5支付则需要指定scene_info,场景信息
+            parameters.put("scene_info", request.getScene());
+        } else if (this.getMode() == PayMode.JSAPI) {
+            // 如果是小程序支付则需要指定openid
+            parameters.put("openid", request.getOpenid());
+        }
+
+        // 请求参数签名
+        parameters.put("sign", this.sign(parameters));
+        boolean debug = log.isDebugEnabled();
+        if (debug) {
+            log.debug("Wechat unifiedorder request: {}", parameters);
+        }
+
+        // 下单接口调用
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.TEXT_XML);
+        String body = WechatContextHolder.map2xml(parameters);
+        HttpEntity<?> entity = new HttpEntity<>(body, headers);
+        String response = RestContextHolder.execute(rest -> rest.exchange(
+                UNIFIEDORDER_URL, HttpMethod.POST, entity, String.class
+        )).getBody();
+        Map<String, String> result = ObjectUtils.ifNull(response, WechatContextHolder::xml2map);
+        if (debug) {
+            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))) {
+            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();
+    }
+}