Browse Source

优化微信支付逻辑

woody 11 tháng trước cách đây
mục cha
commit
eb8082acdf

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

@@ -24,6 +24,16 @@ public class WechatPaymentCallback implements Serializable {
      */
     private String order;
 
+    /**
+     * 商户号
+     */
+    private String merchant;
+
+    /**
+     * 三方单号
+     */
+    private String thirdhand;
+
     /**
      * 交易单号
      */

+ 33 - 11
framework-wechat/src/main/java/com/chelvc/framework/wechat/WechatPaymentHandler.java

@@ -1,6 +1,7 @@
 package com.chelvc.framework.wechat;
 
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * 微信支付操作接口
@@ -15,25 +16,37 @@ public interface WechatPaymentHandler {
      * @param name 应用名称
      * @return true/false
      */
-    boolean supports(String name);
+    default boolean supports(String name) {
+        return this.supports(name, null);
+    }
 
     /**
-     * 判断是否指定渠道
+     * 判断是否支持指定应用
      *
      * @param name    应用名称
      * @param channel 支付渠道
      * @return true/false
      */
-    boolean isChannel(String name, PayChannel channel);
+    boolean supports(String name, PayChannel channel);
 
     /**
-     * 判断是否是指定商户
+     * 获取微信支付处理器
      *
-     * @param name     应用名称
-     * @param merchant 商户标识
-     * @return true/false
+     * @param name 应用名称
+     * @return 支付处理器实例
+     */
+    default WechatPaymentProcessor getProcessor(String name) {
+        return this.getProcessor(name, null);
+    }
+
+    /**
+     * 获取微信支付处理器
+     *
+     * @param name    应用名称
+     * @param channel 支付渠道
+     * @return 支付处理器实例
      */
-    boolean isMerchant(String name, String merchant);
+    WechatPaymentProcessor getProcessor(String name, PayChannel channel);
 
     /**
      * 参数签名
@@ -42,7 +55,10 @@ public interface WechatPaymentHandler {
      * @param parameters 签名参数
      * @return 签名信息
      */
-    String sign(String name, Map<String, String> parameters);
+    default String sign(String name, Map<String, String> parameters) {
+        WechatPaymentProcessor processor = this.getProcessor(name);
+        return Objects.requireNonNull(processor).sign(parameters);
+    }
 
     /**
      * 支付回调参数解析
@@ -51,7 +67,10 @@ public interface WechatPaymentHandler {
      * @param content 支付回调内容
      * @return 支付回调信息
      */
-    WechatPaymentCallback callback(String name, String content);
+    default WechatPaymentCallback callback(String name, String content) {
+        WechatPaymentProcessor processor = this.getProcessor(name);
+        return Objects.requireNonNull(processor).callback(content);
+    }
 
     /**
      * 微信统一下单
@@ -60,5 +79,8 @@ public interface WechatPaymentHandler {
      * @param request 支付请求参数
      * @return 微信统一下单信息
      */
-    WechatUnifiedOrder unifiedorder(String name, WechatPayRequest request);
+    default WechatUnifiedOrder unifiedorder(String name, WechatPayRequest request) {
+        WechatPaymentProcessor processor = this.getProcessor(name);
+        return Objects.requireNonNull(processor).unifiedorder(request);
+    }
 }

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

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

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

@@ -7,12 +7,12 @@ 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.WechatAppletHandler;
-import com.chelvc.framework.wechat.WechatPaymentHandler;
+import com.chelvc.framework.wechat.WechatPaymentProcessor;
 import com.chelvc.framework.wechat.WechatPublicHandler;
 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 com.chelvc.framework.wechat.support.StandardWechatPaymentProcessor;
 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<WechatPaymentHandler> handlers = this.properties.getPayments().stream().map(payment -> {
+            List<WechatPaymentProcessor> processors = this.properties.getPayments().stream().map(payment -> {
                 if (payment.getChannel() == null || payment.getChannel() == PayChannel.WECHAT) {
-                    return new DefaultWechatPaymentHandler(payment);
+                    return new StandardWechatPaymentProcessor(payment);
                 }
                 throw new IllegalArgumentException("Not support payment channel: " + payment.getChannel());
             }).collect(Collectors.toList());
-            this.applicationContext.registerBean(DelegatingWechatPaymentHandler.class, handlers);
+            this.applicationContext.registerBean(DefaultWechatPaymentHandler.class, processors);
         }
     }
 

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

@@ -122,7 +122,7 @@ public class WechatProperties {
         private String mchid;
 
         /**
-         * 商户
+         * 商户
          */
         private String mchkey;
 

+ 43 - 203
framework-wechat/src/main/java/com/chelvc/framework/wechat/support/DefaultWechatPaymentHandler.java

@@ -1,35 +1,18 @@
 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.List;
 import java.util.Objects;
 
-import com.chelvc.framework.base.context.RestContextHolder;
+import com.chelvc.framework.base.context.ApplicationContextHolder;
 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;
 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.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;
-import com.github.wxpay.sdk.WXPayUtil;
-import com.google.common.collect.Maps;
+import com.chelvc.framework.wechat.WechatPaymentProcessor;
+import com.google.common.collect.Lists;
 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;
 
 /**
  * 微信支付操作默认实现
@@ -39,202 +22,59 @@ import org.springframework.http.MediaType;
  */
 @Slf4j
 public class DefaultWechatPaymentHandler implements WechatPaymentHandler {
-    /**
-     * 微信下单接口地址
-     */
-    private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+    private final List<WechatPaymentProcessor> processors;
 
-    /**
-     * 支付回调响应内容
-     */
-    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 DefaultWechatPaymentHandler(@NonNull WechatProperties.Payment properties) {
-        this.properties = properties;
-    }
-
-    /**
-     * 获取处理器名称
-     *
-     * @return 处理器名称
-     */
-    protected String getName() {
-        return this.properties.getName();
-    }
-
-    /**
-     * 获取应用标识
-     *
-     * @return 应用标识
-     */
-    protected String getAppid() {
-        return this.properties.getAppid();
-    }
-
-    /**
-     * 获取商户号标识
-     *
-     * @return 商户号标识
-     */
-    protected String getMchid() {
-        return this.properties.getMchid();
-    }
-
-    /**
-     * 获取支付方式
-     *
-     * @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 boolean supports(String name) {
-        return Objects.equals(name, this.getName());
+    public DefaultWechatPaymentHandler(List<WechatPaymentProcessor> processors) {
+        AssertUtils.nonempty(processors, () -> "Wechat payment processors must not be empty");
+        this.processors = Lists.newArrayList(processors);
     }
 
     @Override
-    public boolean isChannel(String name, PayChannel channel) {
-        return this.supports(name) && channel == this.getChannel();
-    }
-
-    @Override
-    public boolean isMerchant(String name, String merchant) {
-        return this.supports(name) && Objects.equals(merchant, this.getMchid());
-    }
-
-    @Override
-    public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
-        AssertUtils.check(this.supports(name), name);
-        if (log.isDebugEnabled()) {
-            log.error("Wechat payment signature: {}", parameters);
+    public boolean supports(@NonNull String name, PayChannel channel) {
+        for (WechatPaymentProcessor processor : this.processors) {
+            if (Objects.equals(name, processor.getName()) && (channel == null || channel == processor.getChannel())) {
+                return true;
+            }
         }
-        return WechatContextHolder.sign(this.properties.getMchkey(), parameters);
+        return false;
     }
 
     @Override
-    public WechatPaymentCallback callback(@NonNull String name, @NonNull String content) {
-        AssertUtils.check(this.supports(name), name);
-        Map<String, String> notification = WechatContextHolder.xml2map(content);
-        if (log.isDebugEnabled()) {
-            log.error("Wechat payment callback: {}", notification);
+    public WechatPaymentProcessor getProcessor(@NonNull String name, PayChannel channel) {
+        WechatPaymentProcessor optional = null;
+        String merchant = ApplicationContextHolder.getProperty("wechat.payment." + name + ".merchant");
+        for (WechatPaymentProcessor processor : this.processors) {
+            if (Objects.equals(name, processor.getName()) && (channel == null || channel == processor.getChannel())) {
+                if (optional == null) {
+                    optional = processor;
+                }
+
+                // 优先使用指定商户号支付配置
+                if (StringUtils.isEmpty(merchant) || Objects.equals(merchant, processor.getMchid())) {
+                    return processor;
+                }
+            }
         }
-        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();
+        if (optional == null) {
+            throw new IllegalArgumentException("Wechat payment processor not found: " + name);
         }
-        log.error("Wechat payment callback failure: {}", content);
-        return null;
+        return optional;
     }
 
     @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());
-        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", 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());
-        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(name, 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(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);
+    public WechatPaymentCallback callback(@NonNull String name, @NonNull String content) {
+        for (WechatPaymentProcessor processor : this.processors) {
+            if (Objects.equals(name, processor.getName())) {
+                try {
+                    WechatPaymentCallback callback = processor.callback(content);
+                    if (Objects.nonNull(callback)) {
+                        return callback;
+                    }
+                } catch (Exception e) {
+                    log.error("Wechat payment callback process failed: {}", content, e);
+                }
             }
-            throw new RuntimeException("Wechat unifiedorder failed: " + message);
         }
-        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")).channel(this.getChannel())
-                .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;
+        return null;
     }
 }

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

@@ -1,117 +0,0 @@
-package com.chelvc.framework.wechat.support;
-
-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.AssertUtils;
-import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.wechat.PayChannel;
-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);
-    }
-
-    /**
-     * 查找微信支付处理器
-     *
-     * @param name 处理器名称
-     * @return 处理器实例
-     */
-    private WechatPaymentHandler lookupPaymentHandler(String name) {
-        WechatPaymentHandler optional = null;
-        String merchant = ApplicationContextHolder.getProperty("wechat.payment." + name + ".merchant");
-        for (WechatPaymentHandler handler : this.handlers) {
-            if (handler.supports(name)) {
-                if (optional == null) {
-                    optional = handler;
-                }
-
-                // 优先使用指定商户号支付配置
-                if (StringUtils.isEmpty(merchant) || handler.isMerchant(name, merchant)) {
-                    return handler;
-                }
-            }
-        }
-        if (optional == null) {
-            throw new IllegalArgumentException("Wechat payment handler not found: " + name);
-        }
-        return optional;
-    }
-
-    @Override
-    public boolean supports(String name) {
-        for (WechatPaymentHandler handler : this.handlers) {
-            if (handler.supports(name)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean isChannel(String name, PayChannel channel) {
-        for (WechatPaymentHandler handler : this.handlers) {
-            if (handler.isChannel(name, channel)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean isMerchant(String name, String merchant) {
-        for (WechatPaymentHandler handler : this.handlers) {
-            if (handler.isMerchant(name, merchant)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public String sign(@NonNull String name, @NonNull Map<String, String> parameters) {
-        return this.lookupPaymentHandler(name).sign(name, parameters);
-    }
-
-    @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) {
-        return this.lookupPaymentHandler(name).unifiedorder(name, request);
-    }
-}

+ 193 - 0
framework-wechat/src/main/java/com/chelvc/framework/wechat/support/StandardWechatPaymentProcessor.java

@@ -0,0 +1,193 @@
+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.DateUtils;
+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.WechatPaymentCallback;
+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.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/9/10
+ */
+@Slf4j
+public class StandardWechatPaymentProcessor implements WechatPaymentProcessor {
+    /**
+     * 微信下单接口地址
+     */
+    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 StandardWechatPaymentProcessor(@NonNull WechatProperties.Payment properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * 将字符串转换成日期对象
+     *
+     * @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 String getName() {
+        return this.properties.getName();
+    }
+
+    @Override
+    public String getAppid() {
+        return this.properties.getAppid();
+    }
+
+    @Override
+    public String getMchid() {
+        return this.properties.getMchid();
+    }
+
+    @Override
+    public PayMode getMode() {
+        return this.properties.getMode();
+    }
+
+    @Override
+    public PayChannel getChannel() {
+        return PayChannel.WECHAT;
+    }
+
+    @Override
+    public String getCallback() {
+        return this.properties.getCallback();
+    }
+
+    @Override
+    public String sign(@NonNull Map<String, String> parameters) {
+        return WechatContextHolder.sign(this.properties.getMchkey(), parameters);
+    }
+
+    @Override
+    public WechatPaymentCallback callback(@NonNull String content) {
+        Map<String, String> notification = WechatContextHolder.xml2map(content);
+        if (log.isDebugEnabled()) {
+            log.error("Wechat payment callback: {}", notification);
+        }
+        if (Objects.equals(notification.get("return_code"), "SUCCESS")
+                && Objects.equals(this.sign(notification), notification.get("sign"))) {
+            return WechatPaymentCallback.builder().order(notification.get("out_trade_no"))
+                    .merchant(notification.get("mch_id")).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 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", 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());
+        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);
+        }
+        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")).channel(this.getChannel())
+                .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;
+    }
+}