• 微信支付服务商模式(电商收付通)实现分账操作


      😊 @ 作者: 一恍过去
      🎊 @ 社区: Java技术栈交流
      🎉 @ 主题: 微信支付服务商模式(电商收付通)实现分账操作
      ⏱️ @ 创作时间: 2022年08月31日

      在这里插入图片描述

      要开启分账,需要在支付时设置settle_info中的profit_sharing属性为true,在下面第7点(分账相关接口)中的SharingController模拟进行一次Native支付
      主要实现以下接口:

      • 请求分账
      • 查询分账结果
      • 请求分账回退
      • 查询分账回退结果
      • 解冻剩余资金(完结分账)
      • 查询剩余待分金额

      1、引入POM

              
              <dependency>
                  <groupId>com.github.wechatpay-apiv3groupId>
                  <artifactId>wechatpay-apache-httpclientartifactId>
                  <version>0.4.7version>
              dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      2、配置Yaml

      wxpay:
        #应用编号
        appId: xxxx
        #商户号
        mchId: xxx
        # APIv2密钥
        apiKey: xxxx
        # APIv3密钥
        apiV3Key: xxx
        # 微信支付V3-url前缀
        baseUrl: https://api.mch.weixin.qq.com/v3
        # 支付通知回调, pjm6m9.natappfree.cc 为内网穿透地址
        notifyUrl: http://pjm6m9.natappfree.cc/pay/payNotify
        # 退款通知回调, pjm6m9.natappfree.cc 为内网穿透地址
        refundNotifyUrl: http://pjm6m9.natappfree.cc/pay/refundNotify
        # 密钥路径,resources根目录下
        keyPemPath: apiclient_key.pem
        #商户证书序列号
        serialNo: xxxxx
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      3、配置密钥文件

      在商户/服务商平台的”账户中心" => “API安全” 进行API证书、密钥的设置,API证书主要用于获取“商户证书序列号”以及“p12”、“key.pem”、”cert.pem“证书文件,j将获取的apiclient_key.pem文件放在项目的resources目录下。
      在这里插入图片描述

      4、配置PayConfig

      import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
      import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
      import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
      import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
      import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
      import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
      import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
      import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
      import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
      import lombok.Data;
      import lombok.extern.slf4j.Slf4j;
      import org.apache.http.impl.client.CloseableHttpClient;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.context.annotation.Bean;
      import org.springframework.stereotype.Component;
      
      import java.io.IOException;
      import java.io.InputStream;
      import java.nio.charset.StandardCharsets;
      import java.security.GeneralSecurityException;
      import java.security.PrivateKey;
      
      /**
       * @Description:
       **/
      @Component
      @Data
      @Slf4j
      @ConfigurationProperties(prefix = "wxpay")
      public class WechatPayConfig {
          /**
           * 应用编号
           */
          private String appId;
          /**
           * 商户号
           */
          private String mchId;
          /**
           * 服务商商户号
           */
          private String slMchId;
          /**
           * APIv2密钥
           */
          private String apiKey;
          /**
           * APIv3密钥
           */
          private String apiV3Key;
          /**
           * 支付通知回调地址
           */
          private String notifyUrl;
          
          /**
           * 退款回调地址
           */
          private String refundNotifyUrl;
      
          /**
           * API 证书中的 key.pem
           */
          private String keyPemPath;
      
          /**
           * 商户序列号
           */
          private String serialNo;
      
          /**
           * 微信支付V3-url前缀
           */
          private String baseUrl;
      
          /**
           * 获取商户的私钥文件
           * @param keyPemPath
           * @return
           */
          public PrivateKey getPrivateKey(String keyPemPath){
      
                  InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
                  if(inputStream==null){
                      throw new RuntimeException("私钥文件不存在");
                  }
                  return PemUtil.loadPrivateKey(inputStream);
          }
      
          /**
           * 获取证书管理器实例
           * @return
           */
          @Bean
          public  Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
      
              log.info("获取证书管理器实例");
      
              //获取商户私钥
              PrivateKey privateKey = getPrivateKey(keyPemPath);
      
              //私钥签名对象
              PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
      
              //身份认证对象
              WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
      
              // 使用定时更新的签名验证器,不需要传入证书
              CertificatesManager certificatesManager = CertificatesManager.getInstance();
              certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));
      
              return certificatesManager.getVerifier(mchId);
          }
      
      
          /**
           * 获取支付http请求对象
           * @param verifier
           * @return
           */
          @Bean(name = "wxPayClient")
          public CloseableHttpClient getWxPayClient(Verifier verifier)  {
      
              //获取商户私钥
              PrivateKey privateKey = getPrivateKey(keyPemPath);
      
              WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                      .withMerchant(mchId, serialNo, privateKey)
                      .withValidator(new WechatPay2Validator(verifier));
      
              // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
              return builder.build();
          }
      
          /**
           * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
           */
          @Bean(name = "wxPayNoSignClient")
          public CloseableHttpClient getWxPayNoSignClient(){
      
              //获取商户私钥
              PrivateKey privateKey = getPrivateKey(keyPemPath);
      
              //用于构造HttpClient
              WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                      //设置商户信息
                      .withMerchant(mchId, serialNo, privateKey)
                      //无需进行签名验证、通过withValidator((response) -> true)实现
                      .withValidator((response) -> true);
      
              // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
              return builder.build();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154

      5、定义统一枚举

      SharingUrlEnum:

      import lombok.AllArgsConstructor;
      import lombok.Getter;
      
      /**
       * @author 
       */
      
      @AllArgsConstructor
      @Getter
      public enum SharingUrlEnum {
      
      
          /**
           * 请求分账(分批次分账)
           */
          ORDERS_SHARING("/ecommerce/profitsharing/orders"),
      
          /**
           * 请求分账回退
           */
          RETURN_SHARING("/ecommerce/profitsharing/returnorders"),
      
          /**
           * 查询订单剩余待分金额
           */
          SHARING_AMOUNT_BALANCE("/ecommerce/profitsharing/orders/%s/amounts"),
      
          /**
           * 完结分账
           */
          FINISH_SHARING("/ecommerce/profitsharing/finish-order");
      
          /**
           * 类型
           */
          private final String type;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37

      6、封装统一请求处理

      WechatPayRequest:

      import lombok.extern.slf4j.Slf4j;
      import org.apache.http.HttpEntity;
      import org.apache.http.HttpStatus;
      import org.apache.http.client.methods.CloseableHttpResponse;
      import org.apache.http.client.methods.HttpGet;
      import org.apache.http.client.methods.HttpPost;
      import org.apache.http.entity.StringEntity;
      import org.apache.http.impl.client.CloseableHttpClient;
      import org.apache.http.util.EntityUtils;
      import org.springframework.stereotype.Component;
      
      import javax.annotation.Resource;
      import java.io.IOException;
      
      /**
       * @Description:
       **/
      @Component
      @Slf4j
      public class WechatPayRequest {
          @Resource
          private CloseableHttpClient wxPayClient;
      
          public String wechatHttpGet(String url) {
              try {
                  // 拼接请求参数
                  HttpGet httpGet = new HttpGet(url);
                  httpGet.setHeader("Accept", "application/json");
      
                  //完成签名并执行请求
                  CloseableHttpResponse response = wxPayClient.execute(httpGet);
      
                  return getResponseBody(response);
              } catch (Exception e) {
                  throw new RuntimeException(e.getMessage());
              }
          }
      
          public String wechatHttpPost(String url, String paramsStr) {
              try {
                  HttpPost httpPost = new HttpPost(url);
                  StringEntity entity = new StringEntity(paramsStr, "utf-8");
                  entity.setContentType("application/json");
                  httpPost.setEntity(entity);
                  httpPost.setHeader("Accept", "application/json");
      
                  CloseableHttpResponse response = wxPayClient.execute(httpPost);
                  return getResponseBody(response);
              } catch (Exception e) {
                  throw new RuntimeException(e.getMessage());
              }
          }
      
          private String getResponseBody(CloseableHttpResponse response) throws IOException {
      
              //响应体
              HttpEntity entity = response.getEntity();
              String body = entity == null ? "" : EntityUtils.toString(entity);
              //响应状态码
              int statusCode = response.getStatusLine().getStatusCode();
      
              //处理成功,204是,关闭订单时微信返回的正常状态码
              if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
                  log.info("成功, 返回结果 = " + body);
              } else {
                  String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
                  log.error(msg);
                  throw new RuntimeException(msg);
              }
              return body;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72

      7、分账相关接口

      要开启分账,需要在支付时设置settle_info中的profit_sharing属性为true,在SharingController中模拟进行一次Native支付

      7.1、模拟支付

      为了更好的演示分账流程,通过NATIVE方式进行真实的下单支付,并且支付时通过'profit_sharing'参数,开启分账

      SharingController:

      import com.alibaba.fastjson.JSON;
      import com.alibaba.fastjson.JSONObject;
      import com.alibaba.fastjson.TypeReference;
      import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
      import com.lhz.demo.model.enums.SharingUrlEnum;
      import com.lhz.demo.model.enums.WechatPayUrlEnum;
      import com.lhz.demo.pay.WechatPayConfig;
      import com.lhz.demo.pay.WechatPayRequest;
      import io.swagger.annotations.Api;
      import io.swagger.annotations.ApiOperation;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.web.bind.annotation.*;
      
      import javax.annotation.Resource;
      import java.util.*;
      
      /**
       * @Description:
       **/
      @Api(tags = "分账接口(API3)")
      @RestController
      @RequestMapping("/combine")
      @Slf4j
      public class SharingController {
      
          @Resource
          private WechatPayConfig wechatPayConfig;
      
          @Resource
          private WechatPayRequest wechatPayRequest;
      
          /**
           * 模拟子商户号,实际情况应该写在表中,与商家表进行映射
           */
          private static final String subMchId = "xxxx";
      
          /**
           * TODO 模拟任意类型下单,比如:NATIVE下单
           *
           * @return
           */
          @ApiOperation(value = "NATIVE下单", notes = "NATIVE下单")
          @ApiOperationSupport(order = 10)
          @GetMapping("/native")
          public String nativePay() {
              // 统一参数封装
              Map<String, Object> params = new HashMap<>(8);
              params.put("sp_appid", wechatPayConfig.getAppId());
              params.put("sp_mchid", wechatPayConfig.getMchId());
              // 子商户号
              params.put("sub_mchid", "1610625101");
              params.put("description", "测试商品");
              int outTradeNo = new Random().nextInt(999999999);
              params.put("out_trade_no", outTradeNo + "");
              params.put("notify_url", wechatPayConfig.getNotifyUrl());
      
              // 金额单位为分
              Map<String, Object> amountMap = new HashMap<>(4);
              amountMap.put("total", 1);
              amountMap.put("currency", "CNY");
              params.put("amount", amountMap);
      
              // 结算信息
              Map<String, Object> settleInfoMap = new HashMap<>(4);
              settleInfoMap.put("profit_sharing", true);
      
              params.put("settle_info", settleInfoMap);
              String paramsStr = JSON.toJSONString(params);
              log.info("请求参数 ===> {}" + paramsStr);
      
              String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat("/pay/partner/transactions/native"), paramsStr);
              Map<String, String> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
              });
      
              return resMap.get("code_url");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77

      7.2、单次分账请求

      微信订单支付成功后,服务商代特约商户发起分账请求,将结算后的资金分到分账接收方

      • 对同一笔订单最多能发起50次分账请求,每次请求最多分给50个接收方
      • 此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取
      • 服务商需确保向微信支付传输用户身份信息和账号标识信息做一致性校验已合法征得用户授权

      参考API:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_1.shtml

      /**
       * @Description:
       **/
      @Api(tags = "分账接口(API3)")
      @RestController
      @RequestMapping("/combine")
      @Slf4j
      public class SharingController {
      
          @Resource
          private WechatPayConfig wechatPayConfig;
      
          @Resource
          private WechatPayRequest wechatPayRequest;
      
          /**
           * 模拟子商户号,实际情况应该写在表中,与商家表进行映射
           */
          private static final String subMchId = "xxxx";
          
          /**
           * 微信订单支付成功后,由电商平台发起分账请求,将结算后的资金分给分账接收方
           *
           * @param transactionId 微信支付订单号
           * @return
           */
          @ApiOperation(value = "单次分账请求", notes = "单次分账请求")
          @ApiOperationSupport(order = 10)
          @PostMapping("/ordersSharing")
          public String ordersSharing(@RequestBody String transactionId) {
      
              //TODO 为了更严谨,在发起分账前可以先“查询订单剩余待分金额”,如果没有待分账的金额则不发起分账
      
              // 统一参数封装
              Map<String, Object> params = new HashMap<>(8);
              params.put("appid", wechatPayConfig.getAppId());
              // 二级商户号(特约商户号)
              params.put("sub_mchid", subMchId);
              // 微信支付订单号,成功支付后获取,实际项目中:支付回调时需要更新到order表中
              params.put("transaction_id", transactionId);
              // 商户系统内部的分账单号,在商户系统内部唯一
              int outOrderNo = new Random().nextInt(999999999);
              params.put("out_order_no", outOrderNo + "");
              /**
               * 是否完成分账
               * 1、如果为true,该笔订单剩余未分账的金额会解冻回电商平台二级商户;
               * 2、如果为false,该笔订单剩余未分账的金额不会解冻回电商平台二级商户,可以对该笔订单再次进行分账。
               */
              params.put("finish", false);
      
              List<Map<String, Object>> receivers = new ArrayList<>();
              // TODO 设置分账接收方列表,可以分给多个商户或者个人,此处模拟一个
              Map<String, Object> receiversMap = new HashMap<>(4);
              /**
               * 	分账接收方类型,枚举值:
               * MERCHANT_ID:商户
               * PERSONAL_OPENID:个人
               */
              receiversMap.put("type", "MERCHANT_ID");
              /**
               * 分账接收方账号:
               * 类型是MERCHANT_ID时,是商户号(mch_id或者sub_mch_id)
               * 类型是PERSONAL_OPENID时,是个人openid
               */
              receiversMap.put("receiver_account", subMchId);
              // 分账金额,单位为分,只能为整数
              receiversMap.put("amount", 1);
              receiversMap.put("description", "测试分账");
      
              receivers.add(receiversMap);
      
              params.put("receivers", receivers);
              String paramsStr = JSON.toJSONString(params);
              log.info("请求参数 ===> {}" + paramsStr);
      
              String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(SharingUrlEnum.ORDERS_SHARING.getType()), paramsStr);
              Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
              });
      
              // 执行结果
              log.info("响应参数:{}", JSON.toJSONString(resMap));
      
              // 分账接收方列表
              String receiversStr = resMap.get("receivers").toString();
              List<Map<String, Object>> receiversList = JSONObject.parseObject(receiversStr, new TypeReference<List<Map<String, Object>>>() {
              });
              for (Map<String, Object> receiver : receiversList) {
                  // 微信分账明细单号,每笔分账业务执行的明细单号,可与资金账单对账使用
                  String detailId = receiver.get("detail_id").toString();
                  /**
                   * 枚举值:
                   * MERCHANT_ID:商户号(mch_id或者sub_mch_id)
                   * PERSONAL_OPENID:个人openid(由服务商的APPID转换得到)
                   * PERSONAL_SUB_OPENID:个人sub_openid(由品牌主的APPID转换得到)
                   */
                  String type = receiver.get("type").toString();
                  /**
                   * 枚举值:
                   * PENDING:待分账
                   * SUCCESS:分账成功
                   * CLOSED:已关闭
                   */
                  String result = receiver.get("result").toString();
                  // 分账接收方账号
                  String receiverAccount = receiver.get("receiver_account").toString();
                  // 分账完成时间
                  String finishTime = receiver.get("finish_time").toString();
      
                  log.info("detailId:" + detailId);
                  log.info("type:" + type);
                  log.info("result:" + result);
                  log.info("receiverAccount:" + receiverAccount);
                  log.info("finishTime:" + finishTime);
              }
              return "SUCCESS";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117

      7.3、查询单次(完结)分账请求结果

      起分账请求后,可调用此接口查询分账结果

      • 发起解冻剩余资金请求后,可调用此接口查询解冻剩余资金的结果

      参考API:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_2.shtml

      /**
       * @Description:
       **/
      @Api(tags = "分账接口(API3)")
      @RestController
      @RequestMapping("/combine")
      @Slf4j
      public class SharingController {
      
          @Resource
          private WechatPayConfig wechatPayConfig;
      
          @Resource
          private WechatPayRequest wechatPayRequest;
      
          /**
           * 模拟子商户号,实际情况应该写在表中,与商家表进行映射
           */
          private static final String subMchId = "xxxx";
          /**
           * 发起分账请求后,可调用此接口查询分账结果 ;发起分账完结请求后,可调用此接口查询分账完结的结果
           *
           * @param transactionId 微信支付订单号
           * @return
           */
          @ApiOperation(value = "查询单次(完结)分账请求结果", notes = "查询单次(完结)分账请求结果")
          @ApiOperationSupport(order = 10)
          @GetMapping("/ordersSharing")
          public String queryOrdersSharing(@RequestParam("transactionId") String transactionId, @RequestParam("outOrderNo") String outOrderNo) {
      
              // 统一参数封装
              Map<String, Object> params = new HashMap<>(8);
      
              String url = wechatPayConfig.getBaseUrl().concat(SharingUrlEnum.ORDERS_SHARING.getType())
                      .concat("?sub_mchid=" + subMchId)
                      .concat("&transaction_id=" + transactionId)
                      .concat("&out_order_no=" + outOrderNo);
      
              String resStr = wechatPayRequest.wechatHttpGet(url);
              Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
              });
      
              // 执行结果
              log.info("响应参数:{}", JSON.toJSONString(resMap));
      
              // 分账接收方列表
              String receiversStr = resMap.get("receivers").toString();
              List<Map<String, Object>> receiversList = JSONObject.parseObject(receiversStr, new TypeReference<List<Map<String, Object>>>() {
              });
              for (Map<String, Object> receiver : receiversList) {
                  // 微信分账明细单号,每笔分账业务执行的明细单号,可与资金账单对账使用
                  String detailId = receiver.get("detail_id").toString();
                  /**
                   * 枚举值:
                   * MERCHANT_ID:商户号(mch_id或者sub_mch_id)
                   * PERSONAL_OPENID:个人openid(由服务商的APPID转换得到)
                   * PERSONAL_SUB_OPENID:个人sub_openid(由品牌主的APPID转换得到)
                   */
                  String type = receiver.get("type").toString();
                  /**
                   * 枚举值:
                   * PENDING:待分账
                   * SUCCESS:分账成功
                   * CLOSED:已关闭
                   */
                  String result = receiver.get("result").toString();
                  // 分账接收方账号
                  String receiverAccount = receiver.get("receiver_account").toString();
                  // 分账完成时间
                  String finishTime = receiver.get("finish_time").toString();
      
                  log.info("detailId:" + detailId);
                  log.info("type:" + type);
                  log.info("result:" + result);
                  log.info("receiverAccount:" + receiverAccount);
                  log.info("finishTime:" + finishTime);
              }
              return "SUCCESS";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80

      7.4、请求分账回退

      如果订单已经分账,在退款时,可以先调此接口,将已分账的资金从分账接收方的账户回退给分账方,再发起退款。

      • 分账回退以原分账单为依据,支持多次回退,申请回退总金额不能超过原分账单分给该接收方的金额。
      • 此接口采用同步处理模式,即在接收到商户请求后,会实时返回处理结果。
      • 对同一个分账接收方最多能发起20次分账回退请求。
      • 退款和分账回退没有耦合,分账回退可以先于退款请求,也可以后于退款请求。
      • 此功能需要接收方访问商户平台-交易中心-分账-分账接收设置,开启同意分账回退后,才能使用。
      • 不支持针对“分账到零钱”的分账单发起分账回退。
      • 分账回退的时限是180天。

      参考API:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_3.shtml

      /**
       * @Description:
       **/
      @Api(tags = "分账接口(API3)")
      @RestController
      @RequestMapping("/combine")
      @Slf4j
      public class SharingController {
      
          @Resource
          private WechatPayConfig wechatPayConfig;
      
          @Resource
          private WechatPayRequest wechatPayRequest;
          
          /**
           * 订单已经分账,在退款时,可以先调此接口,将已分账的资金从分账接收方的账户回退给分账方,再发起退款。
           *
           * @param orderId 微信分账单号,微信支付系统分账成功后返回的唯一标识。
           * @return
           */
          @ApiOperation(value = "请求分账回退", notes = "请求分账回退")
          @ApiOperationSupport(order = 10)
          @PostMapping("/returnSharing")
          public String returnSharing(@RequestBody String orderId) {
      
              //TODO 注意:实际项目中商户回退单号、回退商户号都需要通过orderId在系统的数据库获取,所以分账成功需要记录并且与支付的订单进行关联
      
              // 统一参数封装
              Map<String, Object> params = new HashMap<>(8);
              // 二级商户号(特约商户号)
              params.put("sub_mchid", subMchId);
              // 微信分账单号,微信支付系统分账成功后返回的唯一标识
              params.put("order_id", orderId);
              // 商户系统内部的退单号,在商户系统内部唯一
              int outReturnNo = new Random().nextInt(999999999);
              params.put("out_return_no", outReturnNo + "");
              // 回退商户号只能填写原分账请求中接收分账的商户号
              params.put("return_mchid", "wx123456");
              // 需要从分账接收方回退的金额,单位为分,只能为整数,不能超过原始分账单分出给该接收方的金额。
              params.put("amount", 1);
              params.put("description", "测试回退");
      
      
              String paramsStr = JSON.toJSONString(params);
              log.info("请求参数 ===> {}" + paramsStr);
      
              String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(SharingUrlEnum.RETURN_SHARING.getType()), paramsStr);
              Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
              });
      
              // 执行结果
              log.info("响应参数:{}", JSON.toJSONString(resMap));
      
              /**
               * 枚举值:
               * PROCESSING:处理中
               * 如果返回为处理中,请勿变更商户回退单号,使用相同的参数再次发起分账回退,否则会出现资金风险。
               * 在处理中状态的回退单如果5天没有成功,会因为超时被设置为已失败。
               * SUCCESS:已成功
               * FAILED:已失败
               */
              String result = resMap.get("result").toString();
              /**
               * 此字段仅回退结果为FAIL时存在,枚举值:
               * ACCOUNT_ABNORMAL:原分账接收方账户异常
               * TIME_OUT_CLOSED::超时关单
               * PAYER_ACCOUNT_ABNORMAL:原分账分出方账户异常
               */
              String failReason = resMap.get("fail_reason").toString();
              String finishTime = resMap.get("finish_time").toString();
      
              log.info("result:" + result);
              log.info("failReason:" + failReason);
              log.info("finishTime:" + finishTime);
      
              return "SUCCESS";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79

      7.5、查询分账回退结果

      商户需要核实回退结果,可调用此接口查询回退结果

      • 如果分账回退接口返回状态为处理中,可调用此接口查询回退结果

      参考API:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_4.shtml

      /**
       * @Description:
       **/
      @Api(tags = "分账接口(API3)")
      @RestController
      @RequestMapping("/combine")
      @Slf4j
      public class SharingController {
      
          @Resource
          private WechatPayConfig wechatPayConfig;
      
          @Resource
          private WechatPayRequest wechatPayRequest;
          
          /**
           * 订单已经分账,在退款时,可以先调此接口,将已分账的资金从分账接收方的账户回退给分账方,再发起退款。
           *
           * @param orderId 微信分账单号,微信支付系统分账成功后返回的唯一标识。
           * @return
           */
          @ApiOperation(value = "查询分账回退结果", notes = "查询分账回退结果")
          @ApiOperationSupport(order = 10)
          @GetMapping("/returnSharing")
          public String queryReturnSharing(@RequestParam String orderId, @RequestParam("outReturnNo") String outReturnNo) {
      
              //TODO 注意:实际项目中商户回退单号、回退商户号都需要通过orderId在系统的数据库获取,所以分账成功需要记录并且与支付的订单进行关联
      
              // 统一参数封装
              Map<String, Object> params = new HashMap<>(8);
              // 二级商户号(特约商户号)
              params.put("sub_mchid", subMchId);
              // 微信分账单号,微信支付系统分账成功后返回的唯一标识
              params.put("order_id", orderId);
              // 调用回退接口提供的商户系统内部的回退单号
              params.put("out_return_no", outReturnNo);
      
              String paramsStr = JSON.toJSONString(params);
              log.info("请求参数 ===> {}" + paramsStr);
      
              String url = wechatPayConfig.getBaseUrl().concat(SharingUrlEnum.RETURN_SHARING.getType())
                      .concat("?sub_mchid=" + subMchId)
                      .concat("&order_id=" + orderId)
                      .concat("&out_return_no=" + outReturnNo);
      
              String resStr = wechatPayRequest.wechatHttpGet(url);
              Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
              });
      
              // 执行结果
              log.info("响应参数:{}", JSON.toJSONString(resMap));
      
              /**
               * 枚举值:
               * PROCESSING:处理中
               * 如果返回为处理中,请勿变更商户回退单号,使用相同的参数再次发起分账回退,否则会出现资金风险。
               * 在处理中状态的回退单如果5天没有成功,会因为超时被设置为已失败。
               * SUCCESS:已成功
               * FAILED:已失败
               */
              String result = resMap.get("result").toString();
              /**
               * 此字段仅回退结果为FAIL时存在,枚举值:
               * ACCOUNT_ABNORMAL:原分账接收方账户异常
               * TIME_OUT_CLOSED::超时关单
               * PAYER_ACCOUNT_ABNORMAL:原分账分出方账户异常
               */
              String failReason = resMap.get("fail_reason").toString();
              String finishTime = resMap.get("finish_time").toString();
      
              log.info("result:" + result);
              log.info("failReason:" + failReason);
              log.info("finishTime:" + finishTime);
      
              return "SUCCESS";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77

      7.6、完结分账

      不需要进行分账的订单或者解冻已经发起分账的订单,可直接调用本接口将订单的金额全部解冻给特约商户

      • 调用分账接口后,需要解冻剩余资金时,调用本接口将剩余的分账金额全部解冻给特约商户
      • 此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取

      参考API:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_5.shtml

      /**
       * @Description:
       **/
      @Api(tags = "分账接口(API3)")
      @RestController
      @RequestMapping("/combine")
      @Slf4j
      public class SharingController {
      
          @Resource
          private WechatPayConfig wechatPayConfig;
      
          @Resource
          private WechatPayRequest wechatPayRequest;
          
          /**
           * 不需要进行分账的订单,可直接调用本接口将订单的金额全部解冻给二级商户。
           *
           * @param transactionId 微信支付订单号。
           * @return
           */
          @ApiOperation(value = "完结分账", notes = "完结分账")
          @ApiOperationSupport(order = 10)
          @PostMapping("/finishSharing")
          public String finishSharing(@RequestBody String transactionId) {
      
              // 统一参数封装
              Map<String, Object> params = new HashMap<>(8);
              // 二级商户号(特约商户号)
              params.put("sub_mchid", subMchId);
              // 微信支付订单号
              params.put("transaction_id", transactionId);
              // 商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次
              int outOrderNo = new Random().nextInt(999999999);
              params.put("out_order_no", outOrderNo + "");
              params.put("description", "测试完结分账");
      
              String paramsStr = JSON.toJSONString(params);
              log.info("请求参数 ===> {}" + paramsStr);
      
              String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(SharingUrlEnum.FINISH_SHARING.getType()), paramsStr);
              Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
              });
      
              // 执行结果
              log.info("响应参数:{}", JSON.toJSONString(resMap));
      
              return "SUCCESS";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50

      7.6、查询订单剩余待分金额

      可调用此接口查询订单剩余待分金额

      参考API:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_6.shtml

      /**
       * @Description:
       **/
      @Api(tags = "分账接口(API3)")
      @RestController
      @RequestMapping("/combine")
      @Slf4j
      public class SharingController {
      
          @Resource
          private WechatPayConfig wechatPayConfig;
      
          @Resource
          private WechatPayRequest wechatPayRequest;
          
          /**
           * 查询订单剩余待分金额,查询订单剩余待分金额
           *
           * @param transactionId
           * @return
           */
          @ApiOperation(value = "查询订单剩余待分金额", notes = "查询订单剩余待分金额")
          @ApiOperationSupport(order = 10)
          @GetMapping("/sharingAmountBalance")
          public String sharingAmountBalance(@RequestParam String transactionId) {
      
              // 请求url
              String url = String.format(wechatPayConfig.getBaseUrl().concat(SharingUrlEnum.SHARING_AMOUNT_BALANCE.getType()), transactionId);
      
              String resStr = wechatPayRequest.wechatHttpGet(url);
              Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
              });
      
              // 执行结果
              log.info("响应参数:{}", JSON.toJSONString(resMap));
              String unsplitAmount = resMap.get("unsplit_amount").toString();
              log.info("unsplitAmount:" + unsplitAmount);
      
              return "SUCCESS";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41

      在这里插入图片描述

    • 相关阅读:
      Jetson-AGX-Orin gstreamer+rtmp+http-flv 推拉流
      Win11、Linux 双系统安装方法
      【JS高级】ES5标准规范之严格模式下的保护对象_09
      C语言进程的相关操作
      编程面试_动态规划
      83 # 静态服务中间件 koa-static 的使用以及实现
      设计模式-结构型模式-桥接模式
      web安全day03
      zaabix实现对nginx监控
      2024北京老博会/北京智慧养老展/北京养老机构管理系统展会
    • 原文地址:https://blog.csdn.net/zhuocailing3390/article/details/126457304