wangqifan преди 3 години
родител
ревизия
74b01d7cc4

+ 5 - 0
pom.xml

@@ -60,6 +60,11 @@
     </build>
 
     <dependencies>
+        <dependency>
+            <groupId>com.github.wechatpay-apiv3</groupId>
+            <artifactId>wechatpay-apache-httpclient</artifactId>
+            <version>0.4.8</version>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>

+ 25 - 0
src/main/java/com/izouma/zhumj/dto/client/TransferResponseEntity.java

@@ -0,0 +1,25 @@
+package com.izouma.zhumj.dto.client;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@NoArgsConstructor
+public class TransferResponseEntity implements Serializable {
+    private String code;
+    private String message;
+    private String batch_id;
+    private String out_batch_no;
+
+    @Override
+    public String toString() {
+        return "TransferResponsEntity{" +
+                "code='" + code + '\'' +
+                ", message='" + message + '\'' +
+                ", batch_id='" + batch_id + '\'' +
+                ", out_batch_no='" + out_batch_no + '\'' +
+                '}';
+    }
+}

+ 81 - 21
src/main/java/com/izouma/zhumj/service/UserMoneyWithdrawApplyService.java

@@ -1,11 +1,16 @@
 package com.izouma.zhumj.service;
 
 
+import cn.hutool.core.util.IdUtil;
 import com.github.binarywang.wxpay.bean.entpay.EntPayRequest;
 import com.github.binarywang.wxpay.bean.entpay.EntPayResult;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.ijpay.wxpay.model.v3.Amount;
 import com.izouma.zhumj.domain.*;
+import com.izouma.zhumj.dto.client.TransferResponseEntity;
 import com.izouma.zhumj.enums.WithdrawApplyStatus;
 import com.izouma.zhumj.enums.WithdrawMethod;
 import com.izouma.zhumj.exception.BusinessException;
@@ -13,28 +18,38 @@ import com.izouma.zhumj.repo.CheckinInfoRepo;
 import com.izouma.zhumj.repo.MemberRepo;
 import com.izouma.zhumj.repo.UserMoneyWithdrawApplyRepo;
 import com.izouma.zhumj.repo.UserRepo;
+import com.izouma.zhumj.utils.HttpUtil;
 import com.izouma.zhumj.utils.SecurityUtils;
+import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
+import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
 import jodd.util.StringUtil;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.aspectj.apache.bcel.classfile.Module;
 import org.springframework.stereotype.Service;
 
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.math.BigDecimal;
+import java.security.PrivateKey;
 import java.time.LocalDateTime;
