之前做了微信商城小程序,第二次接触微信支付,第一次是大学,这次使用新版lv3支付方式。刚开始只做了jsapi支付,后来pc端需要扫描支付,遇到坑了,接触native支付才发现共用点。 支付和退款是分开的两个对象,发现每种支付的查询接口和关闭订单接口是相同的。所以将查询和关闭又单独提了出来。 最终改成了以下的设计
1.支付基础父类
package com.ruoyi.web.pay.base;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.Constant;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
* @author sk
* @date 2024/10/28
* @description 微信支付基础父类
*/
public abstract class BaseSerive {
public static final Logger log = LoggerFactory.getLogger(BaseSerive.class);
public WeChatProperties properties;
public NotificationParser notificationParser;
public BaseSerive(WeChatProperties properties, NotificationParser notificationParser) {
this.properties = properties;
this.notificationParser = notificationParser;
}
/**
* 解析支付回调参数
* @param request
* @return
*/
public <T> R<T> parse(HttpServletRequest request, Class<T> clazz) {
String body = getBody(request);
String timestamp = request.getHeader(Constant.WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(Constant.WECHAT_PAY_NONCE);
String serialNo = request.getHeader(Constant.WECHAT_PAY_SERIAL);
String signature = request.getHeader(Constant.WECHAT_PAY_SIGNATURE);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(body)
.build();
try {
// 以支付通知回调为例,验签、解密并转换成 Transaction
T object = notificationParser.parse(requestParam, clazz);
return R.ok(object);
} catch (ValidationException e) {
return R.fail(500, e.getMessage());
}
}
public String getBody(HttpServletRequest request){
try(ServletInputStream inputStream = request.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));){
StringBuffer stringBuffer = new StringBuffer();
String s;
//读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
return stringBuffer.toString();
}catch (Exception e){
log.error("解析请求失败:"+e.getMessage());
}
return null;
}
}
2.配置类
package com.ruoyi.web.pay.configuration;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaySerive;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.ruoyi.web.pay.service.WeChatRefundService;
import com.ruoyi.web.pay.service.impl.*;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.refund.RefundService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.util.List;
/**
* @author sk
* @date 2024/10/28
* @description 微信支付配置类
*/
@Configuration
@RequiredArgsConstructor
public class PayConfiguration {
@Bean
public JsapiServiceExtension jsapiServiceExtension(RSAAutoCertificateConfig config) {
return new JsapiServiceExtension.Builder().config(config).build();
}
/**
* jsapi支付
* @param weChatProperties
* @return
* @throws IOException
*/
@Bean
public JsapiPaySerive jsapiPaySerive(WeChatProperties weChatProperties, JsapiServiceExtension jsapiServiceExtension) throws IOException {
return new JsapiPaySerive(weChatProperties,jsapiServiceExtension);
}
@Bean
public NativePayService nativePayService(RSAAutoCertificateConfig config) {
return new NativePayService.Builder().config(config).build();
}
/**
* native支付
* @param weChatProperties
* @return
* @throws IOException
*/
@Bean
public NativePaySerive nativePaySerive(WeChatProperties weChatProperties,NativePayService nativePayService) throws IOException {
return new NativePaySerive(weChatProperties,nativePayService);
}
/**
* 微信订单查询对象
* @param config
* @return
* @throws IOException
*/
@Bean
public QuerySerive querySerive(RSAAutoCertificateConfig config) throws IOException {
return new QuerySerive(config);
}
/**
* 支付对象
* @param weChatProperties 参数对象
* @param notificationParser 解密对象
* @param querySerive 查询对象
* @param seriveList 查询对象
* @return
* @throws IOException
*/
@Bean
public WeChatPaySerive weChatPaySerive(WeChatProperties weChatProperties,
NotificationParser notificationParser,
QuerySerive querySerive,
List<WeChatPaymentSerive> seriveList) throws IOException {
return new WeChatPaySeriveImpl(weChatProperties,notificationParser,querySerive,seriveList);
}
@Bean
public RefundService refundService(RSAAutoCertificateConfig config) {
return new com.wechat.pay.java.service.refund.RefundService.Builder().config(config).build();
}
/**
* 微信退款业务对象
* @param weChatProperties
* @param refundService
* @return
*/
@Bean
public WeChatRefundService weChatRefundServiceImpl(WeChatProperties weChatProperties,
RefundService refundService,
NotificationParser notificationParser) {
return new WeChatRefundServiceImpl(weChatProperties,refundService,notificationParser);
}
}
package com.ruoyi.web.pay.configuration;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.cert.*;
/**
* @author sk
* @date 2024/10/23
* @description 微信支付基础配置类
* 配置共用的证书解析对象
*/
@Configuration
@RequiredArgsConstructor
public class WeChatConfiguration {
private final ResourceLoader resourceLoader;
private static final String CLASS_PATH = "classpath:";
@Bean
public WeChatProperties weChatProperties(){
return new WeChatProperties();
}
/**
* 自动更新证书
* @param weChatProperties
* @return
* @throws IOException
*/
@Bean
public RSAAutoCertificateConfig config(WeChatProperties weChatProperties) throws IOException {
String merchantSerialNumber = getCertificateSerialNumber(weChatProperties.getCertPath());
String privateKey = readResourceAsString(weChatProperties.getKeyPath());
return new RSAAutoCertificateConfig.Builder()
.merchantId(weChatProperties.getMchId())
.privateKey(privateKey)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(weChatProperties.getAppLV3Key())
.build();
}
/**
* 解析器
* @param config
* @return
* @throws IOException
*/
@Bean
public NotificationParser notificationParser(RSAAutoCertificateConfig config) throws IOException {
return new NotificationParser(config);
}
/**
* 读取私钥文件,将文件流读取成 string
*
* @param path 文件路径
* @return 文件内容
* @throws IOException
*/
private String readResourceAsString(String path) throws IOException {
Resource resource = resourceLoader.getResource(CLASS_PATH + path);
try (InputStream inputStream = resource.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
return stringBuilder.toString();
}
}
/**
* 获取证书序列号
*
* @param certPath 证书路径
* @return 证书序列号
* @throws IOException
*/
private String getCertificateSerialNumber(String certPath) throws IOException {
Resource resource = resourceLoader.getResource(CLASS_PATH + certPath);
try (InputStream inputStream = resource.getInputStream()) {
X509Certificate certificate = getCertificate(inputStream);
return certificate.getSerialNumber().toString(16).toUpperCase();
}
}
/**
* 获取证书,将文件流转成证书文件
*
* @param inputStream 证书文件流
* @return {@link X509Certificate} 证书对象
*/
public static X509Certificate getCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书", e);
}
}
}
3.枚举定义了支付类型
package com.ruoyi.web.pay.enums;
/**
* @author sk
* @date 2024/10/29
* @description 支付方式枚举类
*/
public enum PayTypeEnum {
JSAPI("JSAPI"),
NATIVE("NATIVE"),
APP("APP"),
MICROPAY("MICROPAY"),
MWEB("MWEB"),
FACEPAY("FACEPAY");
private final String name;
PayTypeEnum(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
4.存放支付信息
package com.ruoyi.web.pay.properties;
import lombok.Data;
/**
* @author sk
* @date 2024/10/23
* @description 微信配置参数对象
*/
@Data
public class WeChatProperties {
private String appSecret = "xxx55416868xxxb96ebxxx";//app密钥
private String mchId = "169xxxxx8" ; //商户id
private String appId = "wx70xxxxxxxx1" ; //appId
private String appLV3Key = "xxxPprmxxxxxxxxP7ZbX" ; //apiLv3密钥
private String appLV2Key = "xxxxtXa3wsGWxxxxxxHTS" ; //apiLv2密钥(目前使用的是v3支付)
private String keyPath = "/apiclient_key.pem" ; //密钥文件的路径
private String certPath = "/apiclient_cert.pem" ; //商户证书的路径
private String payNotifyUrl = "http://ip:port/wxPay/payNotify" ; //微信支付回调接口
private String refundNotifyUrl = "http://ip:port/wxPay/refundNotify" ; //微信退款回调接口
}
我是直接将密钥文件和证书文件放在resource下的 5.1支付接口
package com.ruoyi.web.pay.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.model.WeChatPay;
import com.wechat.pay.java.service.payments.model.Transaction;
import javax.servlet.http.HttpServletRequest;
/**
* @author sk
* @date 2024/10/28
* @description 微信支付服务接口
*/
public interface WeChatPaySerive {
/**
* 微信预支付
* @param weChatPay
* @return 结果
*/
R<?> prepay(WeChatPay weChatPay);
/**
* 根据商户订单号查询订单
* @param outTradeNo 商户订单号
* @return 结果
*/
R<Transaction> queryOrderByOutTradeNo(String outTradeNo);
/**
* 根据微信支付订单号查询订单
* @param transactionId 微信支付订单号
* @return 结果
*/
R<Transaction> queryOrderById(String transactionId);
/**
* 关闭订单
* @param outTradeNo 订单号
*/
R<?> closeOrder(String outTradeNo);
/**
* 解析支付回调参数
* @param request
* @return
*/
R<Transaction> parse(HttpServletRequest request);
}
5.2退款接口
package com.ruoyi.web.pay.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.model.WeChatRefund;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import javax.servlet.http.HttpServletRequest;
/**
* @author sk
* @date 2024/10/23
* @description 微信退款服务接口
*/
public interface WeChatRefundService {
/**
* 微信退款申请
* @param weChatRefund
* @return 结果
*/
R<Refund> refundWithRequest(WeChatRefund weChatRefund);
/**
* 根据退款单号查询退款信息
* @param outRefundNo
* @return
*/
R<Refund> queryByOutRefundNo(String outRefundNo);
/**
* 解析参数
* @param request
* @return
*/
R<RefundNotification> parse(HttpServletRequest request);
}
5.3由于支付有多种方式,定义了一个抽象父类
package com.ruoyi.web.pay.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;
/**
* @author sk
* @date 2024/10/29
* @description 微信支付抽象类
* 每一种支付方式都对应一个实现类
*/
public abstract class WeChatPaymentSerive {
public WeChatProperties properties;
public WeChatPaymentSerive(WeChatProperties properties) {
this.properties = properties;
}
/**
* 微信预支付
* @param weChatPay
* @return 结果
*/
public abstract R<?> prepay(WeChatPay weChatPay);
/**
* 支付类型
* @return
*/
public abstract String payTyp();
}
5.4支付实现类
package com.ruoyi.web.pay.service.impl;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.base.BaseSerive;
import com.ruoyi.web.pay.model.CloseOrder;
import com.ruoyi.web.pay.model.QueryOrderById;
import com.ruoyi.web.pay.model.QueryOrderByNo;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaySerive;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.model.Transaction;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author sk
* @date 2024/10/28
* @description 微信支付
*/
public class WeChatPaySeriveImpl extends BaseSerive implements WeChatPaySerive {
private QuerySerive querySerive;
private Map<String, WeChatPaymentSerive> payServiceMap = new HashMap<>();
public WeChatPaySeriveImpl(WeChatProperties weChatProperties,
NotificationParser notificationParser,
QuerySerive querySerive,
List<WeChatPaymentSerive> seriveList) {
super(weChatProperties, notificationParser);
this.querySerive = querySerive;
seriveList.forEach(service -> {
String payType = service.payTyp();
payServiceMap.put(payType, service);
});
}
@Override
public R<?> prepay(WeChatPay weChatPay) {
if(weChatPay == null || weChatPay.getPayTypeEnum() == null){
return R.fail(500,"请选择支付方式");
}
WeChatPaymentSerive weChatPaymentSerive = payServiceMap.get(weChatPay.getPayTypeEnum().getName());
if(weChatPaymentSerive == null){
return R.fail(500,"目前该支付方式不支持");
}
return weChatPaymentSerive.prepay(weChatPay);
}
@Override
public R<Transaction> queryOrderByOutTradeNo(String outTradeNo) {
QueryOrderByNo queryOrderByNo = new QueryOrderByNo();
queryOrderByNo.setOutTradeNo(outTradeNo);
queryOrderByNo.setMchid(properties.getMchId());
try {
// 请求微信服务器关闭订单
Transaction transaction = querySerive.queryOrderByOutTradeNo(queryOrderByNo);
return R.ok(transaction);
} catch (ServiceException e) {
return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
}
}
@Override
public R<Transaction> queryOrderById(String transactionId) {
QueryOrderById queryOrderById = new QueryOrderById();
queryOrderById.setTransactionId(transactionId);
queryOrderById.setMchid(properties.getMchId());
try {
// 请求微信服务器关闭订单
Transaction transaction = querySerive.queryOrderById(queryOrderById);
return R.ok(transaction);
} catch (ServiceException e) {
return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
}
}
@Override
public R<?> closeOrder(String outTradeNo) {
CloseOrder closeOrder = new CloseOrder();
closeOrder.setOutTradeNo(outTradeNo);
closeOrder.setMchid(properties.getMchId());
try {
// 请求微信服务器关闭订单
querySerive.closeOrder(closeOrder);
return R.ok();
} catch (ServiceException e) {
return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
}
}
@Override
public R<Transaction> parse(HttpServletRequest request) {
R<Transaction> parse = super.parse(request,Transaction.class);
return parse;
}
}
5.5退款实现类
package com.ruoyi.web.pay.service.impl;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.base.BaseSerive;
import com.ruoyi.web.pay.model.WeChatRefund;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatRefundService;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import javax.servlet.http.HttpServletRequest;
/**
* @author sk
* @date 2024/10/23
* @description 微信退款实现类
*/
public class WeChatRefundServiceImpl extends BaseSerive implements WeChatRefundService {
private RefundService refundService;
public WeChatRefundServiceImpl(WeChatProperties properties, RefundService refundService, NotificationParser notificationParser) {
super(properties,notificationParser);
this.refundService = refundService;
}
@Override
public R<Refund> refundWithRequest(WeChatRefund weChatRefund) {
// 判断参数是否为空
if (weChatRefund == null || weChatRefund.getRefund() == null || weChatRefund.getTotal() == null || weChatRefund.getReason() == null) {
return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
}
CreateRequest createRequest = new CreateRequest();
createRequest.setOutTradeNo(weChatRefund.getOutTradeNo());
createRequest.setOutRefundNo(weChatRefund.getOutRefundNo());
createRequest.setReason(weChatRefund.getReason());
createRequest.setNotifyUrl(properties.getRefundNotifyUrl());
AmountReq amountReq = new AmountReq();
//退款金额
amountReq.setRefund(weChatRefund.getRefund());
//原订单金额
amountReq.setTotal(weChatRefund.getTotal());
amountReq.setCurrency("CNY");
createRequest.setAmount(amountReq);
try {
// 请求微信服务器申请退款
Refund refund = refundService.create(createRequest);
return R.ok(refund);
} catch(ServiceException e) {
return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
}
}
@Override
public R<Refund> queryByOutRefundNo(String outRefundNo) {
// 判断参数是否为空
if (outRefundNo == null || outRefundNo.isEmpty()) {
return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
}
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setOutRefundNo(outRefundNo);
request.setSubMchid(properties.getMchId());
try {
// 请求微信服务器获取信息
Refund refund = refundService.queryByOutRefundNo(request);
return R.ok(refund);
} catch(ServiceException e) {
return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
}
}
@Override
public R<RefundNotification> parse(HttpServletRequest request) {
R<RefundNotification> parse = super.parse(request,RefundNotification.class);
return parse;
}
}
5.6jsapi支付
package com.ruoyi.web.pay.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.enums.PayTypeEnum;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import java.util.HashMap;
/**
* @author sk
* @date 2024/10/29
* @description jsapi支付
*/
public class JsapiPaySerive extends WeChatPaymentSerive {
private JsapiServiceExtension jsapiService;
public JsapiPaySerive(WeChatProperties properties, JsapiServiceExtension jsapiService) {
super(properties);
this.jsapiService = jsapiService;
}
@Override
public R<?> prepay(WeChatPay weChatPay) {
// 判断参数是否为空
if (weChatPay == null || weChatPay.getAmount() == null || weChatPay.getDescription() == null || weChatPay.getCode() == null) {
return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
}
//获取openId
String openId = getOpenId(weChatPay.getCode());
PrepayRequest request = new PrepayRequest();
// 1. 设置AppId
request.setAppid(properties.getAppId());
// 2. 设置商户号
request.setMchid(properties.getMchId());
// 3. 设置商品描述
request.setDescription(weChatPay.getDescription());
// 4. 设置商户订单号
request.setOutTradeNo(weChatPay.getOutTradeNo());
// 5. 设置支付回调地址
request.setNotifyUrl(properties.getPayNotifyUrl());
// 6. 设置金额,CNY-人民币
Amount amount = new Amount();
amount.setTotal(weChatPay.getAmount());
amount.setCurrency("CNY");
request.setAmount(amount);
// 7. 设置支付者信息
Payer payer = new Payer();
payer.setOpenid(openId);
request.setPayer(payer);
try {
// 请求微信服务器JSAPI下单
PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(request);
return R.ok(response);
} catch(ServiceException e) {
return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
}
}
@Override
public String payTyp() {
return PayTypeEnum.JSAPI.getName();
}
private String getOpenId(String code) {
String apiUrl = StrUtil.format(
"https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code",
properties.getAppId(), properties.getAppSecret(), code
);
try {
String body = HttpRequest.get(apiUrl).execute().body();
HashMap<String,String> hashMap = JSONObject.parseObject(body, HashMap.class);
if(hashMap.containsKey("errcode")){
throw new com.ruoyi.common.exception.ServiceException("微信获取OpenId失败:"+body);
}
return hashMap.get("openid");
}catch (Exception e){
throw e;
}
}
}
5.7native支付
package com.ruoyi.web.pay.service.impl;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.enums.PayTypeEnum;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
/**
* @author sk
* @date 2024/10/29
* @description native支付
*/
public class NativePaySerive extends WeChatPaymentSerive {
private NativePayService nativePayService;
public NativePaySerive(WeChatProperties properties, NativePayService nativePayService) {
super(properties);
this.nativePayService = nativePayService;
}
@Override
public R<?> prepay(WeChatPay weChatPay) {
// 判断参数是否为空
if (weChatPay == null || weChatPay.getAmount() == null || weChatPay.getDescription() == null) {
return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
}
PrepayRequest request = new PrepayRequest();
// 1. 设置AppId
request.setAppid(properties.getAppId());
// 2. 设置商户号
request.setMchid(properties.getMchId());
// 3. 设置商品描述
request.setDescription(weChatPay.getDescription());
// 4. 设置商户订单号
request.setOutTradeNo(weChatPay.getOutTradeNo());
// 5. 设置支付回调地址
request.setNotifyUrl(properties.getPayNotifyUrl());
// 6. 设置金额,CNY-人民币
Amount amount = new Amount();
amount.setTotal(weChatPay.getAmount());
amount.setCurrency("CNY");
request.setAmount(amount);
try {
PrepayResponse response = nativePayService.prepay(request);
return R.ok(response);
} catch(ServiceException e) {
return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
}
}
@Override
public String payTyp() {
return PayTypeEnum.NATIVE.getName();
}
}
5.8查询对象
package com.ruoyi.web.pay.service.impl;
import com.ruoyi.web.pay.model.CloseOrder;
import com.ruoyi.web.pay.model.QueryOrderById;
import com.ruoyi.web.pay.model.QueryOrderByNo;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.*;
import com.wechat.pay.java.core.util.GsonUtil;
import com.wechat.pay.java.service.payments.model.Transaction;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* @author sk
* @date 2024/10/28
* @description 查询service
*/
public class QuerySerive {
private HttpClient httpClient;
public QuerySerive(RSAAutoCertificateConfig config) {
this.httpClient = new DefaultHttpClientBuilder().config(config).build();
}
/**
* 微信支付订单号查询订单
*
* @param request 请求参数
* @return Transaction
* @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
* @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
* @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
* @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
*/
public Transaction queryOrderById(QueryOrderById request) {
String requestPath = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/{transaction_id}";
// 添加 path param
requestPath = requestPath.replace("{" + "transaction_id" + "}", urlEncode(request.getTransactionId()));
// 添加 query param
QueryParameter queryParameter = new QueryParameter();
if (request.getMchid() != null) {
queryParameter.add("mchid", urlEncode(request.getMchid()));
}
requestPath += queryParameter.getQueryStr();
HttpHeaders headers = new HttpHeaders();
headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
HttpRequest httpRequest =
new HttpRequest.Builder()
.httpMethod(HttpMethod.GET)
.url(requestPath)
.headers(headers)
.build();
HttpResponse<Transaction> httpResponse = httpClient.execute(httpRequest, Transaction.class);
return httpResponse.getServiceResponse();
}
/**
* 商户订单号查询订单
*
* @param request 请求参数
* @return Transaction
* @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
* @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
* @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
* @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
*/
public Transaction queryOrderByOutTradeNo(QueryOrderByNo request) {
String requestPath =
"https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}";
// 添加 path param
requestPath = requestPath.replace("{" + "out_trade_no" + "}", urlEncode(request.getOutTradeNo()));
// 添加 query param
QueryParameter queryParameter = new QueryParameter();
if (request.getMchid() != null) {
queryParameter.add("mchid", urlEncode(request.getMchid()));
}
requestPath += queryParameter.getQueryStr();
HttpHeaders headers = new HttpHeaders();
headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
HttpRequest httpRequest =
new HttpRequest.Builder()
.httpMethod(HttpMethod.GET)
.url(requestPath)
.headers(headers)
.build();
HttpResponse<Transaction> httpResponse = httpClient.execute(httpRequest, Transaction.class);
return httpResponse.getServiceResponse();
}
/**
* 关闭订单
* @param request
*/
public void closeOrder(CloseOrder request) {
String requestPath =
"https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close";
// 添加 path param
requestPath = requestPath.replace("{" + "out_trade_no" + "}", urlEncode(request.getOutTradeNo()));
HttpHeaders headers = new HttpHeaders();
headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
HttpRequest httpRequest =
new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url(requestPath)
.headers(headers)
.body(createRequestBody(request))
.build();
httpClient.execute(httpRequest, null);
}
private RequestBody createRequestBody(Object request) {
return new JsonRequestBody.Builder().body(GsonUtil.toJson(request)).build();
}
public static String urlEncode(String string) {
try {
return URLEncoder.encode(string, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
6.接下来就是各种实体类了
package com.ruoyi.web.pay.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* @author sk
* @date 2024/10/28
* @description 关闭订单对象
*/
public class CloseOrder {
/** 商户订单号 */
@SerializedName("out_trade_no")
@Expose(serialize = false)
private String outTradeNo;
/** mchid 说明:直连商户号 */
@SerializedName("mchid")
private String mchid;
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getMchid() {
return mchid;
}
public void setMchid(String mchid) {
this.mchid = mchid;
}
}
package com.ruoyi.web.pay.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* @author sk
* @date 2024/10/29
* @description 查询对象
*/
public class QueryOrderById {
/**
* 微信交易号
*/
@SerializedName("transaction_id")
@Expose(serialize = false)
private String transactionId;
/** mchid 说明:直连商户号 */
@SerializedName("mchid")
@Expose(serialize = false)
private String mchid;
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public String getMchid() {
return mchid;
}
public void setMchid(String mchid) {
this.mchid = mchid;
}
}
package com.ruoyi.web.pay.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* @author sk
* @date 2024/10/29
* @description 查询对象
*/
public class QueryOrderByNo {
/** mchid 说明:商户订单号 */
@SerializedName("out_trade_no")
@Expose(serialize = false)
private String outTradeNo;
/** mchid 说明:直连商户号 */
@SerializedName("mchid")
@Expose(serialize = false)
private String mchid;
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getMchid() {
return mchid;
}
public void setMchid(String mchid) {
this.mchid = mchid;
}
}
package com.ruoyi.web.pay.model;
import com.ruoyi.web.pay.enums.PayTypeEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author sk
* @date 2024/10/23
* @description 微信支付对象
*/
@Data
public class WeChatPay {
@ApiModelProperty("商品描述")
private String description;
/**
* 单位分
*/
@ApiModelProperty("订单金额")
private Integer amount;
@ApiModelProperty("用户码")
private String code;
@ApiModelProperty("支付订单号")
private String outTradeNo;
@ApiModelProperty("支付方式")
private PayTypeEnum payTypeEnum;
}
package com.ruoyi.web.pay.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author sk
* @date 2024/10/23
* @description 微信退款对象
*/
@Data
public class WeChatRefund {
@ApiModelProperty("退款原因")
private String reason;
/**
* 单位分
*/
@ApiModelProperty("原订单金额")
private Long total;
/**
* 单位分
*/
@ApiModelProperty("退款金额")
private Long refund;
@ApiModelProperty("退款单号")
private String outRefundNo;
@ApiModelProperty("支付订单号")
private String outTradeNo;
}
如果需要加入新的支付方式,只需要实现WeChatPaymentSerive抽象类, 支付jsapi
//生成支付订单号
String payNo = "ZF202410110303443";
//支付
WeChatPay weChatPay = new WeChatPay();
weChatPay.setDescription("预支付下单");
weChatPay.setCode(wechatPayDto.getCode());
weChatPay.setAmount(1);
weChatPay.setOutTradeNo(payNo);
weChatPay.setPayTypeEnum(PayTypeEnum.JSAPI);
return weChatPayService.prepay(weChatPay);
native支付同理,修改 weChatPay.setPayTypeEnum(PayTypeEnum.NATIVE); 7.支付回调
package com.ruoyi.web.controller.pay;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.pay.service.WeChatPaySerive;
import com.ruoyi.web.pay.service.WeChatRefundService;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/wxPay")
public class WeChatController {
@Autowired
private WeChatPaySerive weChatPaySerive;
@Autowired
private WeChatRefundService weChatRefundService;
private static final Logger log = LoggerFactory.getLogger(WeChatController.class);
@ApiModelProperty("微信支付回调")
@PostMapping("/payNotify")
public void payNotify(@RequestBody JSONObject jsonObject, HttpServletRequest request){
log.info("支付回调数据:"+jsonObject);
try {
R<Transaction> parse = weChatPaySerive.parse(request);
Transaction data = parse.getData();
}catch (Exception e){
throw new ServiceException("解析支付回调参数错误");
}
}
@ApiModelProperty("微信退款回调")
@PostMapping("/refundNotify")
public void refundNotify(@RequestBody JSONObject jsonObject, HttpServletRequest request){
log.info("退款回调数据:"+jsonObject);
try {
R<RefundNotification> parse = weChatRefundService.parse(request);
RefundNotification data = parse.getData();
}catch (Exception e){
throw new ServiceException("解析退款回调参数错误");
}
}
}
这样就拿到了回调对象了,可以复制属性到自己定义的对象上,然后添加数据库支付日志表会退款表。