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