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