From 19c5321559b3f050620719dea61b5f3c6cd4c224 Mon Sep 17 00:00:00 2001
From: zhubaomin <zhubaomin>
Date: 星期四, 12 九月 2024 20:43:00 +0800
Subject: [PATCH] 2024-09-12 朱宝民 完善交易汇总查询接口,完善获取交易明细接口

---
 pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java |  247 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 233 insertions(+), 14 deletions(-)

diff --git a/pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java b/pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java
index 032a5d6..c001dc1 100644
--- a/pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java
+++ b/pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java
@@ -1,28 +1,35 @@
 package com.dy.pipIrrSell.util;
 
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
-
+import com.dy.common.webUtil.BaseResponse;
+import com.dy.common.webUtil.BaseResponseUtils;
+import com.dy.pipIrrGlobal.cert.WxCertUtil;
+import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard;
+import com.dy.pipIrrGlobal.voSe.VoOrders;
+import com.dy.pipIrrSell.result.SellResultCode;
+import com.dy.pipIrrSell.virtualCard.VirtualCardSv;
 import com.dy.pipIrrSell.wechatpay.PayInfo;
+import com.dy.pipIrrSell.wechatpay.dto.Refund;
+import com.dy.pipIrrSell.wechatpay.dto.RefundRequest;
+import com.dy.pipIrrSell.wechatpay.dto.RefundResponse;
+import com.dy.pipIrrSell.wechatpay.dto.ToRefund;
 import lombok.RequiredArgsConstructor;
+import org.springframework.core.io.ResourceLoader;
 import org.springframework.stereotype.Component;
 
 import javax.crypto.NoSuchPaddingException;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.security.*;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Base64;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Random;
