04 聚合支付-【代码模板】支付宝支付接入
Kiml Lv5
  • 前言
    支付宝支付接入流程

  • 更新

1
24-07-19 初始记录

支付产品

支付宝为普通商户提供如下支付产品 https://b.alipay.com/page/product-workspace/all-product

image

配置支付宝沙箱环境

接入手机网站支付需要具备如下条件:

  1. 申请前必须拥有经过实名认证的支付宝账户;

  2. 企业或个体工商户可申请;

  3. 需提供真实有效的营业执照,且支付宝账户名称需与营业执照主体一致;

  4. 网站能正常访问且页面显示完整,网站需要明确经营内容且有完整的商品信息;

  5. 网站必须通过 ICP 备案。如为个体工商户,网站备案主体需要与支付宝账户主体名称一致;

  6. 如为个体工商户,则团购不开放,且古玩、珠宝等奢侈品、投资类行业无法申请本产品。具体见:https://opendocs.alipay.com/open/203
    支付宝沙箱环境配置见:沙箱环境 - 支付宝文档中心 (alipay.com)

注册开放平台账号

支付宝开放平台地址:支付宝开放平台 (alipay.com)

配置密钥

进入沙箱页面:https://open.alipay.com/develop/sandbox/app

image

启用证书模式,并根据提示完成对应的配置。

测试环境准备

我们在测试支付宝下单接口时需要使用支付宝扫描二维码,需要在手机安装支付宝客户端(沙箱版本),用沙箱账号登录支付宝,扫二维码,二维码的地址即为下单接口的地址。

或者使用模拟器进行测试。模拟器安装地址:MuMu模拟器官网_安卓12模拟器_网易手游模拟器 (163.com)

安装完成后进行登录。

手机网站支付接口(H5)

小程序文档 - 支付宝文档中心 (alipay.com)

商家在网页应用中调用支付宝提供的网页支付接口,接口会调起支付宝客户端内的支付模块,此时会从商家网页应 用跳转到支付宝客户端中并开始支付;支付完成后会跳转回商家网页应用内,最后商家展示支付结果。

接口交互

手机网站支付快速接入 - 支付宝文档中心 (alipay.com)

  1. 用户在商户的 H5 网站下单支付后,商户系统按照手机网站支付接口 alipay.trade.wap.payAPI 的参数规范生成订 单数据

  2. 前端页面通过 Form 表单的形式请求到支付宝。此时支付宝会自动将页面跳转至支付宝 H5 收银台页面,如果用户手机上安装了支付宝 APP,则自动唤起支付宝 APP。

  3. 输入支付密码完成支付。

  4. 用户在支付宝 APP 或 H5 收银台完成支付后,会根据商户在手机网站支付 API 中传入的前台回跳地址 return_url 自动跳转回商户页面,同时在 URL 请求中以 Query String 的形式附带上支付结果参数,详细回跳参数见“手机网站支付接口 alipay.trade.wap.pay”前台回跳参数。

  5. 支付宝还会根据原始支付 API 中传入的异步通知地址 notify_url,通过 POST 请求的形式将支付结果作为参数通知 到商户系统,详情见支付结果异步通知。

下单接口定义

请求地址

环境 Https 请求地址
沙箱环境 https://openapi.alipaydev.com/gateway.do
正式环境 https://openapi.alipay.com/gateway.do

公共请求参数请求参数说明 - 支付宝文档中心 (alipay.com)

下单接口测试

  1. maven 依赖

1
2
3
4
5
<dependency>  
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>对应的最新版本</version>
</dependency>
  1. 代码编写:可参考示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Slf4j  
