ソースを参照

:new: #3063 【微信支付】增加服务商模式关闭订单的接口

Pursuer丶 1 年間 前
コミット
10b1e4db73

+ 61 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerOrderCloseV3Request.java

@@ -0,0 +1,61 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * 服务商关闭订单请求对象类
+ * 文档见:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_3.shtml
+ *
+ * @author Guo Shuai
+ * @version 1.0
+ * @date 2023/3/2
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class WxPayPartnerOrderCloseV3Request implements Serializable {
+  private static final long serialVersionUID = 1L;
+  /**
+   * <pre>
+   * 字段名:服务商商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  服务商商户号,由微信支付生成并下发。
+   *  示例值:1230000109
+   * </pre>
+   */
+  @SerializedName(value = "sp_mchid")
+  private String spMchId;
+  /**
+   * <pre>
+   * 字段名:特约商户商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  特约商户商户号,由微信支付生成并下发。
+   *  示例值:1230000109
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchId;
+  /**
+   * <pre>
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string[6,32]
+   * 描述:
+   *  商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
+   *  示例值:1217752501201407033233368018
+   * </pre>
+   */
+  private transient String outTradeNo;
+}

+ 0 - 13
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java

@@ -23,19 +23,6 @@ public class WxPayPartnerRefundV3Request extends WxPayRefundV3Request implements
   private static final long serialVersionUID = -1L;
   /**
    * <pre>
-   * 字段名:子商户的商户号
-   * 变量名:sub_mchid
-   * 是否必填:是
-   * 类型:string[1, 32]
-   * 描述:
-   *  子商户商户号,由微信支付生成并下发。
-   *  示例值:1230000109
-   * </pre>
-   */
-  @SerializedName(value = "sub_mchid")
-  private String subMchId;
-  /**
-   * <pre>
    * 字段名:退款资金来源
    * 变量名:funds_account
    * 是否必填:否

+ 11 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundV3Request.java

@@ -238,6 +238,17 @@ public class WxPayRefundV3Request implements Serializable {
     private Integer refundQuantity;
   }
 
+  /**
+   * <pre>
+   * 字段名:子商户的商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  子商户商户号,由微信支付生成并下发。
+   *  示例值:1230000109
+   * </pre>
+   */
   @SerializedName(value = "sub_mchid")
   private String subMchid;
 }

+ 29 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayUnifiedOrderV3Result.java

@@ -1,5 +1,6 @@
 package com.github.binarywang.wxpay.bean.result;
 
+import com.github.binarywang.wxpay.bean.result.enums.PartnerTradeTypeEnum;
 import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
 import com.github.binarywang.wxpay.v3.util.SignUtils;
 import com.google.gson.annotations.SerializedName;
@@ -132,4 +133,32 @@ public class WxPayUnifiedOrderV3Result implements Serializable {
         throw new WxRuntimeException("不支持的支付类型");
     }
   }
+
+  public <T> T getPartnerPayInfo(PartnerTradeTypeEnum tradeType, String appId, String mchId, PrivateKey privateKey) {
+    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+    String nonceStr = SignUtils.genRandomStr();
+    switch (tradeType) {
+      case JSAPI:
+        JsapiResult jsapiResult = new JsapiResult();
+        jsapiResult.setAppId(appId).setTimeStamp(timestamp)
+          .setPackageValue("prepay_id=" + this.prepayId).setNonceStr(nonceStr)
+          //签名类型,默认为RSA,仅支持RSA。
+          .setSignType("RSA").setPaySign(SignUtils.sign(jsapiResult.getSignStr(), privateKey));
+        return (T) jsapiResult;
+      case H5:
+        return (T) this.h5Url;
+      case APP:
+        AppResult appResult = new AppResult();
+        appResult.setAppid(appId).setPrepayId(this.prepayId).setPartnerId(mchId)
+          .setNoncestr(nonceStr).setTimestamp(timestamp)
+          //暂填写固定值Sign=WXPay
+          .setPackageValue("Sign=WXPay")
+          .setSign(SignUtils.sign(appResult.getSignStr(), privateKey));
+        return (T) appResult;
+      case NATIVE:
+        return (T) this.codeUrl;
+      default:
+        throw new WxRuntimeException("不支持的支付类型");
+    }
+  }
 }

