**谷粒随享**
## 第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) {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(RechargeInfo::getOrderNo, orderNo);
return rechargeInfoMapper.selectOne(queryWrapper);
}
}
```
远程调用模块`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) {
//0.根据订单/充值编号查询交易记录 存在直接返回,不存在新增
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(PaymentInfo::getOrderNo, orderNo);
PaymentInfo paymentInfo = this.getOne(queryWrapper);
if (paymentInfo != null) {
return paymentInfo;
}else{
//1.构建本地交易记录对象
paymentInfo = new PaymentInfo();
//1.1 封装对象中简单属性
Long userId = AuthContextHolder.getUserId();
paymentInfo.setUserId(userId);
paymentInfo.setPaymentType(paymentType);
paymentInfo.setOrderNo(orderNo);
paymentInfo.setPayWay(SystemConstant.ORDER_PAY_WAY_WEIXIN);
paymentInfo.setPaymentStatus(SystemConstant.PAYMENT_STATUS_UNPAID);
//1.2 封装本地交易记录中金额跟内容-远程调用其他服务获取
//1.3 判断支付类型:订单
if (SystemConstant.PAYMENT_TYPE_ORDER.equals(paymentType)) {
//1.3.1 远程调用订单服务获取订单信息
OrderInfo orderInfo = orderFeignClient.getOrderInfo(orderNo).getData();
Assert.notNull(orderInfo, "订单不存在");
//1.3.2 校验订单状态是否为:未支付
boolean flag = SystemConstant.ORDER_STATUS_UNPAID.equals(orderInfo.getOrderStatus());
Assert.state(flag, "订单状态有误!");
//1.3.3 封装本地交易记录中交易内容跟金额
paymentInfo.setAmount(orderInfo.getOrderAmount());
paymentInfo.setContent(orderInfo.getOrderTitle());
} else if (SystemConstant.PAYMENT_TYPE_RECHARGE.equals(paymentType)) {
//1.4 判断支付类型:充值
//1.4.1 远程调用账户服务获取充值信息
RechargeInfo rechargeInfo = accountFeignClient.getRechargeInfo(orderNo).getData();
Assert.notNull(rechargeInfo, "充值记录不存在");
//1.4.2 校验充值状态是否为:未支付
boolean flag = SystemConstant.ORDER_STATUS_UNPAID.equals(rechargeInfo.getRechargeStatus());
Assert.state(flag, "充值状态有误!");
//1.4.3 封装本地交易记录中交易内容跟金额
paymentInfo.setAmount(rechargeInfo.getRechargeAmount());
paymentInfo.setContent("用户充值:" + rechargeInfo.getRechargeAmount());
}
//2.保存本地交易记录对象
this.save(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 {}包含五项参数
*/
@GuiGuLogin
@Operation(summary = "对接微信支付返回小程序拉起微信支付页面所需参数")
@PostMapping("/wxPay/createJsapi/{paymentType}/{orderNo}")
public Result