pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/cardOperate/CardOperateCtrl.java
@@ -229,7 +229,7 @@ // 如果传入了退还金额,需要判断老卡(被挂失的水卡)挂失时是否已经退款,无论退款多少都提示用户挂失时已退款 if(reissueAmount != null && reissueAmount > 0) { Double tradeAmount = cardOperateSv.getTradeAmountByCardNo(cardNum); if(tradeAmount != null) { if(tradeAmount != null && tradeAmount > 0) { return BaseResponseUtils.buildErrorMsg(SellResultCode.THE_FEE_CANNOT_BE_REFUNDED.getMessage()); } } pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/client/ClientCtrl.java
@@ -253,7 +253,8 @@ String villageName = map_districts.get("villageName").toString(); // 生成8位行政区划编码,生成农户编号用 String district8 = countyNum + townNum + villageNum; //String district8 = countyNum + townNum + villageNum; String district8 = String.format("%02d", Integer.parseInt(countyNum)) + String.format("%03d", Integer.parseInt(townNum)) + String.format("%03d", Integer.parseInt(villageNum)); // 生成农户编号 String clientNum = generateClientNum(district8); // 生成12位5级行政区划编码串及名称串 pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/result/WechatResultCode.java
@@ -29,7 +29,6 @@ */ RTU_NOT_EXIST(20001, "阀控器不存在"), //RTU_ADDR_CANNOT_BE_NULL(20002, "阀控器地址不能为空"); CLIENT_CARD_NOT_EXIST(30001, "水卡不存在"), /** @@ -45,7 +44,19 @@ VALIDATION_TIMEOUT(20004, "验证超时"), PHONE_NUMBER_IS_ERROR(20004, "手机号错误,非注册农户"), INVALID_CODE(20004, "无效的临时登录凭证"), LOGIN_FAIL(20004, "登录失败"); LOGIN_FAIL(20004, "登录失败"), /** * 虚拟卡 */ ABNORMAL(10001, "退款异常"), PROCESSING(10001, "退款处理中"), VC_OPEN_ACCOUNT_FAIL(90002, "虚拟卡账户注册失败"), CARD_NUMBER_OVERRUN(10002, "水卡编号已满"), VIRTUAL_CARD_NOT_EXIST(90006, "虚拟卡账户不存在"), RECHARGE_NOT_EXIST(90006, "充值记录不存在"), RECHARGE_FAIL(90006, "充值失败"), NO_ACCOUNT(40001, "您指定的虚拟卡未注册"); private final Integer code; private final String message; pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/util/AesUtil.java
New file @@ -0,0 +1,50 @@ package com.dy.pipIrrWechat.util; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Base64; /** * @author ZhuBaoMin * @date 2024-07-15 10:18 * @LastEditTime 2024-07-15 10:18 * @Description */ public class AesUtil { static final int KEY_LENGTH_BYTE = 32; static final int TAG_LENGTH_BIT = 128; /** * 解密 * @param apiV3Key apiV3密钥 * @param associatedData 附加数据 * @param nonce 随机串 * @param ciphertext 数据密文 * @return 解密后字符串 * @throws GeneralSecurityException * @throws IOException */ public static String decryptToString(byte[] apiV3Key, byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec key = new SecretKeySpec(apiV3Key, "AES"); GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); cipher.init(Cipher.DECRYPT_MODE, key, spec); cipher.updateAAD(associatedData); return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new IllegalStateException(e); } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); } } } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/util/HmacSha256.java
New file @@ -0,0 +1,43 @@ package com.dy.pipIrrWechat.util; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; /** * @author ZhuBaoMin * @date 2024-07-15 10:19 * @LastEditTime 2024-07-15 10:19 * @Description */ public class HmacSha256 { public static String getSignature(String secretKey, String data) throws NoSuchAlgorithmException, InvalidKeyException { // 创建密钥对象 byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "HmacSHA256"); // 创建Mac对象并初始化 Mac mac = Mac.getInstance("HmacSHA256"); mac.init(secretKeySpec); // 将待加密的数据转换为字节数组 byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); // 使用密钥对数据进行加密 byte[] encryptedBytes = mac.doFinal(dataBytes); // 将加密后的结果转换为十六进制字符串 StringBuilder hexString = new StringBuilder(); for (byte b : encryptedBytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } String hmacSha256 = hexString.toString(); return hmacSha256; } } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/util/PayHelper.java
New file @@ -0,0 +1,508 @@ package com.dy.pipIrrWechat.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.pojoSe.SeVirtualCard; import com.dy.pipIrrGlobal.voSe.VoOrders; import com.dy.pipIrrWechat.result.WechatResultCode; import com.dy.pipIrrWechat.virtualCard.VirtualCardSv; import com.dy.pipIrrWechat.wechatpay.PayInfo; import com.dy.pipIrrWechat.wechatpay.dto.Refund; import com.dy.pipIrrWechat.wechatpay.dto.RefundRequest; import com.dy.pipIrrWechat.wechatpay.dto.RefundResponse; import com.dy.pipIrrWechat.wechatpay.dto.ToRefund; import lombok.RequiredArgsConstructor; 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.*; /** * @author ZhuBaoMin * @date 2024-07-15 10:11 * @LastEditTime 2024-07-15 10:11 * @Description */ @Component @RequiredArgsConstructor public class PayHelper { private final VirtualCardSv virtualCardSv; private final RestTemplateUtil restTemplateUtil; private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 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 privateCertFileName = PayInfo.privateCertFileName; private String refundUrl = PayInfo.refundUrl; // 平台证书公钥 public Map<String, Certificate> CERTIFICATE_MAP = new HashMap(); /** * 获取32位随机字符串 * @return 随机串 */ public String generateRandomString() { Random random = new Random(); StringBuilder sb = new StringBuilder(32); for (int i = 0; i < 32; i++) { int index = random.nextInt(CHARACTERS.length()); sb.append(CHARACTERS.charAt(index)); } return sb.toString(); } /** * 获取商户证书私钥对象 * @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环境不支持RSA", e); } catch (InvalidKeySpecException e) { throw new RuntimeException("无效的密钥格式"); } } /** * 检验登录态 * @param appid 小程序 appId * @param secret 小程序 appSecret * @param openid 用户唯一标识符 * @param sessionKey 会话密钥 * @return * @throws IOException */ public JSONObject checkSessionKey(String appid, String secret, String openid, String sessionKey) throws IOException, NoSuchAlgorithmException, InvalidKeyException { String accessToken = ""; Integer expiresIn = 0; String signature = HmacSha256.getSignature(sessionKey, ""); String sigMethod = "hmac_sha256"; JSONObject job_token = getAccessToken(appid, secret); if(job_token != null) { accessToken = job_token.getString("access_token"); expiresIn = job_token.getInteger("expires_in"); } Map<String, Object> queryParams = new HashMap<>(); queryParams.put("access_token", accessToken); queryParams.put("openid", openid); queryParams.put("signature", signature); queryParams.put("sig_method", sigMethod); Map<String, String> headerParams = new HashMap<>(); JSONObject result = restTemplateUtil.get(checkSessionUrl, queryParams, headerParams); return result; } /** * 重置登录态 * @param appid 小程序 appId * @param secret 小程序 appSecret * @param openid 用户唯一标识符 * @param sessionKey 会话密钥 * @return * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws IOException */ public JSONObject resetUserSessionKey(String appid, String secret, String openid, String sessionKey) throws NoSuchAlgorithmException, InvalidKeyException, IOException { String accessToken = ""; Integer expiresIn = 0; String signature = HmacSha256.getSignature(sessionKey, ""); String sigMethod = "hmac_sha256"; JSONObject job_token = getAccessToken(appid, secret); if(job_token != null) { accessToken = job_token.getString("access_token"); expiresIn = job_token.getInteger("expires_in"); } Map<String, Object> queryParams = new HashMap<>(); queryParams.put("access_token", accessToken); queryParams.put("openid", openid); queryParams.put("signature", signature); queryParams.put("sig_method", sigMethod); Map<String, String> headerParams = new HashMap<>(); JSONObject result = restTemplateUtil.get(resetUserSessionKeyUrl, queryParams, headerParams); return result; } /** * 获取接口调用凭据 * @param appid 小程序 appId * @param secret 小程序 appSecret * @return 凭据及凭据有效时间 * @throws IOException */ public JSONObject getAccessToken(String appid, String secret) throws IOException { Map<String, Object> queryParams = new HashMap<>(); queryParams.put("grant_type", "client_credential"); queryParams.put("appid", appid); queryParams.put("secret", secret); Map<String, String> headerParams = new HashMap<>(); JSONObject job_result = restTemplateUtil.get(tokenUrl, queryParams, headerParams); return job_result; } /** * 构造签名串_下单 * @param method HTTP请求方法 * @param url URL * @param timestamp 时间戳 * @param nonceStr 随机串 * @param body 报文主题 * @return 签名串 */ public String buildMessage_order(String method, String url, long timestamp, String nonceStr, String body) { return method + "\n" + url + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n"; } /** * 构造签名串_再次下单 * @param appid 小程序唯一标识 * @param timestamp 时间戳 * @param nonceStr 随机串 * @param pkg package * @return 签名串 */ public String buildMessage_signAgain(String appid, String timestamp, String nonceStr, String pkg) { return appid + "\n" + timestamp + "\n" + nonceStr + "\n" + pkg + "\n"; } /** * 签名 * @param message 被签名信息 * @param certFileName 私钥证书文件路径 * @return signature签名值,签名信息中的一项,参与生成签名信息 * @throws NoSuchAlgorithmException * @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 method * @param url * @param body * @return 签名信息,HTTP头中的签名信息 * HTTP头:Authorization: 认证类型 签名信息 * 认证类型,WECHATPAY2-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 wechatpayTimestamp 请求头中返回的时间戳 * @param wechatpayNonce 请求头中返回的随机串 * @param boey 请求返回的body * @return signatureStr构造的验签名串 */ public String responseSign(String wechatpayTimestamp, String wechatpayNonce, String boey) { String signatureStr = wechatpayTimestamp + "\n" + wechatpayNonce + "\n" + boey + "\n"; return signatureStr; } /** * 重新下载证书 */ public void refreshCertificate() throws GeneralSecurityException, IOException { 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); 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"); String wechatpayNonce = job_headers.getJSONArray("Wechatpay-Nonce").getString(0); String wechatpaySerial = job_headers.getJSONArray("Wechatpay-Serial").getString(0); String signature_h = job_headers.getJSONArray("Wechatpay-Signature").getString(0); String signatureType_h = job_headers.getJSONArray("Wechatpay-Signature-Type").getString(0); String wechatpayTimestamp = job_headers.getJSONArray("Wechatpay-Timestamp").getString(0); JSONObject job_body = job_result.getJSONObject("body"); if(job_body != null) { JSONArray array = job_body.getJSONArray("data"); if(array != null && array.size() > 0) { for(int i = 0; i < array.size(); i++) { JSONObject job_data = array.getJSONObject(i); String certificateSerial = job_data.getString("serial_no"); String effective_time = job_data.getString("effective_time"); String expire_time = job_data.getString("expire_time"); JSONObject job_certificate = job_data.getJSONObject("encrypt_certificate"); String algorithm = job_certificate.getString("algorithm"); String nonce = job_certificate.getString("nonce"); String associated_data = job_certificate.getString("associated_data"); String ciphertext = job_certificate.getString("ciphertext"); //对证书密文进行解密得到平台证书公钥 String publicKey = AesUtil.decryptToString(PayInfo.key.getBytes("utf-8"), associated_data.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext); // 将平台公钥字符串转成Certificate对象 final CertificateFactory cf = CertificateFactory.getInstance("X509"); ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8)); Certificate certificate = null; try { certificate = cf.generateCertificate(inputStream); } catch (CertificateException e) { e.printStackTrace(); } // 响应头证书序号与响应体证书序列号一致,且时间差小于5分钟时才将证书存储map Long timeDiff = (System.currentTimeMillis() / 1000 - Long.parseLong(wechatpayTimestamp))/60; if(wechatpaySerial.equals(certificateSerial) && timeDiff <= 5) { // 证书放入MAP CERTIFICATE_MAP.put(certificateSerial, certificate); } } } } } /** * 使用微信平台证书进行响应验签 * @param wechatpaySerial 来自响应头的微信平台证书序列号 * @param signatureStr 构造的验签名串 * @param wechatpaySignature 来自响应头的微信平台签名 * @return * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws SignatureException */ public Boolean responseSignVerify(String wechatpaySerial, String signatureStr, String wechatpaySignature) throws GeneralSecurityException, IOException { if(CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) { CERTIFICATE_MAP.clear(); refreshCertificate(); } Certificate certificate = (Certificate)CERTIFICATE_MAP.get(wechatpaySerial); if(certificate == null) { return false; } // 获取公钥 PublicKey publicKey = certificate.getPublicKey(); // 初始化SHA256withRSA前面器 Signature signature = Signature.getInstance("SHA256withRSA"); // 用微信平台公钥对前面器进行初始化 signature.initVerify(certificate); // 将构造的验签名串更新到签名器中 signature.update(signatureStr.getBytes(StandardCharsets.UTF_8)); // 请求头中微信服务器返回的签名用Base64解码,使用签名器进行验证 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) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException { 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, privateCertFileName); 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(WechatResultCode.PROCESSING.getMessage()); } else { // 退款异常 return BaseResponseUtils.buildError(WechatResultCode.ABNORMAL.getMessage()); } } } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/virtualCard/VirtualCardCtrl.java
New file @@ -0,0 +1,269 @@ package com.dy.pipIrrWechat.virtualCard; import com.dy.common.aop.SsoAop; import com.dy.common.webUtil.BaseResponse; import com.dy.common.webUtil.BaseResponseUtils; import com.dy.common.webUtil.QueryResultVo; import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard; import com.dy.pipIrrGlobal.voSe.VoVcRecharge; import com.dy.pipIrrGlobal.voSe.VoVirtualCard; import com.dy.pipIrrWechat.util.PayHelper; import com.dy.pipIrrWechat.virtualCard.enums.LastOperateENUM; import com.dy.pipIrrWechat.result.WechatResultCode; import com.dy.pipIrrWechat.virtualCard.dto.DtoRegist; import com.dy.pipIrrWechat.virtualCard.dto.DtoVcRecharge; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Optional; /** * @author ZhuBaoMin * @date 2024-07-15 9:55 * @LastEditTime 2024-07-15 9:55 * @Description */ @Slf4j @Tag(name = "虚拟卡管理", description = "虚拟卡管理") @RestController @RequestMapping(path="virtual_card") @RequiredArgsConstructor @Validated public class VirtualCardCtrl { private final VirtualCardSv virtualCardSv; private final PayHelper payHelper; /** * 获取农户全部虚拟卡 * @return */ @GetMapping(path = "/get") @SsoAop() public BaseResponse<List<VoVirtualCard>> getVCs(Long clientId){ try { List<VoVirtualCard> res = virtualCardSv.getVCs(clientId); return BaseResponseUtils.buildSuccess(res); } catch (Exception e) { log.error("获取支付方式记录异常", e); return BaseResponseUtils.buildException(e.getMessage()) ; } } /** * 根据虚拟卡ID获取虚拟卡对象 * @param vcId * @return */ @GetMapping(path = "/getVcById") @SsoAop() public BaseResponse<VoVirtualCard> getVcById(@RequestParam Long vcId){ try { return BaseResponseUtils.buildSuccess(virtualCardSv.getVcById(vcId)); } catch (Exception e) { log.error("获取支付方式记录异常", e); return BaseResponseUtils.buildException(e.getMessage()) ; } } /** * 虚拟卡账号注册 * @param po * @param bindingResult * @return */ @PostMapping(path = "add_vc") @SsoAop() public BaseResponse<Boolean> addVC(@RequestBody @Valid DtoRegist po, BindingResult bindingResult){ if(bindingResult != null && bindingResult.hasErrors()){ return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()); } Long clientId = po.getClientId(); // 获取5级行政区划串areaCode String areaCode = String.valueOf(virtualCardSv.getAreaCodeById(clientId)); /** * 根据行政区划串(areaCode)在虚拟卡表中针对虚拟卡编号(vcNum)进行模糊查询 * 如果5位顺序号已经达到最大值,提示用户联系系统管理员 * 如果5位顺序号未达到最大值,则加1 * cardNum为新的卡号 */ String vcNum = Optional.ofNullable(virtualCardSv.getVcCardNumOfMax(areaCode)).orElse(""); if(vcNum != null && vcNum.trim().length() > 0) { Integer number = Integer.parseInt(vcNum.substring(12)); number = number + 1; if(number > 65535) { return BaseResponseUtils.buildFail(WechatResultCode.CARD_NUMBER_OVERRUN.getMessage()); } vcNum = vcNum.substring(0, 12) + String.format("%05d", number); } else { vcNum = areaCode + "00001"; } SeVirtualCard seVirtualCard = new SeVirtualCard(); seVirtualCard.setVcNum(Long.parseLong(vcNum)); seVirtualCard.setClientId(clientId); seVirtualCard.setMoney(0d); seVirtualCard.setLastOperate(LastOperateENUM.OPEN_ACCOUNT.getCode()); seVirtualCard.setLastOperateTime(new Date()); seVirtualCard.setInUse((byte) 0); seVirtualCard.setCreateTime(new Date()); Long rec = virtualCardSv.insertVirtualCard(seVirtualCard); if(rec == null) { return BaseResponseUtils.buildFail(WechatResultCode.VC_OPEN_ACCOUNT_FAIL.getMessage()); } return BaseResponseUtils.buildSuccess(true) ; } /** * 用户申请退款 * @param po * @param bindingResult * @return */ //@Operation(summary = "虚拟卡申请退款", description = "虚拟卡申请退款") //@ApiResponses(value = { // @ApiResponse( // responseCode = ResultCodeMsg.RsCode.SUCCESS_CODE, // description = "操作结果:true:成功,false:失败(BaseResponse.content)", // content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, // schema = @Schema(implementation = Boolean.class))} // ) //}) //@PostMapping(path = "add_refund", consumes = MediaType.APPLICATION_JSON_VALUE) //@Transactional(rollbackFor = Exception.class) //@SsoAop() //public BaseResponse<Boolean> addRefund(@RequestBody @Valid DtoRefund po, BindingResult bindingResult){ // if(bindingResult != null && bindingResult.hasErrors()){ // return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()); // } // // Long virtualId = po.getVirtualId(); // Integer refundAmount = po.getRefundAmount(); // // // 根据虚拟卡ID获取虚拟卡对象 // SeVirtualCard seVirtualCard = virtualCardSv.selectVirtuCardById(virtualId); // if(seVirtualCard == null) { // return BaseResponseUtils.buildFail(WechatResultCode.VIRTUAL_CARD_NOT_EXIST.getMessage()); // } // Long clientId = seVirtualCard.getClientId(); // Double money = seVirtualCard.getMoney(); // // // 验证退款金额是否大于余额 // if(refundAmount > money) { // return BaseResponseUtils.buildFail(WechatResultCode.REFUND_AMOUNT_CANNOT_GREATER_THAN_MONEY.getMessage()); // } // // // 计算消费后余额 // Double afterRefund = money - refundAmount; // // SeVcRefund seVcRefund = new SeVcRefund(); // seVcRefund.setVcId(virtualId); // seVcRefund.setClientId(clientId); // seVcRefund.setMoney(money); // seVcRefund.setRefundAmount(refundAmount); // seVcRefund.setAfterRefund(afterRefund); // seVcRefund.setApplicationTime(new Date()); // seVcRefund.setRefundStatus(RefundStateENUM.TO_AUDIT.getCode()); // // Long rec = virtualCardSv.addRefund(seVcRefund); // if(rec == 0) { // return BaseResponseUtils.buildFail(WechatResultCode.APPLICATION_REFUND_FAIL.getMessage()); // } // return BaseResponseUtils.buildSuccess(true) ; //} /** * 审核退款申请 * @param po * @param bindingResult * @return */ //@Operation(summary = "审核退款申请", description = "审核退款申请") //@ApiResponses(value = { // @ApiResponse( // responseCode = ResultCodeMsg.RsCode.SUCCESS_CODE, // description = "操作结果:true:成功,false:失败(BaseResponse.content)", // content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, // schema = @Schema(implementation = Boolean.class))} // ) //}) //@PostMapping(path = "audit_refund", consumes = MediaType.APPLICATION_JSON_VALUE) //@Transactional(rollbackFor = Exception.class) //@SsoAop() //public BaseResponse<Boolean> auditRefund(@RequestBody @Valid DtoAudit po, BindingResult bindingResult) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException { // if(bindingResult != null && bindingResult.hasErrors()){ // return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()); // } // // // 根据退款ID获取退款对象,并更新审核人、审核时间、审核备注、退款状态字段 // SeVcRefund seVcRefund = virtualCardSv.selectRefundByRefundId(po.getRefundId()); // Long virtualId = seVcRefund.getVcId(); // Integer refundAmount = seVcRefund.getRefundAmount(); // seVcRefund.setAuditor(po.getAuditor()); // seVcRefund.setAuditTime(new Date()); // seVcRefund.setRemarks(po.getRemarks()); // seVcRefund.setRefundStatus(RefundStateENUM.TO_REFUND.getCode()); // Integer rec = virtualCardSv.updateRefund(seVcRefund); // if(rec == 0) { // return BaseResponseUtils.buildFail(WechatResultCode.AUDIT_REFUND_FAIL.getMessage()); // } // // // 完成审核后获取待退款订单列表 // List<ToRefund> list_ToRefund = payHelper.getToRefunds(virtualId, refundAmount); // if(list_ToRefund == null || list_ToRefund.size() <=0) // return BaseResponseUtils.buildFail(WechatResultCode.NOT_SUFFICIENT_FUNDS.getMessage()); // // //遍历待退款列表 // JSONArray array_ToRefund = (JSONArray) JSON.toJSON(list_ToRefund); // for(int i = 0; i < array_ToRefund.size(); i++) { // JSONObject job_ToRefund = array_ToRefund.getJSONObject(i); // String orderNumber_ToRefund = job_ToRefund.getString("orderNumber"); // Integer refundAmount_ToRefund = job_ToRefund.getInteger("refundAmount"); // // // 生成退款分项记录 // SeVcRefundItem seVcRefundItem = new SeVcRefundItem(); // seVcRefundItem.setRefundId(po.getRefundId()); // seVcRefundItem.setOrderNumber(orderNumber_ToRefund); // String refundNumber = virtualCardSv.generateRefundNumber(orderNumber_ToRefund); // seVcRefundItem.setRefundNumber(refundNumber); // seVcRefundItem.setRefundAmount(refundAmount_ToRefund); // seVcRefundItem.setCreateTime(new Date()); // seVcRefundItem.setRefundStatus(RefundItemStateENUM.NO_REFUND.getCode()); // Long refundItemId = virtualCardSv.addRefundItem(seVcRefundItem); // // // 调用微信退款申请接口 // Refund refund = new Refund(); // refund.setTradeNo(orderNumber_ToRefund); // refund.setRefundNo(refundNumber); // refund.setRefund(refundAmount_ToRefund); // BaseResponse rep = payHelper.refunds(refund); // } // // return BaseResponseUtils.buildSuccess(true) ; //} /** * 获取虚拟卡充值记录 * @return */ @GetMapping(path = "/getVcRechargeRecords") @SsoAop() public BaseResponse<QueryResultVo<List<VoVcRecharge>>> getVcRechargeRecords(DtoVcRecharge dtoVcRecharge){ try { QueryResultVo<List<VoVcRecharge>> res = virtualCardSv.getVcRechargeRecords(dtoVcRecharge); return BaseResponseUtils.buildSuccess(res); } catch (Exception e) { log.error("获取虚拟卡充值记录异常", e); return BaseResponseUtils.buildException(e.getMessage()) ; } } } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/virtualCard/VirtualCardSv.java
New file @@ -0,0 +1,352 @@ package com.dy.pipIrrWechat.virtualCard; import com.dy.common.webUtil.BaseResponse; import com.dy.common.webUtil.BaseResponseUtils; import com.dy.common.webUtil.QueryResultVo; import com.dy.pipIrrGlobal.daoSe.*; import com.dy.pipIrrGlobal.pojoSe.SeVcRecharge; import com.dy.pipIrrGlobal.pojoSe.SeVcRefund; import com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem; import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard; import com.dy.pipIrrGlobal.voSe.VoOrders; import com.dy.pipIrrGlobal.voSe.VoVcRecharge; import com.dy.pipIrrGlobal.voSe.VoVirtualCard; import com.dy.pipIrrWechat.virtualCard.dto.DtoVcRecharge; import com.dy.pipIrrWechat.virtualCard.dto.DtoVirtualCard; import com.dy.pipIrrWechat.virtualCard.enums.LastOperateENUM; import com.dy.pipIrrWechat.virtualCard.enums.OrderStateENUM; import com.dy.pipIrrWechat.result.WechatResultCode; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.common.utils.PojoUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; import java.util.Map; /** * @author ZhuBaoMin * @date 2024-07-15 9:39 * @LastEditTime 2024-07-15 9:39 * @Description */ @Slf4j @Service public class VirtualCardSv { @Autowired private SeVirtualCardMapper seVirtualCardMapper; @Autowired private SeVcRechargeMapper seVcRechargeMapper; @Autowired private SeVcRefundMapper seVcRefundMapper; @Autowired private SeVcRefundItemMapper seVcRefundItemMapper; @Autowired private SeClientMapper seClientMapper; /** * 根据农户ID获取5级行政区划代码,注册虚拟卡使用 * @param clientId 农户ID * @return 5级行政区划代码 */ public Long getAreaCodeById(Long clientId) { return seClientMapper.getAreaCodeById(clientId); } /** * 获取农户全部虚拟卡 * @return */ public List<VoVirtualCard> getVCs(Long clientId) { return seVirtualCardMapper.getVCs(clientId); } /** * 根据虚拟卡ID获取虚拟卡对象 * @param vcId * @return */ public VoVirtualCard getVcById(Long vcId) { return seVirtualCardMapper.getVcById(vcId); } /** * 注册虚拟卡 * @param po * @return */ public Long insertVirtualCard(SeVirtualCard po) { seVirtualCardMapper.insert(po); return po.getId(); } /** * 根据行政区划串模糊查询虚拟卡编号,注册虚拟卡使用 * @param areaCode * @return */ String getVcCardNumOfMax(String areaCode) { return seVirtualCardMapper.getVcCardNumOfMax(areaCode); } /** 废弃 * 验证农户是否拥有指定名称的虚拟卡 * @param po * @return */ //public Integer getRecordCountByName(DtoRegist po) { // return seVirtualCardMapper.getRecordCountByName(po.getClientId(), po.getVcName()); //} /** * 修改虚拟卡 * 充值、消费、申请退款、审核退款时需要修改虚拟卡的:余额、最后操作、最后操作时间 * @param po * @return */ public Integer updateVirtualCard(SeVirtualCard po) { return seVirtualCardMapper.updateByPrimaryKeySelective(po); } /** * 根据虚拟卡编号获取虚拟卡对象 * @param virtualId * @return */ public SeVirtualCard selectVirtuCardById(Long virtualId) { return seVirtualCardMapper.selectByPrimaryKey(virtualId); } /** * 添加虚拟卡充值记录 * JSAPI下单后生成部分充值记录 * @param po * @return */ public BaseResponse<Boolean> insertVCRecharge(DtoVirtualCard po) { String orderNumber = po.getOrderNumber(); Long virtualId = po.getVirtualId(); Long clientId = po.getClientId(); Integer rechargeAmount = po.getRechargeAmount(); // 验证该虚拟卡账户是否存在并取出当前账户余额 SeVirtualCard seVirtualCard = seVirtualCardMapper.selectByPrimaryKey(virtualId); if(seVirtualCard == null) { return BaseResponseUtils.buildFail(WechatResultCode.NO_ACCOUNT.getMessage()); } Double money = seVirtualCard.getMoney(); // 添加充值记录 SeVcRecharge seVcRecharge = new SeVcRecharge(); seVcRecharge.setVcId(virtualId); seVcRecharge.setClientId(clientId); seVcRecharge.setMoney(money); seVcRecharge.setOrderNumber(orderNumber); seVcRecharge.setRechargeAmount(rechargeAmount); seVcRecharge.setOrderTime(new Date()); seVcRecharge.setOrderState(OrderStateENUM.NON_PAYMENT.getCode()); Integer rec = seVcRechargeMapper.insert(seVcRecharge); if(rec == null) { return BaseResponseUtils.buildFail(WechatResultCode.RECHARGE_FAIL.getMessage()); } return BaseResponseUtils.buildSuccess(true) ; } /** * 根据订单号获取虚拟卡充值对象 * @param orderNumber * @return */ public SeVcRecharge getVCRechargeByorderNumber(String orderNumber) { return seVcRechargeMapper.getVCRechargeByorderNumber(orderNumber); } /** * 修改虚拟卡充值记录 * 微信支付通知后: * 1. 更新充值表:充值后余额、支付完成时间、订单状态 * 2. 更新虚拟卡表:账户余额、最后操作、最后操作时间 * @param orderNumber 订单编号 * @return */ @Transactional(rollbackFor = Exception.class) public BaseResponse<Boolean> updateVCRecharge(String orderNumber, Date rechargeTime) { SeVcRecharge seVcRecharge = seVcRechargeMapper.getVCRechargeByorderNumber(orderNumber); if(seVcRecharge == null) { return BaseResponseUtils.buildFail(WechatResultCode.RECHARGE_NOT_EXIST.getMessage()); } Long virtualId = seVcRecharge.getVcId(); Double money = seVcRecharge.getMoney(); Integer rechargeAmount = seVcRecharge.getRechargeAmount(); Double afterRrecharge = money + rechargeAmount; seVcRecharge.setAfterRecharge(afterRrecharge); seVcRecharge.setRechargeTime(rechargeTime); seVcRecharge.setOrderState(OrderStateENUM.PAID.getCode()); Integer rec = seVcRechargeMapper.updateByPrimaryKeySelective(seVcRecharge); if(rec == null) { return BaseResponseUtils.buildFail(WechatResultCode.RECHARGE_FAIL.getMessage()); } SeVirtualCard seVirtualCard = seVirtualCardMapper.selectByPrimaryKey(virtualId); if(seVirtualCard == null) { return BaseResponseUtils.buildFail(WechatResultCode.VIRTUAL_CARD_NOT_EXIST.getMessage()); } seVirtualCard.setMoney(afterRrecharge); seVirtualCard.setLastOperate(LastOperateENUM.RECHARGE.getCode()); seVirtualCard.setLastOperateTime(new Date()); Integer rec2 = seVirtualCardMapper.updateByPrimaryKeySelective(seVirtualCard); if(rec2 == null) { return BaseResponseUtils.buildFail(WechatResultCode.RECHARGE_FAIL.getMessage()); } return BaseResponseUtils.buildSuccess(true) ; } /** * 修改虚拟卡充值记录(废弃) * 微信小程序支付通知后修改:余额、充值后余额、充值完成时间 * @param po * @return */ public Integer updateVCRecharge(SeVcRecharge po) { return seVcRechargeMapper.updateByPrimaryKeySelective(po); } /** * 根据虚拟卡号获取订单列表 * @param virtualId * @return */ public List<VoOrders> selectOrders(Long virtualId) { List<VoOrders> rsVo = seVcRechargeMapper.getOrders(virtualId); return rsVo ; } /** * 根据退款ID获取退款对象 * @param refundId * @return */ public SeVcRefund selectRefundByRefundId(Long refundId) { return seVcRefundMapper.selectByPrimaryKey(refundId); } /** * 添加退款申请 * @param po * @return */ public Long addRefund(SeVcRefund po) { seVcRefundMapper.insert(po); return po.getId(); } /** * 修改退款记录 * @param po * @return */ public Integer updateRefund(SeVcRefund po) { return seVcRefundMapper.updateByPrimaryKeySelective(po); } /** * 根据订单号获取其各笔退款金额 * @param orderNumber * @return */ public List<Integer> selectRefundAmount(String orderNumber) { List<Integer> rsVo = seVcRefundMapper.getRefundAmount(orderNumber); return rsVo; } /** * 添加退款分项 * @param po * @return */ public Long addRefundItem(SeVcRefundItem po) { seVcRefundItemMapper.insert(po); return po.getRefundId(); } /** * 编辑退款分项 * @param po * @return */ public Integer updateRefundItem(SeVcRefundItem po) { return seVcRefundItemMapper.updateByPrimaryKeySelective(po); } /** * 根据订单号生成退款单号 * @param orderNumber * @return */ public String generateRefundNumber(String orderNumber) { String refundNumber = seVcRefundItemMapper.getLastRefundNumber(orderNumber); if(refundNumber == null) { refundNumber = orderNumber + "01"; return refundNumber; } String a = String.format("%02d", (Integer.parseInt(refundNumber.substring(29,30).trim()) + 1)); return a; } /** * 根据订单号获取充值金额,调用退款申请接口使用 * @param orderNumber * @return */ public Integer getRechargeAmountByOrderNumber(String orderNumber) { return seVcRechargeMapper.getRechargeAmountByOrderNumber(orderNumber); } /** * 根据退款通知接口返回的退款单号反查退款ID,查询该退款ID下未退款记录数量 * @param refundNumber * @return */ public Integer getNoRefundedCount(String refundNumber) { return seVcRefundItemMapper.getNoRefundedCount(refundNumber); } /** * 根据退款单号获取退款ID,退款通知后更新退款表所需 * @param refundNumber * @return */ public Long getRefundIdByRefundNumber(String refundNumber) { return seVcRefundItemMapper.getRefundIdByRefundNumber(refundNumber); } /** * 获取虚拟卡充值记录 * @param dtoVcRecharge * @return */ public QueryResultVo<List<VoVcRecharge>> getVcRechargeRecords(DtoVcRecharge dtoVcRecharge){ Map<String, Object> params = (Map<String, Object>) PojoUtils.generalize(dtoVcRecharge); Long itemTotal = seVirtualCardMapper.getRechargeRecordCount(params); QueryResultVo<List<VoVcRecharge>> rsVo = new QueryResultVo<>(); rsVo.pageSize = dtoVcRecharge.pageSize; rsVo.pageCurr = dtoVcRecharge.pageCurr; rsVo.calculateAndSet(itemTotal, params); rsVo.obj = seVirtualCardMapper.getVcRechargeRecords(params); return rsVo; } } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/wechatpay/PayInfo.java
New file @@ -0,0 +1,176 @@ package com.dy.pipIrrWechat.wechatpay; /** * @author ZhuBaoMin * @date 2024-07-15 10:26 * @LastEditTime 2024-07-15 10:26 * @Description */ public class PayInfo { /** * 小程序登录API */ public static String loginUrl = "https://api.weixin.qq.com/sns/jscode2session"; /** * 检验登录态 */ public static String checkSessionUrl = "https://api.weixin.qq.com/wxa/checksession"; /** * 重置登录态 */ public static String resetUserSessionKeyUrl = "https://api.weixin.qq.com/wxa/resetusersessionkey"; /** * 获取接口调用凭据 */ public static String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token"; /** * 统一下单API */ //public static String orderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; public static String orderUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; /** * 平台证书下载URL */ public static String certificates = "https://api.mch.weixin.qq.com/v3/certificates"; /* * 支付结果通知API */ public static String notifyUrl = "https://44978f7456.imdo.co/sell/payment/orderNotify"; /* * 查询订单API */ public static String queryUrl = "https://api.mch.weixin.qq.com/pay/orderquery"; /** * 申请退款API */ public static String refundUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; /* * 退款通知API */ public static String refundNotifyUrl = "https://www.muxiaobao.com/wxpay/pay.action"; /* * 退款查询API */ public static String refundQueryUrl = "https://api.mch.weixin.qq.com/pay/refundquery"; /* * 小程序唯一标识 */ //public static String appid = "wxbc2b6a00dd904ead"; public static String appid = "wxf773810cd5643196"; /* * 小程序的 app secret */ //public static String secret = "796ffe3e9921f756db0499e80d6ed0cd"; public static String secret = "080d4f947095551e988cfe9338e27f15"; /* * 小程序的授权类型,登录凭证校验使用 */ public static String grantType = "authorization_code"; /* * 商户号(微信支付分配的商户号) */ public static String mchid = "1640721520"; /* * 商户平台设置的密钥key */ public static String key = "DaYuJieShuiYanJiuYuan20230412ABC"; /** * 商户API证书序列号 */ public static String serial_no = "52D65AA66405C738670377F467178F4C950E1606"; /* * 终端IP,调用微信支付API的机器IP */ public static String addrIp = "47.104.211.89"; /* * 随机字符串,长度要求在32位以内 */ //public static String nonceStr = PayHelper.generateRandomString(); /* * 时间戳 从1970年1月1日00:00:00至今的秒数,即当前的时间 */ //public static Long timeStamp = PayHelper.getTimeStamp(); /* * 交易类型,小程序取值JSAPI */ public static String tradeType = "JSAPI"; /* * 签名类型 */ //public static String signType = "MD5"; public static String signType = "RSA"; /* * 商品描述 商品简单描述,该字段请按照规范传递 */ //public static String body = "大禹研究院-水费"; public static String description = "大禹研究院-水费"; /* * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 */ public static String attach = "天津"; /* * 签名,参与签名参数:appid、attach、mch_id、nonce_str、body、out_trade_no、total_fee、spbill_create_ip、notify_url、trade_type、openid */ public String sign = ""; /** * HTTP头认证类型 */ public static String schema = "WECHATPAY2-SHA256-RSA2048"; /** * 私钥文件路径 */ public static String privateCertFileName = "C:\\webchat\\apiclient_key.pem"; public static String publicCertFileName = "C:\\webchat\\wxp_cert.pem"; /* * 微信订单号,优先使用 */ public static String transactionid = ""; /* * 商户系统内部订单号 */ public static String out_trade_no = ""; /* * 商户退款单号 */ public static String out_refund_no = ""; /* * 退款金额 */ public static Float refundfee; /* * 订单金额 */ public static Float totalfee; } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/wechatpay/dto/Refund.java
New file @@ -0,0 +1,39 @@ package com.dy.pipIrrWechat.wechatpay.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author ZhuBaoMin * @date 2024-07-15 10:28 * @LastEditTime 2024-07-15 10:28 * @Description */ @Data @Schema(name = "退款请求对象") public class Refund { public static final long serialVersionUID = 202403011607001L; /** * 商户订单号 */ @Schema(description = "商户订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotBlank(message = "商户订单号不能为空") private String tradeNo; /** * 退款单号 */ @Schema(description = "退款单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED) private String refundNo; /** * 退款金额 */ @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotNull(message = "退款金额不能为空") private Integer refund; } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/wechatpay/dto/RefundRequest.java
New file @@ -0,0 +1,70 @@ package com.dy.pipIrrWechat.wechatpay.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author ZhuBaoMin * @date 2024-07-15 10:29 * @LastEditTime 2024-07-15 10:29 * @Description */ @Data @Schema(name = "退款请求对象") public class RefundRequest { public static final long serialVersionUID = 202403011540001L; /** * 商户订单号,下单时的订单号 */ @Schema(description = "商户订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotBlank(message = "商户订单号不能为空") private String out_trade_no; /** * 商户退款单号,订单号前加前缀“R” */ @Schema(description = "商户退款单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotBlank(message = "商户退款单号不能为空") private String out_refund_no; /** * 退款结果回调url,refundUrl */ @Schema(description = "退款结果回调url", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotBlank(message = "退款结果回调url不能为空") private String notify_url; /** * 金额信息 */ @Schema(description = "金额信息", requiredMode = Schema.RequiredMode.NOT_REQUIRED) private RefundRequest.Amount amount; @Data public static class Amount { /** * 退款金额 */ @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotNull(message = "退款金额不能为空") private Integer refund; /** * 原订单金额,根据订单号查询 */ @Schema(description = "原订单金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotNull(message = "原订单金额不能为空") private Integer total; /** * 退款币种,固定为“CNY” */ @Schema(description = "退款币种", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotBlank(message = "退款币种不能为空") private String currency; } } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/wechatpay/dto/RefundResponse.java
New file @@ -0,0 +1,114 @@ package com.dy.pipIrrWechat.wechatpay.dto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * @author ZhuBaoMin * @date 2024-07-15 10:30 * @LastEditTime 2024-07-15 10:30 * @Description */ @Data @Schema(name = "退款申请返回对象") public class RefundResponse { public static final long serialVersionUID = 202403011431001L; /** * 微信支付退款号 */ private String refund_id; /** * 商户退款单号 */ private String out_refund_no; /** * 微信支付订单号 */ private String transaction_id; /** * 商户订单号 */ private String out_trade_no; /** * 退款渠道 */ private String channel; /** * 退款入账账户 */ private String user_received_account; /** * 退款成功时间 */ private String success_time; /** * 退款创建时间 */ private String create_time; /** * 退款状态 */ private String status; /** * 金额信息 */ private RefundResponse.Amount amount; @Data private static class Amount { /** * 订单总金额 */ private Integer total; /** * 退款金额 */ private Integer refund; /** * 用户支付金额 */ private Integer payer_total; /** * 用户退款金额 */ private Integer payer_refund; /** * 应结退款金额 */ private Integer settlement_refund; /** * 应结订单金额 */ private Integer settlement_total; /** * 优惠退款金额 */ private Integer discount_refund; /** * 退款币种 */ private String currency; } } pipIrr-platform/pipIrr-web/pipIrr-web-wechat/src/main/java/com/dy/pipIrrWechat/wechatpay/dto/ToRefund.java
New file @@ -0,0 +1,35 @@ package com.dy.pipIrrWechat.wechatpay.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import lombok.Data; /** * @author ZhuBaoMin * @date 2024-07-15 10:32 * @LastEditTime 2024-07-15 10:32 * @Description */ @Data @Schema(name = "待退款对象") public class ToRefund { public static final long serialVersionUID = 202403072144001L; /** * 订单号 */ @Schema(description = "订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotBlank(message = "订单号不能为空") private String orderNumber; /** * 退款金额 */ @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @NotNull(message = "退款金额不能为空") @Positive(message = "退款金额必须为大于0的整数") private Integer refundAmount; }