+ 40 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/PartnerTradeTypeEnum.java

@@ -0,0 +1,40 @@
+package com.github.binarywang.wxpay.bean.result.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付方式
+ *
+ * @author thinsstar
+ */
+@Getter
+@AllArgsConstructor
+public enum PartnerTradeTypeEnum {
+  /**
+   * APP
+   */
+  APP("/v3/pay/partner/transactions/app", "/v3/combine-transactions/app"),
+  /**
+   * JSAPI 或 小程序
+   */
+  JSAPI("/v3/pay/partner/transactions/jsapi", "/v3/combine-transactions/jsapi"),
+  /**
+   * NATIVE
+   */
+  NATIVE("/v3/pay/partner/transactions/native", "/v3/combine-transactions/native"),
+  /**
+   * H5
+   */
+  H5("/v3/pay/partner/transactions/h5", "/v3/combine-transactions/h5");
+
+  /**
+   * 单独下单url
+   */
+  private final String partnerUrl;
+
+  /**
+   * 合并下单url
+   */
+  private final String combineUrl;
+}

+ 37 - 2
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java

@@ -5,6 +5,7 @@ import com.github.binarywang.wxpay.bean.coupon.*;
 import com.github.binarywang.wxpay.bean.notify.*;
 import com.github.binarywang.wxpay.bean.request.*;
 import com.github.binarywang.wxpay.bean.result.*;
+import com.github.binarywang.wxpay.bean.result.enums.PartnerTradeTypeEnum;
 import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
 import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.constant.WxPayConstants;
