1 简单回顾聚合支付整体架构流程
今日课程任务
- 如何保证参数安全传递到聚合支付平台
- 基于预提交支付参数形式保证接口安全性
- 如何设计提高聚合平台接口的扩展性
- 基于策略模式重构设计聚合支付平台
2 分析预支付提交token参数原理分析
参考腾讯课堂报名点击“去付款”,网站由ke.qq.com(腾讯课堂主站)跳转到pay.qq.com(腾讯内部聚合支付平台),跳转网址带token_id参数,采用token的形式隐藏参数真实性。
RPC接口如何保证支付接口参数安全传输
- 使用非对称加密 可以但是效率极低;
- 使用MD5对参数实现验证签名 采用post请求提交表单
- 直接采用token的形式(推荐)
用户下单的时候,调用订单服务接口,在订单表中插入一条订单的记录,同时调用支付服务接口产生一条预支付的记录。 预支付:提前把参数采用内部rpc传递给了支付系统,在支付表插入一条待支付状态订单的记录,返回令牌传递。生成的令牌称作为支付令牌,对应的value为支付的id。
3 支付接口提供生成预支付令牌接口
分析支付服务需要提供的接口:
- 提供给订单服务预提交参数,返回支付令牌。
- 根据支付令牌查询支付的参数、金额、订单提交form表单给支付宝。
一个大型项目如腾讯课堂、腾讯游戏、腾讯音乐,每个模块有自己独立的订单表,对接聚合支付平台如果只传递订单号不传金额,要额外查询以上所有模块的订单表不合理,因此预支付处理要直接传递金额。
创建模块mt-shop-service-api-pay、mt-shop-service-pay 引入依赖
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.0.0</version>
</dependency>
核心代码
public interface PayTokenService {
@PostMapping("/toPayToken")
BaseResponse<String> toPayOrderToken(@RequestBody PayOrderTokenDto payOrderTokenDto);
}
@RestController
public class PayTokenServiceImpl extends BaseApiService implements PayTokenService {
@Autowired
private PaymentTransactionMapper paymentTransactionMapper;
@Autowired
private TokenUtil tokenUtil;
@Override
public BaseResponse<String> toPayOrderToken(PayOrderTokenDto payOrderTokenDto) {
String orderId = payOrderTokenDto.getOrderId();
if (StringUtils.isEmpty(orderId)) {
return setResultError("订单号码不能为空!");
}
Long payAmount = payOrderTokenDto.getPayAmount();
if (payAmount == null) {
return setResultError("金额不能为空!");
}
Long userId = payOrderTokenDto.getUserId();
if (userId == null) {
return setResultError("userId不能为空!");
}
String orderName = payOrderTokenDto.getOrderName();
if (orderName == null) {
return setResultError("orderName不能为空!");
}
PaymentTransactionEntity paymentTransactionEntity =
dtoToDo(payOrderTokenDto, PaymentTransactionEntity.class);
paymentTransactionEntity.setPaymentId(SnowflakeIdUtils.nextId() + "");
int result = paymentTransactionMapper.insertPaymentTransaction(paymentTransactionEntity);
if (result <= 0) {
return setResultError("系统错误");
}
Long payId = paymentTransactionEntity.getId();
if (payId == null) {
return setResultError("系统错误");
}
String payToken = tokenUtil.createToken("mayikt:pay", payId + "");
if (StringUtils.isEmpty(payToken)) {
return setResultError("生成支付令牌失败");
}
return setResultSuccess(payToken);
}
}
测试效果:
4 基于策略模式分析聚合支付模块设计
支付渠道接口统一采用数据库的渠道表实现管理,对接支付宝、微信支付、其他支付接口。 基本上大多数支付接口设计思想都是一样,每个支付的渠道设计思想都是一样,唯一不同的就是对接支付代码生成html表单不一样。 策略模式可以解决多重if判断的问题。
5 代码实现定义策略类实现聚合支付
策略模式实现聚合支付 核心代码
@Component
public interface PayStrategy {
String toPayHtml();
}
@Component
public class AliPayStrategy implements PayStrategy {
@Override
public String toPayHtml() {
return "调用支付宝接口";
}
}
@Component
public class UnionPayStrategy implements PayStrategy {
@Override
public String toPayHtml() {
return "调用银联的支付接口";
}
}
@RestController
@Slf4j
public class PaymentAggregateServiceImpl extends BaseApiService implements PaymentAggregateService {
@Autowired
private PaymentChannelMapper paymentChannelMapper;
@Override
public BaseResponse<String> toPayHtml(String payToken, String channelId) {
if (StringUtils.isEmpty(payToken)) {
return setResultError("payToken不能为空!");
}
if (StringUtils.isEmpty(channelId)) {
return setResultError("channelId不能为空!");
}
PaymentChannelEntity pce = paymentChannelMapper.selectBychannelId(channelId);
if (pce == null) {
return setResultError("该渠道已关闭或不存在,请联系管理员");
}
String payBeanId = pce.getPayBeanId();
if (StringUtils.isEmpty(payBeanId)) {
return setResultError("没有配置payBeanId");
}
String data = payStrategy.toPayHtml();
log.info("result:" + data);
return setResultSuccess(data);
}
}
测试效果:
6 聚合支付整体代码联调测试
数据库支付渠道表中填入阿里提供沙箱环境公钥、私钥和APPID等信息,完善AliPayStrategy代码生成form表单,测试整个预付款流程。
@RestController
@Slf4j
public class PaymentAggregateServiceImpl extends BaseApiService implements PaymentAggregateService {
@Autowired
private PaymentChannelMapper paymentChannelMapper;
@Autowired
private TokenUtil tokenUtil;
@Autowired
private PaymentTransactionMapper paymentTransactionMapper;
@Override
public BaseResponse<String> toPayHtml(String payToken, String channelId) {
if (StringUtils.isEmpty(payToken)) {
return setResultError("payToken不能为空!");
}
if (StringUtils.isEmpty(channelId)) {
return setResultError("channelId不能为空!");
}
PaymentChannelEntity pce = paymentChannelMapper.selectBychannelId(channelId);
if (pce == null) {
return setResultError("该渠道已关闭或不存在,请联系管理员");
}
String payBeanId = pce.getPayBeanId();
if (StringUtils.isEmpty(payBeanId)) {
return setResultError("没有配置payBeanId");
}
String tokenValue = tokenUtil.getTokenValue(payToken);
if (StringUtils.isEmpty(tokenValue)) {
return setResultError("支付已经超时");
}
Long payId = Long.parseLong(tokenValue);
PaymentTransactionEntity ptc = paymentTransactionMapper.selectById(payId);
if (ptc == null) {
return setResultError("请不要随意攻击我们支付平台");
}
PayStrategy payStrategy = SpringContextUtils.getBean(payBeanId, PayStrategy.class);
String data = payStrategy.toPayHtml(pce, ptc);
log.info("result:" + data);
return setResultSuccess(data);
}
}
@Component
@Slf4j
public class AliPayStrategy implements PayStrategy {
@Override
public String toPayHtml(PaymentChannelEntity pce, PaymentTransactionEntity pte) {
AlipayClient alipayClient = new DefaultAlipayClient(pce.getRequestAddress(),
pce.getMerchantId(), pce.getPrivateKey(), "json",
AlipayConfig.CHARSET, pce.getPublicKey(), AlipayConfig.SIGN_TYPE);
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(pce.getSyncUrl());
alipayRequest.setNotifyUrl(pce.getAsynUrl());
String orderId = pte.getOrderId();
alipayRequest.setBizContent("{\"out_trade_no\":\"" + pte.getPaymentId() + "\","
+ "\"total_amount\":\"" + pte.getPayAmount() / 100 + "\","
+ "\"subject\":\"" + pte.getOrderName() + "\","
+ "\"body\":\"" + pte.getOrderBody() + "\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
try {
String result = alipayClient.pageExecute(alipayRequest).getBody();
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
测试效果:
|