Administrator
Administrator
发布于 2026-01-29 / 33 阅读
0
0

支付宝支付(H5秘钥验签支付)

前置准备工作

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
  1. 在“支付宝开发平台”获取APPID。此处获取到app-id

  2. 通过“支付宝开放平台密钥工具”在本地生成“应用公钥”、“应用私钥”。将“应用公钥”上传至“支付宝开发平台”的“网页/移动应用->网页/移动应用->接口加签方式->自定义密钥->公钥模式”,上传之后支付宝平台会给一个“支付宝公钥”。将“支付宝公钥”、“应用私钥”保存到本地备用。此处获取到alipay-public-keymerchant-private-key

  3. 调用支付宝网关时使用的字符编码为UTF-8;和支付宝交互时使用的签名算法类型使用RSA2;支付宝返回数据的响应格式为json

  4. 跳转地址return-url是用户在支付宝收银台支付完成后,支付宝会把浏览器重定向到这个地址。需要根据实际项目来定,此处以https://pc.example.com/pay/result作为示例。

  5. 异步回调地址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="{&quot;out_trade_no&quot;:&quot;H5_20260212153000001&quot;,
                 &quot;total_amount&quot;:&quot;100.00&quot;,
                 &quot;subject&quot;:&quot;绿茶充值套餐&quot;,
                 &quot;body&quot;:&quot;绿茶充值套餐,充值金额100元&quot;,
                 &quot;product_code&quot;:&quot;QUICK_WAP_WAY&quot;}">

  <!-- 对所有参数签名后的结果 -->
  <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
  }
}


评论