你可能还不知道的 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
主要有六个参数可供配置,分别是 requestPayerName
、requestPayerEmail
、requestPayerPhone
、requestShipping
、requestBillingAddress
、shippingType
,前五项默认为 false,最后一项可设置为 shipping
、delivery
、pickup
,三者根据语境选择合适的 UI 层面对“快递”的描述,这三个单词在中国大陆分别代表送货
、递送
和取货
request 实例的属性和方法
上文提到的 shippingaddresschange
和 shippingoptionchange
就是该实例的两个事件,除此之外还有 paymentmethodchange
和 merchantvalidation
。除事件之外,实例的四个属性分别是 id
、shippingAddress
、shippingOption
、shippingType
可以访问到这个支付请求的相关配置。该实例的三个方法比较重要,用来鉴定是否能够发起支付的 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 方法,并传入 success
、fail
或 unknow
字段来结束支付:
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/
- Steve Boswell:智能口罩让PM2.5滚蛋
- kvm虚拟化管理平台WebVirtMgr部署-完整记录(2)
- objective-C中的扩展方法与partial class
- 仿优酷Android客户端图片左右滑动(自动滑动)
- objective-C: NSString应该用initWithFormat? 还是 stringWithFormat?
- objective-C 的内存管理之-实例分析
- Tim Berners-Lee:网络的自由和开放
- android防止内存溢出浅析
- objective-C 的内存管理之-自动释放池(autorelease pool)
- objective-C 的内存管理之-引用计数
- CompoundButton.OnCheckedChangeListener与RadioGroup.OnCheckedChangeListener冲突
- Liora Rosin & Golan Levi:在北京驾车看洛杉矶的落日
- iphone/ipad/itouch进入DFU模式最简单的操作办法
- 微信小程序for循环里条件判断
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 新手小白的福利,零基础也能上手的项目——学生信息管理系统
- Python制作小脚本,一键可以让你同事的电脑在你指定时间关机
- Python小白爬虫入门的第一个案例:爬取全站小说
- Python基础入门知识点——字符串的介绍
- 文章要保存为TXT文件,其中的图片要怎么办?Python帮你解决
- 分析B站弹幕,川普同志暴露的那一天,没有一个鬼畜up是无辜的
- python-爬取地理坐标
- Python基础第一个案例:猜数字游戏,这个都写不出,那就放弃吧
- 现在听歌要各大平台到处跑,嫌麻烦?制作个人专属的音乐下载器
- 爬取上市公司数据、分析数据,并用可视化现实全国各地区公司数量
- 今天刚上手爬虫,当然要从最简单的开始啦,验证一下所学的知识
- Python数据可视化入门:使用Matplotlib绘图
- 有了音乐下载器,怎么能没有音乐播放器呢,打造自己的音乐播放器
- 七夕节到了,单身狗程序员要对自己好点,用代码送自己点安慰
- 面向对象视角下的前端工程体系