微信分账v2接入流程

194 阅读4分钟

微信分账相关内容,简化版:

分账相关内容:

1:确定对应微信支付的类型,需要区分是v2还是v3,发起分账前需要针对在支付的时候新增参数,确定订单属于分账订单类型:

如果是v2类型支付的话需要新增profit_sharing参数,字符串类型:

pay.weixin.qq.com/doc/v2/merc…

如果是v3类型的分账支付的话,字段名称一样,类型为布尔类型:

pay.weixin.qq.com/doc/v3/merc…

2:分账异常提示内容

1:订单刚发起,暂时不能分账
相关官方文档建议等待一分钟后发起:

原先处理是下单后调用,后面改为定时任务统一处理分账发起以及分账回退的内容

2:appId与openId不一致的问题

如果是个人分账的话小程序用户的openid和 对应app微信授权登录用户的openid不一样,需要区分好,可能混用了openid,比如说app支付了,分账的openid是对应小程序的,会出现异常。

相关文档汇总:

www.cnblogs.com/Amos-Turing…

相关工具类内容:

原先项目支付接入的是v2的微信支付,找了相关v2的sdk接入:封装了一些实体类啥的,不用像微信小店api接入那样,另外再根据文档在多写一些内容:

binarywang里面的xstream的话版本低有风险,另外的引入了一个高版本的

 <properties>
      //...
        <binarywang.version>4.1.0</binarywang.version>
    </properties>
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>${binarywang.version}</version>
    <exclusions>
        <exclusion>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.19</version>
 </dependency>

package com.test.service


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;

import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingQueryRequest;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingQueryResult;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReceiverRequest;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReturnQueryRequest;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReturnRequest;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReturnResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.ProfitSharingService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;


import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

/**
 * @Author vnjohn
 * @since 2022/10/26
 */
@Slf4j
public abstract class AbstractWxPayService {
    public static final String SUCCESS_STRING = "SUCCESS";

    public static final String HMAC_SHA256 = "HMAC-SHA256";

    public static final String MD5 = "MD5";

    /**
     * 证书内容缓存redis key前缀
     */
    public final static String FILE_CERT_REDIS_PREFIX = "cert_data_";

    /**
     * 证书文件路径连接符
     */
    public final static String FILE_CERT_URL_JOIN = "/";

    private static volatile WxPayService wxPayService;

//    /**
//     * 获取支付配置服务实例
//     *
//     * @param payConfig
//     * @param keyContext
//     * @return
//     */
//    protected static WxPayService getWxPayServiceInstance(PayConfig payConfig, byte[] keyContext) {
//        if (null == wxPayService) {
//            synchronized (WxPayService.class) {
//                if (null == wxPayService) {
//                    wxPayService = getWxPayService(payConfig, keyContext);
//                }
//            }
//        }
//        return wxPayService;
//    }

    /**
     * 获取支付配置服务实例
     *
     * @param payConfig
     * @param keyContext
     * @return
     */
    protected static WxPayService getWxPayServiceInstance(PayConfig payConfig, byte[] keyContext) {
        if (null == wxPayService) {
            synchronized (WxPayService.class) {
                if (null == wxPayService) {
                    wxPayService = getWxPayService(payConfig,keyContext);
                }
            }
        }
        return wxPayService;
    }

    /**
     * 添加配置信息
     *
     * @return
     */
    public static WxPayService getWxPayService(PayConfig payConfig, byte[] keyContext) {
        log.info("添加微信配置信息:{},keyContext:{}", JSONUtil.toJsonStr(payConfig), keyContext);
        WxPayConfig wxPayConfig = new WxPayConfig();
        wxPayConfig.setSignType(MD5);
//        wxPayConfig.setKeyPath(payConfig.getCertPath());
        wxPayConfig.setKeyContent(keyContext);
        wxPayConfig.setAppId(StringUtils.trimToNull(payConfig.getWxAppId()));
        wxPayConfig.setMchId(StringUtils.trimToNull(payConfig.getMchId()));
        if (StrUtil.isNotBlank(payConfig.getSubMchId())) {
            wxPayConfig.setSubAppId(payConfig.getSubMchId());
            wxPayConfig.setSubMchId(payConfig.getSubMchId());
        }
        wxPayConfig.setMchKey(StringUtils.trimToNull(payConfig.getMchKey()));
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(wxPayConfig);
        return wxPayService;
    }
    /**
     * 微信分账付款入口,返回外部交易的订单号
     *
     * @param payConfig
     * @return
     */
    public String pay(PayConfig payConfig) {
        if (payConfig == null) {
            log.warn("支付配置为空,不发起转账行为");
            return null;
        }
        if (StringUtils.isEmpty(payConfig.getOutTransactionId())) {
            log.error("无法进行前置分账,微信订单号为空");
            return null;
        }
//        boolean prePayResult;
//        try {
        //  前置操作分账账号在微信平台提前配置,无需每次都调用api
//            prePayResult = preparePay(payConfig);
//        } catch (Exception e) {
//            log.error("微信付款前的准备发生异常:{}", e.getMessage(), e);
//            // 暂时返回 null 预处理下一条
//            return null;
//        }

        String resStr = null;
        try {
            resStr = beginPay(payConfig);
        } catch (Exception e) {
            log.error("微信分账发生异常:{}", e.getMessage(), e);
            return null;
        }

        return resStr;
    }

