Ver código fonte

feat(pay): 添加分账功能并优化支付流程

- 新增分账相关实体类和接口- 实现分账接收方添加功能
- 优化订单支付流程,支持微信支付
- 重构部分代码以提高可维护性
wzq 1 semana atrás
pai
commit
ce814ebab2
12 arquivos alterados com 390 adições e 81 exclusões
  1. 13 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/controller/OrderController.java
  2. 2 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/IOrderService.java
  3. 5 4
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/WeChatPayService.java
  4. 80 44
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/OrderServiceImpl.java
  5. 0 3
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatConstants.java
  6. 33 1
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatPayV3Utils.java
  7. 0 3
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/ProfitSharingRequest.java
  8. 1 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/Receiver.java
  9. 72 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/ReceiverAddForm.java
  10. 89 23
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/payController.java
  11. 2 3
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/routing/ProfitsharingAddReceiverBo.java
  12. 93 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/routing/WeChatProfitSharingService.java

+ 13 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/controller/OrderController.java

@@ -136,6 +136,19 @@ public class OrderController {
         return Result.ok(appOrderService.createOrder(createOrderForm));
     }
 
+    /**
+     * 订单-支付
+     *
+     * @param appOrderId
+     * @return
+     */
+    @Operation(summary = "订单-支付")
+    @RepeatSubmit(serviceId = "payOrder", limitType = RepeatSubmit.Type.PARAM, lockTime = 3)
+    @PutMapping("/payOrder")
+    public Result<UserPayForm> payOrder(@RequestParam("appOrderId") String appOrderId){
+        return Result.ok(appOrderService.payOrder(appOrderId));
+    }
+
     /**
      * 支付回调
      *

+ 2 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/IOrderService.java

@@ -96,4 +96,6 @@ public interface IOrderService {
     String refundOrder(String orderCode, String reason);
 
     Map<String, String> wechatPayNotify(HttpServletRequest request, HttpServletResponse response);
+
+    UserPayForm payOrder(String appOrderId);
 }

+ 5 - 4
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/WeChatPayService.java

@@ -204,8 +204,7 @@ public class WeChatPayService {
                     appOrder.setPayStatus(CommonConstant.ORDER_STATUS_1);
                     appOrder.setPayTime(new Date());
                     appOrder.setPayType(CommonConstant.STATUS_0_INT);
-                    appOrder.setCallbackStatus(CommonConstant.STATUS_1_INT);
-                    appOrderMapper.updateById(appOrder);
+
                     List<AppOrderProInfo> proInfoList = appOrderProInfoMapper.selectList(Wrappers.<AppOrderProInfo>lambdaQuery().eq(AppOrderProInfo::getOrderId, appOrder.getId()));
                     if (ObjectUtil.isNotEmpty(proInfoList)){
                         for (AppOrderProInfo appOrderProInfo : proInfoList) {
@@ -214,6 +213,8 @@ public class WeChatPayService {
                         }
                     }
                 }
+                appOrder.setCallbackStatus(CommonConstant.STATUS_1_INT);
+                appOrderMapper.updateById(appOrder);
             }
             result.put("code", "SUCCESS");
             result.put("message", "OK");
@@ -287,7 +288,7 @@ public class WeChatPayService {
             final String status = res.getString("status");
             switch (status) {
                 case "SUCCESS":
-                    log.info("退款成功");
+                    log.info("订单:{},退款成功!原因:{}",orderCode,reason);
                     break;
                 case "CLOSED":
                     log.info("退款关闭");
@@ -297,7 +298,7 @@ public class WeChatPayService {
 
                     break;
                 case "ABNORMAL":
-                    log.info("退款异常");
+                    log.info("订单:{},退款异常",orderCode);
                     break;
             }
         } catch (Exception e) {

+ 80 - 44
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/OrderServiceImpl.java

@@ -1135,48 +1135,7 @@ public class OrderServiceImpl implements IOrderService {
         if (ObjectUtil.isNotEmpty(appOrder.getOrderOrFree()) && appOrder.getOrderOrFree() == 1 && appOrder.getPrice().compareTo(BigDecimal.ZERO)== 0) {
             payForm.setOrPayOrder(0);
         } else {
-            Calendar calendar = Calendar.getInstance();
-            calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 15);
-            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
-
-            //构建微信支付参数
-            JSONObject params = new JSONObject();
-            params.put("sp_appid", WechatConstants.WECHAT_SP_APPID); //服务商appid
-            params.put("sp_mchid", WechatConstants.WECHAT_SP_MCH_ID);//服务商商户号
-            params.put("sub_appid", WechatConstants.WECHAT_SUB_APPID); //小程序appid
-            params.put("sub_mchid", WechatConstants.WECHAT_SUB_MCH_ID); //特约商户商户号
-
-            params.put("description", "全龄运动"); //商品描述
-            params.put("out_trade_no", appOrder.getOrderCode()); //商户订单号
-            params.put("time_expire", sdf.format(calendar.getTime())); //交易结束时间 选填 时间到了之后将不能再支付 遵循rfc3339标准格式
-            params.put("attach", appOrder.getOrderCode()); //附加数据 选填
-            // 在查询API和支付通知中原样返回 可作为自定义参数使用
-            params.put("notify_url", WechatUrlConstants.PAY_V3_NOTIFY); //支付结果异步通知接口
-            //分账必传参数
-            if (appOrder.getOrderType() != 0){
-                JSONObject settleInfo = new JSONObject();
-                settleInfo.put("profit_sharing", Boolean.TRUE);
-                params.put("settle_info", settleInfo);
-            }
-            //电子发票入口
-            params.put("support_fapiao", Boolean.TRUE);
-
-            //订单金额信息
-            JSONObject amount_json = new JSONObject();
-            //支付金额 单位:分
-            //amount_json.put("total", Integer.parseInt(amount_fee(appOrder.getPrice())));
-            amount_json.put("total", Integer.parseInt(amount_fee(new BigDecimal("0.01"))));//测试0.01元
-            amount_json.put("currency", "CNY");//固定传:CNY,代表人民币
-            params.put("amount", amount_json);
-
-            //支付者信息
-            JSONObject payer = new JSONObject();
-            //用户在小程序侧的openid
-            payer.put("sub_openid", sysUser.getOpenid());
-            params.put("payer", payer);
-
-            //拉起支付-返回JSAPI参数
-            Map<String, String> result = weChatPayService.wechatPay(params);
+            Map<String, String> result = payment(appOrder.getId());
             payForm.setParams(result);
 
             //发布任务到redission延迟队列
@@ -1186,6 +1145,62 @@ public class OrderServiceImpl implements IOrderService {
         return payForm;
     }
 
+    public Map<String, String> payment(String appOrderId){
+        AppOrder appOrder = appOrderMapper.selectById(appOrderId);
+
+        if (Objects.equals(appOrder.getOrderStatus(), CommonConstant.ORDER_STATUS_4)){
+            throw new JeecgBootException("当前订单已过期,请重新下单!");
+        }
+
+        //获取登录用户
+        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+
+        SysUser sysUser = sysUserMapper.selectById(user.getId());
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 15);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
+
+        //构建微信支付参数
+        JSONObject params = new JSONObject();
+        params.put("sp_appid", WechatConstants.WECHAT_SP_APPID); //服务商appid
+        params.put("sp_mchid", WechatConstants.WECHAT_SP_MCH_ID);//服务商商户号
+        params.put("sub_appid", WechatConstants.WECHAT_SUB_APPID); //小程序appid
+        params.put("sub_mchid", WechatConstants.WECHAT_SUB_MCH_ID); //特约商户商户号
+
+        params.put("description", "全龄运动"); //商品描述
+        params.put("out_trade_no", appOrder.getOrderCode()); //商户订单号
+        params.put("time_expire", sdf.format(calendar.getTime())); //交易结束时间 选填 时间到了之后将不能再支付 遵循rfc3339标准格式
+        params.put("attach", appOrder.getOrderCode()); //附加数据 选填
+        // 在查询API和支付通知中原样返回 可作为自定义参数使用
+        params.put("notify_url", WechatUrlConstants.PAY_V3_NOTIFY); //支付结果异步通知接口
+        //分账必传参数
+        if (appOrder.getOrderType() != 0){
+            JSONObject settleInfo = new JSONObject();
+            settleInfo.put("profit_sharing", Boolean.TRUE);
+            params.put("settle_info", settleInfo);
+        }
+        //电子发票入口
+        params.put("support_fapiao", Boolean.TRUE);
+
+        //订单金额信息
+        JSONObject amount_json = new JSONObject();
+        //支付金额 单位:分
+        amount_json.put("total", Integer.parseInt(amount_fee(appOrder.getPrice())));
+//        amount_json.put("total", Integer.parseInt(amount_fee(new BigDecimal("0.01"))));//测试0.01元
+        amount_json.put("currency", "CNY");//固定传:CNY,代表人民币
+        params.put("amount", amount_json);
+
+        //支付者信息
+        JSONObject payer = new JSONObject();
+        //用户在小程序侧的openid
+        payer.put("sub_openid", sysUser.getOpenid());
+        params.put("payer", payer);
+
+        //拉起支付-返回JSAPI参数
+        return weChatPayService.wechatPay(params);
+    }
+
     /**
      * 金额元转分字符串
      *
@@ -1628,9 +1643,8 @@ public class OrderServiceImpl implements IOrderService {
                         appOrderProInfoMapper.updateById(appOrderProInfo);
                     }
                 }
-
-                return "100001";//支付成功
             }
+            return "100001";//支付成功
         }
         if (s == null) {
             //查询订单
@@ -1802,6 +1816,28 @@ public class OrderServiceImpl implements IOrderService {
         return stringStringMap;
     }
 
+    @Override
+    public UserPayForm payOrder(String appOrderId) {
+        UserPayForm payForm = new UserPayForm();
+        AppOrder appOrder = appOrderMapper.selectById(appOrderId);
+        if (ObjectUtil.isEmpty(appOrder)){
+            throw new JeecgBootException("当前订单不存在!");
+        }
+        if (!Objects.equals(appOrder.getOrderStatus(), CommonConstant.ORDER_STATUS_0)){
+            if (appOrder.getOrderStatus().equals(CommonConstant.ORDER_STATUS_1) || appOrder.getPayStatus().equals(CommonConstant.STATUS_1_INT)){
+                throw new JeecgBootException("当前订单已支付!");
+            }
+            if(appOrder.getOrderStatus().equals(CommonConstant.ORDER_STATUS_4)){
+                throw new JeecgBootException("当前订单已过期取消,请重新下单!");
+            }
+        }
+        Map<String, String> payment = payment(appOrderId);
+        payForm.setOrderId(appOrderId)
+                .setOrderCode(appOrder.getOrderCode())
+                .setParams(payment);
+        return payForm;
+    }
+
 
     private ReceiptPaymentDetailsInfoVo getChangeMoney(ReceiptPaymentDetailsInfoVo receiptPaymentDetailsInfoVo, String orgCode, String deptId, Integer deptType, BigDecimal changeMoney) {
         receiptPaymentDetailsInfoVo.setDeptId(deptId);

+ 0 - 3
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatConstants.java

@@ -16,9 +16,6 @@ public class WechatConstants {
 
     public static final String WECHAT_SUB_APPID = "wxc032a09413289004";
 
-    //微信商户平台v2密钥
-    public static final String WECHAT_MCH_SECRET_V2 = "4b64e17419689527b256f07cdf6bd60c";
-
     //微信商户平台v3密钥
     public static final String WECHAT_MCH_SECRET_V3 = "4b64e17419689527b256f07cdf6bd60c";
 

+ 33 - 1
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatPayV3Utils.java

@@ -92,7 +92,7 @@ public class WechatPayV3Utils {
         WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                 .withMerchant(WechatConstants.WECHAT_SP_MCH_ID, WechatConstants.WECHAT_MCH_SERIAL_NUM, merchantPrivateKey)
                 .withValidator(new WechatPay2Validator(verifier));
-        // ... 接下来,仍然可以通过builder设置各种参数,来配置你的HttpClient
+        // ... 接下来,仍然可以通过builder设置各种参数,来配置HttpClient
 
         httpClient = builder.build();
     }
@@ -127,6 +127,38 @@ public class WechatPayV3Utils {
         }
     }
 
+    /**
+     * 发送POST请求
+     *
+     * @param url    请求地址
+     * @param params json参数
+     * @return
+     */
+    public JSONObject profitSharingSendPost(String url, JSONObject params) {
+        try {
+            if (httpClient == null) {
+                setHttpClient();
+            }
+            HttpPost httpPost = new HttpPost(url);
+            httpPost.addHeader("Accept", "application/json");
+            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
+            String WechatPaySerial = verifier.getValidCertificate().getSerialNumber().toString(16);
+            httpPost.addHeader("Wechatpay-Serial", WechatPaySerial);
+            httpPost.setEntity(new StringEntity(params.toJSONString(), StandardCharsets.UTF_8));
+            CloseableHttpResponse response = httpClient.execute(httpPost);
+            String bodyAsString = EntityUtils.toString(response.getEntity());
+            log.info("微信返回的内容:" + bodyAsString);
+            if (StringUtils.isEmpty(bodyAsString)) {
+                return null;
+            }
+            return JSONObject.parseObject(bodyAsString);
+        } catch (Exception e) {
+            log.error("微信请求失败");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
     /**
      * 发送get请求
      *

+ 0 - 3
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/ProfitSharingRequest.java

@@ -12,9 +12,6 @@ public class ProfitSharingRequest implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    private String nonce_str;
-    private String sign_type = "HMAC-SHA256";
-
     private String sub_mchid;
     private String appid;
     private String sub_appid;

+ 1 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/Receiver.java

@@ -13,6 +13,7 @@ public class Receiver implements Serializable {
 
     private String type;          // MERCHANT_ID/PERSONAL_OPENID
     private String account;       // 接收方账号
+    private String name;       // 接收方名称
     private Integer amount;       // 分账金额(分)
     private String description;   // 描述
 }

+ 72 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/ReceiverAddForm.java

@@ -0,0 +1,72 @@
+package org.jeecg.modules.pay.entity;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.jeecg.modules.pay.config.WechatConstants;
+
+import java.io.Serializable;
+
+@Data
+@Accessors(chain = true)
+public class ReceiverAddForm implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 【子商户号】微信支付分配的子商户号,即分账的出资商户号。(直连商户不需要,服务商需要)
+     */
+    private String sub_mchid;
+    /**
+     * 【公众账号ID】微信分配的公众账号ID
+     */
+    private String appid = WechatConstants.WECHAT_SP_APPID;
+    /**
+     * 【子商户公众账号ID】子商户的公众账号ID,分账接收方类型包含PERSONAL_SUB_OPENID时必填。(直连商户不需要,服务商需要)
+     */
+    private String sub_appid = WechatConstants.WECHAT_SUB_APPID;
+    /**
+     * 【接收方类型】枚举值:
+     * MERCHANT_ID:商户ID
+     * PERSONAL_OPENID:个人openid(由父商户APPID转换得到)
+     * PERSONAL_SUB_OPENID:个人sub_openid(由子商户APPID转换得到)
+     */
+    private String type = "MERCHANT_ID";
+    /**
+     * 【接收方账号】类型是MERCHANT_ID时,是商户号
+     * 类型是PERSONAL_OPENID时,是个人openid
+     * 类型是PERSONAL_SUB_OPENID时,是个人sub_openid
+     */
+    private String account;
+    /**
+     * 【分账接收方全称】分账接收方类型是MERCHANT_ID时,是商户全称(必传),当商户是小微商户或个体户时,是开户人姓名
+     * 分账接收方类型是PERSONAL_OPENID时,是个人姓名(选传,传则校验)
+     * 分账接收方类型是PERSONAL_SUB_OPENID时,是个人姓名(选传,传则校验)
+     * 1、此字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引
+     * 2、使用微信支付平台证书中的公钥
+     * 3、使用RSAES-OAEP算法进行加密
+     * 4、将请求中HTTP头部的Wechatpay-Serial设置为证书序列号
+     */
+    private String name;
+    /**
+     * 【与分账方的关系类型】子商户与接收方的关系。
+     * 本字段值为枚举:
+     * SERVICE_PROVIDER:服务商
+     * STORE:门店
+     * STAFF:员工
+     * STORE_OWNER:店主
+     * PARTNER:合作伙伴
+     * HEADQUARTER:总部
+     * BRAND:品牌方
+     * DISTRIBUTOR:分销商
+     * USER:用户
+     * SUPPLIER:供应商
+     * CUSTOM:自定义
+     */
+    private String relation_type = "CUSTOM";
+    /**
+     * 【自定义的分账关系】子商户与接收方具体的关系,本字段最多10个字。
+     * 当字段relation_type的值为CUSTOM时,本字段必填
+     * 当字段relation_type的值不为CUSTOM时,本字段无需填写
+     */
+    private String custom_relation;
+}

+ 89 - 23
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/payController.java

@@ -2,34 +2,46 @@ package org.jeecg.modules.pay.paytest;
 
 
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.jeecg.modules.pay.config.WechatConstants;
 import org.jeecg.modules.pay.config.WechatPayV3Utils;
 import org.jeecg.modules.pay.config.WechatUrlConstants;
 import org.jeecg.modules.pay.entity.ProfitSharingRequest;
 import org.jeecg.modules.pay.entity.Receiver;
+import org.jeecg.modules.pay.entity.ReceiverAddForm;
+import org.jeecg.modules.pay.routing.WeChatProfitSharingService;
 import org.jeecg.modules.system.app.entity.AppOrder;
 import org.jeecg.modules.system.app.service.IAppOrderService;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.web.bind.annotation.*;
 
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.*;
 import java.util.ArrayList;
 import java.util.List;
 
 @Slf4j
 @RestController
 @AllArgsConstructor
-@RequestMapping(value = "/test")
+@RequestMapping(value = "/profitSharing")
 public class payController {
 
     private final WechatPayV3Utils wechatPayV3Utils;
 
     private final IAppOrderService appOrderService;
 
+    private final WeChatProfitSharingService weChatProfitSharingService;
+
     @GetMapping(value = "/getPay")
     public String getPay(String orderSn,int total , String description) {
         PayMerchantUtil payMerchantUtil = new PayMerchantUtil();
@@ -40,14 +52,16 @@ public class payController {
         }
     }
 
-    @GetMapping(value = "/test")
-    public JSONObject test() {
-        //通过订单ID查询流水号
-        String transaction_id  = "88888888888888888888";
+    /**
+     * 设置sub_mchid允许商户与哪些商户进行分账
+     *
+     * @param receiverAddForm
+     * @return
+     */
+    @PostMapping(value = "/receivers/add")
+    public JSONObject receiverAdd(@RequestBody ReceiverAddForm receiverAddForm) {
         try {
-            JSONObject res = wechatPayV3Utils.sendGet(WechatUrlConstants.PAY_V3_QUERY_PROFIT_SHARING+"P001001001"+"?sub_mchid="+ServiceProvider.sub_mchid+"&transaction_id="+transaction_id);
-            log.info("wechatPay res:{}", res.toString());
-            return res;
+            return weChatProfitSharingService.receiversAdd(receiverAddForm);
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -69,7 +83,7 @@ public class payController {
 //    }
 
     @GetMapping(value = "/test1")
-    public JSONObject test(String out_order_no) {
+    public JSONObject test(@RequestParam("out_order_no") String out_order_no) throws Exception {
 
         AppOrder appOrder = appOrderService.getOne(Wrappers.lambdaQuery(AppOrder.class).eq(AppOrder::getOrderCode, out_order_no).last("limit 1"));
 
@@ -83,33 +97,85 @@ public class payController {
             String orgCode = appOrder.getOrgCode();
             //通过orgCode查询订单的分账发起方及接收方
         }
+        String str = "贵州帝侍天健康管理有限公司";
+        ClassPathResource classPathResource = new ClassPathResource("cert/apiclient_cert.pem");
+        InputStream certStream = classPathResource.getInputStream();
+        X509Certificate x509Certificate = getCertificate(certStream);
+
+        String name = rsaEncryptOAEP(str,x509Certificate);
         //查询当前订单的分账接收方列表
         for (int i = 0; i < 1; i++) {
             Receiver receiver = new Receiver();
             receiver.setType("MERCHANT_ID")
-                    .setAccount(ServiceProvider.sp_mchid)
+                    .setAccount("1723757626")
+                    .setName(name)
                     .setAmount(1)
-                    .setDescription("微信文档值得吐槽");
+                    .setDescription("分给帝释天");
             receivers.add(receiver);
         }
         ProfitSharingRequest profitSharingRequest = new ProfitSharingRequest();
         profitSharingRequest
-                .setNonce_str(RandomUtil.randomString(32))
-                .setSign_type("HMAC-SHA256")
-                .setAppid(ServiceProvider.sp_appid)
-                .setSub_mchid(ServiceProvider.sub_mchid)
-                .setTransaction_id("4200001042202104228993583353")
-                .setOut_order_no("P001001001")
+                .setAppid(WechatConstants.WECHAT_SP_APPID)
+                .setSub_mchid(WechatConstants.WECHAT_SUB_MCH_ID)
+                .setTransaction_id("4200002835202509151465989607")
+                .setOut_order_no(out_order_no)
                 .setReceivers(receivers)
                 .setUnfreeze_unsplit(Boolean.TRUE);
         try {
-            JSONObject res = wechatPayV3Utils.sendPost(WechatUrlConstants.PAY_V3_PROFIT_SHARING, JSONObject.from(profitSharingRequest));
-            log.info("wechatPay res:{}", res.toString());
+            JSONObject res = wechatPayV3Utils.profitSharingSendPost(WechatUrlConstants.PAY_V3_PROFIT_SHARING, JSONObject.from(profitSharingRequest));
+            log.info("微信服务商分账--------------------------------------------------wechatPay res:{}", res.toString());
             return res;
         } catch (Exception e) {
             e.printStackTrace();
         }
         return null;
     }
+
+    /**
+     * 获取证书
+     *
+     * @param inputStream 证书文件
+     * @return {@link X509Certificate} 获取证书
+     */
+    public static X509Certificate getCertificate(InputStream inputStream) {
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X509");
+            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
+            cert.checkValidity();
+            return cert;
+        } catch (CertificateExpiredException e) {
+            throw new RuntimeException("证书已过期", e);
+        } catch (CertificateNotYetValidException e) {
+            throw new RuntimeException("证书尚未生效", e);
+        } catch (CertificateException e) {
+            throw new RuntimeException("无效的证书", e);
+        }
+    }
+
+    /**
+     * 公钥加密   加密隐私信息数据
+     *
+     * @param data        待加密数据
+     * @param certificate 平台公钥证书
+     * @return 加密后的数据
+     * @throws Exception 异常信息
+     */
+    public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {
+        try {
+            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
+            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
+            byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
+            byte[] cipherData = cipher.doFinal(dataByte);
+            // String s = new String(cipherData);
+
+            return java.util.Base64.getEncoder().encodeToString(cipherData);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("无效的证书", e);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
+        }
+    }
 }
  

+ 2 - 3
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/routing/ProfitsharingAddReceiverBo.java

@@ -1,6 +1,5 @@
 package org.jeecg.modules.pay.routing;
 
-import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson2.JSONObject;
 import lombok.extern.slf4j.Slf4j;
 import org.jeecg.modules.pay.config.WechatPayV3Utils;
@@ -48,8 +47,8 @@ public class ProfitsharingAddReceiverBo {
         }
         ProfitSharingRequest profitSharingRequest = new ProfitSharingRequest();
         profitSharingRequest
-                .setNonce_str(RandomUtil.randomString(32))
-                .setSign_type("HMAC-SHA256")
+//                .setNonce_str(RandomUtil.randomString(32))
+//                .setSign_type("HMAC-SHA256")
                 .setAppid(ServiceProvider.sp_appid)
                 .setSub_mchid(ServiceProvider.sub_mchid)
                 .setTransaction_id("4200001042202104228993583353")

+ 93 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/routing/WeChatProfitSharingService.java

@@ -0,0 +1,93 @@
+package org.jeecg.modules.pay.routing;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.modules.pay.config.WechatPayV3Utils;
+import org.jeecg.modules.pay.config.WechatUrlConstants;
+import org.jeecg.modules.pay.entity.ReceiverAddForm;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.*;
+
+@Slf4j
+@Service
+public class WeChatProfitSharingService {
+
+    @Resource
+    private WechatPayV3Utils wechatPayV3Utils;
+
+    /**
+     *  添加分账账号
+     */
+    public JSONObject receiversAdd(ReceiverAddForm receiverAddForm) throws Exception {
+        ClassPathResource classPathResource = new ClassPathResource("cert/apiclient_cert.pem");
+        InputStream certStream = classPathResource.getInputStream();
+        X509Certificate x509Certificate = getCertificate(certStream);
+
+        String name = rsaEncryptOAEP(receiverAddForm.getName(),x509Certificate);
+        receiverAddForm.setName(name);
+        JSONObject params = JSONObject.from(receiverAddForm);
+        log.info("分账接收方:{}",receiverAddForm);
+        JSONObject body = wechatPayV3Utils.sendPost(WechatUrlConstants.PAY_V3_RECEIVERS_ADD,params);
+        log.info("添加分账接收方结果:{}",body);
+        return body;
+    }
+
+    /**
+     * 获取证书
+     *
+     * @param inputStream 证书文件
+     * @return {@link X509Certificate} 获取证书
+     */
+    public static X509Certificate getCertificate(InputStream inputStream) {
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X509");
+            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
+            cert.checkValidity();
+            return cert;
+        } catch (CertificateExpiredException e) {
+            throw new RuntimeException("证书已过期", e);
+        } catch (CertificateNotYetValidException e) {
+            throw new RuntimeException("证书尚未生效", e);
+        } catch (CertificateException e) {
+            throw new RuntimeException("无效的证书", e);
+        }
+    }
+
+    /**
+     * 公钥加密   加密隐私信息数据
+     *
+     * @param data        待加密数据
+     * @param certificate 平台公钥证书
+     * @return 加密后的数据
+     * @throws Exception 异常信息
+     */
+    public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {
+        try {
+            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
+            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
+            byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
+            byte[] cipherData = cipher.doFinal(dataByte);
+            // String s = new String(cipherData);
+
+            return java.util.Base64.getEncoder().encodeToString(cipherData);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("无效的证书", e);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
+        }
+    }
+
+}