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

最近公司申请微信服务商,需要给第三方提供支付、分账功能。

商户调用服务商统一支付

首先,服务商小程序支付,基本与普通商户小程序支付一致

支付使用服务商统一下单接口:微信官方文档地址https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=9_1

这里的商户号是服务商的商户号,小程序appid是商家的appid,子商户号是商家的商户号且商家是服务商的特约商户需要授权。

这两个openid建议使用sub_openid,商户小程序的唯一openid。

最重要的参数,传Y才能进行分账。

服务商分账

服务商分账有两种接口:单次分账和多次分账
微信官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/allocation_sl.php?chapter=25_1&index=1

多的不说直接上代码:
引入maven 依赖

<dependency><groupId>org.jodd</groupId><artifactId>jodd-core</artifactId><version>5.1.5</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.55</version></dependency><dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-pay</artifactId><version>3.8.0</version></dependency>

分账请求类

package com.linli.pay.service.pay.req;
import lombok.Data;
@Data
public class WxSharingOrderRequest {/*** 服务商商户号*/private String mch_id;/*** 子商户号*/private String sub_mch_id;/*** 服务商appid*/private String appid;/*** 子商户appid*/private String sub_appid;/*** 随机字符串*/private String nonce_str;/*** 签名*/private String sign;/*** 签名类型(只支持HMAC-SHA256)*/private String sign_type;/*** 微信订单号*/private String transaction_id;/*** 商家订单号*/private String out_trade_no;/*** 商户分账单号(同一个单号多次提交只算一次)*/private String out_order_no;/*** 商户分账金额(小于等于订单金额*(1-手续费)*最大分账比例)*/private Integer amount;/*** 分账接收方列表(单次分账不能即是支付商户又是接收商户,多次分账没有限制)*/private String receivers;}

分账返回类