@Controller
public class PayTestController {
String APP_ID = "XXX";
String APP_PRIVATE_KEY = "XXX";
String ALIPAY_PUBLIC_KEY = "XXX";
String CHARSET = "utf‐8";
String serverUrl = "https://openapi.alipaydev.com/gateway.do";
// 正式 "https://openapi.alipay.com/gateway.do"@GetMapping("/alipaytest")
public void alipaytest(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
// 获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");
// 创建API对应的 request
AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
// 填充业务参数
alipayRequest.setBizContent("{" + " \"out_trade_no\":\"20150320010101123\"," + " \"total_amount\":\"0.01\"," + " \"subject\":\"Iphone6 16G\"," + " \"product_code\":\"QUICK_WAP_PAY\"" + " }");
String form = "";
try {
// 调用SDK生成表单
form = alipayClient.pageExecute(alipayRequest).getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
}
httpResponse.setContentType("text/html;charset=" + CHARSET);
// 直接将完整的表单html输出到页面
httpResponse.getWriter().write(form);
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
}
  1. 二维码生成指定二维码的 URL,模拟器/手机必须可以访问到此地址。(本机测试需要在同一个局域网内并使用局域网地址,或者使用内网穿透的地址

完整代码

  1. 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@Slf4j  
@Component
public class AlipayUtil {

private static AlipayConfig alipayConfig;

public AlipayUtil(AlipayConfig alipayConfig){
AlipayUtil.alipayConfig = alipayConfig;
}

/**
* 创建阿里云链接
* @param alipayConfig 支付宝支付配置
* @return AlipayClient
*/ public static AlipayClient createClient(AlipayConfig alipayConfig) {
AlipayClient alipayClient = null;
try {
alipayConfig.setAppCertContent(ResourceUtil.readUtf8Str(alipayConfig.getAppCertPath()));
alipayConfig.setAlipayPublicCertContent(ResourceUtil.readUtf8Str(alipayConfig.getAlipayPublicCertPath()));
alipayConfig.setRootCertContent(ResourceUtil.readUtf8Str(alipayConfig.getRootCertPath()));
alipayClient = new DefaultAlipayClient(alipayConfig);
} catch (AlipayApiException e) {
log.error("AlipayClient创建失败:" + e.getMessage());
// 构建异常日志
val operLog = OperLog.builder().title("支付宝支付")
.businessType(BusinessType.OTHER.ordinal())
.operUrl(alipayConfig.getServerUrl())
.operParam(JSONUtil.toJsonPrettyStr(alipayConfig))
.status(BusinessStatus.FAIL.ordinal())
.errorMsg(e.getErrMsg())
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
return alipayClient;
}

/**
* 预支付接口调用
* @param alipayTradePrecreateRequest 支付宝预支付接口请求
* @return AlipayTradePrecreateResponse 支付宝预支付接口返回
*/
public static AlipayTradePrecreateResponse alipayTradePrecreate(AlipayTradePrecreateRequest alipayTradePrecreateRequest) {
alipayTradePrecreateRequest.setNotifyUrl(alipayConfig.getNotifyUrl());
AlipayTradePrecreateResponse alipayTradePrecreateResponse = new AlipayTradePrecreateResponse();
try {
alipayTradePrecreateResponse = createClient(alipayConfig).certificateExecute(alipayTradePrecreateRequest);

log.info("[返回参数]:\n" + JSONUtil.toJsonPrettyStr(alipayTradePrecreateResponse));

if(alipayTradePrecreateResponse.isSuccess()){
return alipayTradePrecreateResponse;
} else {
log.error(alipayTradePrecreateResponse.getOutTradeNo() + "支付宝支付失败:" + alipayTradePrecreateResponse.getMsg());
// 构建异常日志
val operLog = OperLog.builder().title("支付宝支付")
.businessType(BusinessType.OTHER.ordinal())
.operUrl(alipayConfig.getServerUrl())
.requestMethod(alipayTradePrecreateRequest.getApiMethodName())
.operParam(JSONUtil.toJsonPrettyStr(alipayTradePrecreateRequest))
.status(BusinessStatus.FAIL.ordinal())
.errorMsg(JSONUtil.toJsonPrettyStr(alipayTradePrecreateResponse))
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
} catch (AlipayApiException e) {
log.error(alipayTradePrecreateResponse.getOutTradeNo() + "支付宝支付失败:" + e.getMessage());
// 构建异常日志
val operLog = OperLog.builder().title("支付宝支付")
.businessType(BusinessType.OTHER.ordinal())
.operUrl(alipayConfig.getServerUrl())
.operParam(JSONUtil.toJsonPrettyStr(alipayTradePrecreateRequest))
.status(BusinessStatus.FAIL.ordinal())
.errorMsg(e.getMessage())
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
return alipayTradePrecreateResponse;
}

/**
* 订单查询接口调用
* @param alipayTradeQueryRequest 支付宝查单接口请求
* @return AlipayTradeQueryResponse 支付宝查单接口返回
*/
public static AlipayTradeQueryResponse alipayTradeQuery(AlipayTradeQueryRequest alipayTradeQueryRequest) {
AlipayTradeQueryResponse alipayTradeQueryResponse = new AlipayTradeQueryResponse();
try {
alipayTradeQueryResponse = createClient(alipayConfig).certificateExecute(alipayTradeQueryRequest);
if(alipayTradeQueryResponse.isSuccess()){
log.info("支付宝查单返回:" + JSONUtil.toJsonPrettyStr(alipayTradeQueryResponse));
return alipayTradeQueryResponse;
} else {
log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝查单失败:" + alipayTradeQueryResponse.getMsg());
// 构建异常日志
val operLog = OperLog.builder().title("支付宝查单")
.businessType(BusinessType.OTHER.ordinal())
.operUrl(alipayConfig.getServerUrl())
.requestMethod(alipayTradeQueryRequest.getApiMethodName())
.operParam(JSONUtil.toJsonPrettyStr(alipayTradeQueryRequest))
.status(BusinessStatus.FAIL.ordinal())
.errorMsg(alipayTradeQueryResponse.getMsg())
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
} catch (AlipayApiException e) {
log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝查单失败:" + e.getMessage());
// 构建异常日志
val operLog = OperLog.builder().title("支付宝查单")
.businessType(BusinessType.OTHER.ordinal())
.operUrl(alipayConfig.getServerUrl())
.operParam(JSONUtil.toJsonPrettyStr(alipayTradeQueryRequest))
.status(BusinessStatus.FAIL.ordinal())
.errorMsg(e.getMessage())
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
return alipayTradeQueryResponse;
}

/**
* 支付宝订单关闭
* @param alipayTradeCloseRequest 支付宝关单接口请求
* @return AlipayTradeCloseResponse 支付宝关单接口返回
*/
public static AlipayTradeCloseResponse alipayTradeClose(AlipayTradeCloseRequest alipayTradeCloseRequest) {
AlipayTradeCloseResponse alipayTradeQueryResponse = new AlipayTradeCloseResponse();
try {
alipayTradeQueryResponse = createClient(alipayConfig).certificateExecute(alipayTradeCloseRequest);
if(alipayTradeQueryResponse.isSuccess()){
return alipayTradeQueryResponse;
} else {
log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝关单失败:" + alipayTradeQueryResponse.getMsg());
// 构建异常日志
val operLog = OperLog.builder().title("支付宝关单")
.businessType(BusinessType.OTHER.ordinal())
.operUrl(alipayConfig.getServerUrl())
.requestMethod(alipayTradeCloseRequest.getApiMethodName())
.operParam(JSONUtil.toJsonPrettyStr(alipayTradeCloseRequest))
.status(BusinessStatus.FAIL.ordinal())
.errorMsg(alipayTradeQueryResponse.getSubMsg())
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
} catch (AlipayApiException e) {
log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝关单失败:" + e.getMessage());
// 构建异常日志
val operLog = OperLog.builder().title("支付宝关单")
.businessType(BusinessType.OTHER.ordinal())
.operUrl(alipayConfig.getServerUrl())
.operParam(JSONUtil.toJsonPrettyStr(alipayTradeCloseRequest))
.status(BusinessStatus.FAIL.ordinal())
.errorMsg(e.getMessage())
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
return alipayTradeQueryResponse;
}
}
  1. 创建支付宝 V3 版本请求对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private AlipayTradePrecreateRequest createAlipayBeanV3ByOrder(Order order) {  
AlipayTradePrecreateRequest alipayTradePrecreateRequest = new AlipayTradePrecreateRequest();
JSONObject bizContent = new JSONObject();
bizContent.set("out_trade_no", order.getOrderNo());
// 支付宝支付 订单总金额,单位为元,精确到小数点后两位,取值范围为 [0.01,100000000],金额不能为 0。
bizContent.set("total_amount", order.getTotal());

// 标题部分根据需求修改
// String subject = order.getGoodName();
// if (ObjectUtil.isNotEmpty(order.getSize())) {
// subject = subject + "[" + order.getSize() + "]";
// }
// subject = "x-design风格化定制周边订单_" + subject;

bizContent.set("subject", subject);

alipayTradePrecreateRequest.setBizContent(bizContent.toString());
return alipayTradePrecreateRequest;
}
  1. 支付宝查单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
private BaseResponse<Integer> getAlipayBaseResponse(Order order, HttpServletRequest request) {  
AlipayTradeQueryRequest alipayTradeQueryRequest = new AlipayTradeQueryRequest();
JSONConfig jsonConfig = new JSONConfig();
jsonConfig.setIgnoreNullValue(false);
JSONObject bizContent = new JSONObject(jsonConfig);
bizContent.set("out_trade_no", order.getOrderNo());
bizContent.set("trade_no", null);
alipayTradeQueryRequest.setBizContent(bizContent.toString());
log.info("订单:" + order.getOrderNo() + "支付宝查单:" + JSONUtil.parse(alipayTradeQueryRequest));
AlipayTradeQueryResponse alipayTradeQueryResponse = AlipayUtil.alipayTradeQuery(alipayTradeQueryRequest);
log.info("订单:" + order.getOrderNo() + "支付宝查单结果:" + JSONUtil.parse(alipayTradeQueryResponse));
// 支付宝订单号
String tradeNo = alipayTradeQueryResponse.getTradeNo();
OtherPayInfo otherPayInfo = new OtherPayInfo();
otherPayInfo.setTradeNo(tradeNo);
// 支付时间
LocalDateTime time = null;
if (ObjectUtil.isNotNull(alipayTradeQueryResponse.getSendPayDate())) {
time = LocalDateTime.ofInstant(alipayTradeQueryResponse.getSendPayDate().toInstant(), ZoneId.systemDefault());
}

// 客户未扫码直接调用会出现订单不存在
if (ObjectUtil.isNull(alipayTradeQueryResponse.getTradeStatus())) {
return new BaseResponse<>(VMSystem.ORDER_STATUS_CREATE);
}

if ("TRADE_SUCCESS".equals(alipayTradeQueryResponse.getTradeStatus())) {
// 其他业务需求 修改订单信息 发送打印邮件等
// // 修改订单信息
// val updateOrderByWx = this.updateOrderStatusByPay(order.getOrderNo(), time, VMSystem.ORDER_STATUS_PAYED, otherPayInfo);
// // 发送打印邮件
// this.sendMailtoPrint(order);
// if (updateOrderByWx) {
// return new BaseResponse<>(VMSystem.ORDER_STATUS_PAYED);
// } else {
// throw new LogicException("订单状态更新异常");
// }
} else if ("WAIT_BUYER_PAY".equals(alipayTradeQueryResponse.getTradeStatus())) {
return new BaseResponse<>(VMSystem.ORDER_STATUS_CREATE);
} else {
log.error(order.getOrderNo() + "支付宝支付失败:网关描述:" + alipayTradeQueryResponse.getMsg() + "业务描述:" + alipayTradeQueryResponse.getSubMsg());
// 构建异常日志
OperLog operLog = OperLog.builder().title("支付宝支付")
.businessType(BusinessType.OTHER.ordinal())
.operIp(IpUtils.getIpAddr(request))
.operParam(JSONUtil.toJsonPrettyStr(alipayTradeQueryRequest.getTextParams()))
.status(BusinessStatus.FAIL.ordinal())
.method(alipayTradeQueryRequest.getApiMethodName())
.requestMethod("GET")
.errorMsg("支付宝支付失败:网关描述:" + alipayTradeQueryResponse.getMsg() + "业务描述:" + alipayTradeQueryResponse.getSubMsg())
.build();
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
// 修改订单信息
this.updateOrderStatusByPay(order.getOrderNo(), time, VMSystem.ORDER_STATUS_FAILED, otherPayInfo);
val baseResponse = new BaseResponse<>(VMSystem.ORDER_STATUS_FAILED);
baseResponse.setMessage("支付宝支付失败:网关描述:" + alipayTradeQueryResponse.getMsg() + "业务描述:" + alipayTradeQueryResponse.getSubMsg());
return baseResponse;
}
}
  1. 订单关闭

1
2
3
4
5
6
7
8
9
// 支付宝订单关闭  
AlipayTradeCloseRequest alipayTradeCloseRequest = new AlipayTradeCloseRequest();
JSONConfig jsonConfig = new JSONConfig();
jsonConfig.setIgnoreNullValue(false);
JSONObject bizContent = new JSONObject();
bizContent.set("out_trade_no", order.getOrderNo());
bizContent.set("trade_no", order.getTradeNo());
alipayTradeCloseRequest.setBizContent(bizContent.toString());
AlipayUtil.alipayTradeClose(alipayTradeCloseRequest);

BUG

验签出错,建议检查签名字符串或签名私钥与应用公钥是否匹配

技术文档:https://opendocs.alipay.com/support/01ravw

问题原因
密钥不匹配、编码格式不统一(本次测试后更换中文编码格式)、请求参数数据有误、接口调用加签方式和应用上选择的加签方式不对应、SDK 调用的提交方法有误、SDK 运行环境有误

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
访客数 访问量