@@ -479,6 +480,23 @@ public interface WxPayService {
 
   /**
    * <pre>
+   * 服务商关闭订单
+   * 应用场景
+   * 以下情况需要调用关单接口:
+   * 1、商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
+   * 2、系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
+   * 注意:关单没有时间限制,建议在订单生成后间隔几分钟(最短5分钟)再调用关单接口,避免出现订单状态同步不及时导致关单失败。
+   * 接口地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_3.shtml
+   * </pre>
+   *
+   * @param outTradeNo 商户系统内部的订单号
+   * @return the wx pay order close result
+   * @throws WxPayException the wx pay exception
+   */
+  void closePartnerOrderV3(String outTradeNo) throws WxPayException;
+
+  /**
+   * <pre>
    * 关闭订单
    * 应用场景
    * 以下情况需要调用关单接口:
@@ -496,6 +514,23 @@ public interface WxPayService {
 
   /**
    * <pre>
+   * 服务商关闭订单
+   * 应用场景
+   * 以下情况需要调用关单接口:
+   * 1、商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
+   * 2、系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
+   * 注意:关单没有时间限制,建议在订单生成后间隔几分钟(最短5分钟)再调用关单接口,避免出现订单状态同步不及时导致关单失败。
+   * 接口地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_3.shtml
+   * </pre>
+   *
+   * @param request 关闭订单请求对象
+   * @return the wx pay order close result
+   * @throws WxPayException the wx pay exception
+   */
+  void closePartnerOrderV3(WxPayPartnerOrderCloseV3Request request) throws WxPayException;
+
+  /**
+   * <pre>
    * 合单关闭订单API
    * 请求URL: https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close
    * 文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_12.shtml">https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_12.shtml</a>
@@ -559,7 +594,7 @@ public interface WxPayService {
    * @return 返回 {@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段
    * @throws WxPayException the wx pay exception
    */
-  <T> T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException;
+  <T> T createPartnerOrderV3(PartnerTradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException;
 
   /**
    * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"
@@ -569,7 +604,7 @@ public interface WxPayService {
    * @return the wx pay unified order result
    * @throws WxPayException the wx pay exception
    */
-  WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException;
+  WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(PartnerTradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException;
 
   /**
    * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"

+ 30 - 22
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java

@@ -10,6 +10,7 @@ import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
 import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
 import com.github.binarywang.wxpay.bean.request.*;
 import com.github.binarywang.wxpay.bean.result.*;
+import com.github.binarywang.wxpay.bean.result.enums.PartnerTradeTypeEnum;
 import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
 import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.config.WxPayConfigHolder;
@@ -539,6 +540,16 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
   }
 
   @Override
+  public void closePartnerOrderV3(String outTradeNo) throws WxPayException {
+    if (StringUtils.isBlank(outTradeNo)) {
+      throw new WxPayException("out_trade_no不能为空");
+    }
+    WxPayPartnerOrderCloseV3Request request = new WxPayPartnerOrderCloseV3Request();
+    request.setOutTradeNo(StringUtils.trimToNull(outTradeNo));
+    this.closePartnerOrderV3(request);
+  }
+
+  @Override
   public void closeOrderV3(WxPayOrderCloseV3Request request) throws WxPayException {
     if (StringUtils.isBlank(request.getMchid())) {
       request.setMchid(this.getConfig().getMchId());
@@ -548,6 +559,18 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
   }
 
   @Override
+  public void closePartnerOrderV3(WxPayPartnerOrderCloseV3Request request) throws WxPayException {
+    if (StringUtils.isBlank(request.getSpMchId())) {
+      request.setSpMchId(this.getConfig().getMchId());
+    }
+    if (StringUtils.isBlank(request.getSubMchId())) {
+      request.setSubMchId(this.getConfig().getSubMchId());
+    }
+    String url = String.format("%s/v3/pay/partner/transactions/out-trade-no/%s/close", this.getPayBaseUrl(), request.getOutTradeNo());
+    this.postV3(url, GSON.toJson(request));
+  }
+
+  @Override
   public void closeCombine(CombineCloseRequest request) throws WxPayException {
     String url = String.format("%s/v3/combine-transactions/out-trade-no/%s/close", this.getPayBaseUrl(), request.getCombineOutTradeNo());
     this.postV3(url, GSON.toJson(request));
@@ -664,34 +687,19 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
   }
 
   @Override
-  public <T> T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
+  public <T> T createPartnerOrderV3(PartnerTradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
     WxPayUnifiedOrderV3Result result = this.unifiedPartnerOrderV3(tradeType, request);
     //获取应用ID
     String appId = StringUtils.isBlank(request.getSubAppid()) ? request.getSpAppid() : request.getSubAppid();
-    return result.getPayInfo(tradeType, appId, request.getSubMchId(), this.getConfig().getPrivateKey());
+    return result.getPartnerPayInfo(tradeType, appId, request.getSubMchId(), this.getConfig().getPrivateKey());
   }
 
   @Override
-  public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
-    if (StringUtils.isBlank(request.getSpAppid())) {
-      request.setSpAppid(this.getConfig().getAppId());
-    }
-    if (StringUtils.isBlank(request.getSpMchId())) {
-      request.setSpMchId(this.getConfig().getMchId());
-    }
-    if (StringUtils.isBlank(request.getNotifyUrl())) {
-      request.setNotifyUrl(this.getConfig().getNotifyUrl());
-    }
-    if (StringUtils.isBlank(request.getSubAppid())) {
-      request.setSubAppid(this.getConfig().getSubAppId());
-    }
-    if (StringUtils.isBlank(request.getSubMchId())) {
-      request.setSubMchId(this.getConfig().getSubMchId());
-    }
-
-    String url = this.getPayBaseUrl() + tradeType.getPartnerUrl();
-    String response = this.postV3(url, GSON.toJson(request));
-    return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
+  public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(PartnerTradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
+    WxPayUnifiedOrderV3Result result = this.unifiedPartnerOrderV3(tradeType, request);
+    //获取应用ID
+    String appId = StringUtils.isBlank(request.getSubAppid()) ? request.getSpAppid() : request.getSubAppid();
+    return result.getPartnerPayInfo(tradeType, appId, request.getSubMchId(), this.getConfig().getPrivateKey());
   }
 
   @Override