引入依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.9</version>
</dependency>
依赖引入后的依赖目录结构
配置微信支付所需参数,我这边是YML文件,自己对应吧:
wechat:
enabled: true
appId: 你的微信服务号信息
secret: 你的微信服务号信息
merchantId: xxxxxx#微信支付商户号
#privateKeyPath:xxxxxx/wx/apiclient_key.pem #微信支付密钥地址相对地址
privateKeyPath:xxxxxx/wx/apiclient_key.pem #微信支付密钥地址相对地址
merchantSerialNumber: 55BA8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #微信支付密钥对应的序列号
apiV3key: F811A3B28405720C5934939Cxxxxxx # 微信支付apiV3key
notifyUrl: https://xxxxxx/no-xxxxxx/xxxxxx/notify
#notifyUrl: https://xxxxxx/xxxxxx/xxxxxx/notify
miniProgramAppId: wxaxxxxxx
miniProgramSecret: 9ae9caxxxxxxbcxxxxxx
其中enabled 是控制微信支付功能的,所以做了一个对应的类并注入Bean,这里如果有退款的话也需要在此处注入:
import com.cyl.wechat.WechatPayConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
@Configuration
@DependsOn("WechatPayData")
@ConditionalOnProperty(prefix = "wechat", name = "enabled", havingValue = "true")
public class WechatConfig {
@Bean
public JsapiService jsapiService(){
return new JsapiService.Builder().config(WechatPayConfig.getInstance()).build();
}
@Bean
public RefundService refundService(){
return new RefundService.Builder().config(WechatPayConfig.getInstance()).build();
}
}
WechatPayConfig.getInstance()方法对应的类:
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
public class WechatPayConfig {
private static Config wechatPayConfig;
private WechatPayConfig(){}
public static Config getInstance() {
if (wechatPayConfig == null) {
wechatPayConfig = new RSAAutoCertificateConfig.Builder()
.merchantId(WechatPayData.merchantId)
.privateKeyFromPath(WechatPayData.privateKeyPath)
.merchantSerialNumber(WechatPayData.merchantSerialNumber)
.apiV3Key(WechatPayData.apiV3key)
.build();
}
return wechatPayConfig;
}
}
微信支付相关的工具类,如签名啥的:
import cn.hutool.crypto.PemUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.*;
public class WechatPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException {
//replace 根据实际情况,不一定都需要
String replace = privateKey.replace("\\n", "\n");
InputStream certStream = Files.newInputStream(Paths.get(privateKey));
PrivateKey merchantPrivateKey = PemUtil.readPemPrivateKey(certStream);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(sign.sign());
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 获取当前时间戳,单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
支付功能类,只有支付操作,不包含业务:
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
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.PrepayResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@ConditionalOnProperty(prefix = "wechat", name = "enabled", havingValue = "true")
public class WechatPayService {
@Autowired
private JsapiService service;
/**
* jsapi下单
* @param orderNo 订单号
* @param desc 订单描述
* @param totalAmount 总金额,单位:分
* @param openId 用户openid
* @return prepay_id
*/
public String jsapiPay(String orderNo,String desc,Integer totalAmount,String openId, Long memberId,String appId){
PrepayRequest prepayRequest = new PrepayRequest();
prepayRequest.setAppid(appId);
prepayRequest.setMchid(WechatPayData.merchantId);
prepayRequest.setDescription(desc);
prepayRequest.setOutTradeNo(orderNo);
prepayRequest.setAttach(String.valueOf(memberId));
prepayRequest.setNotifyUrl(WechatPayData.notifyUrl);
Amount amount = new Amount();
amount.setTotal(totalAmount);
prepayRequest.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(openId);
prepayRequest.setPayer(payer);
PrepayResponse response = service.prepay(prepayRequest);
return response.getPrepayId();
}
}
到这里基本就完事了,根据官方文档的参数看一下就懂了,这里的service.prepay()是依赖自带的。同样退款也有自带的方法,找到依赖jar,refund文件夹下RefundService类,看了就懂了。
退款功能:
/**
* 退款接口
* @param dto
* @return
*/
public AjaxResult refundOrder(RefundOrderDto dto){
Long id = dto.getId();
Boolean aBoolean=false;
Order order = orderMapper.getById(id);
Integer payType = order.getPayType();
Integer status = order.getStatus();
Long payId = order.getPayId();
WechatPaymentHistory byOrderId = wechatPaymentHistoryMapper.getByOrderId(payId);
if(payType==2 ){
if(status==1 || status==6){
String paymentId = byOrderId.getPaymentId();
Integer opType = byOrderId.getOpType();
Integer paymentStatus = byOrderId.getPaymentStatus();
BigDecimal money = byOrderId.getMoney();
long l = money.multiply(new BigDecimal("100")).longValue();
//判断要退款的订单必须是支付订单,并且是已经发生交易的订单
if(paymentStatus==1 && opType==1 ){
aBoolean = wechatPayService.refundPay(paymentId, l);
}
if(aBoolean){
byOrderId.setOpType(3);
if(dto.getRemark()!=null)byOrderId.setRemark(dto.getRemark());
int i = wechatPaymentHistoryMapper.updateById(byOrderId);
order.setAftersaleStatus(4);
return AjaxResult.success(i);
}
}
}
return AjaxResult.error(801,"您的订单不支持退款");
}
支付回调:
@PostMapping("/notify")
public void weChatPayNotify(HttpServletRequest request) throws Exception {
log.info("收到了微信支付回调");
// 从请求头中获取信息
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
String singType = request.getHeader("Wechatpay-Signature-Type");
String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
// 拿到请求体body
StringBuilder requestBody = new StringBuilder();
String line;
BufferedReader reader;
reader = request.getReader();
while (null != (line = reader.readLine())) {
requestBody.append(line);
}
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPayCertificateSerialNumber)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(requestBody.toString())
.build();
log.info("【requestParam】" + JSONObject.toJSON(requestParam));
//初始化了 RSAAutoCertificateConfig
Config config = WechatPayConfig.getInstance();
// 初始化解析器 NotificationParser
NotificationParser parser = new NotificationParser((NotificationConfig) config);
// 以支付通知回调为例,验签、解密并转换成 Transaction
Transaction transaction = parser.parse(requestParam, Transaction.class);
log.info("【transaction】" + JSONObject.toJSON(transaction));
PayNotifyMessageDTO message = new PayNotifyMessageDTO();
message.setOutTradeNo(Long.valueOf(transaction.getOutTradeNo()));
message.setMemberId(Long.valueOf(transaction.getAttach()));
message.setTradeStatus(transaction.getTradeState());
if (StrUtil.isEmpty(transaction.getSuccessTime())){
throw new RuntimeException("微信支付回调失败");
}
message.setPayTime(formatter.parse(transaction.getSuccessTime().substring(0, transaction.getSuccessTime().indexOf("+"))));
message.setTradeNo(transaction.getTransactionId());
h5OrderService.payCallBack(message);
}
payCallBack方法是回调后的逻辑
退款回调:
@PostMapping("/refunds/notify")
public String callback(HttpServletRequest request, HttpServletResponse response){
log.info("退款通知执行");
Map<String, String> map = new HashMap<>();//应答对象
try {
String signature = request.getHeader("Wechatpay-Signature");
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
// 拿到请求体body
StringBuilder requestBody = new StringBuilder();
String line;
BufferedReader reader;
reader = request.getReader();
while (null != (line = reader.readLine())) {
requestBody.append(line);
}
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPayCertificateSerialNumber)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(requestBody.toString())
.build();
//初始化了 RSAAutoCertificateConfig
Config config = WechatPayConfig.getInstance();
// 初始化解析器 NotificationParser
NotificationParser parser = new NotificationParser((NotificationConfig) config);
// 验签、解密并转换成 Transaction
RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class);
String orderTradeNo = refundNotification.getOutTradeNo();
Status refundStatus = refundNotification.getRefundStatus();
if("SUCCESS".equals(refundStatus.toString())){
log.info("更新退款记录:已退款");
h5OrderService.refundCallBack(orderTradeNo, refundStatus );
}
//成功应答
response.setStatus(200);
map.put("code", "SUCCESS");
return JSONObject.toJSONString(map);
} catch (Exception e) {
log.error(ExceptionUtils.getStackTrace(e));
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return JSONObject.toJSONString(map);
}
}
refundCallBack方法是退款回调的逻辑