当前位置: 首页 > news >正文

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

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

目录

  • 1、引入POM
  • 2、配置Yaml
  • 3、配置密钥文件
  • 4、配置PayConfig
  • 5、定义统一枚举
  • 6、封装统一请求处理
  • 7、分账相关接口
    • 7.1、模拟支付
    • 7.2、单次分账请求
    • 7.3、查询单次(完结)分账请求结果
    • 7.4、请求分账回退
    • 7.5、查询分账回退结果
    • 7.6、完结分账
    • 7.6、查询订单剩余待分金额

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

1、引入POM

        <!--微信支付SDK-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.7</version>
        </dependency>

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

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();
    }
}

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

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

7、分账相关接口

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

7.1、模拟支付

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");
    }
}

7.2、单次分账请求

/**
 * @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";
    }
}

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

/**
 * @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";
    }
}

7.4、请求分账回退

/**
 * @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";
    }
}

7.5、查询分账回退结果

/**
 * @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";
    }
}

7.6、完结分账

/**
 * @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";
    }
}

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

/**
 * @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";
    }
}

相关文章:

  • LeetCode 946 验证栈序列[栈 模拟] HERODING的LeetCode之路
  • 第十七天计算机视觉之光学字符识别基础理论
  • 混迹职场10多年的数据开发老鸟,居然被一个职场新人上了一课
  • PHP - AJAX 与 PHP
  • 微服务项目调用外部接口
  • 【Python】第八课 异常处理
  • Atomic Mail Sender 9.6.X 中文版Crack
  • 【重识云原生】第六章容器6.1.4节——Docker核心技术LXC
  • mysql—自增长和索引
  • 【C语言】带你深入剖析字符串相关知识(详细讲解+源码展示)
  • PostgreSQL 常用管理命令
  • Canny边缘检测数学原理及Python代码实现
  • 代码解析MixFormer: Mixing Features across Windows and Dimensions
  • 墨者-网络安全
  • Thread类的基本用法
  • 「面试题」如何实现一个圣杯布局?
  • 【node学习】协程
  • 2019.2.20 c++ 知识梳理
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • Angular 响应式表单 基础例子
  • co.js - 让异步代码同步化
  • css布局,左右固定中间自适应实现
  • css属性的继承、初识值、计算值、当前值、应用值
  • Druid 在有赞的实践
  • LeetCode18.四数之和 JavaScript
  • MySQL-事务管理(基础)
  • Odoo domain写法及运用
  • 第十八天-企业应用架构模式-基本模式
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 技术胖1-4季视频复习— (看视频笔记)
  • 深入浅出webpack学习(1)--核心概念
  • 微服务入门【系列视频课程】
  • 我与Jetbrains的这些年
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • # Java NIO(一)FileChannel
  • #define用法
  • (1)Nginx简介和安装教程
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (第一天)包装对象、作用域、创建对象
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (论文阅读30/100)Convolutional Pose Machines
  • (算法)求1到1亿间的质数或素数
  • (一)Java算法:二分查找
  • (转)linux下的时间函数使用
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .NET CLR基本术语
  • .Net 中Partitioner static与dynamic的性能对比
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • .NET中两种OCR方式对比
  • @manytomany 保存后数据被删除_[Windows] 数据恢复软件RStudio v8.14.179675 便携特别版...
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • []常用AT命令解释()