前置准备工作
alipay:
server-url: https://openapi.alipay.com/gateway.do
app-id:
alipay-public-key:
merchant-private-key:
charset: UTF-8
sign-type: RSA2
format: json
return-url: https://admin.example.com/pay/alipay/notify
notify-url: https://pc.example.com/pay/result在“支付宝开发平台”获取APPID。此处获取到
app-id。通过“支付宝开放平台密钥工具”在本地生成“应用公钥”、“应用私钥”。将“应用公钥”上传至“支付宝开发平台”的“网页/移动应用->网页/移动应用->接口加签方式->自定义密钥->公钥模式”,上传之后支付宝平台会给一个“支付宝公钥”。将“支付宝公钥”、“应用私钥”保存到本地备用。此处获取到
alipay-public-key和merchant-private-key。调用支付宝网关时使用的字符编码为
UTF-8;和支付宝交互时使用的签名算法类型使用RSA2;支付宝返回数据的响应格式为json。跳转地址
return-url是用户在支付宝收银台支付完成后,支付宝会把浏览器重定向到这个地址。需要根据实际项目来定,此处以https://pc.example.com/pay/result作为示例。异步回调地址
notify-url是当支付结果确定后,支付宝后台会向这个地址发送 POST 通知,并且在没有收到success前会重复向该回调地址发送支付结果。需要根据实际项目来定,此处以https://pc.example.com/pay/result作为示例。
前端向后端提交商品信息
{
"price": 100, // 必填:金额(元),用于 BigDecimal 校验 & 传给支付宝 total_amount
"subject": "绿茶充值套餐", // 必填(业务上):商品标题,用于 model.setSubject(...)
"body": "绿茶充值套餐,充值金额 100 元" // 可选:商品描述,用于 model.setBody(...)
}前端需要向后端传递的参数需要包括价格、商品标题、商品描述。
后端需要向支付宝发起支付请求
使用支付宝官方SDK
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.38.85.ALL</version>
</dependency>生成商户订单号
String outTradeNo = "ALIPAY" + System.currentTimeMillis();格式 / 字符要求:
最大长度 64 个字符。
仅支持字母、数字和下划线(实际很多系统也用纯数字或字母数字混合)。
构造支付表单
使用到的支付宝SDK的方法
// 支付宝 Java SDK 定义的异常类型
import com.alipay.api.AlipayApiException;
// 支付宝 Java SDK 的客户端接口,定义了和支付宝网关打交道的能力。
import com.alipay.api.AlipayClient;
// 支付宝 Java SDK 的默认客户端实现,封装了和支付宝网关的通信细节。
import com.alipay.api.DefaultAlipayClient;
// 支付宝 Java SDK 的请求对象,封装了和支付宝网关的通信细节。
import com.alipay.api.request.AlipayTradeWapPayRequest;
// 支付宝 Java SDK 的响应对象,封装了和支付宝网关的通信细节。
import com.alipay.api.domain.AlipayTradeWapPayModel;构造支付表单(传入前端传的orderMsg和后端生成的outTradeNo)
private String buildH5PayForm(OrderMsg orderMsg, String outTradeNo) throws AlipayApiException {
// 获取支付宝 H5 支付客户端
AlipayClient alipayClient = getH5AlipayClient();
// 创建支付宝 H5 支付请求
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
// 创建支付宝 H5 支付模型
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
// 设置订单号
model.setOutTradeNo(outTradeNo);
// 设置商品标题
String subject = orderMsg.getSubject();
if (subject == null || subject.trim().isEmpty()) {
subject = "商品";
}
// 设置商品描述
String body = orderMsg.getBody();
if (body == null || body.trim().isEmpty()) {
body = "商品描述";
}
// 设置商品标题
model.setSubject(subject);
// 设置商品金额
model.setTotalAmount(orderMsg.getPrice());
// 设置商品描述
model.setBody(body);
// 设置商品编码
model.setProductCode("QUICK_WAP_WAY");
// 设置支付宝 H5 支付请求
request.setBizModel(model);
// 设置同步回跳地址
request.setReturnUrl(returnUrl);
// 设置异步通知地址
request.setNotifyUrl(notifyUrl);
// 执行支付宝 H5 支付请求
String form = alipayClient.pageExecute(request).getBody();
// 打印支付宝 H5 支付表单
System.out.println("支付宝 H5 支付表单:" + form);
// 返回支付宝 H5 支付表单
return form;
}构造完成后,返回给前端的支付表单示例如下(示例中使用https://admin.example.com/pay/alipay/notify作为异步回调地址,使用https://pc.example.com/pay/result作为前端支付结果跳转页面)
<form name="punchout_form" method="post" action="https://openapi.alipay.com/gateway.do?charset=UTF-8">
<!-- 基础参数 -->
<input type="hidden" name="app_id" value="2026000000001234">
<input type="hidden" name="method" value="alipay.trade.wap.pay">
<input type="hidden" name="format" value="json">
<input type="hidden" name="charset" value="UTF-8">
<input type="hidden" name="sign_type" value="RSA2">
<input type="hidden" name="timestamp" value="2026-02-12 15:30:45">
<input type="hidden" name="version" value="1.0">
<input type="hidden" name="notify_url" value="https://admin.example.com/pay/alipay/notify">
<input type="hidden" name="return_url" value="https://pc.example.com/pay/result">
<!-- 业务参数:JSON 字符串,内部还会再被 URL 编码 -->
<input type="hidden" name="biz_content"
value="{"out_trade_no":"H5_20260212153000001",
"total_amount":"100.00",
"subject":"绿茶充值套餐",
"body":"绿茶充值套餐,充值金额100元",
"product_code":"QUICK_WAP_WAY"}">
<!-- 对所有参数签名后的结果 -->
<input type="hidden" name="sign"
value="VfX8qVqXyP1v5tP4P7xvF1Jr7lq3...一大串RSA2签名...g4hY=">
<!-- 一般不会有可见按钮,由前端自己决定是否展示/自动提交 -->
<input type="submit" value="正在跳转到支付宝..." style="display:none;">
</form>
<script>
// SDK 返回时通常会自带这段自动提交脚本,你现在是自己在前端调用 form.submit()
document.forms[0].submit();
</script>前端在调用完后端接口后让表单自动提交
<div v-if="showPayForm" id="alipayFormContainer" ref="alipayFormContainer">
<div v-html="payFormHtml"></div>
</div>// 支付表单HTML
const payFormHtml = ref('')
// async 把函数变成 Promise 风格,await 在函数内部“同步风格地等接口结果”,配合 try/catch/finally 做统一的错误处理和状态收尾。
const payWithAlipay = async () => {
paying.value = true
errorMsg.value = ''
try {
// 商品金额固定为 100 元
const price = 100
// 商品名称与描述由前端构造并传给后端
const subject = '绿茶充值套餐'
const body = '绿茶充值套餐,充值金额 100 元'
// 试用await等待后端给出,创建支付订单
const res = await createOrder({
price,
subject,
body
})
if (res.code === 200 && res.msg) {
// 直接使用后端返回的完整表单(包含自动提交脚本)
payFormHtml.value = res.msg
// 显示支付表单
showPayForm.value = true
} else {
// 如果未获取到有效的支付表单,则抛出错误
throw new Error(res.msg || '未获取到有效的支付表单')
}
// 如果支付成功,则跳转支付宝收银台
} catch (error) {
// 如果支付失败,则显示错误信息
errorMsg.value = error.message || String(error)
// 显示错误信息
uni.showToast({ title: '支付失败,请重试', icon: 'none' })
} finally {
// 支付完成,设置支付状态为 false
paying.value = false
}
}