**谷粒随享**
## 第8章 微信支付充值业务
**学习目标:**
- 本地交易记录保存(用于对账)
- 下单
- 充值
- 微信支付(对接第三方支付平台)
- 基于微信支付完成付款
- 下单
- 充值
- 基于微信支付完成充值业务
# 1、微信支付

## 1.1 保存交易记录
需求:当用户进行订单、充值 微信支付为每笔交易产生一条本地交易记录,将来用于对账。
### 1.1.1 获取订单对象
> YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/100
已有Restful接口实现。只需要在`service-order-client`模块中新增Feign远程调用方法
**OrderFeignClient**
```java
package com.atguigu.tingshu.order.client;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.order.client.impl.OrderDegradeFeignClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
*
* 订单模块远程调用API接口
*
*
* @author atguigu
*/
@FeignClient(value = "service-order", path = "api/order", fallback = OrderDegradeFeignClient.class)
public interface OrderFeignClient {
/**
* 根据订单编号查询订单信息
* @param orderNo
* @return
*/
@GetMapping("/orderInfo/getOrderInfo/{orderNo}")
public Result getOrderInfo(@PathVariable String orderNo);
}
```
**OrderDegradeFeignClient**熔断类:
```java
package com.atguigu.tingshu.order.client.impl;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.order.client.OrderFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class OrderDegradeFeignClient implements OrderFeignClient {
@Override
public Result getOrderInfo(String orderNo) {
log.error("[订单服务]提供远程调用方法getOrderInfo执行服务降级");
return null;
}
}
```
### 1.1.2 获取充值记录信息
> YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/101
`service-account` 微服务**RechargeInfoApiController**控制器中添加
```java
package com.atguigu.tingshu.account.api;
import com.atguigu.tingshu.account.service.RechargeInfoService;
import com.atguigu.tingshu.common.login.GuiGuLogin;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.account.RechargeInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "充值管理")
@RestController
@RequestMapping("api/account")
@SuppressWarnings({"all"})
public class RechargeInfoApiController {
@Autowired
private RechargeInfoService rechargeInfoService;
/**
* 根据充值订单编号查询充值信息
* @param orderNo
* @return
*/
@Operation(summary = "根据充值订单编号查询充值信息")
@GetMapping("/rechargeInfo/getRechargeInfo/{orderNo}")
public Result getRechargeInfo(@PathVariable String orderNo){
RechargeInfo rechargeInfo = rechargeInfoService.getRechargeInfo(orderNo);
return Result.ok(rechargeInfo);
}
}
```
**RechargeInfoService**接口
```java
package com.atguigu.tingshu.account.service;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.baomidou.mybatisplus.extension.service.IService;
public interface RechargeInfoService extends IService {
/**
* 根据充值订单编号查询充值记录
* @param orderNo
* @return
*/
RechargeInfo getRechargeInfo(String orderNo);
}
```
**RechargeInfoServiceImpl实现类**
```java
package com.atguigu.tingshu.account.service.impl;
import com.atguigu.tingshu.account.mapper.RechargeInfoMapper;
import com.atguigu.tingshu.account.service.RechargeInfoService;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@SuppressWarnings({"all"})
public class RechargeInfoServiceImpl extends ServiceImpl implements RechargeInfoService {
@Autowired
private RechargeInfoMapper rechargeInfoMapper;
/**
* 根据充值订单编号查询充值信息
*
* @param orderNo
* @return
*/
@Override
public RechargeInfo getRechargeInfo(String orderNo) {
return rechargeInfoMapper.selectOne(
new LambdaQueryWrapper()
.eq(RechargeInfo::getOrderNo, orderNo)
);
}
}
```
远程调用模块`service-account-client`中**AccountFeignClient**增加远程调用方法
```java
/**
* 根据充值订单编号查询充值信息
* @param orderNo
* @return
*/
@GetMapping("/rechargeInfo/getRechargeInfo/{orderNo}")
public Result getRechargeInfo(@PathVariable String orderNo);
```
**AccountDegradeFeignClient**熔断类:
```java
@Override
public Result getRechargeInfo(String orderNo) {
log.error("[账户服务]提供远程调用接口getRechargeInfo服务降级");
return null;
}
```
### 1.1.3 保存本地交易记录
`service-payment`模块中增加保存本地交易业务处理
**PaymentInfoService**
```java
package com.atguigu.tingshu.payment.service;
import com.atguigu.tingshu.model.payment.PaymentInfo;
import com.baomidou.mybatisplus.extension.service.IService;
public interface PaymentInfoService extends IService {
/**
* 保存本地交易记录
*
* @param paymentType:支付类型 1301-订单 1302-充值
* @param orderNo: 订单编号
* @return
*/
public PaymentInfo savePaymentInfo(String paymentType, String orderNo);
}
```
**PaymentInfoServiceImpl**实现类
```java
package com.atguigu.tingshu.payment.service.impl;
import cn.hutool.core.lang.Assert;
import com.atguigu.tingshu.account.AccountFeignClient;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.model.payment.PaymentInfo;
import com.atguigu.tingshu.order.client.OrderFeignClient;
import com.atguigu.tingshu.payment.mapper.PaymentInfoMapper;
import com.atguigu.tingshu.payment.service.PaymentInfoService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@SuppressWarnings({"all"})
public class PaymentInfoServiceImpl extends ServiceImpl implements PaymentInfoService {
@Autowired
private OrderFeignClient orderFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
/**
* 保存本地交易记录
*
* @param paymentType:支付类型 1301-订单 1302-充值
* @param orderNo: 订单编号
* @return
*/
@Override
public PaymentInfo savePaymentInfo(String paymentType, String orderNo) {
Long userId = AuthContextHolder.getUserId();
//1.根据订单编号查询本地交易记录,如果存在则返回即可
PaymentInfo paymentInfo = baseMapper.selectOne(
new LambdaQueryWrapper()
.eq(PaymentInfo::getOrderNo, orderNo)
);
if (paymentInfo != null) {
return paymentInfo;
}
//2.构建本地交易记录
paymentInfo = new PaymentInfo();
//2.1 封装直接赋值属性:用户ID,支付类型、订单号、付款方式、支付状态
paymentInfo.setUserId(userId);
paymentInfo.setPaymentType(paymentType);
paymentInfo.setOrderNo(orderNo);
paymentInfo.setPayWay(SystemConstant.ORDER_PAY_WAY_WEIXIN);
paymentInfo.setPaymentStatus(SystemConstant.PAYMENT_STATUS_UNPAID);
//2.2 处理支付类型为:1301订单,封装交易金额跟内容
if (SystemConstant.PAYMENT_TYPE_ORDER.equals(paymentType)) {
//2.2.1 远程调用订单服务获取订单信息
OrderInfo orderInfo = orderFeignClient.getOrderInfo(orderNo).getData();
Assert.notNull(orderInfo, "订单{}不存在", orderNo);
//2.2.2 判断订单状态,未支付才能支付 订单状态:0901-未支付 0902-已支付 0903-已取消
String orderStatus = orderInfo.getOrderStatus();
if (!SystemConstant.ORDER_STATUS_UNPAID.equals(orderStatus)) {
throw new GuiguException(500, "订单状态有误:" + orderStatus);
}
//2.2.3 封装交易金额跟内容
paymentInfo.setAmount(orderInfo.getOrderAmount());
paymentInfo.setContent(orderInfo.getOrderTitle());
}
//2.3 处理支付类型为:1302充值,封装交易金额跟内容
if (SystemConstant.PAYMENT_TYPE_RECHARGE.equals(paymentType)) {
//2.2.1 远程调用账户服务获取充值信息
RechargeInfo rechargeInfo = accountFeignClient.getRechargeInfo(orderNo).getData();
Assert.notNull(rechargeInfo, "充值{}不存在", orderNo);
//2.2.2 判断充值状态,未支付才能支付 充值状态:0901-未支付 0902-已支付 0903-已取消
String rechargeStatus = rechargeInfo.getRechargeStatus();
if (!SystemConstant.ORDER_STATUS_UNPAID.equals(rechargeStatus)) {
throw new GuiguException(500, "充值状态有误:" + rechargeStatus);
}
//2.3.3 封装交易金额跟内容
paymentInfo.setAmount(rechargeInfo.getRechargeAmount());
paymentInfo.setContent("充值:" + rechargeInfo.getRechargeAmount());
}
//2.4 TODO 其他属性,等支付成功后异步回调获取
//paymentInfo.setOutTradeNo();
//paymentInfo.setCallbackTime();
//paymentInfo.setCallbackContent();
//3.保存本地交易记录
baseMapper.insert(paymentInfo);
return paymentInfo;
}
}
```
## 1.2 对接微信支付
> YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/102
异步回调需要在微信端配置,并且只能配置一个接口地址,而现在这个接口地址有其他项目在使用。所以我们在前端模拟了一下回调功能,主动查询一下结果!