package com.linli.pay.service.pay.resp;
import lombok.Data;@Data
public class WxSharingOrderResp {//返回状态码,通信标识,SUCCESS/FAILprivate String return_code;//返回信息,通信标识OKprivate String return_msg;//业务结果,交易标识,SUCCESS/FAILprivate String result_code;//错误代码private String err_code;//错误代码描述private String err_code_des;//商户号private String mch_id;//子商户号private String sub_mch_id;//公众账号idprivate String appid;private String sub_appid;//随机字符串private String nonce_str;//签名private String sign;//微信支付订单号private String transaction_id;//商户分账单号(商户订单号)private String out_order_no;//商户分账单号private String order_id;}

分账接收方

package com.linli.pay.service.pay.req;import lombok.Data;@Data
public class WxSharingReceiversVO {/*** 分账接收方类型*/private String type;/*** 分账接收方帐号*/private String account;/*** 分账金额*/private Integer amount;/*** 分账描述*/private String description;
}

application.yml配置类

wx:mini:keyPath: classpath:cert/apiclient_cert.p12 # 商户p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)busAppId: busMchId: busMchKey: busSubAppId: busSubMchId:

package com.linli.pay.config.wx;import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Data
@Configuration
@ConfigurationProperties(prefix = "wx.mini")
public class WxMiniPayProperties {/*** apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定*/private String keyPath;/*** 服务商appid*/private String busAppId;/***服务商商户号*/private String busMchId;/*** 服务商商户密钥*/private String busMchKey;/*** 调起支付的小程序APPID*/private String busSubAppId;/*** 微信支付子商户号*/private String busSubMchId;@Bean(name = "wxBusMiniPayService")public WxPayService wxBusMiniPayService() {WxPayConfig payConfig = new WxPayConfig();payConfig.setAppId(StringUtils.trimToNull(this.busAppId));payConfig.setMchId(StringUtils.trimToNull(this.busMchId));payConfig.setMchKey(StringUtils.trimToNull(this.busMchKey));payConfig.setSubAppId(StringUtils.trimToNull(this.busSubAppId));payConfig.setSubMchId(StringUtils.trimToNull(this.busSubMchId));payConfig.setKeyPath(StringUtils.trimToNull(this.keyPath));// 可以指定是否使用沙箱环境payConfig.setUseSandboxEnv(false);WxPayService wxPayService = new WxPayServiceImpl();wxPayService.setConfig(payConfig);return wxPayService;}}

service实现类

/*** 单次分账* @param data* @return*/public WxSharingOrderResp oncePaySharing(WxSharingOrderRequest data)throws Exception{WxSharingOrderRequest wxSharingOrderRequest = new WxSharingOrderRequest();wxSharingOrderRequest.setAppid(wxBusMiniPayService.getConfig().getAppId());wxSharingOrderRequest.setMch_id(wxBusMiniPayService.getConfig().getMchId());wxSharingOrderRequest.setSub_mch_id(wxBusMiniPayService.getConfig().getSubMchId());wxSharingOrderRequest.setSub_appid(wxBusMiniPayService.getConfig().getSubAppId());wxSharingOrderRequest.setTransaction_id(data.getTransaction_id());wxSharingOrderRequest.setNonce_str(WxUtils.makeNonStr());wxSharingOrderRequest.setOut_order_no(data.getOut_order_no());List<WxSharingReceiversVO> list = Lists.newArrayList();WxSharingReceiversVO receiversVO = new WxSharingReceiversVO();receiversVO.setAccount(wxBusMiniPayService.getConfig().getSubMchId());receiversVO.setType("MERCHANT_ID");receiversVO.setAmount(1);receiversVO.setDescription("分到商户");list.add(receiversVO);wxSharingOrderRequest.setReceivers(FastJsonUtils.listToString(list));BeanMap beanMap = BeanMap.create(wxSharingOrderRequest);wxSharingOrderRequest.setSign(WxUtils.makeSign(beanMap,wxBusMiniPayService.getConfig().getMchKey(),"SHA256"));String xmlStr = WxUtils.truncateDataToXML(WxSharingOrderRequest.class, wxSharingOrderRequest).replace("&quot;","\"");String url = "https://api.mch.weixin.qq.com/secapi/pay/profitsharing";String result = WxCertHttpUtil.postData(url,xmlStr,wxBusMiniPayService.getConfig().getMchId(),wxBusMiniPayService.getConfig().getKeyPath());Object obj = WxUtils.truncateDataFromXML(WxSharingOrderResp.class, result);WxSharingOrderResp resp = new WxSharingOrderResp();BeanUtils.copyProperties(obj,resp);return resp;}/*** 多次分账* @param data* @return*/public WxSharingOrderResp multiPaySharing(WxSharingOrderRequest data)throws Exception{WxSharingOrderRequest wxSharingOrderRequest = new WxSharingOrderRequest();wxSharingOrderRequest.setAppid(wxBusMiniPayService.getConfig().getAppId());wxSharingOrderRequest.setMch_id(wxBusMiniPayService.getConfig().getMchId());wxSharingOrderRequest.setSub_mch_id(wxBusMiniPayService.getConfig().getSubMchId());wxSharingOrderRequest.setSub_appid(wxBusMiniPayService.getConfig().getSubAppId());wxSharingOrderRequest.setTransaction_id(data.getTransaction_id());wxSharingOrderRequest.setNonce_str(WxUtils.makeNonStr());wxSharingOrderRequest.setOut_order_no(data.getOut_order_no());List<WxSharingReceiversVO> list = Lists.newArrayList();WxSharingReceiversVO receiversVO = new WxSharingReceiversVO();receiversVO.setAccount(wxBusMiniPayService.getConfig().getSubMchId());receiversVO.setType("MERCHANT_ID");receiversVO.setAmount(data.getAmount());receiversVO.setDescription("给商家分账");list.add(receiversVO);wxSharingOrderRequest.setReceivers(FastJsonUtils.listToString(list));BeanMap beanMap = BeanMap.create(wxSharingOrderRequest);wxSharingOrderRequest.setSign(WxUtils.makeSign(beanMap,wxBusMiniPayService.getConfig().getMchKey(),"SHA256"));String xmlStr = WxUtils.truncateDataToXML(WxSharingOrderRequest.class, wxSharingOrderRequest).replace("&quot;","\"");String url = "https://api.mch.weixin.qq.com/secapi/pay/multiprofitsharing";String result = WxCertHttpUtil.postData(url,xmlStr,wxBusMiniPayService.getConfig().getMchId(),wxBusMiniPayService.getConfig().getKeyPath());Object obj = WxUtils.truncateDataFromXML(WxSharingOrderResp.class, result);WxSharingOrderResp resp = new WxSharingOrderResp();BeanUtils.copyProperties(obj,resp);return resp;}

微信工具类

/*** 微信工具类*/
public class WxUtils {private static Logger logger = LoggerFactory.getLogger(WxUtils.class);private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";private static final Random RANDOM = new SecureRandom();/*** 数据转换为xml格式** @param object* @param obj* @return*/public static String truncateDataToXML(Class<?> object, Object obj) {XStream xStream = new XStream(new XppDriver(new NoNameCoder()));xStream.alias("xml", object);return xStream.toXML(obj);}/*** 数据转换为对象** @param object* @param str* @return*/public static Object truncateDataFromXML(Class<?> object, String str) {XStream xstream = new XStream(new StaxDriver());xstream.alias("xml", object);return xstream.fromXML(str);}/*** 获取随机字符串 Nonce Str** @return String 随机字符串*/public static String makeNonStr() {char[] nonceChars = new char[32];for (int index = 0; index < nonceChars.length; ++index) {nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));}return new String(nonceChars);}public static String map2XmlString(Map<String, String> map) {String xmlResult = "";StringBuffer sb = new StringBuffer();sb.append("<xml>");for (String key : map.keySet()) {String value = "<![CDATA[" + map.get(key) + "]]>";sb.append("<" + key + ">" + value + "</" + key + ">");System.out.println();}sb.append("</xml>");xmlResult = sb.toString();return xmlResult;}/*** 拼接签名数据** @return*/public static String makeSign(BeanMap beanMap,String mchKey,String signType)throws Exception {SortedMap<String, String> signMaps = Maps.newTreeMap();for (Object key : beanMap.keySet()) {Object value = beanMap.get(key);// 排除空数据if (value == null) {continue;}signMaps.put(key + "", String.valueOf(value));}if(signType.equals("MD5")) {// 生成签名return generateSign(signMaps, mchKey);}else if(signType.equals("SHA256")){return generateSignSHA256(signMaps, mchKey);}else{return null;}}/*** 生成签名** @param signMaps* @return* @throws Exception*/public static String generateSign(SortedMap<String, String> signMaps,String mchKey) {StringBuffer sb = new StringBuffer();// 字典序for (Map.Entry signMap : signMaps.entrySet()) {String key = (String) signMap.getKey();String value = (String) signMap.getValue();// 为空不参与签名、参数名区分大小写if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)) {sb.append(key).append("=").append(value).append("&");}}// 拼接keysb.append("key=").append(mchKey);// MD5加密String sign = MD5Encode(sb.toString(), "UTF-8").toUpperCase();return sign;}public static String generateSignSHA256(SortedMap<String, String> signMaps,String mchKey)throws Exception{StringBuffer sb = new StringBuffer();// 字典序for (Map.Entry signMap : signMaps.entrySet()) {String key = (String) signMap.getKey();String value = (String) signMap.getValue();// 为空不参与签名、参数名区分大小写if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)) {sb.append(key).append("=").append(value).append("&");}}// 拼接keysb.append("key=").append(mchKey);// MD5加密String sign = HMACSHA256(sb.toString(), mchKey).toUpperCase();return sign;}private static String byteArrayToHexString(byte b[]) {StringBuffer resultSb = new StringBuffer();for (int i = 0; i < b.length; i++)resultSb.append(byteToHexString(b[i]));return resultSb.toString();}private static String byteToHexString(byte b) {int n = b;if (n < 0)n += 256;int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}public static String MD5Encode(String origin, String charsetname) {String resultString = null;try {resultString = new String(origin);MessageDigest md = MessageDigest.getInstance("MD5");if (charsetname == null || "".equals(charsetname))resultString = byteArrayToHexString(md.digest(resultString.getBytes()));elseresultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));} catch (Exception exception) {}return resultString;}private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };private static HashMap<String, String> sortAsc(Map<String, String> map) {HashMap<String, String> tempMap = new LinkedHashMap<String, String>();List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(map.entrySet());//排序Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {@Overridepublic int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {return o1.getKey().compareTo(o2.getKey());}});for (int i = 0; i < infoIds.size(); i++) {Map.Entry<String, String> item = infoIds.get(i);tempMap.put(item.getKey(), item.getValue());}return tempMap;}private static String SHA1(String str) {try {MessageDigest digest = MessageDigest.getInstance("SHA-1"); //如果是SHA加密只需要将"SHA-1"改成"SHA"即可digest.update(str.getBytes());byte messageDigest[] = digest.digest();// Create Hex StringStringBuffer hexStr = new StringBuffer();// 字节数组转换为 十六进制 数for (int i = 0; i < messageDigest.length; i++) {String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);if (shaHex.length() < 2) {hexStr.append(0);}hexStr.append(shaHex);}return hexStr.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return null;}/*** 生成 HMACSHA256* @param data 待处理数据* @param key 密钥* @return 加密结果* @throws Exception*/public static String HMACSHA256(String data, String key) throws Exception {String hash = "";try {Mac sha256_HMAC = Mac.getInstance("HmacSHA256");SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");sha256_HMAC.init(secret_key);byte[] bytes = sha256_HMAC.doFinal(data.getBytes());hash = byteArrayToHexString(bytes);} catch (Exception e) {System.out.println("Error HmacSHA256 ===========" + e.getMessage());}return hash.toUpperCase();}
}

带双向证书的post请求工具类

import jodd.util.ResourcesUtil;
import org.apache.commons.lang3.RegExUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;public class WxCertHttpUtil {private static int socketTimeout = 10000;// 连接超时时间,默认10秒private static int connectTimeout = 30000;// 传输超时时间,默认30秒private static RequestConfig requestConfig;// 请求配置private static CloseableHttpClient httpClient;// HTTP请求/**** @param url API地址* @param xmlObj 要提交的XML* @param mchId 服务商商户ID* @param certPath证书路径* @return*/public static String postData(String url, String xmlObj, String mchId, String certPath) {// 加载证书try {loadCert(mchId, certPath);} catch (Exception e) {e.printStackTrace();}String result = null;HttpPost httpPost = new HttpPost(url);StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");httpPost.addHeader("Content-Type", "text/xml");httpPost.setEntity(postEntity);requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();httpPost.setConfig(requestConfig);try {HttpResponse response = null;try {response = httpClient.execute(httpPost);} catch (IOException e) {e.printStackTrace();}HttpEntity entity = response.getEntity();try {result = EntityUtils.toString(entity, "UTF-8");} catch (IOException e) {e.printStackTrace();}} finally {httpPost.abort();}return result;}/***加载证书** @param mchId 服务商商户ID* @param certPath 证书路径* @throws Exception*/private static void loadCert(String mchId, String certPath) throws Exception {// 证书密码,默认为服务商商戶IDString key = mchId;// 证书路径String path = RegExUtils.removeFirst(certPath, "classpath:");if (!path.startsWith("/")) {path = "/" + path;}// 指定证书格式为PKCS12KeyStore keyStore = KeyStore.getInstance("PKCS12");// 读取PKCS12证书文件InputStream instream = ResourcesUtil.getResourceAsStream(path);try {// 指定PKCS12的密碼(商戶ID)keyStore.load(instream, key.toCharArray());} finally {instream.close();}SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE);httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();}
}

最后,可以通过微信工具检查是否签名正确。

微信小程序后端java服务商分账实现相关推荐

  1. 微信小程序篇_01 微信小程序与Java后端接口交互

    微信小程序与Java后端接口交互 准备 创建后端项目 创建小程序项目 本文主要介绍小程序前后端数据的交互,实践演示. 准备 创建后端项目 我这里就创建一个SpringBoot项目作为演示. 在创建项目 ...

  2. Java小程序post如何传参,微信小程序向Java后台传输参数的方法实现

    微信小程序向Java后台传输参数的方法实现 首先,微信小程序我使用的是微信web开发者工具 想要向后台传输数据,需要在js中写 在微信小程序的官方文档中可以看到一个api叫wx.request它的作用 ...

  3. 微信小程序与Java后台的通信

    一.写在前面 最近接触了小程序的开发,后端选择Java,因为小程序的代码运行在腾讯的服务器上,而我们自己编写的Java代码运行在我们自己部署的服务器上,所以一开始不是很明白小程序如何与后台进行通信的, ...

  4. 《微信小程序》微信小程序用java后台连接数据库进行操作。

    微信小程序与Java后台的通信 一.写在前面 最近接触了小程序的开发,后端选择Java,因为小程序的代码运行在腾讯的服务器上,而我们自己编写的Java代码运行在我们自己部署的服务器上,所以一开始不是很 ...

  5. 微信小程序登录Java后台接口

    微信小程序登录java后台接口 首先看一下微信小程序的开发文档: 微信小程序开发文档 步骤: 小程序端向微信接口服务发送请求--wx.login():获取到登录临时凭证code 小程序端拿着获取到的c ...

  6. chatgpt智能问答微信小程序+后端源码+视频搭建教程

    chatgpt智能问答微信小程序+后端源码+视频搭建教程,这是一套微信小程序,后端是thinkphp框架为接口的,后端是前后端分离用elmentUI的源码框架. 小狐狸GPT付费体验系统是一款基于Th ...

  7. 用 Django 开发微信小程序后端实现用户登录

    本文将介绍采用 Django 开发微信小程序后端,通过将用户模块进行重构,并采用JWT来进行用户认证,来解决以下问题: 微信小程序不支持 Cookie,因此不能采用 Django 默认的 Sessio ...

  8. 关于开发微信小程序后端linux使用xampp配置https

    关于开发微信小程序后端linux使用xampp配置https 背景 由于最近开发微信小程序,前后端交互需要使用https协议,故需要配置https服务 服务器环境 服务器系统 ubuntu 环境 xa ...

  9. 微信小程序后端框架|微信公众号后端框架(C# WebAPI)

    微信小程序后端框架|微信公众号后端框架(C# WebAPI) 微信小程序 简称小程序,英文名Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用"触手可及"的 ...

最新文章

  1. @ini_get php,php中get_cfg_var()和ini_get()的用法及区别_php技巧_脚本之家
  2. JavaScript中正则表达式学习(一)
  3. DB2性能调节工作总结
  4. 03-树3 Tree Traversals Again (c++递归实现)
  5. startlogging中设置setstdout=false来禁用这个功能。_Windows 10禁用USB选择性暂停设置,峰哥教你解决USB设备失灵问题...
  6. 烽火戏诸侯于计算机相关联系,烽火戏诸侯的成语典故
  7. Mysql一些重要配置参数的学习与整理(二)
  8. 短实体,长句实体抽取
  9. win10无法修改mac地址_路由器无线MAC地址过滤如何设置
  10. 关于mac下连接mysql和mysql workbench连接mysql的异常
  11. 苹果cmsv10精仿好看的挖片网免费自适应简约模板
  12. ListView更新的几种方法
  13. python 典型相关分析_CCA典型关联分析原理与Python案例
  14. iOS 给文字添加删除线
  15. 十七年未盈利,硅谷最神秘独角兽Palantir的盈利魔咒何时破?
  16. Android error: “Apostrophe not preceded by \” 解决办法
  17. [06]python3 shutil高级文件操作模块
  18. 阿里云服务器ECS中扩容云盘后磁盘容量没有增加的解决方法
  19. 调用百度翻译机器人接口纯代码
  20. 百度SEO Photo相册图库个人网站模板

热门文章

  1. vue 生命周期的详解
  2. 儿童玩具出口欧盟CE认证测试标准
  3. D75 LeetCode 812.最大三角形面积(简单)
  4. 小诀窍:不妨尝试从交付质量上打败对手
  5. 使用自然语言处工具HanLP获取人名
  6. 51单片机:利用外部中断实现按键按一下数码管数字加1直到加到99,另一个按键实现清零
  7. 小米10s微距模式使用教程分享
  8. 定义销售组织(Sales Organization)
  9. 贪心算法在找钱问题上的使用
  10. paho.mqtt.cpp库编译