Payment Request API

深入理解 Payment Request API:基本流程、PaymentRequest 构造参数、canMakePayment 和 show 的调用流程、Payment Handler API,以及浏览器支持现状。

Payment Request API

一、为什么需要 Payment Request API

1.1 传统支付流程的问题

传统 Web 支付流程需要用户填写大量信息:卡号、有效期、CVV、账单地址等。每次购物都要重复填写,体验差,且表单容易出错。

1.2 Payment Request API 的解决方案

Payment Request API 允许浏览器存储用户的支付信息,用户只需几次点击即可完成支付:

  • 浏览器存储支付凭证
  • 用户选择支付方式
  • 商家只获取必要的支付信息
  • 标准化、安全的支付流程

二、基本流程

2.1 完整支付流程

// 1. 检查浏览器支持
if (!window.PaymentRequest) {
  console.log('浏览器不支持 Payment Request API');
  return;
}

// 2. 创建 PaymentRequest
const supportedMethods = [{
  supportedMethods: 'basic-card',
  data: {
    supportedNetworks: ['visa', 'mastercard', 'amex'],
    supportedTypes: ['credit', 'debit']
  }
}];

const paymentDetails = {
  total: {
    label: '总金额',
    amount: { currency: 'CNY', value: '299.00' }
  },
  displayItems: [
    {
      label: '商品 A',
      amount: { currency: 'CNY', value: '199.00' }
    },
    {
      label: '运费',
      amount: { currency: 'CNY', value: '100.00' }
    }
  ]
};

const request = new PaymentRequest(supportedMethods, paymentDetails);

// 3. 检查是否可以支付
async function checkCanMake() {
  const canMake = await request.canMakePayment();
  if (canMake) {
    console.log('可以发起支付');
  } else {
    console.log('无法支付');
  }
}

// 4. 显示支付界面
async function showPayment() {
  try {
    const response = await request.show();
    console.log('支付响应:', response);

    // 处理支付结果
    await response.complete('success');

    // 发送 paymentMethodData 到支付网关
    await sendToPaymentGateway(response.details);

  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('用户取消支付');
    } else {
      console.error('支付错误:', err);
    }
  }
}

三、PaymentRequest 构造参数

3.1 PaymentMethodData

const supportedMethods = [
  // 基础卡片支持
  {
    supportedMethods: 'basic-card',
    data: {
      supportedNetworks: ['visa', 'mastercard', 'unionpay'],
      supportedTypes: ['credit', 'debit', 'prepaid']
    }
  },

  // Apple Pay(iOS Safari)
  {
    supportedMethods: 'https://apple.com/apple-pay',
    data: {
      version: 3,
      merchantIdentifier: 'merchant.com.example',
      merchantCapabilities: ['supports3DS', 'supportsCredit', 'supportsDebit'],
      supportedNetworks: ['amex', 'discover', 'masterCard', 'visa'],
      countryCode: 'CN'
    }
  },

  // Google Pay
  {
    supportedMethods: 'https://google.com/pay',
    data: {
      environment: 'TEST',
      apiVersion: 2,
      merchantInfo: {
        merchantName: '示例商家'
      },
      allowedPaymentMethods: [{
        type: 'CARD',
        parameters: {
          allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
          allowedCardNetworks: ['VISA', 'MASTERCARD']
        }
      }]
    }
  }
];

3.2 PaymentDetails

const paymentDetails = {
  // 金额(必需)
  total: {
    label: '总金额', // 人类可读标签
    amount: {
      currency: 'CNY',
      value: '299.00' // 必须是字符串
    }
  },

  // 显示项目(可选)
  displayItems: [
    {
      label: '商品:无线蓝牙耳机',
      amount: { currency: 'CNY', value: '299.00' }
    },
    {
      label: '运费',
      amount: { currency: 'CNY', value: '0.00' }
    }
  ],

  //  shipping 选项(可选)
  shippingOptions: [
    {
      id: 'standard',
      label: '标准配送',
      amount: { currency: 'CNY', value: '0.00' },
      selected: true
    },
    {
      id: 'express',
      label: '快速配送',
      amount: { currency: 'CNY', value: '20.00' }
    }
  ],

  //  modifiers(可选)- 针对特定支付方式的调整
  modifiers: [
    {
      supportedMethods: 'basic-card',
      total: {
        label: '总金额(含折扣)',
        amount: { currency: 'CNY', value: '279.00' }
      },
      additionalDisplayItems: [
        {
          label: '会员折扣',
          amount: { currency: 'CNY', value: '-20.00' }
        }
      ]
    }
  ]
};

3.3 PaymentOptions

const paymentOptions = {
  requestPayerName: true,         // 请求付款人姓名
  requestPayerEmail: true,        // 请求付款人邮箱
  requestPayerPhone: true,        // 请求付款人电话
  requestShipping: true,          // 请求收货地址
  shippingType: 'delivery'         // 配送类型:'shipping' | 'delivery' | 'pickup'
};

四、canMakePayment 和 show

4.1 canMakePayment

检查用户是否有可用的支付方式:

const request = new PaymentRequest(supportedMethods, paymentDetails);

