|
@@ -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;
|
|
|
}
|
|
|
}
|