支付宝生活号支付预研

结论
当前在PC端生成二维码后,用户通过支付宝app扫码进入生活号完成支付操作是行不通。

https://opendocs.alipay.com/fw/guide这个地址是生活号快速接入的文档,但是是旧文档,不要再看!

目前没有生活号了,均升级为生活号+,生活号+是不支持调用开放平台产品接口的。生活号+支持商家平台(https://b.alipay.com/page/home)或者个人账户(https://c.alipay.com/page/portal/home)进行创建,但是生活号+不能做支付操作(我是通过支付宝开放平台的技术支持沟通过了解到的,支付宝开放平台的技术支持咨询方式放在文档最后)。

在这里插入图片描述

公告地址:https://open.alipay.com/portal/forum/post/158401031

这里我把商家创建生活号+的步骤放出来,有条件的话可以尝试一下。注意:个人账号创建生活号+请在互联网环境下进行,内网环境无法上传头像(个人创建生活号+的必填项)的
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

下面是与支付宝技术支持的沟通记录

在这里插入图片描述

解决方案

通过创建小程序来完成,核心链路:PC生成带参二维码 -> 小程序获取参数 -> 小程序向服务端请求支付 -> 服务端调用支付宝创建交易 -> 小程序发起支付 -> 服务端处理异步通知 -> 服务端通知PC端。此时的二维码是专门为小程序生成的二维码时,它会直接拉起对应的小程序,并打开指定的页面,同时将二维码中携带的参数传递给这个页面

详细步骤

  1. 支付宝小程序注册

    支付宝小程序需要注册个人账号(必须完成个体工商户认证)或者企业账号,没有认证个体工商户的个人账号可以用于开发和调试小程序,但不能提审和发布

    • 个人账号注册及实名认证(https://opendocs.alipay.com/common/02kg61?pathHash=f705b4eb)
    • 个人账号认证个体工商户(https://opendocs.alipay.com/common/02kkul?pathHash=7777cc49)
    • 企业账号注册及实名认证(https://opendocs.alipay.com/common/02kkum?pathHash=e8db7ff6)
  2. 创建支付宝小程序
    1. 登录开放平台(https://open.alipay.com/),使用个人账号或者企业账号登录,然后登陆成功后点击接入开发下的小程序开发,进入如下页面:

在这里插入图片描述

  1. 点击去创建小程序

    在这里插入图片描述

    我是用自己的账号创建的小程序,如下图所示:

    在这里插入图片描述

    1. 在开放平台完成基础配置(开放平台首页-右上角控制台-选择你的小程序-点击详情按钮,内容如下图)
      在这里插入图片描述

      1. 配置密钥(左侧的开发设置菜单

        • 在您的应用详情页,找到 「接口加签方式」

        • 生成您的应用私钥(可以使用开放平台提供的工具生成)。

          具体操作步骤补充
          1. 生成密钥对(密钥相关文档:https://opendocs.alipay.com/common/02kipl)

            # 使用支付宝官方工具(推荐)
            # 下载地址:https://opendocs.alipay.com/common/02kipl
            # 生成2048位的RSA2密钥
            
          2. 配置应用公钥

            1. 登录 支付宝开放平台
            2. 进入「控制台」→ 选择你的小程序
            3. 点击「开发设置」→ 「接口加签方式」
            4. 点击「设置/查看」→ 「启用公钥」
            5. 粘贴你的应用公钥内容
          3. 获取支付宝公钥

            • 在同一个「接口加签方式」页面

            • 系统会自动显示对应的支付宝公钥

            • 复制这个公钥到你的后端配置中

              // 在你的后端配置中
              const alipaySdk = new AlipaySdk({
                appId: '你的小程序APPID',
                privateKey: '你的应用私钥', // 从app_private_key.pem读取
                alipayPublicKey: '支付宝公钥', // 从开放平台获取
                gateway: 'https://openapi.alipay.com/gateway.do'
              });
              
          4. 后端配置验证

            // 验证配置是否正确
            const result = await alipaySdk.exec('alipay.system.oauth.token', {
              grantType: 'authorization_code',
              code: 'test_code'
            });
            
        • 设置支付宝公钥:将支付宝提供的公钥配置到您的平台上。

      2. 配置域名白名单

        • 「开发设置」 中,配置 「授权回调域名」「小程序应用域名」
        • 将您后端API所在的服务器域名填入其中。只有在此白名单中的域名才能被小程序访问
      3. 申请所需功能

        • 「功能列表」 中,找到并申请 「小程序支付(必须是完成个体工商户认证的个人账号或者企业账号才会有)」。通常名称是 “小程序支付”“JSAPI支付”
        • 点击右侧的 “申请”“添加” 按钮。
        • 根据指引完成协议的签约。这是实现支付功能的前提
      4. 其他的关于小程序的基础信息也要完成配置
        在这里插入图片描述

  2. 准备开发环境

    1. 安装开发工具(工具概述地址:https://opendocs.alipay.com/mini/ide,下载小程序开发者工具)
    2. 初始化项目,最主要的一步是关联APPID
      在这里插入图片描述

PC端二维码选择

URL Scheme 解析(存在兼容性问题)

二维码内容:alipays://platformapi/startapp?appId=xxx&page=pages/pay/pay&query=orderId=ORDER_123456

各组成部分的作用

组件作用示例
alipays://支付宝专用协议头告诉系统用支付宝App打开
platformapi/平台API路由固定路径,标识为平台功能
startapp启动应用动作固定参数,表示启动应用
appId=xxx小程序唯一标识关键参数,告诉支付宝要打开哪个小程序
page=pages/pay/pay小程序页面路径指定要打开的具体页面
query=orderId=ORDER_123456页面参数传递给目标页面的数据

如果跳转失败,可以检查:

  1. 小程序状态:是否已上架或处于开发版
  2. 页面路径pages/pay/pay 是否在 app.json 中正确定义
  3. 参数编码:复杂的query参数需要URL编码
  4. APPID核对:确认APPID与目标小程序一致

前面已经给出了**URL Scheme **地址的解析参数,后端可以根据这些参数手动拼接,然后通过qrcode 库(Node.js)将 urlScheme 字符串生成二维码图片(base64格式)。

URL Scheme二维码的兼容性问题
  1. 浏览器拦截与提示(最常见)
    // iOS Safari 提示
    "支付宝"想打开"支付宝"
    [取消] [打开]
    
    // 某些安卓浏览器提示  
    "是否允许支付宝打开此链接?"
    [拒绝] [允许]
    
    // 微信内置浏览器(最严重)
    "已停止访问该网页"
    "网页包含诱导分享、关注等诱导行为内容..."
    
  2. 支付宝未安装
    // 用户未安装支付宝时:
    1. 部分浏览器:提示"未安装应用"
    2. 部分浏览器:跳转到应用商店
    3. 部分浏览器:完全无反应
    
  3. 支付宝版本过旧
    // 旧版本支付宝可能:
    1. 无法识别新的Scheme格式
    2. 跳转到错误的页面
    3. 直接报错
    
  4. URL长度限制
    // 某些浏览器对URL长度有限制
    const longScheme = `alipays://platformapi/startapp?appId=xxx&page=pages/pay/pay&query=orderId=ORDER_123456&timestamp=xxx&sign=xxx`;
    
    // 可能被截断,导致参数丢失
    

替代方案:小程序码(推荐)

优势
  1. 无浏览器拦截:不依赖系统浏览器,无确认弹窗
  2. 无应用屏蔽:在支付宝内,微信/QQ等无法干扰
  3. 无系统限制:不触发iOS/安卓的系统级安全提示
  4. 用户体验流畅:扫码 → 直接进入小程序,一步到位
小程序码的生成步骤

接入准备:https://opendocs.alipay.com/mini/02owto?pathHash=10e164cb

接入指南:https://opendocs.alipay.com/mini/02owtp?pathHash=cc9e3957

  1. 调用支付宝API:使用 alipay.open.app.qrcode.create 接口。

  2. 组装关键参数:在调用API时,你需要传入以下核心参数(bizContent):

    • url_param: 指定要跳转的小程序页面路径,例如 pages/pay/pay
    • query_param: 这里放入你系统的订单号,这是后续支付的唯一凭证,例如 orderId=202410310001
    • describe: 二维码的描述信息,可为空。
  3. 后端代码示例 (Node.js)

    // 引入支付宝SDK等依赖
    const AlipaySdk = require('alipay-sdk').default;
    const AlipayFormData = require('alipay-sdk/lib/form').default;
    
    // 初始化
    const alipaySdk = new AlipaySdk({
      appId: '你的小程序APPID',
      privateKey: '你的应用私钥',
      alipayPublicKey: '支付宝公钥',
      gateway: 'https://openapi.alipay.com/gateway.do'
    });
    
    // 生成小程序码的函数
    async generatePayQRCode(orderId) {
      const formData = new AlipayFormData();
      formData.setMethod('get');
      formData.addField('bizContent', {
        url_param: 'pages/pay/pay', // 你的支付页面路径
        query_param: `orderId=${orderId}`, // 将订单号作为参数
        describe: '订单支付'
      });
    
      try {
        const result = await alipaySdk.exec('alipay.open.app.qrcode.create', {}, { formData });
        if (result.code === '10000' && result.qrCodeUrl) {
          // 返回小程序码图片的URL地址
          return { success: true, qrCodeUrl: result.qrCodeUrl };
        }
        return { success: false, message: result.msg };
      } catch (error) {
        console.error('生成小程序码失败:', error);
        return { success: false, message: '系统异常' };
      }
    }
    
小程序码使用的前置条件清单
## 必需条件
- [ ] 小程序已完成企业认证或个体工商户认证
- [ ] 小程序支付功能已申请并通过审核
- [ ] 小程序支付签约已完成
- [ ] 目标页面(pages/pay/pay)在app.json中正确定义
- [ ] 后端API域名已配置到小程序域名白名单
- [ ] 域名已完成ICP备案且支持HTTPS

## 环境要求
- [ ] 开发环境:开发版小程序 + 真机调试
- [ ] 测试环境:体验版小程序 + 体验成员权限
- [ ] 生产环境:小程序已上架

## 配置验证
- [ ] 密钥配置正确(应用公钥/支付宝公钥)
- [ ] 支付参数配置正确(APPIDPID等)
- [ ] 异步通知地址配置正确且可访问
URL Scheme二维码与小程序码的比喻
  • URL Scheme二维码:房间的门
    // 用户站在房间外(各种浏览器、微信等)
    用户 → 看到门(URL Scheme二维码) → 
    需要:1. 找到钥匙(支付宝App) → 
         2. 开门(确认跳转) → 
         3. 进入房间(小程序)
    
    // 风险:可能没带钥匙、门锁坏了、不想开门...
    
  • 小程序码:人在房间内
    // 用户已经在房间里(支付宝App内)
    用户 → 看到室内指引牌(小程序码) → 
    直接:走到目标位置(跳转小程序页面)
    
    // 优势:没有门的阻碍,直达目标
    

自研商家接入JSAPI支付文档:https://opendocs.alipay.com/mini/05xmim?pathHash=e9ca29c2

JSAPI接入指南:https://opendocs.alipay.com/mini/05x9ku?pathHash=a7b61cca

支付相关API列表:https://opendocs.alipay.com/mini/05xhsr?pathHash=d4709298

// 页面逻辑:pay.js
Page({
  data: {
    orderId: '',
    productName: '', 
    amount: '0.00',
    loading: false,
    pollTimer: null,      // 轮询定时器
    pollCount: 0,         // 轮询次数计数
    maxPollCount: 30,     // 最大轮询次数
    retryCount: 0,        // 重试次数
    maxRetryCount: 3      // 最大重试次数
  },

  onLoad(options) {
    if (options.orderId) {
      this.setData({ orderId: options.orderId });
      this.fetchOrderAndPay();
    } else {
      this.showErrorAlert('参数错误', '未找到订单信息,请重新扫描二维码。', () => {
        my.navigateBack();
      });
    }
  },

  onUnload() {
    // 防止内存泄漏,确保组件卸载时清理定时器。
    this.clearPolling();
  },

  clearPolling() {
    if (this.data.pollTimer) {
      clearTimeout(this.data.pollTimer);
      this.setData({ pollTimer: null });
    }
  },

  // 统一的错误提示方法
  showErrorAlert(title, content, callback = null) {
    my.alert({
      title,
      content,
      success: () => {
        callback && callback();
      }
    });
  },

  // 显示重试确认框
  showRetryConfirm(title, content) {
    return new Promise((resolve) => {
      my.confirm({
        title,
        content,
        confirmButtonText: '重试',
        cancelButtonText: '退出',
        success: (res) => {
          resolve(res.confirm);
        }
      });
    });
  },

  // 网络请求重试机制
  async requestWithRetry(requestConfig, retryTimes = 3) {
    for (let i = 0; i < retryTimes; i++) {
      try {
        const response = await my.request({
          timeout: 10000, // 10秒超时
          ...requestConfig
        });
        return response;
      } catch (error) {
        console.warn(`网络请求第${i + 1}次失败:`, error);
        
        if (i === retryTimes - 1) {
          throw new Error('网络不稳定,请检查网络连接后重试');
        }
        
        // 等待一段时间后重试
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
      }
    }
  },

  async fetchOrderAndPay() {
    this.setData({ loading: true });

    try {
      // 1. 获取订单详情(仅用于展示,不用于支付)
      const orderResponse = await this.requestWithRetry({
        url: 'https://您的后端域名.com/api/getOrderDetail',
        method: 'POST',
        data: { orderId: this.data.orderId }
      });

      if (!orderResponse.data.success) {
        throw new Error(orderResponse.data.message || '获取订单失败');
      }

      const orderData = orderResponse.data.data;

      // 检查订单状态,避免重复支付
      if (orderData.status === 'PAID') {
        this.showErrorAlert('订单已支付', '该订单已完成支付,请勿重复操作', () => {
          this.notifyPCSuccess();
          my.navigateBack();
        });
        return;
      }

      if (orderData.status === 'CLOSED' || orderData.status === 'EXPIRED') {
        throw new Error('订单已关闭或超时,请重新下单');
      }

      // 2. 更新页面展示信息
      this.setData({
        productName: orderData.productName,
        amount: orderData.amount,
      });

      // 3. 创建支付订单(后端会调用支付宝创建交易)
      await this.createPayment();

    } catch (error) {
      console.error('处理订单失败:', error);
      
      const shouldRetry = await this.showRetryConfirm(
        '获取订单失败', 
        `${error.message},是否重试?`
      );
      
      if (shouldRetry && this.data.retryCount < this.data.maxRetryCount) {
        this.setData({ retryCount: this.data.retryCount + 1 });
        this.fetchOrderAndPay();
      } else {
        this.setData({ loading: false });
        this.showErrorAlert('操作失败', '请重新扫描二维码或联系客服');
      }
    }
  },

  async createPayment() {
    try {
      // 只传递orderId,不传递金额信息
      const payResponse = await this.requestWithRetry({
        url: 'https://您的后端域名.com/api/createPayment',
        method: 'POST',
        data: { orderId: this.data.orderId }
      });

      if (!payResponse.data.success) {
        const errorMsg = payResponse.data.message;
        const errorCode = payResponse.data.code;
        
        // 根据错误码进行不同的处理
        switch (errorCode) {
          case 'ORDER_ALREADY_PAID':
            // 订单已支付,跳转到成功页面
            this.handleOrderAlreadyPaid();
            return;
            
          case 'ORDER_CLOSED':
            throw new Error('订单已关闭或过期,请重新下单');
            
          case 'MAX_RETRY_EXCEEDED':
            throw new Error('支付尝试次数过多,请重新下单');
            
          case 'ALIPAY_TRADE_SUCCESS':
            // 支付宝侧显示已支付,但本地状态不同步,需要特殊处理
            this.handleAlipayTradeSuccess();
            return;
            
          case 'SYSTEM_BUSY':
            throw new Error('支付系统繁忙,请稍后重试');
            
          default:
            if (errorMsg.includes('余额不足')) {
              throw new Error('账户余额不足,请充值或更换支付方式');
            } else if (errorMsg.includes('风险')) {
              throw new Error('交易存在风险,暂无法完成支付');
            } else if (errorMsg.includes('频率')) {
              throw new Error('操作过于频繁,请稍后再试');
            } else {
              throw new Error(errorMsg);
            }
        }
      }

      // 如果是重复使用的支付订单,给用户提示
      if (payResponse.data.reused) {
        my.showToast({
          content: '检测到未完成支付,继续支付流程...',
          type: 'none',
          duration: 2000
        });
      }

      // 使用支付宝返回的tradeNO发起支付
      const result = await my.tradePay({
        tradeNO: payResponse.data.tradeNO
      });

      this.handlePaymentResult(result);

    } catch (error) {
      console.error('创建支付订单异常:', error);
      
      const shouldRetry = await this.showRetryConfirm(
        '创建支付失败',
        `${error.message},是否重试?`
      );
      
      if (shouldRetry) {
        this.createPayment();
      } else {
        this.setData({ loading: false });
      }
    }
  },
    
  // 处理订单已支付的情况
  handleOrderAlreadyPaid() {
    this.clearPolling();
    this.showErrorAlert('支付成功', '该订单已完成支付,请勿重复支付', () => {
      this.notifyPCSuccess();
      my.navigateBack();
    });
    this.setData({ loading: false });
  },

  handlePaymentResult(result) {
    console.log('支付同步返回结果:', result);

    // 开始轮询后端确认最终状态
    this.startPollingOrderStatus();

    // 同步结果用于用户界面快速响应和特殊错误处理
    switch(result.resultCode) {
      case '9000':
        my.showToast({ 
          content: '支付请求成功,确认中...',
          type: 'success',
          duration: 2000
        });
        break;
        
      case '6001':
        my.showToast({ 
          content: '支付已取消',
          type: 'none',
          duration: 2000
        });
        this.handleUserCancel();
        break;
        
      case '4000':
        this.showErrorAlert('支付失败', '系统异常,请稍后重试');
        this.setData({ loading: false });
        break;
        
      case '5000':
        this.showErrorAlert('支付失败', '重复请求,请勿重复支付');
        this.setData({ loading: false });
        break;
        
      case '6002':
        this.showErrorAlert('网络错误', '网络连接失败,请检查网络设置');
        this.setData({ loading: false });
        break;
        
      case '8000':
        my.showToast({ 
          content: '支付处理中,请确认支付结果',
          type: 'none',
          duration: 3000
        });
        break;
        
      case 'ACQ.SYSTEM_ERROR':
      case 'ACQ.INVALID_PARAMETER':
        this.showErrorAlert('系统错误', '支付系统繁忙,请稍后重试');
        this.setData({ loading: false });
        break;
        
      case 'ACQ.ACCESS_FORBIDDEN':
        this.showErrorAlert('支付限制', '暂无支付权限,请联系客服');
        this.setData({ loading: false });
        break;
        
      case 'ACQ.EXIST_FORBIDDEN_WORD':
        this.showErrorAlert('商品信息异常', '商品描述包含敏感词,请联系商家修改');
        this.setData({ loading: false });
        break;
        
      case 'ACQ.PAYMENT_AUTH_CODE_INVALID':
        this.showErrorAlert('支付码无效', '请刷新支付码后重试');
        this.setData({ loading: false });
        break;
        
      case 'ACQ.BUYER_BALANCE_NOT_ENOUGH':
        this.showErrorAlert('余额不足', '支付宝余额不足,请更换支付方式');
        this.setData({ loading: false });
        break;
        
      case 'ACQ.CONTEXT_INCONSISTENT':
        this.showErrorAlert('交易环境异常', '请返回后重新扫码支付');
        this.setData({ loading: false });
        break;
        
      case 'ACQ.TRADE_HAS_CLOSE':
        this.showErrorAlert('交易已关闭', '支付超时已关闭,请重新下单');
        this.setData({ loading: false });
        break;

      default:
        my.showToast({ 
          content: '支付处理中...',
          type: 'none',
          duration: 2000
        });
    }
  },

  // 处理用户取消支付的场景
  handleUserCancel() {
    this.clearPolling();
    this.setData({ loading: false });
    
    // 可以额外通知服务端用户取消了支付
    my.request({
      url: 'https://您的后端域名.com/api/cancelPayment',
      method: 'POST',
      data: { orderId: this.data.orderId },
      fail: (error) => {
        console.error('通知取消支付失败:', error);
      }
    });
  },
    
  // 处理支付宝侧显示已支付但本地状态异常的情况
  async handleAlipayTradeSuccess() {
    try {
      // 强制查询一次订单状态
      const statusResponse = await this.requestWithRetry({
        url: 'https://您的后端域名.com/api/checkOrderStatus',
        method: 'POST',
        data: { orderId: this.data.orderId }
      });

      if (statusResponse.data.success && statusResponse.data.data.status === 'PAID') {
        this.handlePaymentSuccess();
      } else {
        // 如果查询不到支付状态,需要人工干预
        this.showErrorAlert('状态异常', '支付状态异常,请联系客服处理', () => {
          this.setData({ loading: false });
        });
      }
    } catch (error) {
      this.showErrorAlert('状态查询失败', '支付状态确认失败,请联系客服', () => {
        this.setData({ loading: false });
      });
    }
  },

  // 修改轮询逻辑,增加支付订单状态检查
  startPollingOrderStatus() {
    this.clearPolling();
    this.setData({ pollCount: 0 });

    const poll = async () => {
      if (this.data.pollCount >= this.data.maxPollCount) {
        this.handlePollTimeout();
        return;
      }

      try {
        const statusResponse = await this.requestWithRetry({
          url: 'https://您的后端域名.com/api/checkOrderStatus',
          method: 'POST',
          data: { orderId: this.data.orderId }
        }, 2);

        if (statusResponse.data.success) {
          const orderData = statusResponse.data.data;
          const orderStatus = orderData.status;

          switch (orderStatus) {
            case 'PAID':
              this.handlePaymentSuccess();
              return;

            case 'CLOSED':
              this.handleOrderClosed();
              return;

            case 'FAILED':
              this.handlePaymentFailed();
              return;

            case 'EXPIRED':
              this.handleOrderExpired();
              return;

            case 'CREATED':
              // 检查支付订单是否已创建但未支付
              if (orderData.paymentCreated && 
                  this.data.pollCount > 10) { // 轮询10次后仍未支付
                this.showErrorAlert('支付超时', '支付订单已创建但未支付,请重新发起支付', () => {
                  this.setData({ loading: false });
                });
                return;
              }
              // 继续轮询
              break;

            default:
              break;
          }
        }

        // 继续轮询
        this.setData({ pollCount: this.data.pollCount + 1 });
        this.setData({ 
          pollTimer: setTimeout(poll, 2000)
        });

      } catch (error) {
        console.error('轮询查询失败:', error);
        this.setData({ pollCount: this.data.pollCount + 1 });
        this.setData({ 
          pollTimer: setTimeout(poll, 2000)
        });
      }
    };

    poll();
  },

  handlePaymentSuccess() {
    this.clearPolling();
    this.showErrorAlert('支付成功', `订单 ${this.data.orderId} 支付成功!`, () => {
      this.notifyPCSuccess();
      my.navigateBack();
    });
    this.setData({ loading: false });
  },

  handleOrderClosed() {
    this.clearPolling();
    this.showErrorAlert('订单已关闭', '订单已关闭,如需购买请重新下单');
    this.setData({ loading: false });
  },

  async handlePaymentFailed() {
    this.clearPolling();
    const shouldRetry = await this.showRetryConfirm(
      '支付失败',
      '支付失败,是否重新尝试支付?'
    );
    
    if (shouldRetry) {
      this.createPayment();
    } else {
      this.setData({ loading: false });
    }
  },

  handleOrderExpired() {
    this.clearPolling();
    this.showErrorAlert('订单已过期', '支付超时,订单已过期,请重新下单');
    this.setData({ loading: false });
  },

  handlePollTimeout() {
    this.clearPolling();
    this.showErrorAlert('查询超时', '支付结果确认超时,请稍后在订单中心查看状态', () => {
      this.setData({ loading: false });
      my.navigateBack();
    });
  },

  notifyPCSuccess() {
    my.request({
      url: 'https://您的后端域名.com/api/notifyPCSuccess',
      method: 'POST',
      data: { orderId: this.data.orderId },
      fail: (error) => {
        console.error('通知PC端失败:', error);
      }
    });
  }
});
代码解析
  • 方法执行流程解析

    在这里插入图片描述

    支付流程:
    扫码进入 → 订单验证 → 防重检查 → 创建支付 → 调起支付宝 → 轮询确认 → 状态同步 → 结果通知
    
  • 核心方法详解
    1. onLoad(options) - 页面入口

      onLoad(options) {
        if (options.orderId) {
          this.setData({ orderId: options.orderId });
          this.fetchOrderAndPay(); // 自动开始支付流程
        }
      }
      

      作用:页面加载时的生命周期函数,从二维码URL中解析订单参数并自动启动支付流程。

      参数来源

      alipays://platformapi/startapp?appId=xxx&page=pages/pay/pay&query=orderId=ORDER_123456
      

      这里的 orderId=ORDER_123456 会解析到 options.orderId 中。

    2. 网络请求优化

      async requestWithRetry(requestConfig, retryTimes = 3) {
        for (let i = 0; i < retryTimes; i++) {
          try {
            const response = await my.request({
              timeout: 10000, // 10秒超时
              ...requestConfig
            });
            return response;
          } catch (error) {
            // 指数退避重试
            await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
          }
        }
      }
      

      优势

      • 自动重试机制
      • 超时控制
      • 指数退避策略
    3. 订单状态检查与防重复支付

      // 检查订单状态,避免重复支付
      if (orderData.status === 'PAID') {
        this.showErrorAlert('订单已支付', '该订单已完成支付,请勿重复操作', () => {
          this.notifyPCSuccess();
          my.navigateBack();
        });
        return;
      }
      

      关键安全特性:有效防止重复支付,提升用户体验。

    4. createPayment() - 支付核心

      async createPayment() {
        // 请求后端创建支付订单
        // 只传递orderId,不传递金额信息
        const payResponse = await this.requestWithRetry({
          url: 'https://您的后端域名.com/api/createPayment',
          method: 'POST',
          data: { orderId: this.data.orderId }
        });
        
        // 调起支付宝支付
        const result = await my.tradePay({
          tradeNO: payResponse.data.tradeNO
        });
      }
      

      关键点

      • tradeNO:由支付宝生成,是调起支付的唯一凭证
      • my.tradePay():支付宝小程序的支付API
    5. handlePaymentResult() - 结果处理

      // 同步结果用于用户界面快速响应和特殊错误处理
      switch(result.resultCode) {
        case '9000':
          my.showToast({ 
            content: '支付请求成功,确认中...',
            type: 'success',
            duration: 2000
          });
          break;
          
        case '6001':
          my.showToast({ 
            content: '支付已取消',
            type: 'none',
            duration: 2000
          });
          this.handleUserCancel();
          break;
          
        case '4000':
          this.showErrorAlert('支付失败', '系统异常,请稍后重试');
          this.setData({ loading: false });
          break;
          
        case '5000':
          this.showErrorAlert('支付失败', '重复请求,请勿重复支付');
          this.setData({ loading: false });
          break;
          
        case '6002':
          this.showErrorAlert('网络错误', '网络连接失败,请检查网络设置');
          this.setData({ loading: false });
          break;
          
        case '8000':
          my.showToast({ 
            content: '支付处理中,请确认支付结果',
            type: 'none',
            duration: 3000
          });
          break;
          
        case 'ACQ.SYSTEM_ERROR':
        case 'ACQ.INVALID_PARAMETER':
          this.showErrorAlert('系统错误', '支付系统繁忙,请稍后重试');
          this.setData({ loading: false });
          break;
          
        case 'ACQ.ACCESS_FORBIDDEN':
          this.showErrorAlert('支付限制', '暂无支付权限,请联系客服');
          this.setData({ loading: false });
          break;
          
        case 'ACQ.EXIST_FORBIDDEN_WORD':
          this.showErrorAlert('商品信息异常', '商品描述包含敏感词,请联系商家修改');
          this.setData({ loading: false });
          break;
          
        case 'ACQ.PAYMENT_AUTH_CODE_INVALID':
          this.showErrorAlert('支付码无效', '请刷新支付码后重试');
          this.setData({ loading: false });
          break;
          
        case 'ACQ.BUYER_BALANCE_NOT_ENOUGH':
          this.showErrorAlert('余额不足', '支付宝余额不足,请更换支付方式');
          this.setData({ loading: false });
          break;
          
        case 'ACQ.CONTEXT_INCONSISTENT':
          this.showErrorAlert('交易环境异常', '请返回后重新扫码支付');
          this.setData({ loading: false });
          break;
          
        case 'ACQ.TRADE_HAS_CLOSE':
          this.showErrorAlert('交易已关闭', '支付超时已关闭,请重新下单');
          this.setData({ loading: false });
          bre
        default:
          my.showToast({ 
            content: '支付处理中...',
            type: 'none',
            duration: 2000
          });
      }
      

      支付状态码说明

      • 9000:订单支付成功
      • 6001:用户中途取消支付
      • 6002:网络连接出错
      • 4000:订单支付失败(系统异常)
      • 5000:重复请求
      • 6002:网络错误
      • 8000:支付处理中
    6. 轮询状态确认机制

      startPollingOrderStatus() {
        const poll = async () => {
          // 轮询检查订单状态
          const orderStatus = orderData.status;
          
          switch (orderStatus) {
            case 'PAID': this.handlePaymentSuccess(); return;
            case 'CLOSED': this.handleOrderClosed(); return;
            case 'FAILED': this.handlePaymentFailed(); return;
            case 'EXPIRED': this.handleOrderExpired(); return;
          }
        };
      }
      

      优势:以后端异步通知为最终依据,确保支付状态准确性。

    后端接口要求
    1. 获取订单详情接口
      POST https://您的后端域名.com/api/getOrderDetail
      请求参数:{ orderId: "ORDER_123456" }
      响应格式:{ 
        success: true, 
        data: { 
          productName: "商品名称", 
          amount: "0.01" 
        } 
      }
      
    2. 创建支付订单接口(幂等性实现)

      // 后端:/api/createPayment - 幂等性版本
      app.post('/api/createPayment', async (req, res) => {
        const { orderId } = req.body;
        
        // 参数验证
        if (!orderId) {
          return res.json({ success: false, message: '订单号不能为空' });
        }
      
        const db = await getDatabaseConnection();
        const transaction = await db.startTransaction();
        
        try {
          // 1. 使用 SELECT ... FOR UPDATE 锁定订单记录
          const order = await transaction.query(
            `SELECT * FROM orders WHERE order_id = ? FOR UPDATE`,
            [orderId]
          );
      
          if (!order || order.length === 0) {
            await transaction.rollback();
            return res.json({ success: false, message: '订单不存在' });
          }
      
          const orderData = order[0];
      
          // 2. 检查订单状态 - 幂等性关键判断
          if (orderData.status === 'PAID') {
            await transaction.rollback();
            return res.json({ 
              success: false, 
              message: '订单已支付',
              code: 'ORDER_ALREADY_PAID'
            });
          }
      
          if (orderData.status === 'CLOSED' || orderData.status === 'EXPIRED') {
            await transaction.rollback();
            return res.json({ 
              success: false, 
              message: '订单已关闭或过期',
              code: 'ORDER_CLOSED'
            });
          }
      
          // 3. 检查是否已经创建过支付订单(防重复创建)
          if (orderData.payment_created && orderData.alipay_trade_no) {
            // 如果支付订单已创建且在15分钟内,返回相同的tradeNO
            const createTime = new Date(orderData.payment_create_time);
            const now = new Date();
            const diffMinutes = (now - createTime) / (1000 * 60);
            
            if (diffMinutes < 15) { // 15分钟内返回相同的支付订单
              await transaction.rollback();
              return res.json({
                success: true,
                tradeNO: orderData.alipay_trade_no,
                reused: true // 标记是重复使用的支付订单
              });
            }
          }
      
          // 4. 检查重试次数
          if (orderData.retry_count >= 5) {
            await transaction.rollback();
            return res.json({ 
              success: false, 
              message: '支付尝试次数过多,请重新下单',
              code: 'MAX_RETRY_EXCEEDED'
            });
          }
      
          // 5. 调用支付宝创建交易
          const alipayResponse = await alipaySdk.exec('alipay.trade.create', {
            notify_url: 'https://您的域名.com/api/alipay/notify',
            bizContent: {
              out_trade_no: orderId, // 使用商户订单号,确保唯一性
              total_amount: orderData.amount,
              subject: orderData.product_name,
              time_expire: '15m', // 15分钟过期
              // 其他参数...
            }
          });
      
          if (!alipayResponse.trade_no) {
            throw new Error('支付宝创建交易失败');
          }
      
          // 6. 更新订单状态(标记支付订单已创建)
          await transaction.query(
            `UPDATE orders 
             SET payment_created = TRUE, 
                 alipay_trade_no = ?,
                 payment_create_time = NOW(),
                 retry_count = retry_count + 1,
                 update_time = NOW()
             WHERE order_id = ?`,
            [alipayResponse.trade_no, orderId]
          );
      
          await transaction.commit();
      
          res.json({
            success: true,
            tradeNO: alipayResponse.trade_no,
            reused: false
          });
      
        } catch (error) {
          await transaction.rollback();
          console.error('创建支付订单失败:', error);
          
          // 根据错误类型返回不同的提示
          if (error.message.includes('ACQ.TRADE_HAS_SUCCESS')) {
            res.json({ 
              success: false, 
              message: '该订单已支付成功,请勿重复支付',
              code: 'ALIPAY_TRADE_SUCCESS'
            });
          } else if (error.message.includes('ACQ.SYSTEM_ERROR')) {
            res.json({ 
              success: false, 
              message: '支付系统繁忙,请稍后重试',
              code: 'SYSTEM_BUSY'
            });
          } else {
            res.json({ 
              success: false, 
              message: '创建支付订单失败,请重试',
              code: 'CREATE_PAYMENT_FAILED'
            });
          }
        }
      });
      
    3. 异步通知处理接口 (/api/alipay/notify)

      // 后端:处理支付宝异步通知
      app.post('/api/alipay/notify', async (req, res) => {
        try {
          // 1. 验证签名(必须做!)
          const signVerified = verifyAlipaySignature(req.body);
          if (!signVerified) {
            return res.send('fail');
          }
      
          // 2. 获取通知参数
          const { out_trade_no, total_amount, trade_status, trade_no } = req.body;
          
          // 3. 【安全增强】验证金额一致性
          const order = await getOrderById(out_trade_no);
          if (!order) {
            return res.send('fail');
          }
      
          // 验证支付宝通知的金额与数据库订单金额是否一致
          if (parseFloat(total_amount) !== parseFloat(order.amount)) {
            console.error(`金额不一致: 订单${order.amount}, 支付宝${total_amount}`);
            return res.send('fail');
          }
      
          // 4. 处理支付成功
          if (trade_status === 'TRADE_SUCCESS') {
            await updateOrderStatus(out_trade_no, 'PAID', {
              alipayTradeNo: trade_no,
              paidAt: new Date(),
              actualAmount: total_amount // 记录实际支付金额
            });
            
            // 通知PC端
            notifyPCOrderSuccess(out_trade_no);
          }
      
          res.send('success');
        } catch (error) {
          console.error('处理支付宝通知失败:', error);
          res.send('fail');
        }
      });
      
    4. 订单状态查询接口 (/api/checkOrderStatus)
      // 查询订单状态
      app.post('/api/checkOrderStatus', async (req, res) => {
        const { orderId } = req.body;
        
        const order = await getOrderById(orderId);
        if (!order) {
          return res.json({ success: false, message: '订单不存在' });
        }
      
        res.json({
          success: true,
          data: {
            status: order.status, // CREATED, PAID, CLOSED, FAILED
            orderId: order.id,
            amount: order.amount
          }
        });
      });
      
支付宝开放平台技术支持咨询方式(有问题可以咨询)
  1. 进入支付宝开放平台(open.alipay.com)
  2. 点击页面右侧的蚂蚁头的图标,进入经营助手进行提问
  3. 可通过机器人客服进行咨询,若机器人未能解答您的问题,点击解答内容右下方的向下的大拇指图标
  4. 点【转人工客服】咨询支付宝技术支持(支付宝技术支持客服的人工服务时间为工作日9:00-22:00,非工作日9:00-18:00)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值