写在前面
:之前写过几次支付系统,每次都是重新去git拉代码或者是从各种途径copy,这次做个简单的总结,大体流程做个记录,业务代码就不往上放了,之后再用的时候,就缝缝补补业务类代码就可以了。
流程介绍:
接入支付宝app支付其实是很简单的,导入下jar包,申请些参数,基本上就没什么问题了,这里不做过多概述
1:首先服务端提供创建商户订单接口,将订单信息返回给前端
2:前端拿到订单信息展示给用户,用户选择支付途径,也就是支付宝支付或者微信支付
3:用户选择支付宝支付后,前端将订单信息给到服务端,服务端拿到订单信息封装各种参数调用支付宝接口进行一个预付单的操作
4:服务端将支付宝方返回的信息返回给前端,前端唤起支付宝app收银台,进行支付行为
5:用户支付或者没有支付,支付宝会将结果通过我们上面传的某个回调地址参数,通知到我们
所以整个流程下来仅需要两个接口即可完成,一个是去支付宝进行预下单的接口,一个是提供给支付宝进行回调的接口,话不多说,上代码
先是调用支付接口所需的参数:
public class AliPayProperties {
// 1.商户appid
public static String APPID = "2018050860126085";
// 应用公钥
public static String APP_RSA_KEY = "";
//2.私钥 pkcs8格式的
public static String RSA_PRIVATE_KEY = "";
// 3.支付宝公钥
public static String ALIPAY_PUBLIC_KEY = "";
// 4.服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url = "";
//5.页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址
public static String return_url = "https://www.baidu.com";
// 6.请求支付宝的网关地址
public static String URL = "https://openapi.alipay.com/gateway.do";
// 7.编码
public static String CHARSET = "UTF-8";
// 8.返回格式
public static String FORMAT = "json";
// 9.加密类型
public static String SIGNTYPE = "RSA2";
//10.签约产品代码
public static String QUICK_MSECURITY_PAY = "QUICK_MSECURITY_PAY";
}
参数的作用及申请流程移步支付宝公众平台申请哈,此处只贴代码
1. 支付接口
控制层代码:
@PostMapping("/pay")
@ApiOperation("支付宝-支付")
public Result<String> pay(@RequestBody IPayReq req) throws ArtException, IOException {
return iPayService.pay(req);
}
这里就是去支付宝进行预下单的接口了,也就是用户选择使用支付宝渠道支付的时候,前端要带着订单信息调用服务端的接口,这里传的参数也是很简单的一个参数类:
@Data
@ApiModel(value = "提交支付参数类")
public class IPayReq implements Serializable {
/**
* 订单ID
*/
@ApiModelProperty(value = "订单ID", required = true)
private Integer orderId;
/**
* 支付渠道
*/
@ApiModelProperty(value = "支付渠道 0:支付宝 1:微信", required = true)
private Integer payChannel;
}
下一步就是我们的业务层了:
public Result<String> pay(IPayReq req) throws ArtException{
// 获取订单信息
Order order = orderMapper.selectByPrimaryKey(req.getOrderId());
// 此处可以对取出的订单信息做一系列的验证(订单状态,有效性。。。。)
//。。。。。。以防止出现重复提交的情况
try {
// 这里我是构建了一个请求参数,因为之后迭代可能不止订单模块一个地方要使用支付
AliPayParams aliOrder = AliPayParams.builder()
.orderId(order.getId())
.body(order.getOrderBody())
.subject(order.getOrderSubject())
.totalAmount(order.getOrderAmount().toString())
.OutTradeNo(order.getOrderNum())
.build();
return Result.success(aliPayUtils.aliPay(aliOrder));
} catch (ArtException e) {
log.error("============支付宝支付失败");
e.printStackTrace();
throw new ArtException(ExceptionEnums.PAY_PORT_ERROR);
}
}
请求参数内部构造:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ApiModel("调用支付宝支付接口所用参数转换")
public class AliPayParams implements Serializable {
/**
* 这个请求参数类就是把来自各个模块下的需要支付的信息在调用支付工具类的时候,有个统一的传参
*/
/**
* 订单相对超时时间
*/
private String timeoutExpress = "30";
/**
* 订单总金额
*/
@ApiModelProperty(required = true)
private String totalAmount;
/**
* 产品码 我方在支付宝方某种产品申请的某个产品码
*/
private String productCode;
/**
* 订单描述
*/
private String body = "版权认证次数充值服务";
/**
* 订单标题
*/
@ApiModelProperty(required = true)
private String subject = "充值服务";
/**
* 外部订单号 (我方订单号)
*/
@ApiModelProperty(required = true)
private String OutTradeNo;
/**
* 订单绝对超时时间 time_expire和timeout_express两者只需传入一个或者都不传,如果两者都传,优先使用time_expire
*/
private String timeExpire = "30";
/**
* 商品主类型 0:虚拟类商品;
* 1:实物类商品。
*/
private String goodsType = "0";
/**
* 优惠参数 (暂时用不到,如需使用,需要和支付宝协商后可用)
*/
private String promoParams;
/**
* 公用回传参数 (如果请求时传递了该参数,支付宝会在异步通知时将该参数原样返回,作用在于确定请求唯一性)
*/
private String passbackParams;
/**
* 商户原始订单号
*/
private String merchantOrderNo;
/**
* 支付渠道
*/
private String enablePayChannels;
/**
* 商户门店编号 (商户创建门店时输入的门店编号)
*/
private String storeId;
/**
* 指定的 单通道支付
*/
private String specifiedChannel;
/**
* 禁用的 某些通道支付
*/
private String desablePayChannels;
/**
* 订单ID 仅用于支付宝保存订单信息
*/
private Integer orderId;
}
好的,封装好参数之后,就是调用支付接口了,我把支付的操作总结了一个工具类,放在下面
@Component
@Slf4j
public class AliPayUtils extends BaseService{
@Autowired
private AliPayInfoMapper aliPayInfoMapper;
/**
* 根据传过来的套餐id跳转支付宝支付
*
* @return
* @Param id 套餐id
*/
public String aliPay(AliPayParams aliOrder){
AlipayClient alipayClient = new DefaultAlipayClient(AliPayProperties.URL, AliPayProperties.APPID,
AliPayProperties.RSA_PRIVATE_KEY, "json", AliPayProperties.CHARSET, AliPayProperties.ALIPAY_PUBLIC_KEY,
AliPayProperties.SIGNTYPE);
//实例化客户端
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody(aliOrder.getBody());
model.setSubject(aliOrder.getSubject());
model.setOutTradeNo(aliOrder.getOutTradeNo());
model.setTimeoutExpress("30m");
model.setTotalAmount(aliOrder.getTotalAmount());
model.setProductCode(AliPayProperties.QUICK_MSECURITY_PAY);
request.setBizModel(model);
request.setNotifyUrl(AliPayProperties.notify_url);
log.info("========================支付宝支付接口,参数打印:{}", JSONObject.toJSONString(request));
try {
//这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
log.info("=====================支付宝支付接口,返回结果:{}",response.getBody());
// 判断订单创建状态
if (response.isSuccess()) {
// .............这里在支付宝返回成功之后可以做些操作(创建支付宝关联订单,修改商户订单状态等等,或者保存下请求日志都可以)
}
// 支付宝返回的信息可以直接给到前端
return response.getBody();
} catch (AlipayApiException e) {
log.error("===================支付宝支付接口,调用异常");
e.printStackTrace();
throw new ArtException(ExceptionEnums.PAY_PORT_ERROR);
}
}
}
ok,和支付宝的预付单流程到这里就结束了,下面就是用户在支付后对我们的回调通知了
回调通知
先是控制层:
@PostMapping("/aliPay/notify")
@ApiOperation("支付宝-支付回调")
public String notify(HttpServletRequest request) throws AlipayApiException, Exception {
return iPayService.notify(request);
}
然后是业务层:
public String notify(HttpServletRequest request) throws AlipayApiException, Exception {
// 解析参数
Map<String, String> params = new HashMap<String, String>();
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
return notifyDispose(params);
}
private String notifyDispose(Map<String, String> params) throws Exception{
log.info("===============支付宝异步请求通知结果,请求参数:{}",params.toString());
try {
// 验签
if (aliPayUtils.getType(params)){
//回调成功代码逻辑 此处保留所有的参数提取,后期根据业务不同需求获取
String appId=params.get("app_id");//支付宝分配给开发者的应用Id
String notifyTime=params.get("notify_time");//通知时间:yyyy-MM-dd HH:mm:ss
String gmtCreate=params.get("gmt_create");//交易创建时间:yyyy-MM-dd HH:mm:ss
String gmtPayment=params.get("gmt_payment");//交易付款时间
String gmtRefund=params.get("gmt_refund");//交易退款时间
String gmtClose=params.get("gmt_close");//交易结束时间
String tradeNo=params.get("trade_no");//支付宝的交易号
String outTradeNo = params.get("out_trade_no");//获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
String outBizNo=params.get("out_biz_no");//商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
String buyerLogonId=params.get("buyer_logon_id");//买家支付宝账号
String sellerId=params.get("seller_id");//卖家支付宝用户号
String sellerEmail=params.get("seller_email");//卖家支付宝账号
String totalAmount=params.get("total_amount");//订单金额:本次交易支付的订单金额,单位为人民币(元)
String receiptAmount=params.get("receipt_amount");//实收金额:商家在交易中实际收到的款项,单位为元
String invoiceAmount=params.get("invoice_amount");//开票金额:用户在交易中支付的可开发票的金额
String buyerPayAmount=params.get("buyer_pay_amount");//付款金额:用户在交易中支付的金额
String tradeStatus = params.get("trade_status");
Order order = orderMapper.selectByTradeNo(outTradeNo);
// 校验参数是否正确
if (order!=null&&totalAmount.equals(order.getOrderAmount().toString())&&AliPayProperties.APPID.equals(appId)) {
// 修改商户订单状态
switch (tradeStatus) // 判断交易结果
{
case "TRADE_FINISHED": // 交易结束并不可退款
order.setOrderStatus(3);
break;
case "TRADE_SUCCESS": // 交易支付成功
order.setOrderStatus(2);
break;
case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
order.setOrderStatus(1);
break;
case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
order.setOrderStatus(0);
break;
default:
break;
}
// ...............这里可以对商户订单信息及一些业务设计上面的信息进行操作,持久化订单,或者是保存请求日志
}else {
log.error("===================支付回调参数不正确, 参数打印:{}", params.toString());
return "fail";
}
}else {
log.error("===================支付回调验签失败,参数打印:{}", params.toString());
return "fail";
}
return "success";
} catch (Exception e) {
throw new AlipayApiException(String.valueOf(ExceptionEnums.PAY_SYSTEM_ERROR.getCode()), ExceptionEnums.PAY_SYSTEM_ERROR.getMsg());
}
}
总之呢,对接接口其实是很简单的,只是在支付前或者支付后的各种业务逻辑设计是比较值得考究的,支付成功后需要做什么,失败后需要做什么,这些业务逻辑是比较值得我们去研磨的地方
本篇代码在编写之初参考了网络的一些文章,基本上都是大同小异的,写出来也给自己做个保留,下次使用时直接copy就可以了。