https://www.bilibili.com/video/BV1nW421R7qJ
小程序前端官方文档:https://developers.weixin.qq.com/miniprogram/dev/api/payment/wx.requestPayment.html
服务器端官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
申请并绑定微信支付
微信支付商户平台:https://pay.weixin.qq.com/index.php/core/home/login
对于商家来说,想要开通微信支付,必须要去“微信支付商户平台”注册,然后把需要的资料提交上去,经过审核通过,你就开通了微信支付功能。
企业申请资料:
1、营业执照:彩色扫描件或数码照片
2、组织机构代码证:彩色扫描件或数码照片,若已三证合一,则无需提供
3、对公银行账户:包含开户行省市信息,开户账号
4、法人身份证:彩色扫描件或数码照片
支付接口编写
官方文档地址:https://github.com/wechatpay-apiv3/wechatpay-java
导入依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
</dependency>
common-account.yaml 配置文件
异步回调地址必须外网能够访问,本地可以使用内网穿透。
wx:
v3pay:
#小程序微信公众平台appId
appid: wxcc651fcbab275e33
#商户号
merchantId: 1631833859
#商户API私钥路径
privateKeyPath: /root/daijia/apiclient_key.pem
#商户证书序列号
merchantSerialNumber: 4AE80B52EBEAB2B96F68E02510A42801E952E889
#商户APIV3密钥
apiV3key: 84dba6dd51cdaf779e55bcabae564b53
#异步回调地址
notifyUrl: http://139.198.127.41:8600/payment/wxPay/notify
配置类
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix="wx.v3pay") //读取节点
@Data
public class WxPayV3Properties {
private String appid;
/** 商户号 */
public String merchantId;
/** 商户API私钥路径 */
public String privateKeyPath;
/** 商户证书序列号 */
public String merchantSerialNumber;
/** 商户APIV3密钥 */
public String apiV3key;
/** 回调地址 */
private String notifyUrl;
@Bean
public RSAAutoCertificateConfig getConfig(){
return new RSAAutoCertificateConfig.Builder()
.merchantId(this.getMerchantId())
.privateKeyFromPath(this.getPrivateKeyPath())
.merchantSerialNumber(this.getMerchantSerialNumber())
.apiV3Key(this.getApiV3key())
.build();
}
}
WxPayController
@Operation(summary = "创建微信支付")
@PostMapping("/createJsapi")
public Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm) {
return Result.ok(wxPayService.createWxPayment(paymentInfoForm));
}
WxPayService
WxPrepayVo createWxPayment(PaymentInfoForm paymentInfoForm);
WxPayServiceImpl
@Override
public WxPrepayVo createWxPayment(PaymentInfoForm paymentInfoForm) {
try {
PaymentInfo paymentInfo = paymentInfoMapper.selectOne(new LambdaQueryWrapper<PaymentInfo>().eq(PaymentInfo::getOrderNo, paymentInfoForm.getOrderNo()));
if(null == paymentInfo) {
paymentInfo = new PaymentInfo();
BeanUtils.copyProperties(paymentInfoForm, paymentInfo);
paymentInfo.setPaymentStatus(0);
paymentInfoMapper.insert(paymentInfo);
}
// 构建service
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(paymentInfoForm.getAmount().multiply(new BigDecimal(100)).intValue());
request.setAmount(amount);
request.setAppid(wxPayV3Properties.getAppid());
request.setMchid(wxPayV3Properties.getMerchantId());
//string[1,127]
String description = paymentInfo.getContent();
if(description.length() > 127) {
description = description.substring(0, 127);
}
request.setDescription(paymentInfo.getContent());
request.setNotifyUrl(wxPayV3Properties.getNotifyUrl());
request.setOutTradeNo(paymentInfo.getOrderNo());
//获取用户信息
Payer payer = new Payer();
payer.setOpenid(paymentInfoForm.getCustomerOpenId());
request.setPayer(payer);
//是否指定分账,不指定不能分账
SettleInfo settleInfo = new SettleInfo();
settleInfo.setProfitSharing(true);
request.setSettleInfo(settleInfo);
// 调用下单方法,得到应答
// response包含了调起支付所需的所有参数,可直接用于前端调起支付
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
log.info("微信支付下单返回参数:{}", JSON.toJSONString(response));
WxPrepayVo wxPrepayVo = new WxPrepayVo();
BeanUtils.copyProperties(response, wxPrepayVo);
wxPrepayVo.setTimeStamp(response.getTimeStamp());
return wxPrepayVo;
} catch (Exception e) {
e.printStackTrace();
throw new GuiguException(ResultCodeEnum.WX_CREATE_ERROR);
}
}
Feign接口
WxPayFeignClient
/**
* 创建微信支付
* @param paymentInfoForm
* @return
*/
@PostMapping("/payment/wxPay/createWxPayment")
Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm);
乘客端web接口
OrderController
@Operation(summary = "创建微信支付")
@GuiguLogin
@PostMapping("/createWxPayment")
public Result<WxPrepayVo> createWxPayment(@RequestBody CreateWxPaymentForm createWxPaymentForm) {
Long customerId = AuthContextHolder.getUserId();
createWxPaymentForm.setCustomerId(customerId);
return Result.ok(orderService.createWxPayment(createWxPaymentForm));
}
OrderService
WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm);
OrderServiceImpl
@Autowired
private DriverInfoFeignClient driverInfoFeignClient;
@Autowired
private CustomerInfoFeignClient customerInfoFeignClient;
@Autowired
private WxPayFeignClient wxPayFeignClient;
@Override
public WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm) {
//1.获取订单支付相关信息
OrderPayVo orderPayVo = orderInfoFeignClient.getOrderPayVo(createWxPaymentForm.getOrderNo(), createWxPaymentForm.getCustomerId()).getData();
//判断是否在未支付状态
if (orderPayVo.getStatus().intValue() != OrderStatus.UNPAID.getStatus().intValue()) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}
//2.获取乘客微信openId
String customerOpenId = customerInfoFeignClient.getCustomerOpenId(orderPayVo.getCustomerId()).getData();
//3.获取司机微信openId
String driverOpenId = driverInfoFeignClient.getDriverOpenId(orderPayVo.getDriverId()).getData();
//4.封装微信下单对象,微信支付只关注以下订单属性
PaymentInfoForm paymentInfoForm = new PaymentInfoForm();
paymentInfoForm.setCustomerOpenId(customerOpenId);
paymentInfoForm.setDriverOpenId(driverOpenId);
paymentInfoForm.setOrderNo(orderPayVo.getOrderNo());
paymentInfoForm.setAmount(orderPayVo.getPayAmount());
paymentInfoForm.setContent(orderPayVo.getContent());
paymentInfoForm.setPayWay(1);
WxPrepayVo wxPrepayVo = wxPayFeignClient.createWxPayment(paymentInfoForm).getData();
return wxPrepayVo;
}
支付结果通知
- 方法一:主动进行微信支付查询
@Override
public Boolean queryPayStatus(String orderNo) {
// 构建service
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wxPayV3Properties.getMerchantId());
queryRequest.setOutTradeNo(orderNo);
try {
Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
log.info(JSON.toJSONString(transaction));
if(null != transaction && transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
//更改订单状态
this.handlePayment(transaction);
return true;
}
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage());
}
return false;
}
//如果支付成功,调用其他方法实现支付后处理逻辑
public void handlePayment(Transaction transaction) {
//1 更新支付记录,状态修改为 已经支付
//订单编号
String orderNo = transaction.getOutTradeNo();
//根据订单编号查询支付记录
LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PaymentInfo::getOrderNo,orderNo);
PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper);
//如果已经支付,不需要更新
if(paymentInfo.getPaymentStatus() == 1) {
return;
}
paymentInfo.setPaymentStatus(1);
paymentInfo.setOrderNo(transaction.getOutTradeNo());
paymentInfo.setTransactionId(transaction.getTransactionId());
paymentInfo.setCallbackTime(new Date());
paymentInfo.setCallbackContent(JSON.toJSONString(transaction));
paymentInfoMapper.updateById(paymentInfo);
//2 发送端:发送mq消息,传递 订单编号
// 接收端:获取订单编号,完成后续处理
rabbitService.sendMessage(MqConst.EXCHANGE_ORDER,
MqConst.ROUTING_PAY_SUCCESS,
orderNo);
}
- 方法二:微信平台异步通知
腾讯服务器会异步回调我们提供给其的接口 (进行支付接口的时候,配置了接口路径了request.setNotifyUrl(wxPayV3Properties.getNotifyUrl());
)
WxPayController
@Operation(summary = "微信支付异步通知接口")
@PostMapping("/notify")
public Map<String,Object> notify(HttpServletRequest request) {
try {
wxPayService.wxnotify(request);
//返回成功
Map<String,Object> result = new HashMap<>();
result.put("code", "SUCCESS");
result.put("message", "成功");
return result;
} catch (Exception e) {
e.printStackTrace();
}
//返回失败
Map<String,Object> result = new HashMap<>();
result.put("code", "FAIL");
result.put("message", "失败");
return result;
}
WxPayService
void wxnotify(HttpServletRequest request);
WxPayServiceImpl
@Autowired
private RabbitService rabbitService;
@Transactional
@Override
public void wxnotify(HttpServletRequest request) {
//1.回调通知的验签与解密
//从request头信息获取参数
//HTTP 头 Wechatpay-Signature
// HTTP 头 Wechatpay-Nonce
//HTTP 头 Wechatpay-Timestamp
//HTTP 头 Wechatpay-Serial
//HTTP 头 Wechatpay-Signature-Type
//HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
String wechatPaySerial = request.getHeader("Wechatpay-Serial");
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String signature = request.getHeader("Wechatpay-Signature");
String requestBody = RequestUtils.readData(request);
log.info("wechatPaySerial:{}", wechatPaySerial);
log.info("nonce:{}", nonce);
log.info("timestamp:{}", timestamp);
log.info("signature:{}", signature);
log.info("requestBody:{}", requestBody);
//2.构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPaySerial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(requestBody)
.build();
//3.初始化 NotificationParser
NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
//4.以支付通知回调为例,验签、解密并转换成 Transaction
Transaction transaction = parser.parse(requestParam, Transaction.class);
log.info("成功解析:{}", JSON.toJSONString(transaction));
if(null != transaction && transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
//5.处理支付业务
this.handlePayment(transaction);
}
}
public void handlePayment(Transaction transaction) {
PaymentInfo paymentInfo = paymentInfoMapper.selectOne(new LambdaQueryWrapper<PaymentInfo>().eq(PaymentInfo::getOrderNo, transaction.getOutTradeNo()));
if (paymentInfo.getPaymentStatus() == 1) {
return;
}
//更新支付信息
paymentInfo.setPaymentStatus(1);
paymentInfo.setOrderNo(transaction.getOutTradeNo());
paymentInfo.setTransactionId(transaction.getTransactionId());
paymentInfo.setCallbackTime(new Date());
paymentInfo.setCallbackContent(JSON.toJSONString(transaction));
paymentInfoMapper.updateById(paymentInfo);
// 表示交易成功!
// 后续更新订单状态! 使用消息队列!
rabbitService.sendMessage(MqConst.EXCHANGE_ORDER, MqConst.ROUTING_PAY_SUCCESS, paymentInfo.getOrderNo());
}
rabbitmq异步
rabbitmq:微信支付成功之后,还需要进行一些其他的后续操作(更新数据库的表什么的)。我们可以使用消息队列,进行异步操作,保证数据的最终一致性。先将支付成功的结果返回给用户
编写支付成功后的方法-发送端
- 在handlePayment方法编写
//如果支付成功,调用其他方法实现支付后处理逻辑
public void handlePayment(Transaction transaction) {
//1 更新支付记录,状态修改为 已经支付
//订单编号
String orderNo = transaction.getOutTradeNo();
//根据订单编号查询支付记录
LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PaymentInfo::getOrderNo,orderNo);
PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper);
//如果已经支付,不需要更新
if(paymentInfo.getPaymentStatus() == 1) {
return;
}
paymentInfo.setPaymentStatus(1);
paymentInfo.setOrderNo(transaction.getOutTradeNo());
paymentInfo.setTransactionId(transaction.getTransactionId());
paymentInfo.setCallbackTime(new Date());
paymentInfo.setCallbackContent(JSON.toJSONString(transaction));
paymentInfoMapper.updateById(paymentInfo);
//2 发送端:发送mq消息,传递 订单编号
// 接收端:获取订单编号,完成后续处理
rabbitService.sendMessage(MqConst.EXCHANGE_ORDER,
MqConst.ROUTING_PAY_SUCCESS,
orderNo);
}
编写支付成功后的方法-接收端
@Component
public class PaymentReceiver {
@Autowired
private WxPayService wxPayService;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = MqConst.QUEUE_PAY_SUCCESS,durable = "true"),
exchange = @Exchange(value = MqConst.EXCHANGE_ORDER),
key = {MqConst.ROUTING_PAY_SUCCESS}
))
public void paySuccess(String orderNo, Message message, Channel channel) {
wxPayService.handleOrder(orderNo);
}
}
//支付成功后续处理
@Override
public void handleOrder(String orderNo) {
//1 远程调用:更新订单状态:已经支付
//2 远程调用:获取系统奖励,打入到司机账户
//3 TODO 其他
}