    /**
     * 微信分账退款入口,返回外部交易的订单号
     *
     * @param payConfig
     * @return
     */
    public ProfitSharingReturnResult refund(PayConfig payConfig,ProfitSharingReturnRequest profitSharingReturnRequest) {
        if (payConfig == null) {
            log.warn("支付配置为空,不发起转账行为");
            return null;
        }
        if (StringUtils.isEmpty(payConfig.getOutTransactionId())) {
            log.error("无法进行前置分账回退,微信订单号为空");
            return null;
        }

        ProfitSharingReturnResult resStr = null;
        try {
            resStr = refundSharingOrder(payConfig, profitSharingReturnRequest);
        } catch (Exception e) {
            log.error("微信分账发生异常:{}", e.getMessage(), e);
            return null;
        }

        return resStr;
    }

    /**
     * 分账前置工作
     *
     * @param payConfig
     * @return
     * @throws Exception
     */
    protected abstract boolean preparePay(PayConfig payConfig) throws Exception;

    /**
     * 请求分账
     *
     * @param payConfig
     * @return
     * @throws Exception
     */
    protected abstract String beginPay(PayConfig payConfig) throws Exception;

    /**
     * 分账回退
     *
     * @param payConfig
     * @return
     * @throws Exception
     */
    protected abstract Map<String, String> afterPay(PayConfig payConfig) throws Exception;

    /**
     * 创建支付随机字符串
     *
     * @return
     */
    protected static String getNonceStr() {
        return RandomStringUtils.randomAlphanumeric(32);
    }

    protected abstract  byte[] getCertBytes(String mchId, String certFilePath);

    /**
     * 构建sha256参数的签名值
     *
     * @param params
     * @param paternerKey
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String getSha256Sign(Map<String, String> params, String paternerKey) throws UnsupportedEncodingException {
        String stringSignTemp = createSign(params, false) + "&key=" + paternerKey;
        return HMACUtil.sha256_HMAC(stringSignTemp, paternerKey).toUpperCase();
    }

    /**
     * 构造签名
     *
     * @param params
     * @param encode
     * @return
     * @throws UnsupportedEncodingException
     */
    protected static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException {
        Set<String> keysSet = params.keySet();
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuffer temp = new StringBuffer();
        boolean first = true;
        for (Object key : keys) {
            // 参数为空不参与签名
            if (key == null || StringUtils.isEmpty(params.get(key))) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                temp.append("&");
            }
            temp.append(key).append("=");
            Object value = params.get(key);
            String valueStr = "";
            if (null != value) {
                valueStr = value.toString();
            }
            if (encode) {
                temp.append(URLEncoder.encode(valueStr, "UTF-8"));
            } else {
                temp.append(valueStr);
            }
        }
        return temp.toString();
    }

    /**
     * 分账信息查询
     * @param profitSharingQueryRequest
     * @return
     */
    public ProfitSharingQueryResult profitSharingQuery(ProfitSharingQueryRequest profitSharingQueryRequest){
        ProfitSharingQueryResult profitSharingQueryResult = null;
        ProfitSharingService profitSharingService = wxPayService.getProfitSharingService();
        try{
             profitSharingQueryResult = profitSharingService.profitSharingQuery(profitSharingQueryRequest);
        }catch (Exception e) {
            log.error("微信分账信息查询异常:{}", e.getMessage(), e);
            return null;
        }

        return profitSharingQueryResult;
    }

    /**
     * 分账回退
     * @param profitSharingQueryRequest
     * @return
     */
    public ProfitSharingReturnResult refundSharingOrder(PayConfig payConfig,ProfitSharingReturnRequest profitSharingReturnRequest){
        ProfitSharingReturnResult profitSharingReturnResult = null;
        ProfitSharingService profitSharingService = AbstractWxPayService.getWxPayServiceInstance(payConfig,getCertBytes(payConfig.getMchId(), payConfig.getCertPath())).getProfitSharingService();
        try{
             profitSharingReturnResult = profitSharingService.profitSharingReturn(profitSharingReturnRequest);
        }catch (Exception e) {
            log.error("微信分账回退发生异常:{}", e.getMessage(), e);
            return null;
        }

        return profitSharingReturnResult;
    }

    /**
     * 分账回退查询
     * @param profitSharingQueryRequest
     * @return
     */
    public ProfitSharingReturnResult refundQuery(ProfitSharingReturnQueryRequest returnQueryRequest){
        ProfitSharingReturnResult profitSharingReturnResult = null;
        ProfitSharingService profitSharingService = wxPayService.getProfitSharingService();
        try{
            profitSharingReturnResult = profitSharingService.profitSharingReturnQuery(returnQueryRequest);

        }catch (Exception e) {
            log.error("微信分账回退发生异常:{}", e.getMessage(), e);
            return null;
        }

        return profitSharingReturnResult;
    }


}

//payConfig 需要保持和对应支付时的一些参数一致 v2的话sdk CertPath需要传入对应的证书地址
//payConfig.setWxAppId(accountSharingMerchant.getWxAppId());
//payConfig.setMchKey(wxDevelopProperties.getMchSecret());
//payConfig.setMchId(wxDevelopProperties.getMchId());
//payConfig.setCertPath(cert_path);

其他内容:

自动续费的相关流程以及文档:

第三方交互,都是调用,以及回调处理,要么是多组回调事件区分,要么就是单个事件多个状态位的区分

针对首次自动续费的话,多一次签约api的发起:

相关配置内容:

1:分账商户后台 添加对应的分账方,也可以用通过相关api调用添加对应的分账方:

2:设置相关分账的最大比例: