你可能还不知道的 Web 支付流程标准化

时间:2022-06-19
本文章向大家介绍你可能还不知道的 Web 支付流程标准化,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

PaymentRequest API 是一种跨浏览器的标准 API,主要的目的是以浏览器充当中介,尽可能标准化支付通信的流程。 ?

整个流程主要是创建 PaymentRequest,将购买货物的详细信息传递给浏览器,在 UI 层面显示支付的 UI,用户填入支付信息或从缓存中一键填充并确认支付。

他的最大的优势是信用卡、收货地址等支付信息都统一存储在浏览器,如果网站都能够使用该 API,那么就不再需要重复填写支付信息。

⚠️ 使用 PaymentRequest 非常简单,但由于兼容性问题,目前不要在生产环境中使用,API 层面亦会有可能改动。

创建 PaymentRequest 实例

第一步是通过调用 PaymentRequest 构造函数创建一个 PaymentRequest 对象

构造函数需要三个参数分别是 methodData、details 和可选的 options

methodData 支付方式

首先需要设定支付方式,传入卖家支持的支付手段,如 visa 或 mastercard 等信用卡或其他方式:

methodData 是一个数组,数组中每一项应为一个对象,对象内包含两个属性:

  • supportedMethods 付款方式识别符
  • data 额外信息
  • supportedNetworks 支付网络

supportedMethods 需要填写 付款方式识别符,一般填写 basic-card,也可填写 url 的识别符如:

如果是 google pay url 付款识别符,那么调用的时候长这样:

这里以 basic-card 为例,那么 data 属性则需要填写一些额外的信息,如果是 basic-card 方式,那么还可以选择配置 supportedNetworks,这个选项指定了 card networks

card networks 是一个数组,如支持 visa、mastercard 等

['visa', 'mastercard', 'amex', 'jcb', 'diners', 'discover', 'mir', 'unionpay']

什么是 card networks 见这篇文章:https://www.cardinalcommerce.com/startups/online-payments/what-is-a-card-network

根据规范,我们编写以下代码 ?

const methodData = [{
    supportedMethods: 'basic-card',
    data: {
        supportedNetworks: ['visa', 'mastercard', 'amex', 'jcb', 'diners', 'discover', 'mir', 'unionpay']
    }
}]

在调用的时候长这样:

另外,Apple Pay 也支持该特性,详细见文档:https://webkit.org/blog/8182/introducing-the-payment-request-api-for-apple-pay/

details 交易详情

details 保存的是交易详情,主要有以下字段:

  • total 需要支付的总额
  • id 交易 ID,如果不填写浏览器自动生成
  • displayItems 主要是一些货品信息、税费、运费等详细清单
  • shippingOptions 则是运输相关的选项,有事件监听如果不能送达,则应在 UI 层面给用户提示
  • modifier 主要是针对用户付款方式,修改交易详情,比方说针对某种支付手段给予优惠,展示不同金额等
  • additionalDisplayItems 额外需要展示的新增订单项目
  • data 额外信息
  • total 修改后的总价

total

这一字段需要填写支付总额,API 不会自动计算,需要计算后填入 total 字段需要满足 PaymentItem 规范

也就是说至少需要一个 label 字符串和一个 PaymentCurrencyAmount 金额,另外还有一个可选项 pending 用来表示是否为最终金额,默认为 false:

PaymentCurrencyAmount 则需要两个属性,一个是 currency 一个是 value,两个都是字符串:

const details = {
    total: {
        label: '合计总金额 ? ',
        amount: { currency: 'CNY', value: '100.00' }
    }
}

增加 details 对象,编写代码如上所示

效果如下:

id

id 则代表了交易 ID,可以自定义,字符串格式:

