系统支付对接、避免返回过多数据

This commit is contained in:
2025-04-16 14:55:28 +08:00
parent f6f847e41c
commit 877f37b6f9
16 changed files with 275 additions and 547 deletions

View File

@@ -1,24 +1,26 @@
package com.ycwl.basic.service.mobile;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.model.wx.WxchatCallbackSuccessData;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Map;
public interface WxPayService {
/**
* 微信预支付
*/
WxPayRespVO createOrder(WXPayOrderReqVO req) throws Exception;
Map<String, Object> createOrder(Long scenicId, WXPayOrderReqVO req) throws Exception;
/**
* 微信支付回调
*/
void payNotify(HttpServletRequest xml);
void payNotify(HttpServletRequest request);
void payNotify(Long scenicId, HttpServletRequest request);
/**
@@ -38,7 +40,7 @@ public interface WxPayService {
/**
* 微信退款回调
*/
boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException;
boolean refundNotify(Long scenicId, HttpServletRequest request) throws IOException;
/**
* 关闭订单
@@ -46,4 +48,5 @@ public interface WxPayService {
* @param orderId 订单id(订单编号)
*/
void closeOrder(String orderId) ;
}

View File

@@ -3,7 +3,6 @@ package com.ycwl.basic.service.mobile.impl;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;

View File

