1. 准备开发所需的账号以及配置信息
1.1 注册微信公众号或者小程序
无论是接入微信支付 还是微信小程序接入支付都要先获取APPID
微信公众平台网址:mp.weixin.qq.com
1.2 开通微信支付
微信支付目前仅支持商家开通,个人如果要使用的话 可以找第三方平台。
微信支付官网:pay.weixin.qq.com/index.php/a…
- 申请完成后将mch_id和appid关联起来
- mch_id:
- api-v3-key:
- merchant-serial-numberh和 证书
根据平台提示正常下载即可
下载完成示例
到此准备工作就都完成了
2. 开发接入
开发前请详细阅读微信支付开发文档:pay.weixin.qq.com/docs/mercha…
本章以微信小程序接入为案例,在wechatpay-java给的案例中 Native 支付下单使用NativePayService类。JSAPI 支付和 APP 支付推荐使用服务拓展类 JsapiServiceExtension 和 AppServiceExtension
2.1. 代码准备
2.1.1. 配置文件
# 支付配置;如果你申请了支付渠道,则可以配置 enable = true,否则就配置 false 走流程测试
wxpay:
config:
# 状态;true = 开启、false 关闭
enabled: true
# 申请支付主体的 appid
appid:
# 商户号
mchid:
# 回调地址
notify-url: // 必须是https的接口回调
# 商户API私钥路径
private-key-path: cert/apiclient_key.pem
# 商户证书序列号:openssl x509 -in apiclient_cert.pem -noout -serial
merchant-serial-number:
# 商户APIV3密钥
api-v3-key:
2.1.2. 配置文件类
@Data
@ConfigurationProperties(prefix = "wxpay.config",ignoreInvalidFields = true)
public class WeChatPayConfigProperties {
/** 状态;open = 开启、close 关闭 */
private boolean enable;
/** 申请支付主体的 appid */
private String appid;
/** 商户号 */
private String mchid;
/** 回调地址 */
private String notifyUrl;
/** 商户API私钥路径 */
private String privateKeyPath;
/** 商户证书序列号:openssl x509 -in apiclient_cert.pem -noout -serial */
private String merchantSerialNumber;
/** 商户APIV3密钥 */
private String apiV3Key;
}
2.1.3. spring注入的方式注入JsapiServiceExtension类
@Slf4j
@Configuration
@EnableConfigurationProperties(WeChatPayConfigProperties.class)
public class WeChatPayConfig {
// /**
// * 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
// *
// * @param properties 支付配置
// * @return NativePay
// */
// @Bean
// @ConditionalOnProperty(value = "wxpay.config.enabled", havingValue = "true", matchIfMissing = false)
// public NativePayService buildNativePayService(WeChatPayConfigProperties properties) {
// Config config = new RSAAutoCertificateConfig.Builder()
// .merchantId(properties.getMchid())
// .privateKeyFromPath(getFilePath(properties.getPrivateKeyPath()))
// .merchantSerialNumber(properties.getMerchantSerialNumber())
// .apiV3Key(properties.getApiV3Key())
// .build();
// return new NativePayService.Builder().config(config).build();
// }
/**
* 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错--微信小程序支付
*
* @param properties 支付配置
* @return NativePay
*/
@Bean
@ConditionalOnProperty(value = "wxpay.config.enabled", havingValue = "true", matchIfMissing = false)
public JsapiServiceExtension jsapiService(WeChatPayConfigProperties properties) {
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(properties.getMchid())
.privateKeyFromPath(getFilePath(properties.getPrivateKeyPath()))
.merchantSerialNumber(properties.getMerchantSerialNumber())
.apiV3Key(properties.getApiV3Key())
.build();
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
return service;
}
// 支付回调配置参数注入
@Bean
@ConditionalOnProperty(value = "wxpay.config.enabled", havingValue = "true", matchIfMissing = false)
public NotificationConfig buildNotificationConfig(WeChatPayConfigProperties properties) {
return new RSAAutoCertificateConfig.Builder()
.merchantId(properties.getMchid())
.privateKeyFromPath(getFilePath(properties.getPrivateKeyPath()))
.merchantSerialNumber(properties.getMerchantSerialNumber())
.apiV3Key(properties.getApiV3Key())
.build();
}
@Bean
@ConditionalOnBean(NotificationConfig.class)
@ConditionalOnProperty(value = "wxpay.config.enabled", havingValue = "true", matchIfMissing = false)
public NotificationParser notificationParser(NotificationConfig notificationConfig) {
return new NotificationParser(notificationConfig);
}
public static String getFilePath(String classFilePath) {
String filePath = "";
try {
String templateFilePath = "tempfiles/classpathfile/";
File tempDir = new File(templateFilePath);
if (!tempDir.exists()) {
tempDir.mkdirs();
}
String[] filePathList = classFilePath.split("/");
String checkFilePath = "tempfiles/classpathfile";
for (String item : filePathList) {
checkFilePath += "/" + item;
}
File tempFile = new File(checkFilePath);
if (tempFile.exists()) {
filePath = checkFilePath;
} else {
//解析
ClassPathResource classPathResource = new ClassPathResource(classFilePath);
InputStream inputStream = classPathResource.getInputStream();
checkFilePath = "tempfiles/classpathfile";
for (int i = 0; i < filePathList.length; i++) {
checkFilePath += "/" + filePathList[i];
if (i == filePathList.length - 1) {
//文件
File file = new File(checkFilePath);
if (!file.exists()) {
FileUtils.copyInputStreamToFile(inputStream, file);
}
} else {
//目录
tempDir = new File(checkFilePath);
if (!tempDir.exists()) {
tempDir.mkdirs();
}
}
}
inputStream.close();
filePath = checkFilePath;
}
} catch (Exception e) {
e.printStackTrace();
}
return filePath;
}
}
这块有个细节点注意一下,商户API私钥路径会自动生成在项目根路径temprifles中,如果配置错了 重新配置的话记得删除这个目录后在启动项目,避坑!
2.1.4. 调用小程序下单
// 依赖注入
@Autowired(required = false)
private JsapiServiceExtension jsapiService;
public PrepayWithRequestPaymentResponse openMembership(String openId,Long vipId){
BossVipProduct bossVipProduct = bossVipProductMapper.selectByPrimaryProductId(vipId);
BigDecimal totalAmount = bossVipProduct.getPrice();
String orderId = RandomStringUtils.randomNumeric(12);
// 创建订单--- 根据自己实际项目需求编写
BossOrder bossOrder = BossOrder.builder()
.payAmount(totalAmount)
.productId(bossVipProduct.getProductId())
.productName(bossVipProduct.getProductName())
.orderId(orderId)
.orderTime(new Date())
// 此处代表订单类型 1 代表用户报名培训 2 代表开通会员
.orderStatus(2)
.totalAmount(totalAmount)
.payType(0)
.payStatus(0)
.userId(openId)
.createTime(new Date())
.build();
int i = bossOrderMapper.insertSelective(bossOrder);
Amount amount=new Amount();
amount.setTotal(totalAmount.multiply(new BigDecimal(100)).intValue());
PrepayRequest prepayRequest=new PrepayRequest();
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appid);
prepayRequest.setMchid(mchid);
prepayRequest.setNotifyUrl(notifyUrl);
prepayRequest.setOutTradeNo(orderId);
prepayRequest.setDescription(bossVipProduct.getProductName());
PayOrderAggregation payOrderAggregation = PayOrderAggregation.builder()
.orderType(PayOrderType.VIP_APPLY)
.productId(bossVipProduct.getProductId())
.build();
prepayRequest.setAttach(JSON.toJSONString(payOrderAggregation));
Payer payer = new Payer();
payer.setOpenid(openId);
prepayRequest.setPayer(payer);
PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = jsapiService.prepayWithRequestPayment(prepayRequest);
// PrepayWithRequestPaymentResponse 返回的就是前端调起支付所需的全部参数,直接返回就好
return prepayWithRequestPaymentResponse;
}
2.1.5. 支付回调
package com.christielu.boss.controller;
import com.alibaba.fastjson2.JSON;
import com.christielu.boss.domain.PayOrderAggregation;
import com.christielu.boss.enums.PayOrderType;
import com.christielu.boss.service.TrainPayService;
import com.christielu.boss.service.UserService;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author: cxc
* @CreateTime: 2024-03-27 22:42
* @Description: TODO
* @Version: 1.0
*/
@RestController
@CrossOrigin
@RequestMapping("/pay")
@Slf4j
public class PayController {
@Autowired
private NotificationParser notificationParser; // 在配置类中已经注入过了
@PostMapping("payNotify")
public void payNotify(@RequestBody String requestBody, HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
log.info("PayController:" + requestBody);
// 微信传过来的签名
String header = request.getHeader("Wechatpay-Signature");
// 随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");
// 微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
// 证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");
// 时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonceStr)
.signature(signature)
.timestamp(timestamp)
.body(requestBody)
.build();
// 以支付通知回调为例,验签、解密并转换成 Transaction
Transaction transaction = notificationParser.parse(requestParam, Transaction.class);
log.info("transaction:{}",JSON.toJSONString(transaction));
Transaction.TradeStateEnum tradeState = transaction.getTradeState();
if (Transaction.TradeStateEnum.SUCCESS.equals(tradeState)){
String attach = transaction.getAttach();
PayOrderAggregation payOrderAggregation = JSON.parseObject(attach, PayOrderAggregation.class);
// 区分订单类型 // 根据自己代码实际逻辑编写
if (payOrderAggregation.getOrderType().equals(PayOrderType.TRAIN_APPLY)) {
trainPayService.payNotify(transaction,payOrderAggregation);
}else if (payOrderAggregation.getOrderType().equals(PayOrderType.VIP_APPLY)){
userService.openMembershipPayNotify(transaction,payOrderAggregation);
}
}else {
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
}
} catch (IOException e) {
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
}
}
}
3. 总结
一次完整的发起支付,到支付回调流程就完毕了,如果有其他需求,参考文档即可,最重要的就是注入我们所需要的类,和将相关的初始化配置 ,在上面的WeChatPayConfig类中。