接收前端传递的**支付类型**以及**订单编号**
1. 先在配置文件中添加支付相关信息配置:
```properties
spring.application.name=service-payment
spring.profiles.active=dev
spring.main.allow-bean-definition-overriding=true
spring.cloud.nacos.discovery.server-addr=192.168.200.130:8848
spring.cloud.nacos.config.server-addr=192.168.200.130:8848
spring.cloud.nacos.config.prefix=${spring.application.name}
spring.cloud.nacos.config.file-extension=yaml
wechat.v3pay.appid=wxcc651fcbab275e33
wechat.v3pay.merchantId=1631833859
#TODO 确保能加载本地应用私钥文件
wechat.v3pay.privateKeyPath=D:\\tmp\\apiclient_key.pem
wechat.v3pay.merchantSerialNumber=4AE80B52EBEAB2B96F68E02510A42801E952E889
wechat.v3pay.apiV3key=84dba6dd51cdaf779e55bcabae564b53
#TODO 改为公网域名能确保微信调用成功
wechat.v3pay.notifyUrl=http://127.0.0.1:8500/api/payment/wxPay/notify
```
2. **apiclient_key.pem** 商户API私钥路径 的私钥要放入指定位置,让程序读取!
3. 注意:RSAAutoCertificateConfig 对象必须在配置文件中注入到spring 容器中,不要直接使用原生的,否则会报错!
```java
package com.atguigu.tingshu.payment.config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
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="wechat.v3pay") //读取节点
@Data
public class WxPayV3Config {
private String appid;
/** 商户号 */
public String merchantId;
/** 商户API私钥路径 */
public String privateKeyPath;
/** 商户证书序列号 */
public String merchantSerialNumber;
/** 商户APIV3密钥 */
public String apiV3key;
/** 回调地址 */
private String notifyUrl;
@Bean
public RSAAutoCertificateConfig rsaAutoCertificateConfig(){
return new RSAAutoCertificateConfig.Builder()
.merchantId(this.merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3key)
.build();
}
}
```
### 1.2.1 支付控制器
```java
package com.atguigu.tingshu.payment.api;
import com.atguigu.tingshu.common.login.GuiGuLogin;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.payment.service.WxPayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@Tag(name = "微信支付接口")
@RestController
@RequestMapping("api/payment")
@Slf4j
public class WxPayApiController {
@Autowired
private WxPayService wxPayService;
/**
* 下单或充值选择微信支付,返回小程序拉起微信支付所需参数
* @param paymentType 支付类型:1301下单 1302充值
* @param orderNo 订单/充值订单编号
* @return {"timeStamp":"","package":"","paySign":"","signType":"RSA","nonceStr":""}
*/
@GuiGuLogin
@Operation(summary = "下单或充值选择微信支付,返回小程序拉起微信支付所需参数")
@PostMapping("/wxPay/createJsapi/{paymentType}/{orderNo}")
public Result