微信服务商分账

好久不见,文章很长时间没有更新了,一致追求于文章的质量输出,为了避免大家再次遇坑,特此记录一下分享给大家

需求背景

在服务商多商户运营下,需要在业务方「门店」这侧平摊给品牌「分公司或经销商」一定金额入账,以此作为基准,需要多商户底座来支撑需求实现,目前文章是在服务商模式实现分账逻辑

前期准备

1、准备一个服务商账号,申请步骤可以查看网上资料进行阅览,一般公司的运营都会这个操作
2、门店需要进行分账的,都需要给它申请一个商户号,并将商户号绑定到服务商下的特约商户,每个商户号都需要与wxAppId 进行关联「小程序或公众号」
3、登录「小程序或公众号」后台设置页面进行商户号授权操作

新商户号入驻流程
1、申请商户号绑定到服务商特约商户下
2、商户号绑定小程序appId
3、小程序管理员登录后台进行同意商户号绑定

实现步骤

微信接口文档:开发文档-微信支付服务商平台
1、在 PC 后台加一个功能模块,用于设置门店的商户号信息和是否开启分账功能来支撑动态是否进行分账的实现,重要的一个字段为分账比例,该比例字段取值范围在 0~30 之间,最大分账比例不能超过 30%
2、微信 JSAPI 统一下单接口除了下单时所需的参数之外,另外增加一个参数:profit_sharing「是否分账:Y-是,需要分账、N-否,不分账,字母要求大写,不传默认不分账」,该参数值在业务中不是即传即用的,而是根据第一点「后台门店分账配置」来设置参数值,如果该门店未开启分账或未设置分账比例和商户号,即传 N,其他都满足时传递 Y
3、在订单表中增加字段业务类型「businessType」,用于区分当前订单在统一下单时是否进行了分账,以便于在支付回调以后进行订单的判别,如果该订单支持分账,则生成一条分账记录,用于后续定时扫描该分账记录表,调用微信服务商分账接口
4、在支付回调和退款回调接口中,支付回调负责生成分账记录,退款回调用于更新分账记录「记录分账回退信息,考虑到分账回退可能会发生多次退款,所以该订单每个子商品进行退款都需要记录一下回退金额信息,以便于持平付款金额和退款金额」
5、注意:关于支付的商户号信息,subMchId 代表的是服务商下的特约商户ID,如果在当前门店进行下单的话,subMchId 应当取用门店在 PC 后台设置的商户号

extra:在公司业务中,可能会分为两种模式:直连商户和服务商商户模式,在直连商户中不需要设置 subMchId 其他信息,所以它取值字段为 mchId,当然它的支付证书和密钥也应该用门店的,这时候在 PC 后台仅仅设置商户号和比例信息就不能满足需求了,这取决于公司的业务范围;服务商商户中进行下单和分账都需要设置 subMchId,但它的证书和密钥不需要使用门店商户的,服务商下所有特约商户统一使用服务商后台的证书和密钥即可。

流程分析


以上流程图涉及到的只是分账发起前的一些前置工作,也是必备工作


以上流程图,是如何运用分账记录进行实时的请求分账,以及分账前后的预处理工作流程分解

实现过程中问题点

1、商户号与订单不匹配错误(分账时商户号必须和下单支付商户号一致,统一下单时「服务商模式」子商户号填写的应是门店商户号,不可用品牌商户号)
2、请求分账的交易模式和下单的交易模式不匹配,普通商户的交易只能普通商户发起分账,服务商下单的交易只能服务商发起分账「该问题就是直连商户模式和服务商模式不能混乱使用」
3、分账接收方全称未设置「调用分账接口时,商户接收方全称没设值,微信分账接口返回该错误」
4、证书文件有问题,请核实!「在服务商模式下,证书统一使用的是服务商证书;直连商户模式下,证书使用的是当前门店所配置的证书,目前该流程待后续扩展 TODO」
5、微信回调接口处:调用微信接口查询订单时,子商户号填写错误「微信返回子商户号与订单不匹配」,导致无法形成入账记录「解决:在支付流水表中加一个当前支付商户号字段,最好在订单信息表中也追加一个支付商户号方便后续涉及到的相关业务用到」
6、区分服务商/直连模式,解决:在订单表中追加一个 mode 字段判别其属于那种模式「后台切换支付模式(服务商、直连商品模式)可能会导致之前的分账记录无法请求分账」,该业务属于后续扩展直连商户分账方案提前预备