+import java.util.*;
 
 /**
  * @author ZhuBaoMin
@@ -33,6 +40,7 @@
 @Component
 @RequiredArgsConstructor
 public class PayHelper {
+    private final VirtualCardSv virtualCardSv;
     private final RestTemplateUtil restTemplateUtil;
 
     private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -40,6 +48,9 @@
     private String checkSessionUrl = PayInfo.checkSessionUrl;
     private String tokenUrl = PayInfo.tokenUrl;
     private String resetUserSessionKeyUrl = PayInfo.resetUserSessionKeyUrl;
+    private String notifyUrl = PayInfo.notifyUrl;
+    private String schema = PayInfo.schema;
+    private String refundUrl = PayInfo.refundUrl;
 
     // 骞冲彴璇佷功鍏挜
     public Map<String, Certificate> CERTIFICATE_MAP = new HashMap();
@@ -63,9 +74,31 @@
      * @param filename 绉侀挜鏂囦欢璺緞
      * @return 绉侀挜瀵硅薄
      * @throws IOException
-     */
+
     public PrivateKey getPrivateKey(String filename) throws IOException {
         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+", "");
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("褰撳墠Java鐜涓嶆敮鎸丷SA", e);
+        } catch (InvalidKeySpecException e) {
+            throw new RuntimeException("鏃犳晥鐨勫瘑閽ユ牸寮�");
+        }
+    }
+     */
+
+    /**
+     * 鑾峰彇鍟嗘埛璇佷功绉侀挜瀵硅薄
+     * @param bs 绉侀挜鏂囦欢鍐呭
+     * @return 绉侀挜瀵硅薄
+     * @throws IOException
+     */
+    public PrivateKey getPrivateKey(byte[] bs) throws IOException {
+        String content = new String(bs, "utf-8");
         try {
             String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                     .replace("-----END PRIVATE KEY-----", "")
@@ -201,10 +234,25 @@
      * @throws InvalidKeyException
      * @throws SignatureException
      * @throws IOException
-     */
     public String sign(byte[] message, String certFileName) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, IOException {
         Signature sign = Signature.getInstance("SHA256withRSA");
         sign.initSign(getPrivateKey(certFileName));
+        sign.update(message);
+        return Base64.getEncoder().encodeToString(sign.sign());
+    }*/
+   /**
+     * 绛惧悕
+     * @param message 琚鍚嶄俊鎭�
+     * @param certFileBs 绉侀挜璇佷功鏂囦欢鍐呭
+     * @return signature绛惧悕鍊硷紝绛惧悕淇℃伅涓殑涓�椤癸紝鍙備笌鐢熸垚绛惧悕淇℃伅
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeyException
+     * @throws SignatureException
+     * @throws IOException
+     */
+    public String sign(byte[] message, byte[] certFileBs) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, IOException {
+        Signature sign = Signature.getInstance("SHA256withRSA");
+        sign.initSign(getPrivateKey(certFileBs));
         sign.update(message);
         return Base64.getEncoder().encodeToString(sign.sign());
     }
@@ -217,10 +265,30 @@
      * @return 绛惧悕淇℃伅锛孒TTP澶翠腑鐨勭鍚嶄俊鎭�
      * HTTP澶达細Authorization: 璁よ瘉绫诲瀷 绛惧悕淇℃伅
      * 璁よ瘉绫诲瀷锛學ECHATPAY2-SHA256-RSA2048
-     */
     public String getToken(String method, String url, String body, String nonceStr, Long timestamp, String certFileName) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException, NoSuchPaddingException {
         String message = buildMessage_order(method, url, timestamp, nonceStr, body);
         String signature = sign(message.getBytes("utf-8"), certFileName);
+
+        return "mchid=\"" + PayInfo.mchid + "\","
+                + "nonce_str=\"" + nonceStr + "\","
+                + "timestamp=\"" + timestamp + "\","
+                + "serial_no=\"" + PayInfo.serial_no + "\","
+                + "signature=\"" + signature + "\"";
+    }
+    */
+
+    /**
+     * 鑾峰彇绛惧悕淇℃伅
+     * @param method
+     * @param url
+     * @param body
+     * @return 绛惧悕淇℃伅锛孒TTP澶翠腑鐨勭鍚嶄俊鎭�
+     * HTTP澶达細Authorization: 璁よ瘉绫诲瀷 绛惧悕淇℃伅
+     * 璁よ瘉绫诲瀷锛學ECHATPAY2-SHA256-RSA2048
+     */
+    public String getToken(String method, String url, String body, String nonceStr, Long timestamp, byte[] certFileBs) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException, NoSuchPaddingException {
+        String message = buildMessage_order(method, url, timestamp, nonceStr, body);
+        String signature = sign(message.getBytes("utf-8"), certFileBs);
 
         return "mchid=\"" + PayInfo.mchid + "\","
                 + "nonce_str=\"" + nonceStr + "\","
@@ -246,17 +314,18 @@
     /**
      * 閲嶆柊涓嬭浇璇佷功
      */
-    public void refreshCertificate() throws GeneralSecurityException, IOException {
+    public void refreshCertificate(byte[] keyPemBs) throws GeneralSecurityException, IOException, Exception {
         String method = "GET";
         String httpUrl = "/v3/certificates";
         String nonceStr = generateRandomString();
         Long timestamp = System.currentTimeMillis() / 1000;
 
-        String header = PayInfo.schema + " " + getToken(method, httpUrl, "", nonceStr, timestamp, PayInfo.privateCertFileName);
+        String header = PayInfo.schema + " " + getToken(method, httpUrl, "", nonceStr, timestamp, keyPemBs);
 
         Map<String, String> headers = new HashMap<>();
         headers.put("Authorization", header);
         headers.put("Accept", "application/json");
+        //headers.put("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
 
         JSONObject job_result = restTemplateUtil.getHeaders(PayInfo.certificates,null, headers);
         JSONObject job_headers = job_result.getJSONObject("headers");
@@ -316,10 +385,10 @@
      * @throws InvalidKeyException
      * @throws SignatureException
      */
-    public Boolean responseSignVerify(String wechatpaySerial, String signatureStr, String wechatpaySignature) throws GeneralSecurityException, IOException {
+    public Boolean responseSignVerify(String wechatpaySerial, String signatureStr, String wechatpaySignature, byte[] keyPemBs) throws GeneralSecurityException, IOException, Exception {
         if(CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
             CERTIFICATE_MAP.clear();
-            refreshCertificate();
+            refreshCertificate(keyPemBs);
         }
         Certificate certificate = (Certificate)CERTIFICATE_MAP.get(wechatpaySerial);
         if(certificate == null) {
@@ -341,4 +410,154 @@
         boolean valid = signature.verify(Base64.getDecoder().decode(wechatpaySignature));
         return valid;
     }
+
+
+    /**
+     * 鑾峰彇寰呴��娆惧璞″垪琛�
+     *      寰呴��娆惧璞″寘鍚鍗曞彿鍜屽彲閫�娆鹃噾棰�
+     *      璁㈠崟瀵硅薄鍖呭惈璁㈠崟鍙枫�佸厖鍊奸噾棰濄�佸厖鍊煎畬鎴愭椂闂�
+     *      1. 鏍规嵁铏氭嫙鍗″彿鍒拌櫄鎷熷崱琛ㄤ腑鍙栧嚭璇ュ崱浣欓
+     *      2. 鏍规嵁铏氭嫙鍗″彿鍒板厖鍊艰〃鍙栧嚭璁㈠崟瀵硅薄鍒楄〃
+     * @param virtualId
+     * @param refundAmount
+     * @return
+     */
+    public List<ToRefund> getToRefunds(Long virtualId, Integer refundAmount) {
+        ToRefund toRefund = new ToRefund();
+        List<ToRefund> list = new ArrayList<>();
+        Double money = 0d;
+
+        // 鏍规嵁铏氭嫙鍗″彿鑾峰彇褰撳墠铏氭嫙鍗′綑棰�
+        SeVirtualCard seVirtualCard = virtualCardSv.selectVirtuCardById(virtualId);
+        if(seVirtualCard != null) {
+            money = seVirtualCard.getMoney();
+        }
+
+        // 瑕侀��閲戦澶т簬璇ュ崱浣欓锛岃繑鍥炵┖鍒楄〃
+        if(refundAmount > money) {
+            return list;
+        }
+
+        // 鏍规嵁铏氭嫙鍗″彿鑾峰彇璁㈠崟鍒楄〃锛堜粎闄愬厖鍊兼垚鍔熺殑锛�
+        List<VoOrders> list_Orders = virtualCardSv.selectOrders(virtualId);
+        // 閬嶅巻璁㈠崟鍒楄〃锛岃幏鍙�
+        if(list_Orders != null && list_Orders.size() > 0) {
+            JSONArray array_Orders = (JSONArray) JSON.toJSON(list_Orders);
+            for(int i = 0; i < array_Orders.size(); i++) {
+                JSONObject job_order = array_Orders.getJSONObject(i);
+                String orderNumber = job_order.getString("orderNumber");
+                Integer rechargeAmount = job_order.getInteger("rechargeAmount");
+                Date rechargeTime = job_order.getDate("rechargeTime");
+
+                // 璁$畻鍏呭�艰嚦浠婃椂闂村樊锛堝垎閽燂級
+                Long timestamp_Recharge = rechargeTime.getTime() / 1000;
+                Long timestamp_Current = System.currentTimeMillis() / 1000;
+                Long timeDiff_Minute = (timestamp_Current - timestamp_Recharge)/60;
+
+                // 鑾峰彇璇ヨ鍗曞凡閫�娆剧瑪鏁�
+                Integer refundCount = 0;
+                List<Integer> list_RefundAmount = virtualCardSv.selectRefundAmount(orderNumber);
+                if(list_RefundAmount != null && list_RefundAmount.size() > 0) {
+                    refundCount = list_RefundAmount.size();
+                }
+
+                // 鍏呭�艰嚦浠婃湭瓒呰繃涓�骞翠笖璇ヨ鍗曢��娆炬�绘鏁版湭瓒呰繃50娆�
+                if(timeDiff_Minute/(365*24*60) >= 1 && (refundCount + 1) > 50)
+                    return list;
+
+                /**
+                 * 1. 濡傛灉瑕侀��閲戦灏忎簬褰撳墠璁㈠崟鐨勫厖鍊奸噾棰濓紝瑕侀��閲戦鍗充负搴旈��閲戦骞惰繑鍥�
+                 * 2. 濡傛灉瑕佹帹閲戦澶т簬褰撳墠璁㈠崟鍏呭�奸噾棰濓紝褰撳墠璁㈠崟鍏呭�奸噾棰濆嵆涓哄簲閫�閲戦
+                 *      a. 鐢熸垚搴旈��娆惧璞�
+                 *      b. 璁$畻鏂扮殑浣欓
+                 *      c. 閲戣挏鏂扮殑瑕侀��娆鹃噾棰�
+                 *      d. 濡傛灉瑕侀��閲戦澶т簬0锛岄亶鍘嗕笅涓�涓鍗�
+                 */
+                if(refundAmount <= rechargeAmount) {
+                    toRefund = new ToRefund();
+                    toRefund.setOrderNumber(orderNumber);
+                    toRefund.setRefundAmount(refundAmount);
+                    list.add(toRefund);
+                    // 璁$畻鏂扮殑浣欓鍜屾柊鐨勮閫�閲戦
+                    money = money - refundAmount;
+                    refundAmount = refundAmount - refundAmount;
+                    return list;
+                }else {
+                    toRefund = new ToRefund();
+                    toRefund.setOrderNumber(orderNumber);
+                    toRefund.setRefundAmount(rechargeAmount);
+                    list.add(toRefund);
+                    // 璁$畻鏂扮殑浣欓鍜屾柊鐨勮閫�閲戦
+                    money = money - rechargeAmount;
+                    refundAmount = refundAmount - rechargeAmount;
+                    if(refundAmount > 0) {
+                        continue;
+                    }else {
+                        return list;
+                    }
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 閫�娆剧敵璇凤紝璋冪敤寰俊鏀粯閫�娆剧敵璇锋帴鍙�
+     * @param po 閫�娆捐姹傚璞★紝鍖呭惈璁㈠崟鍙枫�侀��娆惧崟鍙枫�侀��娆鹃噾棰�
+     * @return
+     * @throws NoSuchPaddingException
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeySpecException
+     * @throws IOException
+     * @throws SignatureException
+     * @throws InvalidKeyException
+     */
+    public BaseResponse<Boolean> refunds(Refund po, ResourceLoader resourceLoader) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException, Exception {
+        String tradeNo = po.getTradeNo();
+        String refundNo = po.getRefundNo();
+        Integer refund = po.getRefund();
+
+        // 鐢熸垚body
+        Integer total = virtualCardSv.getRechargeAmountByOrderNumber(tradeNo);
+        RefundRequest.Amount amount = new RefundRequest.Amount();
+        amount.setRefund(refund);
+        amount.setTotal(total);
+        amount.setCurrency("CNY");
+
+        RefundRequest refundRequest = new RefundRequest();
+        refundRequest.setOut_trade_no(tradeNo);
+        refundRequest.setOut_refund_no(refundNo);
+        refundRequest.setNotify_url(notifyUrl);
+        refundRequest.setAmount(amount);
+
+        // 鐢熸垚header
+        String nonceStr = generateRandomString();
+        Long timestamp = System.currentTimeMillis() / 1000;
+
+        String method = "POST";
+        String httpUrl = "/v3/refund/domestic/refunds";
+
+        String body = JSONObject.toJSONString(refundRequest);
+        String header = schema + " " + getToken(method, httpUrl, body, nonceStr, timestamp, WxCertUtil.getKey_pemBytes(resourceLoader));
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", header);
+        headers.put("Accept", "application/json");
+        headers.put("Content-Type", "application/json");
+
+        JSONObject job_refundResponse = restTemplateUtil.post(PayInfo.refundUrl, body, headers);
+        RefundResponse refundResponse = JSON.parseObject(job_refundResponse.toJSONString(), RefundResponse.class);
+
+        String status = refundResponse.getStatus();
+        if(status != null && status.equals("SUCCESS")) {
+            // 閫�娆剧敵璇峰凡鍙楃悊
+            return BaseResponseUtils.buildSuccess(true) ;
+        } else if(status != null && status.equals("PROCESSING")) {
+            // 閫�娆惧鐞嗕腑
+            return BaseResponseUtils.buildFail(SellResultCode.PROCESSING.getMessage());
+        } else {
+            // 閫�娆惧紓甯�
+            return BaseResponseUtils.buildError(SellResultCode.ABNORMAL.getMessage());
+        }
+    }
 }

--
Gitblit v1.8.0