-import java.util.Optional;
+import java.util.*;
 
 @Service
 @Slf4j
 @AllArgsConstructor
 public class UserMoneyWithdrawApplyService {
-    private UserMoneyWithdrawApplyRepo userMoneyWithdrawApplyRepo;
-    private UserRepo                   userRepo;
-    private UserMoneyRecordService     userMoneyRecordService;
-    private WxPayService               wxPayService;
-    private CheckinInfoRepo            checkinInfoRepo;
-    private MemberRepo                 memberRepo;
+    private static final Gson                       GSON = new GsonBuilder().create();
+    private              UserMoneyWithdrawApplyRepo userMoneyWithdrawApplyRepo;
+    private              UserRepo                   userRepo;
+    private              UserMoneyRecordService     userMoneyRecordService;
+    private              WxPayService               wxPayService;
+    private              CheckinInfoRepo            checkinInfoRepo;
+    private              MemberRepo                 memberRepo;
 //    private AlipayClient               alipayClient;
 
     public UserMoneyWithdrawApply apply(Long userId, WithdrawMethod method, String account, String name) {
@@ -70,6 +85,64 @@ public class UserMoneyWithdrawApplyService {
         return userMoneyWithdrawApplyRepo.save(apply);
     }
 
+    private void sendApply(String openId, BigDecimal money, String tradeNo) {
+        //商户号
+        String mchid = "1611300657";
+        //申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid)
+        String appId = "wx80d1b03fccb22538";
+        //用户在直连商户应用下的用户标示
+//        String openId = openId;
+        //商户证书编号
+        String wechatPayserialNo = "5F10587C55EE72BCAB5C3C286400FF8199D58137";
+        //商户证书路径(在你本机测试时放你本机路径中的就可以)
+        String privatekeypath = "src/main/resources/cert/apiclient_key.pem";
+
+
+        Map<String, Object> postMap = new HashMap<String, Object>();
+
+        //商家批次单号 长度 1~32
+//        String outNo = IdUtil.getSnowflake(0, 0).nextIdStr();
+
+        postMap.put("appid", appId);
+        postMap.put("out_batch_no", tradeNo);
+        //该笔批量转账的名称
+        postMap.put("batch_name", "转账");
+        //转账说明,UTF8编码,最多允许32个字符
+        postMap.put("batch_remark", "转账");
+        //转账金额单位为“分”。 总金额
+        postMap.put("total_amount", money.multiply(BigDecimal.valueOf(100)));
+        //。转账总笔数
+        postMap.put("total_num", 1);
+
+
+        List<Map> list = new ArrayList<>();
+        Map<String, Object> subMap = new HashMap<>(4);
+        //商家明细单号
+        subMap.put("out_detail_no", tradeNo);
+        //转账金额
+        subMap.put("transfer_amount", money.multiply(BigDecimal.valueOf(100)));
+        //转账备注
+        subMap.put("transfer_remark", "提现");
+        //用户在直连商户应用下的用户标示
+        subMap.put("openid", openId);
+//		subMap.put("user_name", RsaCryptoUtil.encryptOAEP(userName, x509Certificate));
+        list.add(subMap);
+        postMap.put("transfer_detail_list", list);
+
+        //发起转账操作
+        String resStr = HttpUtil.postTransBatRequest(
+                "https://api.mch.weixin.qq.com/v3/transfer/batches",
+                GSON.toJson(postMap),
+                wechatPayserialNo,
+                mchid,
+                privatekeypath);
+        TransferResponseEntity transferResponseEntity = GSON.fromJson(resStr, TransferResponseEntity.class);
+        if (StringUtils.isNotBlank(transferResponseEntity.getCode())) {
+            throw new BusinessException("转账失败:" + transferResponseEntity.getCode() + transferResponseEntity
+                    .getMessage());
+        }
+    }
+
     public void pass(Long id) {
         UserMoneyWithdrawApply apply = userMoneyWithdrawApplyRepo.findById(id)
                 .orElseThrow(new BusinessException("无记录"));
@@ -106,20 +179,7 @@ public class UserMoneyWithdrawApplyService {
                 }
                 break;
             case WECHAT:
-                try {
-                    EntPayRequest request = new EntPayRequest();
-                    request.setPartnerTradeNo(apply.getTradeNo());
-                    request.setAmount(apply.getAmount().multiply(BigDecimal.valueOf(100)).intValue());
-                    request.setOpenid(user.getOpenId());
-                    request.setCheckName("NO_CHECK");
-                    request.setDescription(body);
-                    request.setSpbillCreateIp("192.168.31.12");
-                    EntPayResult result = wxPayService.getEntPayService().entPay(request);
-                    result.checkResult(wxPayService, "MD5", true);
-                } catch (WxPayException e) {
-                    log.error("余额提现失败", e);
-                    throw new BusinessException(e.getMessage());
-                }
+                sendApply(user.getOpenId(), apply.getAmount(), apply.getTradeNo());
                 break;
 //            case ALIPAY:
 //                AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();

+ 73 - 0
src/main/java/com/izouma/zhumj/utils/HttpUtil.java

@@ -0,0 +1,73 @@
+package com.izouma.zhumj.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+import org.apache.http.util.EntityUtils;
+
+
+/**
+ * 微信支付专用类 请求操作方法
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class HttpUtil {
+    /**
+     * 发起批量转账API 批量转账到零钱
+     *
+     * @param requestUrl
+     * @param requestJson       组合参数
+     * @param wechatPayserialNo 商户证书序列号
+     * @param mchID4M           商户号
+     * @param privatekeypath    商户私钥证书路径
+     * @return
+     */
+    public static String postTransBatRequest(
+            String requestUrl,
+            String requestJson,
+            String wechatPayserialNo,
+            String mchID4M,
+            String privatekeypath) {
+        CloseableHttpClient httpclient = HttpClients.createDefault();
+        CloseableHttpResponse response = null;
+        HttpEntity entity = null;
+        try {
+            //商户私钥证书
+            HttpPost httpPost = new HttpPost(requestUrl);
+            // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
+            httpPost.addHeader("Content-Type", "application/json");
+            httpPost.addHeader("Accept", "application/json");
+            //"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
+            httpPost.addHeader("Wechatpay-Serial", wechatPayserialNo);
+            //-------------------------核心认证 start-----------------------------------------------------------------
+            String strToken = WechatPayV3Util.getToken("POST",
+                    "/v3/transfer/batches",
+                    requestJson, mchID4M, wechatPayserialNo, privatekeypath);
+
+            log.error("微信转账token " + strToken);
+            // 添加认证信息
+            httpPost.addHeader("Authorization",
+                    "WECHATPAY2-SHA256-RSA2048" + " "
+                            + strToken);
+            //---------------------------核心认证 end---------------------------------------------------------------
+            httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));
+            //发起转账请求
+            response = httpclient.execute(httpPost);
+            entity = response.getEntity();//获取返回的数据
+            log.info("-----getHeaders.Request-ID:" + response.getHeaders("Request-ID"));
+            return EntityUtils.toString(entity);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            // 关闭流
+        }
+        return null;
+    }
+
+}