// 方法1:直接调用
async function checkPayment() {
  const canMake = await request.canMakePayment();
  if (!canMake) {
    // 回退到传统表单支付
    showTraditionalPaymentForm();
    return;
  }
  showPaymentButton();
}

// 方法2:使用 PaymentRequest.canMakePayment 静态方法
async function checkSupport() {
  const result = await PaymentRequest.canMakePayment({
    supportedMethods: 'basic-card'
  });
  console.log('支持 basic-card:', result);
}

4.2 show

显示支付界面:

async function processPayment() {
  const request = new PaymentRequest(supportedMethods, paymentDetails, paymentOptions);

  try {
    // 显示支付 UI
    const paymentResponse = await request.show();

    // 获取支付数据
    const { methodName, details } = paymentResponse;
    console.log('支付方式:', methodName);
    console.log('支付详情:', details);

    // 验证支付(发送到服务器)
    const result = await verifyPayment(details);

    if (result.success) {
      // 完成支付
      await paymentResponse.complete('success');
      showSuccessMessage();
    } else {
      // 支付失败
      await paymentResponse.complete('fail');
      showErrorMessage(result.error);
    }

  } catch (err) {
    if (err.name === 'AbortError') {
      // 用户取消
      console.log('用户取消支付');
    } else {
      console.error('支付错误:', err);
    }
  }
}

4.3 abort

取消支付请求:

async function userCancelPayment() {
  const request = new PaymentRequest(supportedMethods, paymentDetails);

  // 显示支付 UI
  const paymentResponse = await request.show();

  // 用户点击取消按钮
  await paymentResponse.abort();
  console.log('支付已取消');
}

五、Payment Handler API

5.1 什么是 Payment Handler

Payment Handler 允许第三方支付应用(如各银行的支付插件)处理支付:

// 注册 Payment Handler
async function registerPaymentHandler() {
  if (!('PaymentHandler' in window)) {
    return;
  }

  await window.PaymentHandler.registerPaymentHandler({
    supportedMethods: ['https://example.com/pay']
  });
}

// 处理支付请求
self.addEventListener('paymentrequest', event => {
  console.log('收到支付请求:', event.paymentRequest);

  // 显示支付应用 UI
  showPaymentAppUI(event.paymentRequest);
});

六、实际应用示例

6.1 完整的购物结账流程

class PaymentProcessor {
  constructor() {
    this.supportedMethods = this.getSupportedMethods();
  }

  getSupportedMethods() {
    return [
      {
        supportedMethods: 'basic-card',
        data: {
          supportedNetworks: ['visa', 'mastercard', 'unionpay'],
          supportedTypes: ['credit', 'debit']
        }
      }
    ];
  }

  async canMakePayment() {
    if (!window.PaymentRequest) {
      return false;
    }

    const request = new PaymentRequest(
      this.supportedMethods,
      this.getPaymentDetails(),
      { requestShipping: true }
    );

    return await request.canMakePayment();
  }

  getPaymentDetails() {
    return {
      total: {
        label: '订单总金额',
        amount: { currency: 'CNY', value: this.getCartTotal() }
      },
      displayItems: this.getCartItems()
    };
  }

  getCartTotal() {
    // 计算购物车总金额
    return '299.00';
  }

  getCartItems() {
    // 返回购物车商品列表
    return [
      { label: '商品 A', amount: { currency: 'CNY', value: '199.00' } },
      { label: '运费', amount: { currency: 'CNY', value: '100.00' } }
    ];
  }

  async requestPayment() {
    const request = new PaymentRequest(
      this.supportedMethods,
      this.getPaymentDetails(),
      {
        requestPayerName: true,
        requestPayerEmail: true,
        requestPayerPhone: true,
        requestShipping: true,
        shippingType: 'delivery'
      }
    );

    request.addEventListener('shippingoptionchange', event => {
      // 运费选项变化
      const selectedOption = request.shippingOption;
      this.updateShipping(selectedOption);
      event.updateWith(this.getPaymentDetails());
    });

    request.addEventListener('shippingaddresschange', event => {
      // 收货地址变化
      const address = request.shippingAddress;
      this.validateAddress(address).then(isValid => {
        if (!isValid) {
          event.updateWith({
            total: this.getPaymentDetails().total,
            error: '不支持的配送地址'
          });
        } else {
          event.updateWith(this.getPaymentDetails());
        }
      });
    });

    try {
      const response = await request.show();
      await this.processPayment(response);
      await response.complete('success');
      return { success: true };
    } catch (err) {
      if (err.name === 'AbortError') {
        return { success: false, error: 'cancelled' };
      }
      return { success: false, error: err.message };
    }
  }

  async processPayment(response) {
    // 发送支付数据到服务器
    const result = await fetch('/api/pay', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(response.details)
    });
    return result.json();
  }
}

七、浏览器支持

7.1 支持情况

Payment Request API 的支持情况:

  • Chrome:完全支持
  • Safari:完全支持
  • Firefox:部分支持
  • Edge:完全支持

7.2 特性检测

function isPaymentRequestSupported() {
  return 'PaymentRequest' in window;
}

function isBasicCardSupported() {
  return PaymentRequest.isTypeSupported('basic-card');
}

延展阅读