@@ -1,28 +1,8 @@
package com.ycwl.basic.service.mobile.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.utils.StringUtils;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.HttpConstant;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.enums.StatisticEnum;
@@ -35,37 +15,28 @@ import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.order.req.OrderUpdateReq;
import com.ycwl.basic.model.pc.payment.entity.PaymentEntity;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.model.wx.WxchatCallbackSuccessData;
import com.ycwl.basic.pay.adapter.IPayAdapter;
import com.ycwl.basic.pay.entity.CancelOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderResponse;
import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.pay.entity.RefundResponse;
import com.ycwl.basic.pay.entity.RefundOrderRequest;
import com.ycwl.basic.pay.entity.RefundOrderResponse;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.wechat.pay.java.core.http.Constant.*;
import static com.wechat.pay.java.service.refund.model.Status.SUCCESS;
import static com.ycwl.basic.constant.WeiXinConstant.*;
/**
* @Author: songmingsong
@@ -77,8 +48,6 @@ import static com.ycwl.basic.constant.WeiXinConstant.*;
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private PaymentMapper paymentMapper;
@Autowired
@@ -89,116 +58,50 @@ public class WxPayServiceImpl implements WxPayService {
private OrderBiz orderBiz;
@Autowired
private OrderMapper orderMapper;
@Autowired
private ScenicService scenicService;
@Override
public WxPayRespVO createOrder(WXPayOrderReqVO req) {
public Map<String, Object> createOrder(Long scenicId, WXPayOrderReqVO req) {
PaymentEntity entity = new PaymentEntity();
entity.setOrderId(req.getOrderSn());
entity.setMemberId(req.getMemberId());
entity.setPayPrice(new BigDecimal(BigInteger.valueOf(req.getTotalPrice()), 2));
Long entityId = paymentMapper.addGetId(entity);
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(scenicId);
try {
// 使用自动更新平台证书的RSA配置
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(req.getTotalPrice());
request.setAmount(amount);
request.setAppid(wechatConfig.getMiniProgramAppId());
request.setMchid(wechatConfig.getMchId());
request.setDescription(req.getGoodsName());
request.setNotifyUrl(wechatConfig.getPayNotifyUrl());
request.setOutTradeNo(req.getOrderSn().toString());
request.setDescription(req.getDescription());
Payer payer = new Payer();
payer.setOpenid(req.getOpenId());
request.setPayer(payer);
// SettleInfo settleInfo = new SettleInfo();
// settleInfo.setProfitSharing(true);
// request.setSettleInfo(settleInfo);
// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
WxPayRespVO vo = new WxPayRespVO();
Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
vo.setTimeStamp(timeStamp);
String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
vo.setNonceStr(substring);
String signatureStr = Stream.of(wechatConfig.getMiniProgramAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
.collect(Collectors.joining("\n", "", "\n"));
String sign = WXPayUtil.getSign(signatureStr, wechatConfig.getKeyPath());
vo.setPaySign(sign);
vo.setSignType("RSA-SHA256");
vo.setPrepayId("prepay_id=" + response.getPrepayId());
return vo;
} catch (ServiceException e) {
JSONObject parse = JSONObject.parseObject(e.getResponseBody());
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString(HttpConstant.Message));
CreateOrderRequest request = new CreateOrderRequest()
.setOrderNo(String.valueOf(req.getOrderSn()))
.setPriceInCents(req.getTotalPrice())
.setDescription(req.getDescription())
.setGoodsName(req.getGoodsName())
.setUserIdentify(req.getOpenId())
.setNotifyUrl("https://zhentuai.com/api/mobile/wx/pay/v1/"+scenicId+"/payNotify");
CreateOrderResponse order = scenicPayAdapter.createOrder(request);
Map<String, Object> paymentParams = scenicPayAdapter.getPaymentParams(order);
paymentParams.put("needPay", !order.isSkipPay());
return paymentParams;
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, e.toString());
}
}
@Override
public void payNotify(HttpServletRequest request) {
public void payNotify(Long scenicId, HttpServletRequest request) {
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(scenicId);
try {
// 读取请求体的信息
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
String s1 = stringBuffer.toString();
log.warn("微信支付回调:{}", s1);
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader(WeiXinConstant.WECHATPAY_SIGNATURE_TYPE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(s1)
.build();
Transaction parse = parser.parse(requestParam, Transaction.class);
log.info("[微信支付]parse = {}", parse);
PayResponse callbackResponse = scenicPayAdapter.handleCallback(request);
log.info("[微信支付]parse = {}", callbackResponse);
// 更新订单信息
new Thread(() -> {
long orderId = Long.parseLong(parse.getOutTradeNo());
switch (parse.getTradeState()) {
case SUCCESS:
orderBiz.paidOrder(orderId);
break;
case NOTPAY:
case CLOSED:
case REVOKED:
orderBiz.cancelOrder(orderId);
break;
case REFUND:
orderBiz.refundOrder(orderId);
break;
long orderId = Long.parseLong(callbackResponse.getOrderNo());
if (callbackResponse.isPay()) {
orderBiz.paidOrder(orderId);
} else if (callbackResponse.isCancel()) {
orderBiz.cancelOrder(orderId);
} else if (callbackResponse.isRefund()) {
orderBiz.refundOrder(orderId);
}
}).start();
} catch (Exception e) {
@@ -207,59 +110,29 @@ public class WxPayServiceImpl implements WxPayService {
}
@Override
public WxchatCallbackSuccessData queryPay(Long orderId) {
WxchatCallbackSuccessData wxchatCallbackSuccessData = new WxchatCallbackSuccessData();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wechatConfig.getMchId());
queryRequest.setOutTradeNo(orderId.toString());
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
try {
com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
String successTime = transaction.getSuccessTime();
public void payNotify(HttpServletRequest request) {
payNotify(0L, request);
}
wxchatCallbackSuccessData.setOrderId(orderId.toString());
wxchatCallbackSuccessData.setSuccessTime(successTime);
wxchatCallbackSuccessData.setTradetype(WeiXinConstant.getDescriptionType(transaction.getTradeType()));
wxchatCallbackSuccessData.setTradestate(WeiXinConstant.getDescriptionState(transaction.getTradeState()));
TransactionAmount amount = transaction.getAmount();
Integer total = amount.getTotal();
wxchatCallbackSuccessData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
log.error("[微信退款] code={}, message={}", e.getErrorCode(), e.getErrorMessage());
log.error("[微信退款] Response_body={}", e.getResponseBody());
}
return wxchatCallbackSuccessData;
@Override
public WxchatCallbackSuccessData queryPay(Long orderId) {
return null;
}
@Override
public Boolean refundOrder(String orderId) {
OrderEntity order = orderRepository.getOrder(Long.parseLong(orderId));
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(order.getScenicId());
BigDecimal payPrice = order.getPayPrice();
long priceInCents = payPrice.multiply(new BigDecimal(NumberConstant.HUNDRED)).longValue(); // 转换为分(int)
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
// 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
RefundService service = new RefundService.Builder().config(config).build();
CreateRequest request = new CreateRequest();
request.setNotifyUrl(wechatConfig.getRefundNotifyUrl());
AmountReq amountReq = new AmountReq();
amountReq.setTotal(priceInCents);
amountReq.setRefund(priceInCents);
amountReq.setCurrency(WECHATPAY_CURRENCY_CNY);
request.setAmount(amountReq);
request.setOutTradeNo(orderId);
request.setOutRefundNo(SnowFlakeUtil.getId());
Refund refundResult = service.create(request);
if (refundResult.getStatus() == SUCCESS) {
int priceInCents = payPrice.multiply(new BigDecimal(NumberConstant.HUNDRED)).intValue(); // 转换为分(int)
RefundOrderRequest request = new RefundOrderRequest()
.setOrderNo(orderId)
.setPrice(priceInCents)
.setRefundNo(SnowFlakeUtil.getId())
.setRefundPrice(priceInCents)
.setNotifyUrl("https://zhentuai.com/api/mobile/wx/pay/v1/"+order.getScenicId()+"/refundNotify");
RefundOrderResponse response = scenicPayAdapter.refund(request);
if (response.isSuccess()) {
OrderUpdateReq orderUpdateReq = new OrderUpdateReq();
orderUpdateReq.setId(Long.parseLong(orderId));
orderUpdateReq.setRefundStatus(OrderStateEnum.REFUNDED.getType());
@@ -272,31 +145,14 @@ public class WxPayServiceImpl implements WxPayService {
}
@Override
public boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException {
// 转为map格式
Map<String, String> jsonMap = JSONObject.parseObject(refundResult, Map.class);
public boolean refundNotify(Long scenicId, HttpServletRequest request) throws IOException {
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(scenicId);
try {
RefundResponse callbackResponse = scenicPayAdapter.handleRefundCallback(request);
log.info("[微信支付]parse = {}", callbackResponse);
/*
* 退款成功后返回一个加密字段resource,以下为解密
* 解密需要从resource参数中,获取到ciphertext,nonce,associated_data这三个参数进行解密
*/
String resource = JSONObject.toJSONString(jsonMap.get(REFUNDS_RESOURCE));
JSONObject object = JSONObject.parseObject(resource);
String ciphertext = String.valueOf(object.get(REFUNDS_CIPHERTEXT));
String nonce = String.valueOf(object.get(REFUNDS_NONCE));
String associated_data = String.valueOf(object.get(REFUNDS_ASSOCIATED_DATA));
String resultStr = decryptToString(associated_data.getBytes(String.valueOf(StandardCharsets.UTF_8)),
nonce.getBytes(String.valueOf(StandardCharsets.UTF_8)),
ciphertext);
Map<String, String> reqInfo = JSONObject.parseObject(resultStr, Map.class);
String refund_status = reqInfo.get(REFUNDS_REFUND_STATUS);// 退款状态
String out_trade_no = reqInfo.get(WECHATPAY_OUT_TRADE_NO); // 订单号
if (!StringUtils.isEmpty(refund_status) && WECHATPAY_SUCCESS.equals(refund_status)) {
long orderId = Long.parseLong(out_trade_no);
// 更新订单信息
long orderId = Long.parseLong(callbackResponse.getOrderNo());
orderBiz.refundOrder(orderId);
OrderEntity order = orderRepository.getOrder(orderId);
@@ -306,117 +162,19 @@ public class WxPayServiceImpl implements WxPayService {
statisticsRecordAddReq.setScenicId(order.getScenicId());
statisticsRecordAddReq.setMorphId(orderId);
statisticsMapper.addStatisticsRecord(statisticsRecordAddReq);
log.info("[微信退款回调]退款成功");
return true;
} else {
log.error("[微信退款回调]退款失败");
} catch (Exception e) {
log.error("微信退款回调失败", e);
return false;
}
}
@Override
public void closeOrder(String orderId) {
CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
closeOrderRequest.setOutTradeNo(orderId);
closeOrderRequest.setMchid(wechatConfig.getMchId());
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
service.closeOrder(closeOrderRequest);
}
/**
* 退款回调 解密数据
*
* @param associatedData
* @param nonce
* @param ciphertext
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(wechatConfig.getApiV3().getBytes(), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 生成退款请求token
*
* @param method
* @param orderId
* @param body
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
* @throws SignatureException
*/
public String getToken(String method, String orderId, String body) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, SignatureException {
String nonceStr = WXPayUtil.generateNonceStr();
long timestamp = System.currentTimeMillis() / NumberConstant.THOUSAND; // 生成时间戳
String parameter = method + "\n"
+ REFUNDS_URi + orderId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
// 对参数进行加密
byte[] bytes = parameter.getBytes(String.valueOf(StandardCharsets.UTF_8));
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey privateKey = PemUtil.loadPrivateKeyFromString(wechatConfig.getKeyPath()); // privateKeyPath是商户证书密钥的位置apiclient_key.pem
sign.initSign(privateKey); // 商户密钥文件路径
sign.update(bytes);
String signature = Base64.getEncoder().encodeToString(sign.sign());
// 获取token
String token = "mchid=\"" + wechatConfig.getMchId() + "\"," // 商户号
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + wechatConfig.getMchSerialNo() + "\"," // merchantSerialNumber是微信支付中申请的证书序列号
+ "signature=\"" + signature + "\"";
return REFUNDS_SCHEMA + token;
}
/**
* 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
*/
private static class Holder {
private static volatile Config instance;
static void init(WechatConfig wechatConfig) {
instance = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
}
}
public static Config getInstance(WechatConfig wechatConfig) {
if (Holder.instance == null) {
synchronized (Holder.class) {
if (Holder.instance == null) {
Holder.init(wechatConfig);
}
}
}
return Holder.instance;
OrderEntity order = orderRepository.getOrder(Long.parseLong(orderId));
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(order.getScenicId());
CancelOrderRequest request = new CancelOrderRequest()
.setOrderNo(orderId);
scenicPayAdapter.cancelOrder(request);
}
}