+ 114 - 0
src/main/java/com/izouma/zhumj/utils/WechatPayV3Util.java

@@ -0,0 +1,114 @@
+package com.izouma.zhumj.utils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Random;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+
+import org.springframework.util.StringUtils;
+
+@Slf4j
+public class WechatPayV3Util {
+
+    /**
+     *
+     * @param method 请求方法 post
+     * @param canonicalUrl 请求地址
+     * @param body 请求参数
+     * @param merchantId 这里用的商户号
+     * @param certSerialNo 商户证书序列号
+     * @param keyPath 商户证书地址
+     * @return
+     * @throws Exception
+     */
+    public static String getToken(
+            String method,
+            String canonicalUrl,
+            String body,
+            String merchantId,
+            String certSerialNo,
+            String keyPath) throws Exception {
+        String signStr = "";
+        //获取32位随机字符串
+        String nonceStr = getRandomString(32);
+        //当前系统运行时间
+        long timestamp = System.currentTimeMillis() / 1000;
+        if (StringUtils.isEmpty(body)) {
+            body = "";
+        }
+        //签名操作
+        String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);
+        //签名操作
+        String signature = sign(message.getBytes("utf-8"), keyPath);
+        //组装参数
+        signStr = "mchid=\"" + merchantId + "\",timestamp=\"" +  timestamp+ "\",nonce_str=\"" + nonceStr
+                + "\",serial_no=\"" + certSerialNo + "\",signature=\"" + signature + "\"";
+
+        return signStr;
+    }
+
+    public static String buildMessage(String method, String canonicalUrl, long timestamp, String nonceStr, String body) {
+//		String canonicalUrl = url.encodedPath();
+//		if (url.encodedQuery() != null) {
+//			canonicalUrl += "?" + url.encodedQuery();
+//		}
+        return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
+    }
+
+    public static String sign(byte[] message, String keyPath) throws Exception {
+        Signature sign = Signature.getInstance("SHA256withRSA");
+        sign.initSign(getPrivateKey(keyPath));
+        sign.update(message);
+        return Base64.encodeBase64String(sign.sign());
+    }
+
+    /**
+     * 微信支付-前端唤起支付参数-获取商户私钥
+     *
+     * @param filename 私钥文件路径  (required)
+     * @return 私钥对象
+     */
+    public static PrivateKey getPrivateKey(String filename) throws IOException {
+
+        log.error("签名 证书地址是 "+filename);
+        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
+        try {
+            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
+                    .replace("-----END PRIVATE KEY-----", "")
+                    .replaceAll("\\s+", "");
+            //System.out.println("--------privateKey---------:"+privateKey);
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePrivate(
+                    new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("当前Java环境不支持RSA", e);
+        } catch (InvalidKeySpecException e) {
+            throw new RuntimeException("无效的密钥格式");
+        }
+    }
+    /**
+     * 获取随机位数的字符串
+     * @param length
+     * @return
+     */
+    public static String getRandomString(int length) {
+        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
+        Random random = new Random();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < length; i++) {
+            int number = random.nextInt(base.length());
+            sb.append(base.charAt(number));
+        }
+        return sb.toString();
+    }
+}
+

+ 9 - 1
src/test/java/com/izouma/zhumj/WxTest.java

@@ -3,13 +3,16 @@ package com.izouma.zhumj;
 import com.github.binarywang.wxpay.bean.entpay.EntPayRequest;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
+import com.izouma.zhumj.service.UserMoneyWithdrawApplyService;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 public class WxTest extends ZhumjApplicationTests {
     @Autowired
-    private WxPayService wxPayService;
+    private WxPayService                  wxPayService;
+    @Autowired
+    private UserMoneyWithdrawApplyService userMoneyWithdrawApplyService;
 
     @Test
     public void testTransfer() throws WxPayException {
@@ -22,4 +25,9 @@ public class WxTest extends ZhumjApplicationTests {
         request.setSpbillCreateIp("192.168.31.12");
         wxPayService.getEntPayService().entPay(request);
     }
+
+    @Test
+    public void testBatch() throws WxPayException {
+        userMoneyWithdrawApplyService.pass(199910L);
+    }
 }