const details = {
    id: 'Order-Funny-ID-000-001',
    total: {

displayItems

订单详情,每个单个条目最终是否展示取决于浏览器。这是一个数组,数组内每一个对象都是一个 PaymentItem,因此数组内每一项的规范参照 total:

const details = {
    id: 'Order-Funny-ID-000-001',
    displayItems: [{
        label: '西瓜 ?',
        amount: { currency: 'CNY', value: '20.00' }
    }, {
        label: '草莓 ?',
        amount: { currency: 'CNY', value: '90.00' }
    }, {
        label: 'VIP 会员 ?',
        amount: { currency: 'CNY', value: '-10.00' },
        pending: true
    }],
    total: {
        label: '合计总金额 ? ',
        amount: { currency: 'CNY', value: '100.00' }
    }
}

上面代码执行效果长这样:

shippingOptions

根据规范 shippingOptions 应该有三个必选参数,id、label、amount(同样,amount 必须符合 PaymentCurrencyAmount),selected 默认为 false,是可选参数:

const details = {
    id: 'Order-Funny-ID-000-001',
    displayItems: [{
    }],
    total: {
    },
    shippingOptions: [{
        id: '标准快递',
        label: '? 免费普通快递 1 天全国范围内',
        amount: {
            currency: 'CNY',
            value: '0.00'
        },
        selected: true
    }, {
        id: '东风快递',
        label: '? 超快速递 3 小时全球范围内',
        amount: {
            currency: 'CNY',
            value: '100.00'
        }
    }]
}

const options = {
    requestShipping: true // 别忘记这里需要设置为 true
}

let request = new PaymentRequest(
    methodData, // 支付方式
    details, // 交易信息
    options // 其他额外信息
)

按上述规范配置 shippingOptions,并监听配送选项改变或地址改变动态调整收费价格标准

// 监听配送选项改变,动态修改收费标准
request.onshippingoptionchange = function (e) {
    console.log('快递选项改变,重新计算价格')

    e.updateWith(Promise.resolve(updateDetail(details, request.shippingOption))) // 接收 promise
}
// 监听地址选项改变,动态修改收费标准
request.onshippingaddresschange = function (e) {
    console.log('地址选项改变,重新计算价格')

    e.updateWith(Promise.resolve(updateDetail(details, request.shippingOption))) // 接收 promise
}

function updateDetail(details, shippingOpts) {
    console.log({ details, shippingOpts }) // shippingOpts 代表选择的快递 id
    // fetch 后台数据
    // 各种判断
    // 修改 details 最后 return 出去
    // if (shippingOpts) {} else {}
    // 这里仅作演示没修改数据
    return details
}

modifier

用于修改账单,这里以 visa 卡为例,使用此类型信用卡会在账单中增加一条 additionalDisplayItems,并通过 total 修改账单总额

modifier 需要一个 supportedMethods 为必选参数:

modifiers: [
    {
        additionalDisplayItems: [{
            label: 'visa 手续费',
            amount: { currency: 'CNY', value: '10.00' }
        }],
        supportedMethods: "basic-card",
        total: {
            label: "Total due",
            amount: { currency: "USD", value: "110.00" },
        },
        data: {
            supportedNetworks: ['visa'],
        },
    },
]

效果如下:

options

主要有六个参数可供配置,分别是 requestPayerNamerequestPayerEmailrequestPayerPhonerequestShippingrequestBillingAddressshippingType,前五项默认为 false,最后一项可设置为 shippingdeliverypickup,三者根据语境选择合适的 UI 层面对“快递”的描述,这三个单词在中国大陆分别代表送货递送取货

request 实例的属性和方法

上文提到的 shippingaddresschangeshippingoptionchange 就是该实例的两个事件,除此之外还有 paymentmethodchangemerchantvalidation。除事件之外,实例的四个属性分别是 idshippingAddressshippingOptionshippingType 可以访问到这个支付请求的相关配置。该实例的三个方法比较重要,用来鉴定是否能够发起支付的 canMakePayment、调起 UI 支付界面的 show 以及放弃支付的 abort

canMakePayment

检测是否支持此种支付方式,在使用之前必须先要判断浏览器是否支持 canMakePayment 本身:

if (request.canMakePayment) {

} else {
+   ;(() => { console.log('浏览器不支持 canMakePayment 特性') })()
}

然后调用方法检测支付:

if (request.canMakePayment) {
+   request.canMakePayment().then(res => {

+   }).catch(console.error)
} else {
    ; (() => { console.log('浏览器不支持 canMakePayment 特性') })()
}

根据检测结果判断是否进一步调用 show,发起支付:

if (request.canMakePayment) {
    request.canMakePayment().then(res => {
+       if (res) {
+           request.show() // true 如果支持
+       } else {
+           console.log('未能发起支付')
+       }
    }).catch(console.error)
} else {
    ; (() => { console.log('浏览器不支持 canMakePayment 特性') })()
}

show

最终获取支付成功的 response 是通过 show 方法返回的,最后确认无误后,调用 response 的 complete 方法,并传入 successfailunknow 字段来结束支付:

if (request.canMakePayment) {
    request.canMakePayment().then(res => {
        if (res) {
+           request.show().then(response => {
+               console.log(response)
+               setTimeout(() => { // 模拟发送支付信息到服务端,并调用 response 的 complete 方法完成支付
+                   response.complete()
+               }, 2000)
            })
        } else {
            console.log('未能发起支付')
        }
    }).catch(console.error)
} else {
    ; (() => { console.log('浏览器不支持 canMakePayment 特性') })()
}

此外 response 还有 retry 方法,可以在遇到支付 response 出现错误的时候重新发起支付

关于 response 的属性和方法见如下截图:

附上用于测试的信用卡卡号

最后附上用于测试的信用卡卡号,日期随便填,CVC 随便填

Test Credit Card Account Numbers http://www.blogjava.net/sealyu/archive/2008/04/16/193473.html

最后代码在此:https://jsfiddle.net/aL5gczm3/