做为一个全栈的小趴菜,刚接触微信支付业务模块的时候真的是一个头两个大,不知道怎么写,手足无措,在网上翻找各种微信支付的教程,结果对照微信支付官方文档才发现,网上 大部分的微信支付教程都是v2版本的,现在官网已经v3版本了,所以版本不同无法适用,今天给大家解决这个没有知识的痛苦。
一、废话不多说,直接上教程:
写代码之前首先要明白微信支付的支付流程。
二、支付流程:
小程序调用后端预支付接口 => 预支付接口调用成功返回给小程序支付凭证id => 小程序拿到支付凭证调用微信后台支付接口 => 小程序支付成功后,微信后台执行支付回调将支付订单信息返回(预支付调用微信后台是需要传入支付成功后自己本地的回调接口地址)
三、官方文档:
如果我们按照微信支付官方文档去一步步写会很慢很繁琐,我教大家使用微信支付官网推荐java-sdk工具包去实现。
进入到库里面看下面的简介,有maven和gradle导包安装示例和使用这个工具包实现二维码支付的逻辑示例代码。
四、开始撸代码:
我们要写的是微信小程序支付(JSAPI),所以需要自己写。
1.导入依赖
Gradle:
implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.11'
Maven:
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.11</version>
</dependency>
2.配置微信支付前必要的密钥和商户信息
资源文件配置:
# 微信小程序支付配置信息
wx:
# 微信小程序appid
app-id: xxxxx
# 商户号
mch-id: xxxx
# 证书序列号
mch-serial-no: xxxxx
# 小程序密钥
app-secret: xxxxxx
# api密钥
api-key: xxxxxxxx
# 回调接口地址
notify-url: https://xxxx/a/biz/wxpay/payNotify
# 证书地址
key-path: module-app/src/main/resources/cert/apiclient_key.pem
获取配置信息:
@Component
@ConfigurationProperties(prefix = "wx")
@Data
@ToString
public class WxPayV3Bean {
private String appId;
private String mchId;
private String mchSerialNo;
private String appSecret;
private String apiKey;
private String notifyUrl;
private String keyPath;
}
3.创建接口请求类和返回类
查看官方文档,查看调用微信支付时需要传哪些必传的参数。
预支付请求类:
@Data
@Accessors(chain = true)
public class WXPayOrderReqVO {
@ApiModelProperty(value = "订单支付类型(商品订单;预约订单)",required = true)
@NotBlank(message = "订单支付类型不能为空!")
private String orderType;//附加数据,回调时可根据这个数据辨别订单类型或其他
@ApiModelProperty(value = "总金额(单位:分)",required = true)
@NotNull(message = "总金额不能为空!")
private Integer totalPrice;
@ApiModelProperty(value = "商品名称",required = true)
@NotBlank(message = "商品名称不能为空!")
private String goodsName;
@ApiModelProperty(value = "openid",required = true)
@NotBlank(message = "openId不能为空!")
private String openId;
@ApiModelProperty(value = "商品订单号",required = true)
@NotNull(message = "商品订单号不能为空!")
private Long orderSn;
}
由于使用的这个sdk工具类里面有返回类,可以不用自己创建返回类。
这里我自己创了个返回类。
返回类:
@Data
@Accessors(chain = true)
public class WxPayRespVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 预支付交易会话标识小程序下单接口返回的prepay_id参数值
*/
@ApiModelProperty("预支付交易会话标识小程序下单接口返回的prepay_id参数值")
private String prepayId;
/**
* 随机字符串
*/
@ApiModelProperty("随机字符串")
private String nonceStr;
/**
* 时间戳
*/
@ApiModelProperty("时间戳")
private Long timeStamp;
/**
* 签名
*/
@ApiModelProperty("签名")
private String paySign;
}
工具类:
import com.wechat.pay.java.core.util.PemUtil;
import org.springframework.util.Base64Utils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Random;
/**
* @project 医美
* @Classname WXPayUtil
* @Description TODO
* @Author: LJJie
* @CreateTime: 2023-07-26 16:00
*/
public class WXPayUtil {
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");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(replace);
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);
}
}
4.开始写支付接口
Controller层:
预支付:
@ApiOperation(value = "微信预支付", notes = "微信预支付")
@PostMapping("/createOrder")
public Result<WxPayRespVO> createOrder(@RequestBody @Validated WXPayOrderReqVO req) throws Exception {
return Result.success(wxPayService.createOrder(req));
}
支付回调:
@ApiOperation(value = "微信支付回调", notes = "微信支付回调")
@PostMapping("/payNotify")
public Result payNotify(HttpServletRequest request){
wxPayService.payNotify(request);//注意:回调接口需要暴露到公网上,且要放开token验证
return Result.success();
}
Service、ServiceImpl层:
预支付:
/**
* 功能描述:
* 〈微信预支付〉
* @Param: [req]
* @Return: [WxPayRespVO]
* @Author: LJJie
* @Date: 2023/7/27 9:46
*/
WxPayRespVO createOrder(WXPayOrderReqVO req) throws Exception;
@Override
public WxPayRespVO createOrder(WXPayOrderReqVO req) throws Exception {
try {
// 使用自动更新平台证书的RSA配置
// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayV3Bean.getMchId())
.privateKeyFromPath(wxPayV3Bean.getKeyPath())
.merchantSerialNumber(wxPayV3Bean.getMchSerialNo())
.apiV3Key(wxPayV3Bean.getApiKey())
.build();
// 构建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(wxPayV3Bean.getAppId());
request.setMchid(wxPayV3Bean.getMchId());
request.setDescription(req.getGoodsName());
request.setNotifyUrl(wxPayV3Bean.getNotifyUrl());
request.setOutTradeNo(req.getOrderSn().toString());
request.setAttach(req.getOrderType());
Payer payer = new Payer();
payer.setOpenid(req.getOpenId());
request.setPayer(payer);
// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
WxPayRespVO vo = new WxPayRespVO();
Long timeStamp = System.currentTimeMillis() / 1000;
vo.setTimeStamp(timeStamp);
String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
vo.setNonceStr(substring);
String signatureStr = Stream.of(wxPayV3Bean.getAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
.collect(Collectors.joining("\n", "", "\n"));
String sign = WXPayUtil.getSign(signatureStr, wxPayV3Bean.getKeyPath());
vo.setPaySign(sign);
vo.setPrepayId("prepay_id=" + response.getPrepayId());
return vo;
}catch (ServiceException e){
JSONObject parse = JSONObject.parseObject(e.getResponseBody());
throw new ResultException(ResultEnum.ERROR,parse.getString("message"));
}catch (Exception e){
throw new ResultException(ResultEnum.ERROR,e.toString());
}
}
这个签名是工具类生成的,之前找v3版本的生成签名找半天才找到,这个特别难找。
注意:这里有个特别大的一个坑,大家一定要注意。JSONObject千万要引fastjson2的包,之前没注意引用的jsonfast的包测试环境一直报错,特别坑。找了半天才找到,直接当场破防。
支付回调:
/**
* 功能描述:
* 〈微信支付回调〉
* @Param: [request, response]
* @Return: [void]
* @Author: LJJie
* @Date: 2023/7/27 10:45
*/
void payNotify(HttpServletRequest request);
@Override
public void payNotify(HttpServletRequest request) {
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();
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader("Wechatpay-Signature-Type");
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
// 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
// 没有的话,则构造一个
log.error(com.alibaba.fastjson2.JSON.toJSONString(wxPayV3Bean));
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayV3Bean.getMchId())
.privateKeyFromPath(wxPayV3Bean.getKeyPath())
.merchantSerialNumber(wxPayV3Bean.getMchSerialNo())
.apiV3Key(wxPayV3Bean.getApiKey())
.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);
System.out.println("parse = " + parse);
} catch (Exception e) {
throw new ResultException(ResultEnum.ERROR,e.toString());
}
}
成长的过程是痛苦的,尽全力的去努力,不负自己不负未来。