目录
概述
-
本文章介绍如何接入支付宝支付功能。
-
使用SpringBoot作为后端代码提供示例,前端使用Vue3做表单提交测试。
-
本文仅演示“电脑网站支付”这一种支付方式,但是其他支付方式如“JSAPI支付”,“手机网站支付”等等接入过程基本一样,看完本文应当可以一通百通。
接入过程
一、前期准备
首先需要注册/登录支付宝的“开放平台”和“商家平台”。
1.开放平台(open.alipay.com):用于创建应用并获取APPID和密钥等信息。
2.商家平台(b.alipay.com):用于设置收款信息和支付产品的签约等操作。
二、创建应用并配置加签方式
1. 我们需要到“开放平台”创建一个应用,点击右上角“控制台”
2. 创建自己需求类型的应用,本文使用网站支付测试,因此选择“网页/移动应用”。
3. 填写应用基本信息
值得注意的是应用名称不要带有"测试"或"test"之类的字样,否则应用提交审核通不过的。
4. 设置“接口加签方式”
进入刚创建的应用中,在“开发设置”里找到“接口加签方式”进行设置。
完成加签方式的设置之后,我们会拿到调用支付接口时的参数“应用私钥”,“支付宝公钥”...
点击设置后,按照表单提示一步步来:
生成应用密钥后自己妥善保管,将“应用公钥”复制回传到刚才的位置。
上传后我们就可以得到参数“支付宝公钥”
至此,我们就已经拿到了调用支付接口时的一些参数“应用的公私钥”,“支付宝公钥”。
三、应用提交审核
在我们设置了接口加签方式之后,就可以先提交审核了。
如果不提交审核可能会导致支付接口调用失败,提示错误"isv.invalid-app-id"。
四、签约/开通支付产品
在应用审核期间,我们可以到“商家平台(b.alipay.com)”去开通我们对应的支付产品。
例如本文演示使用的网站支付,所以我们就应该点击“电脑网站支付”这个产品进行签约开通。
开通信息按要求填写提交申请等待一下基本就审核好了。
如果没有签约开通产品,会提示错误:ISV权限不足....
五、支付API代码查看/编写
在我们的应用审核成功并且支付产品签约开通成功之后,我们就可以进行代码的编写了。
在编写之前,我们需要先了解支付宝支付API的介绍。
我们进入支付产品最下面找到开放文档:
在开发文档中会看到官方提供的开发SDK和API列表。
以Java为例,我们能找到Maven依赖包的描述。
点击“API列表”能看到SDK使用的代码示例和请求和响应参数的说明
六、实际开发
在此仅演示“下单支付”,“订单查询”和“支付成功回调”的代码。
其他功能代码请参考官网“API列表”进行编写。
如下仅贴出关键性代码,完整代码调用链请到最后的"Gitee演示Demo代码"处获取查看。
@RestController
@RequestMapping("/alipay")
@Api("支付")
@CrossOrigin
public class AliPayController {
private static final String GATEWAY_URL = "https://openapi.alipay.com/gateway.do";
@Autowired
private AliPayConfig aliPayConfig;
/**
*
* @param aliPay 支付请求参数
* @param httpResponse ·
* @throws Exception ·
*/
@GetMapping("/pay") // &subject=xxx&traceNo=xxx&totalAmount=xxx
public void pay(AliPay aliPay, HttpServletResponse httpResponse) throws Exception {
AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL, aliPayConfig.getAppId(),
aliPayConfig.getAppPrivateKey(), FORMAT_JSON, CHARSET_UTF8, aliPayConfig.getAlipayPublicKey(), SIGN_TYPE_RSA2);
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(aliPayConfig.getNotifyUrl());
request.setBizContent("{\"out_trade_no\":\"" + aliPay.getTraceNo() + "\","
+ "\"total_amount\":\"" + aliPay.getTotalAmount() + "\","
+ "\"subject\":\"" + aliPay.getSubject() + "\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
String form = "";
try {
form = alipayClient.pageExecute(request).getBody(); // 调用SDK生成表单
} catch (AlipayApiException e) {
e.printStackTrace();
}
httpResponse.setContentType("text/html;charset=" + CHARSET_UTF8);
httpResponse.getWriter().write(form);// 直接将完整的表单html输出到页面
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
/**
* 订单查询
* @param out_trade_no 商户订单号
* @param trade_no 支付宝交易订单号
* @throws AlipayApiException ·
*/
@GetMapping("/queryOrder/{out_trade_no}/{trade_no}")
public void queryOrder(@PathVariable("out_trade_no") String out_trade_no, @PathVariable("trade_no") String trade_no) throws AlipayApiException {
// 初始化SDK
AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL, aliPayConfig.getAppId(),
aliPayConfig.getAppPrivateKey(), FORMAT_JSON, CHARSET_UTF8, aliPayConfig.getAlipayPublicKey(), SIGN_TYPE_RSA2);
// 构造请求参数以调用接口
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
// 设置订单支付时传入的商户订单号
model.setOutTradeNo(out_trade_no);
// 设置支付宝交易号
model.setTradeNo(trade_no);
// 设置查询选项
List<String> queryOptions = new ArrayList<String>();
/*
* 交易结算信息: trade_settle_info
* 交易支付使用的资金渠道: fund_bill_list
* 交易支付时使用的所有优惠券信息: voucher_detail_list
* 交易支付使用单品券优惠的商品优惠信息: discount_goods_detail
* 商家优惠金额: mdiscount_amount
* 医保信息: medical_insurance_info
*/
queryOptions.add("trade_settle_info"); // 交易结算信息
model.setQueryOptions(queryOptions);
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.execute(request);
System.out.println("===>订单信息:\n" + response.getBody());
if (response.isSuccess()) {
System.out.println("调用成功");
} else {
System.out.println("调用失败");
// sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
// String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
// System.out.println(diagnosisUrl);
}
}
@PostMapping("/notify") // 注意这里必须是POST接口
public String payNotify(HttpServletRequest request) throws Exception {
if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) {
System.out.println("=========支付成功回调========");
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
params.put(name, request.getParameter(name));
// System.out.println(name + " = " + request.getParameter(name));
}
// 支付宝验签
if (Factory.Payment.Common().verifyNotify(params)) {
// 验签通过
System.out.println("交易名称: " + params.get("subject"));
System.out.println("交易状态: " + params.get("trade_status"));
System.out.println("支付宝交易凭证号(trade_no): " + params.get("trade_no"));
System.out.println("商户订单号(out_trade_no): " + params.get("out_trade_no"));
System.out.println("交易金额: " + params.get("total_amount"));
System.out.println("买家在支付宝唯一id: " + params.get("buyer_id"));
System.out.println("买家付款时间: " + params.get("gmt_payment"));
System.out.println("买家付款金额: " + params.get("buyer_pay_amount"));
}
}
return "success";
}
}
实际上,如上的所有流程描述皆在官方的文档中,但是为什么还是有许多人“博客文章”优先,以至于出现“收费博文浏览”的博文设定,也许是当下人们丢失了沉稳的耐心。
测试
支付测试
前端测试表单代码:
<template>
<div id="alipay_container">
<h1>支付宝支付测试</h1>
<form @submit.prevent="submitPayment">
<div class="form-group">
<label for="alipayTraceNo">支付宝交易号:</label>
<input type="text" id="alipayTraceNo" v-model="form.alipayTraceNo" required />
</div>
<div class="form-group">
<label for="subject">支付说明:</label>
<input type="text" id="subject" v-model="form.subject" required />
</div>
<div class="form-group">
<label for="totalAmount">支付金额:</label>
<input type="number" id="totalAmount" v-model="form.totalAmount" step="0.01" required />
</div>
<div class="form-group">
<label for="traceNo">订单号:</label>
<input type="text" id="traceNo" v-model="form.traceNo" required />
</div>
<button type="submit" class="submit-btn">提交支付</button>
</form>
</div>
</template>
<script setup>
import axios from 'axios';
// 表单数据
const form = {
alipayTraceNo: 'alipayTraceNo20241215ss1a1',
subject: '支付测试',
totalAmount: 0.1,
traceNo: 'traceNo20241215122100000',
};
// 提交支付请求
const submitPayment = async () => {
const url = `http://IP:9075/alipay/pay?alipayTraceNo=${form.alipayTraceNo}&subject=${encodeURIComponent(form.subject)}&totalAmount=${form.totalAmount}&traceNo=${form.traceNo}`;
try {
const response = await axios.get(url, { responseType: 'text' }); // 接收 HTML 表单作为字符串
const tempDiv = document.createElement('div');
tempDiv.innerHTML = response.data; // 插入返回的 HTML 表单
document.body.appendChild(tempDiv);
tempDiv.querySelector('form').submit(); // 自动提交表单
} catch (error) {
alert(`支付请求失败:${error.message}`);
}
};
</script>
<style scoped>
...已省略
<style>
1. 点击“提交支付”
手机扫码支付,或点击右侧的“登录支付宝”进行支付。
回调测试
在各种支付的回调测试中,老生常谈的提示:把回调地址设置为公网可访问的地址。
否则支付成功后没有办法通知到程序做订单后续处理。
在支付成功后查看日志打印:
发现支付成功后,回调地址成功被调用,并且返回了支付宝交易订单号。
订单查询测试
在支付成功之后,我们一般在回调中将“支付宝交易订单号”存储起来,可用于后续的订单查询。
关于开发文档对于查询订单接口的参数描述:
调用接口,查看日志:
结果复合预期,测试完毕。
演示代码地址(Gitee)
Gitee地址:gitee.com/maohe101/alipay_test/tree/master/