源码

  <binarywang.version>4.1.0</binarywang.version><dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-pay</artifactId><version>${binarywang.version}</version></dependency>
/*** @Author vnjohn* @since 2022/10/24*/
@Data
@EqualsAndHashCode(callSuper = true)
public class StoreCommissionRecordDTO extends BaseDTO implements Serializable {private static final long serialVersionUID = 4170369961010123978L;/*** 品牌ID*/private Long brandId;/*** 门店ID*/private Long storeId;/*** 门店商户号*/private String mchId;/*** 订单编号*/private String orderNo;/*** 当前分账金额*/private BigDecimal commission;/*** 状态:0-冻结中、1-已结算/可提现、2-已失效),默认0* @see BuCommissionStatusEnum*/private Integer status;/*** 分账执行次数*/private Integer callNum;/*** 订单支付时间*/private LocalDateTime orderPayTime;/*** 支付交易号:更新时传的是退款交易号,新增是支付交易号*/private String outTransactionId;/*** 部分退款,出现记录多次分账情况* 扩展字段:{"outTransactionId":xx,"commission":1}* outTransactionId:退款交易号* commission:当次退款金额*/private String extInfo;/*** 分账占比*/private Integer commissionPercent;/*** 退款金额:单位分*/private Long refundAmount;/*** 分账后交易后的订单号*/private String profitSharingOrderNo;}
/*** @Author vnjohn* @since 2022/10/26*/
@Data
@Builder
public class PayConfig implements Serializable {private static final long serialVersionUID = 1618292038545334525L;/*** 微信公众号或者小程序APPID*/private String wxAppId;/*** 根据证书路径解析出的商户证书数据流*/private InputStream sslCertInputStream;/*** 证书路径*/private String certPath;/*** 证书密码*/private String certPassword;/*** 微信商户Id*/private String mchId;/*** 微信商户密钥*/private String mchKey;/*** 子商户Id*/private String subMchId;/*** 子微信id*/private String subAppId;/*** 微信子商户密钥*/private String subMchKey;/*** 订单号+分销人Id,分账单号(内部生成)*/private String partnerTradeNo;/*** 分账接收方类型:个人或商户*/private String type;/*** 分账接收方微信用户openId*/private String receiveWxOpenId;/*** 分账接收方商户号ID*/private String receiveMchId;/*** 分账接收方商户名称*/private String receiveMchName;/*** 支付描述*/private String desc;/*** 支付金额*/private Long amount;/*** 微信统一支付transaction_id*/private String outTransactionId;/*** 商户支付设置类型*/private Integer payType;}
/*** 业务单元分账结算任务执行器* 五分钟执行一次,一次处理 200 条分账** @Author vnjohn* @since 2022/10/25*/
@Slf4j
@Component
public class StoreCommissionSettleJobExecutor {@Resourceprivate StoreCommissionRecordGateway commissionRecordGateway;@Resourceprivate StoreLedgerRpcService storeLedgerRpcService;@Resourceprivate AbstractWxPayService profitSharingWxPayService;/*** redis标记已发放佣金记录key前缀*/private static final String ORDER_COMMISSION_KEY_PREFIX = "LOCK_STORE_COMMISSION_LEDGER";/*** 分账订单标识前缀*/private static final String PROFIT_SHARING_ORDER_PREFIX = "BU";/*** 业务单元分账描述*/private static String PROFIT_SHARING_DESC = "门店「%s」分到商户「%s」";@XxlJob("storeCommissionSettleJobExecutor")public ReturnT<String> execute(String param) {StoreCommissionRecordQuery query = new StoreCommissionRecordQuery();// 0-冻结、1-结算、2-失效query.setStatus(StoreCommissionStatusEnum.FREEZE.getCode());query.setPageNo(PageConstant.DEFAULT_PAGE_NO);query.setPageSize(PageConstant.DEFAULT_PAGE_SIZE);List<StoreCommissionRecordDTO> storeCommissionRecordList = commissionRecordGateway.page(query);log.info("分账记录:{}",JsonUtil.toJson(storeCommissionRecordList));if (CollectionUtils.isEmpty(storeCommissionRecordList)) {return ReturnT.SUCCESS;}// 筛选出所有品牌和门店 id 后查询出其下支付配置和商户号配置信息List<Long> brandIdList = storeCommissionRecordList.stream().map(StoreCommissionRecordDTO::getBrandId).distinct().collect(Collectors.toList());List<Long> storeIdList = storeCommissionRecordList.stream().map(StoreCommissionRecordDTO::getStoreId).distinct().collect(Collectors.toList());Map<Long, PayConfigDTO> payConfigGroupBrandMap = new HashMap<>(brandIdList.size());Map<Long, storeLedgerDTO> storeLedgerConfigByStoreMap = new HashMap<>(storeIdList.size());List<StoreCommissionRecordDTO> records = new ArrayList<>();// 支付配置、分账记录存入 Map,避免遍历时出现相同的配置去重复查询for (StoreCommissionRecordDTO commissionRecord : storeCommissionRecordList) {// 品牌-支付配置信息PayConfigDTO payConfigDTO = payConfigGroupBrandMap.get(commissionRecord.getBrandId());if (null == payConfigDTO) {ResultDTO<PayConfigDTO> payConfigByBrandIdResult = storeLedgerRpcService.getPayConfigByBrandId(commissionRecord.getTenantId(), commissionRecord.getBrandId());log.info("支付配置信息:{}",JsonUtil.toJson(payConfigByBrandIdResult));if (!payConfigByBrandIdResult.isSuccess() || Objects.isNull(payConfigByBrandIdResult.getData())) {continue;}payConfigDTO = payConfigByBrandIdResult.getData();payConfigGroupBrandMap.put(commissionRecord.getBrandId(), payConfigDTO);}// 门店-分账配置信息StoreLedgerDTO storeLedgerDTO = storeLedgerConfigByStoreMap.get(commissionRecord.getStoreId());if (null == storeLedgerDTO) {ResultDTO<storeLedgerDTO> ledgerConfigResult = storeLedgerRpcService.getOneBuLeader(commissionRecord.getTenantId(), commissionRecord.getStoreId());log.info("分账配置信息:{}",JsonUtil.toJson(ledgerConfigResult));if (ledgerConfigResult.isSuccess() && Objects.nonNull(ledgerConfigResult.getData().getId())) {storeLedgerDTO = ledgerConfigResult.getData();storeLedgerConfigByStoreMap.put(commissionRecord.getStoreId(), storeLedgerDTO);}}// 返回的是可正常更新的分账记录records.add(processOrderLedgerCommission(payConfigDTO, commissionRecord, storeLedgerDTO));}if (CollectionUtils.isEmpty(records)) {log.info("该次任务暂无完成分账数");return ReturnT.SUCCESS;}log.info("该次任务完成分账数:{}", records.size());return ReturnT.SUCCESS;}@RedisLock(keys = {"#commissionRecord.orderNo"}, name = ORDER_COMMISSION_KEY_PREFIX)@Transactional(rollbackFor = Exception.class)public StoreCommissionRecordDTO processOrderLedgerCommission(PayConfigDTO payConfigDTO, StoreCommissionRecordDTO commissionRecord, storeLedgerDTO storeLedgerDTO) {if (commissionRecord.getStatus().equals(StoreCommissionStatusEnum.SETTLE.getCode())) {log.warn("该支付订单已分账过,请勿重复执行,{}", commissionRecord.getOrderNo());return null;}if (null == commissionRecord.getCommission() || commissionRecord.getCommission().doubleValue() <= 0) {log.warn("分账金额为空,recordId:{}", commissionRecord.getId());return null;}// 构建支付配置参数// TODO 此处追加一个 mode 字段,判别其是服务商模式还是自助申请模式PayConfig payConfig = buildPayConfig(payConfigDTO, commissionRecord, storeLedgerDTO);// 请求模版函数服务发起微信分账请求String outOrderNo = profitSharingWxPayService.pay(payConfig);if (StringUtils.isEmpty(outOrderNo)) {log.error("该笔订单:{},分账失败", commissionRecord.getOrderNo());return null;}commissionRecord.setProfitSharingOrderNo(outOrderNo);// 更新分账表记录状态commissionRecord.setStatus(StoreCommissionStatusEnum.SETTLE.getCode());commissionRecordGateway.batchUpdateSettleStatus(Collections.singletonList(commissionRecord));return commissionRecord;}/*** 构建微信分账配置参数* 请求分账的交易模式和下单的交易模式不匹配,普通商户的交易只能普通商户发起分账,服务商下单的交易只能服务商发起分账** @param payConfigDTO* @param storeLedgerDTO* @return*/private PayConfig buildPayConfig(PayConfigDTO payConfigDTO, StoreCommissionRecordDTO commissionRecord, StoreLedgerDTO storeLedgerDTO) {PayConfig payConfig = PayConfig.builder().build();payConfig.setWxAppId(payConfigDTO.getExtAppId());payConfig.setMchId(payConfigDTO.getExtMchId());payConfig.setMchKey(payConfigDTO.getMchKey());payConfig.setCertPath(payConfigDTO.getCertPath());// 判断模式是否为服务商模式if (payConfigDTO.getMode().equals(PayModeEnum.SERVICE_PROVIDER.getCode())) {WxServiceProviderConfig serviceProviderConfig = JsonUtil.toObject(payConfigDTO.getExtData(), WxServiceProviderConfig.class);payConfig.setWxAppId(serviceProviderConfig.getAppId());payConfig.setSubAppId(payConfigDTO.getExtAppId());payConfig.setMchId(serviceProviderConfig.getMchId());payConfig.setMchKey(serviceProviderConfig.getMchKey());payConfig.setCertPath(serviceProviderConfig.getCertPath());payConfig.setSubMchId(Objects.nonNull(storeLedgerDTO.getMchId()) ? storeLedgerDTO.getMchId() : payConfigDTO.getExtMchId());}payConfig.setReceiveMchId(payConfigDTO.getExtMchId());// 「分账接收方商户名称」不能为空payConfig.setReceiveMchName(payConfigDTO.getMchName());payConfig.setPartnerTradeNo(PROFIT_SHARING_ORDER_PREFIX + commissionRecord.getOrderNo() + commissionRecord.getStoreId());payConfig.setAmount(commissionRecord.getCommission().longValue());payConfig.setType(ProfitSharingTypeEnum.MERCHANT_ID.name());payConfig.setOutTransactionId(commissionRecord.getOutTransactionId());payConfig.setDesc(String.format(PROFIT_SHARING_DESC, storeLedgerDTO.getStoreId(), payConfig.getReceiveMchName()));log.info("微信分账配置参数:{}", JsonUtil.toJson(payConfig));return payConfig;}}
/*** @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";/*** 证书内容缓存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;}/*** 添加配置信息** @return*/public static WxPayService getWxPayService(PayConfig payConfig, byte[] keyContext) {log.info("添加微信配置信息:{},keyContext:{}", JsonUtil.toJson(payConfig), keyContext);WxPayConfig wxPayConfig = new WxPayConfig();wxPayConfig.setSignType(HMAC_SHA256);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;}boolean prePayResult;try {prePayResult = preparePay(payConfig);} catch (Exception e) {log.error("微信付款前的准备发生异常:{}", e.getMessage(), e);// 暂时返回 null 预处理下一条return null;
//            throw new BizException("分账前的准备发生异常");}String resStr = null;if (prePayResult) {try {resStr = beginPay(payConfig);} catch (Exception e) {log.error("微信付款发生异常:{}", e.getMessage(), e);// 暂时返回 null 预处理下一条return null;
//                throw new BizException("微信付款发生异常00000001");}if (resStr == null) {throw new BizException("微信付款发生异常: 响应值为空");}}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);}/*** 构建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 hmacSHA256(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();}}
/*** 商户通过微信分账给个人或商户 service** @author vnjohn* @since 2022/10/25*/
@Slf4j
@Service("wxProfitSharingPayService")
public class WxProfitSharingPayServiceImpl extends AbstractWxPayService {@Resourceprivate FileStorageService fileStorageService;@Resourceprivate RestTemplate restTemplate;private final static String AUTH_TYPE = "WECHATPAY2-SHA256-RSA2048";private final static String ADD_RECEIVERS_URL = "https://api.mch.weixin.qq.com/v3/profitsharing/receivers/add";private final static String PROFIT_SHARING_URL = "https://api.mch.weixin.qq.com/v3/profitsharing/orders";private final static String SERIAL_NO = "3815DE178035C04BD26DEE2C1CD0E4DDE6FD0347";/*** 获取证书文件流信息,提供给具体实现读取证书用的** @param certFilePath* @return*/public byte[] getCertBytes(String mchId, String certFilePath) {if (StrUtil.isBlank(certFilePath)) {return null;}certFilePath = StrUtil.trimToNull(certFilePath);RedisUtil redisUtil = RedisUtil.getInstance();String key = FILE_CERT_REDIS_PREFIX + mchId + certFilePath;// 看下是否存在redis里面。String fileVal = redisUtil.get(key);if (fileVal != null) {try {return Base64.getDecoder().decode(fileVal.getBytes());}catch(Exception e){log.error(e.toString());return getCertificateFromCloud(certFilePath, redisUtil, key);}} else {return getCertificateFromCloud(certFilePath, redisUtil, key);}}private byte[] getCertificateFromCloud(String certFilePath, RedisUtil redisUtil, String key) {String[] bucketAndKey = certFilePath.split(FILE_CERT_URL_JOIN);InputStream in = fileStorageService.getFileStream(BucketEnum.CERT,bucketAndKey[1]);try {byte[] fileBytes = IOUtils.toByteArray(in);//缓存一天String findContents = Base64.getEncoder().encodeToString(fileBytes);redisUtil.set(key, findContents, 86400);return fileBytes;} catch (IOException e) {log.error(e.toString());}return null;}/*** 添加分账接收方** @param payConfig* @return* @throws Exception*/@Overrideprotected boolean preparePay(PayConfig payConfig) throws Exception {if (StringUtils.isEmpty(payConfig.getOutTransactionId())) {log.error("无法进行前置分账,微信订单号为空");return false;}ProfitSharingService profitSharingService = getWxPayServiceInstance(payConfig, getCertBytes(payConfig.getMchId(), payConfig.getCertPath())).getProfitSharingService();// 封装微信请求参数ProfitSharingReceiverRequest receiverRequest = BeanUtil.copy(buildBaseParam(payConfig), ProfitSharingReceiverRequest.class);ProfitSharingReceiver profitSharingReceiver = ProfitSharingReceiver.builder().account(payConfig.getReceiveMchId()).type(payConfig.getType()).name(payConfig.getReceiveMchName()).build();profitSharingReceiver.setRelationType();receiverRequest.setReceiver(JsonUtil.toJson(profitSharingReceiver));// 这里会取出所有参数封装为 Map 返回Map<String, String> params = receiverRequest.getSignParams();String sha256Sign = getSha256Sign(params, payConfig.getMchKey());receiverRequest.setSign(sha256Sign);log.info("prePay addReceiver params:{},receiverRequest:{}", JsonUtil.toJson(params), JsonUtil.toJson(receiverRequest));ProfitSharingReceiverResult sharingReceiverResult = profitSharingService.addReceiver(receiverRequest);if (null != sharingReceiverResult && sharingReceiverResult.getResultCode().equals(SUCCESS_STRING)) {log.info("添加分账接收方成功,响应信息:{}", JsonUtil.toJson(sharingReceiverResult));return true;}log.error("添加分账接收方,响应异常:{}", JsonUtil.toJson(sharingReceiverResult));return false;}/*** 请求单次分账** @param payConfig* @return* @throws Exception*/@Overrideprotected String beginPay(PayConfig payConfig) throws Exception {// 封装参数ProfitSharingService profitSharingService = getWxPayServiceInstance(payConfig, getCertBytes(payConfig.getMchId(), payConfig.getCertPath())).getProfitSharingService();// 基本参数BaseWxPayRequest wxPayRequest = buildBaseParam(payConfig);// 单次分账参数ProfitSharingRequest request = BeanUtil.copy(wxPayRequest, ProfitSharingRequest.class);request.setTransactionId(payConfig.getOutTransactionId());request.setOutOrderNo(payConfig.getPartnerTradeNo());// 分账接收方列表ProfitSharingReceiver profitSharingReceiver = ProfitSharingReceiver.builder().account(payConfig.getReceiveMchId()).type(payConfig.getType()).amount(payConfig.getAmount()).name(payConfig.getReceiveMchName()).description(payConfig.getDesc()).build();profitSharingReceiver.setRelationType();List<ProfitSharingReceiver> receivers = Collections.singletonList(profitSharingReceiver);request.setReceivers(JsonUtil.toJson(receivers));// 生成签名且设值Map<String, String> signParams = request.getSignParams();String sha256Sign = getSha256Sign(signParams, payConfig.getMchKey());request.setSign(sha256Sign);// 发出请求ProfitSharingResult sharingResult = profitSharingService.profitSharing(request);log.info("beginPay profitSharing params:{},receiverRequest:{}", JsonUtil.toJson(signParams), JsonUtil.toJson(request));if (null != sharingResult && sharingResult.getResultCode().equals(SUCCESS_STRING)) {log.info("请求单次分账成功,响应信息:{}", JsonUtil.toJson(sharingResult));return sharingResult.getOutOrderNo();}log.error("请求单次分账失败,响应异常:{}", JsonUtil.toJson(sharingResult));return null;}@Overrideprotected Map<String, String> afterPay(PayConfig payConfig) throws Exception {return null;}/*** 构建分账请求前基本参数信息** @param payConfig* @return*/public static BaseWxPayRequest buildBaseParam(PayConfig payConfig) {BaseWxPayRequest wxPayRequest = new BaseWxPayRequest() {@Overrideprotected void checkConstraints() {}@Overrideprotected void storeMap(Map<String, String> map) {}};wxPayRequest.setAppid(payConfig.getWxAppId());wxPayRequest.setMchId(payConfig.getMchId());wxPayRequest.setSubMchId(payConfig.getSubMchId());wxPayRequest.setSubAppId(payConfig.getSubAppId());wxPayRequest.setNonceStr(getNonceStr());wxPayRequest.setSignType(HMAC_SHA256);return wxPayRequest;}//    /**
//     * 分账回退
//     * @param payConfig
//     * @return
//     * @throws Exception
//     */
//    @Override
//    protected Map<String, String> afterPay(PayConfig payConfig) throws Exception {//        /**
//         * 封装参数
//         */
//        Map<String, String> parm = new LinkedHashMap<>();
//        buildBaseParam(payConfig, parm);
//
//        parm.put("transaction_id", payConfig.getOutTransactionId()); //微信支付订单号
//        parm.put("out_order_no", payConfig.getPartnerTradeNo()); //商户系统内部的分账单号,使用orderNo+distributorId
//        parm.put("description", "分账已完成");
//
//        parm.put("sign", getSha256Sign(parm, payConfig.getMchKey()));
//
//        String xmlStrParam = XmlUtil.xmlFormat(parm, false);
//
//        log.info("beginPay finish-profitSharing url:{}, xmlStrParam:{}, payConfig:{}",
//                FINISH_PROFITSHARING_PAY_URL, xmlStrParam, payConfig.toString());
//        String respXml = HttpUtil.post(FINISH_PROFITSHARING_PAY_URL, xmlStrParam, payConfig);
//
//        log.info("beginPay finish-profitSharing respXml:{}", respXml);
//        return XmlUtil.xmlParse(respXml);
//    }}

源码部分分享到此处,其他代码由于涉及隐私问题,无法贴出,有问题可以私信或留言
代码不重要,重要是流程和思路能够梳理清楚,代码至简,书写好的风格相信每个人都不一样,TODO

码农的成果

总结

遇到事情不要慌,一点一点去攻破,到头来你就会感谢自己的付出没有白费,从一开始什么都没有,到一点一点去研究和分析,才能够最终实现该需求,路上的坑都踩过了,欢迎大家询问和指教!

网上资料也很多,该业务实现,杂七杂八的涉及也很多,也感谢各位博主的付出,贴出此文方案和自身的理解也是为了让各位大佬能够更快的熟悉和切入,避免将时间花费在无效的用处上

更多技术文章可以查看:vnjohn 个人博客

微信服务商分账思路剖析、设计流程及源码实现相关推荐

  1. 微信服务商分账功能 PHP

    项目说明 微信服务商分账接口说明: 链接: 微信服务商分账接口说明. 开通分账功能 链接: 微信服务商分账接口说明. 服务商代子商户发起添加分账接收方请求 在统一下单API.付款码支付API.委托扣款 ...

  2. JAVA 小程序支付+服务商分账

    产品介绍: 服务商分账,主要用于服务商帮助特约商户完成订单收单成功后的资金分配. 使用场景举例: 1.服务商抽成 在各个行业中,服务商为特约商户提供增值服务,服务商与特约商户协商,可以从特约商户的交易 ...

  3. 微信支付服务商分账-请求单次分账

    注意事项 服务商请求单次分账与普通商户请求单次分账的区别.1 下预付单时 务必要添加 profit_sharing 为 Y 否则该笔订单不支持分账. 参考链接 https://pay.weixin.q ...

  4. 超级简单thinkphp微信小程序服务商分账。以及小程序普通支付,微信特约商户

    产品介绍 服务商分账,主要用于服务商帮助特约商户完成订单收单成功后的资金分配. 使用场景举例 1.服务商抽成 在各个行业中,服务商为特约商户提供增值服务,服务商与特约商户协商,可以从特约商户的交易流水 ...

  5. 微信小程序后端java服务商分账实现

    微信小程序后端java服务商分账实现 最近公司申请微信服务商,需要给第三方提供支付.分账功能. 商户调用服务商统一支付 首先,服务商小程序支付,基本与普通商户小程序支付一致 支付使用服务商统一下单接口 ...

  6. 微信商家分账热门问题解答合集

    微信商家分账是微信支付将自身清结算能力赋能给平台商户,今天小编针对常见的一些问题整理了一期微信商家分账热门问题合集,希望可以帮助大家更进一步地了解微信商家分账. 1.微信商家分账可以按比例分配的吗? ...

  7. 智慧停车平台为什么迫切需要服务商分账功能?

    (图源:pexels网站) 随着各行各业数字化转型速度的加快,企业也需要搭建好对应的数字化运营策略,以应对越来越复杂的发展趋势,智慧停车行业也不例外.据统计,2021年全国智慧停车行业市场规模已经达到 ...

  8. 基于FPGA数字时钟的设计(附源码)

    大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分.大侠可以关注"FPGA技术江湖"微信公众号,在"闯荡江湖"."行侠仗义"栏里获取其 ...

  9. (仿微信Android)IM聊天+抢红包+直播+朋友圈源码发布了

    (仿微信Android)IM聊天+抢红包+直播+朋友圈源码发布了 功能概览: IM聊天 单聊/群聊/聊天室--基于环信sdk 红包功能: 1.一对一红包 2.群红包(抢红包.答题红包.专属红包) 3. ...

  10. 【朝花夕拾】Android自定义View之(一)手把手教你看懂View绘制流程——向源码要答案

    前言 原文:Android自定义View之(一)手把手教你看懂View绘制流程--向源码要答案 View作为整个app的颜值担当,在Android体系中占有重要的地位.深入理解Android View ...

最新文章

  1. php ole word,介绍 · PHPword新版开发指南 · 看云
  2. MySQL【问题记录 01】报错 1709 - Index column size too large. The maximum column size is 767 bytes. 可能是最简单的方法
  3. em算法 实例 正态分布_【机器学习】EM算法详细推导和讲解
  4. 开源的DevOps开发工具箱
  5. MaxCompute full outer join改写left anti join实践
  6. 【Linux】kali linux 安装 google chrome
  7. 快速掌握:大型分布式系统中的缓存架构
  8. Java多线程学习十六:读写锁 ReadWriteLock 获取锁有哪些规则
  9. iOS 关于TouchID指纹解锁的实现
  10. PHP上传文件大小和时间限制
  11. 软件工程理论与实践第二版吕云翔课后习题答案
  12. python数据处理(招聘信息薪资字段的处理)
  13. gg修改器修改数值没有用怎么办_gg修改器如何使用?gg修改器使用方法
  14. [古文观止]《相州昼锦堂记》(宋·欧阳修)
  15. ie11兼容问题汇总及解决方案
  16. LPC1768 UART超时中断的使用
  17. 读取三维数据.stl文件
  18. 计算机图形学和数据科学实验,计算机图形学作业-图像处理实验室——中国科学技.PDF...
  19. 解决ThinkPad联想 笔记本电脑无法连接隐藏网络问题-提示“无法连接这个网络”
  20. 浙江计算机二级word试题,浙江省计算机办公二级新增试题(word、Excel)

热门文章

  1. 网站SEO优化知识梳理
  2. mongodb常用方法
  3. 怎样写一个lemon的spj
  4. ubuntu开启客户端nfs服务_ubuntu16.04搭建nfs服务的方法
  5. HDR:Recovering High Dynamic Range Radiance Maps from Photographs
  6. 马克飞象 Markdown 使用和学习
  7. oracle自学OCA,上海学习oracle OCA
  8. php 自适应 博客,三种方法让网站背景自动适应各浏览器大小
  9. Install Ubuntu18.04.1 and Win7 on A53S
  10. DNW使用和常见问题