将 Android Pay 集成到支付请求中
作者:Eiji Kitamura、Alex Sieke
Android Pay 支持简单而又安全的网上购物,让用户不必记忆和手动输入其支付信息。集成 Android Pay 可接触数以百万计的 Android 用户、推动转化率的提升以及为用户提供真正的一键式结账体验。
简单:接受 Android Pay 很简便,不需要对支付处理做任何更改。 领先的支付网关和处理平台同样在添加相关支持,为开发者实现 Android Pay 提供更大的便利。
安全:Android Pay 通过以安全方式存储对应用户支付账户的虚拟账号来完成支付。 这样一来,用户网上购物时就不必发送其实际的信用卡或借记卡号。 Android Pay 对每一笔支付交易加密,因此可确保用户数据的安全。
支持:越来越多的国家/地区以及大多数主要信用卡网络和银行都支持 Android Pay,并且所有 KitKat 及更高版本的 Android 手机均提供该功能。请参阅此帮助中心页面,查看有关该功能在不同国家/地区和银行卡类型上提供情况的完整文档。
工作方式
1.按“Checkout”。
2.弹出支付请求 UI。
3.选择支付方式等,然后按“Pay”。
4.弹出 Android Pay 应用时,点按以继续(系统可能提示用户解锁手机/使用指纹验证身份)
5.结账完毕。
做好准备
必备知识
- 由于 Chrome 中的 Android Pay 使用 PaymentRequest API,因此必须先熟悉集成指南,然后再继续下一步。
- 即使您并非 Android 开发者,熟悉Android Pay 应用内 API仍有帮助。因为 Android Pay 在 Android 和 Chrome 上返回的响应相同,所以有关响应处理的信息会有帮助。
- 查看 Android Pay 详细的内容政策,确保其支持您的特定商品或服务。
设置环境
- 确保设备上已安装 Android Pay 应用。您所在的国家/地区必须是其中一个受支持国家/地区,才能安装该应用。查看android.com/pay,确认支持您所在的国家/地区。
- 如需测试,您需要向设备上的 Android Pay添加一张信用卡。
- 注册 Android Pay
- 使用此表单添加您的公司、网站来源以及公司电子邮件,等等。
- 确保您的支付网关/处理机构支持 Android Pay 令牌。
- 如果使用的是网络令牌方法,获取用于对来自 Android Pay 的响应加密的密钥对。
- Google 建议与支付处理机构合作来获取一个公钥。这可以简化流程,因为处理机构将能处理 Android Pay 有效负荷的解密。详情请参阅支付处理机构文档。
- 如果想自行处理加密,请参阅支付令牌加密技术,了解如何生成 base64 编码椭圆曲线集成加密密钥。
将 Android Pay 集成到支付请求中
借助 Android Pay for Payment Request API,可以请求网关或网络这两种支付令牌类型中的其中一种类型。如果使用 Braintree、Stripe 或 Vantiv 作为支付网关,则可从 Android Pay 请求网关令牌。否则,可以请求加密网络令牌包。可以自行处理网络令牌,也可与处理机构合作,共同处理令牌包的解密。
网关令牌方法
Android Pay 并不处理支付。商家仍需调用网关 API,才能对从 Android Pay 返回的网关令牌进行收费/处理。
让 Android Pay API 返回网关令牌。以下是使用 Braintree、Stripe 或 Vantiv 时的建议流程。
网络令牌方法
让 Android Pay API 返回加密网络令牌包。然后您可以自行解密令牌,或利用处理机构 API 来处理解密和对令牌收费。
使用网关令牌集成
下例概述了如何直接从支付网关请求令牌。在本例中,我们概述如何请求 Stripe 令牌。如果使用的是 Braintree 或 Vantiv 等其他支付网关,请联系处理机构,了解不同支付网关的参数。
请求网关令牌时,Android Pay 会代表您调用处理机构,并返回可收费的网关令牌。
参数
var supportedInstruments = [
{
supportedMethods: ['amex', 'discover','mastercard','visa']
},
{
supportedMethods: ['https://android.com/pay'],
data: {
//merchant ID obtained from Google that maps to your origin
merchantId: '02510116604241796260',
environment: 'TEST',
// Credit Cards allowed via Android Pay
allowedCardNetworks: ['AMEX', 'MASTERCARD', 'VISA', 'DISCOVER'],
paymentMethodTokenizationParameters: {
tokenizationType: 'GATEWAY_TOKEN',
parameters: {
'gateway': 'stripe',
// Place your own Stripe publishable key here.
'stripe:publishableKey': 'pk_live_fD7ggZCtrB0vJNApRX5TyJ9T',
'stripe:version': '2016-07-06'
}
}
}
}
];
要想按照网关令牌方法使用 Android Pay,请按上例添加一个包含下列参数的 JSON 对象。
supportedMethods: [ 'https://android.com/pay' ]
:表示这是一种使用 Android Pay 的支付方式。data
:这些是尚未标准化的 Android Pay 专属值。merchantId
:通过注册 Android Pay获得的 Android Pay 商家 ID。environment:'TEST'
:使用 Android Pay 进行测试时添加此参数。生成的网关令牌将是无效令牌。allowedCardNetworks
:提供一个数组,其中包含的信用卡网络构成一个有效的 Android Pay 响应。它接受“AMEX”、“DISCOVER”、“MASTERCARD”和“VISA”。paymentMethodTokenizationParameters
:tokenizationType
:'GATEWAY_TOKEN':表示采用的是网关令牌方法。parameters
:支付网关专属参数。请参阅特定支付网关的文档。
处理 Android Pay 响应
添加 Android Pay 对象后,Chrome 可以请求可收费网关令牌。
var payment = new PaymentRequest(
supportedInstruments, // required payment method data
details, // required information about transaction
options // optional parameter for things like shipping, etc.
);
payment.show().then(function(response) {
// Process response
response.complete("success");
}).catch(function(err) {
console.error("Uh oh, something bad happened", err.message);
});
来自 PaymentRequest 的响应将包含PaymentRequest 集成指南中所述示例内的配送和联系信息,但现在还会包括来自 Android Pay 的响应,其中包含
- 收费地址信息
- 联系信息
- 有关支付工具的信息
- 有关支付令牌的详情
对提交的网关令牌的处理方式取决于支付网关。请参阅特定网关的文档,了解更多详情。
规则汇总
function onBuyClicked() {
const ANDROID_PAY = 'https://android.com/pay';
if (!window.PaymentRequest) {
// PaymentRequest API is not available. Forwarding to
// legacy form based experience.
location.href = '/checkout';
return;
}
var supportedInstruments = [
{
supportedMethods: [
'visa', 'mastercard', 'amex', 'discover', 'maestro',
'diners', 'jcb', 'unionpay', 'bitcoin'
]
},
{
supportedMethods: [ ANDROID_PAY ],
data: {
merchantId: '02510116604241796260',
environment: 'TEST',
allowedCardNetwork: [ 'AMEX', 'MASTERCARD', 'VISA', 'DISCOVER' ],
paymentMethodTokenizationParameters: {
tokenizationType: 'GATEWAY_TOKEN',
parameters: {
'gateway': 'stripe',
'stripe:publishableKey': 'pk_live_fD7ggZCtrB0vJNApRX5TyJ9T',
'stripe:version': '2016-07-06'
}
}
}
}
];
var details = {
displayItems: [{
label: 'Original donation amount',
amount: { currency: 'USD', value: '65.00' }
}, {
label: 'Friends and family discount',
amount: { currency: 'USD', value: '-10.00' }
}],
total: {
label: 'Total due',
amount: { currency: 'USD', value : '55.00' }
}
};
var options = {
requestShipping: true,
requestPayerEmail: true,
requestPayerPhone: true,
requestPayerName: true
};
// Initialization
var request = new PaymentRequest(supportedInstruments, details, options);
// When user selects a shipping address
request.addEventListener('shippingaddresschange', e => {
e.updateWith(((details, addr) => {
var shippingOption = {
id: '',
label: '',
amount: { currency: 'USD', value: '0.00' },
selected: true
};
// Shipping to US is supported
if (addr.country === 'US') {
shippingOption.id = 'us';
shippingOption.label = 'Standard shipping in US';
shippingOption.amount.value = '0.00';
details.total.amount.value = '55.00';
// Shipping to JP is supported
} else if (addr.country === 'JP') {
shippingOption.id = 'jp';
shippingOption.label = 'International shipping';
shippingOption.amount.value = '10.00';
details.total.amount.value = '65.00';
// Shipping to elsewhere is unsupported
} else {
// Empty array indicates rejection of the address
details.shippingOptions = [];
return Promise.resolve(details);
}
// Hardcode for simplicity
if (details.displayItems.length === 2) {
details.displayItems[2] = shippingOption;
} else {
details.displayItems.push(shippingOption);
}
details.shippingOptions = [shippingOption];
return Promise.resolve(details);
})(details, request.shippingAddress));
});
// When user selects a shipping option
request.addEventListener('shippingoptionchange', e => {
e.updateWith(((details) => {
// There should be only one option. Do nothing.
return Promise.resolve(details);
})(details));
});
// Show UI then continue with user payment info
request.show().then(result => {
// POST the result to the server
return fetch('/pay', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(result.toJSON())
}).then(res => {
// Only if successful
if (res.status === 200) {
return res.json();
} else {
throw 'Failure';
}
}).then(response => {
// You should have received a JSON object
if (response.success == true) {
return result.complete('success');
} else {
return result.complete('fail');
}
}).then(() => {
console.log('Thank you!',
result.shippingAddress.toJSON(),
result.methodName,
result.details.toJSON());
}).catch(() => {
return result.complete('fail');
});
}).catch(function(err) {
console.error('Uh oh, something bad happened: ' + err.message);
});
}
document.querySelector('#start').addEventListener('click', onBuyClicked);
使用网络令牌集成
请求网络令牌需要在 PaymentRequest 中包括两段信息。
merchantId
:在注册时获得publicKey
:作为paymentMethodTokenizationParameters
的一部分传递
参数
var supportedInstruments = [
{
supportedMethods: ['amex', 'discover','mastercard','visa']
},
{
supportedMethods: ['https://android.com/pay'],
data: {
//merchant ID obtained from Google that maps to your origin
merchantId: '02510116604241796260',
environment: 'TEST',
allowedCardNetworks: ['AMEX', 'MASTERCARD', 'VISA', 'DISCOVER'],
paymentMethodTokenizationParameters: {
tokenizationType: 'NETWORK_TOKEN',
parameters: {
//public key to encrypt response from Android Pay
'publicKey': 'BC9u7amr4kFD8qsdxnEfWV7RPDR9v4gLLkx3jfyaGOvxBoEuLZKE0Tt5O/2jMMxJ9axHpAZD2Jhi4E74nqxr944='
}
}
}
}
];
要想按照网络令牌方法使用 Android Pay,请按上例添加一个包含下列参数的 JSON 对象。
supportedMethods: [ 'https://android.com/pay' ]
:表示这是一种使用 Android Pay 的支付方式。data
:merchantId
:通过注册 Android Pay获得的 Android Pay 商家 ID。environment:'TEST'
:使用 Android Pay 进行测试时添加此参数。生成的令牌将是无效令牌。在生产环境下,请移除这行代码。allowedCardNetworks
:提供一个数组,其中包含的信用卡网络构成一个有效的 Android Pay 响应。paymentMethodTokenizationParameters
:tokenizationType: 'NETWORK_TOKEN'
:表示采用的是网络令牌方法。parameters
:接收网络令牌所需的公钥。(请参阅如何生成加密密钥。)
处理 Android Pay 响应
添加 Android Pay 对象后,Chrome 可以请求可收费网络令牌。
var payment = new PaymentRequest(
supportedInstruments, // required payment method data
details, // required information about transaction
options // optional parameter for things like shipping, etc.
);
payment.show().then(function(response) {
// Process response
response.complete("success");
}).catch(function(err) {
console.error("Uh oh, something bad happened", err.message);
});
来自 PaymentRequest 的加密响应将包含PaymentRequest 集成指南中所述示例内的配送和联系信息,但现在还会包括来自 Android Pay 的响应,其中包含
- 令牌化信用卡信息
- 收费地址信息
- 有关支付工具的信息
- 有关支付令牌的详情
为简化网络令牌的集成,我们建议直接将加密有效负荷传递给支付网关,让它们处理解密。自行解密有效负荷更为复杂,涉及私钥的管理。请联系您的支付网关,确认是否提供该功能。
对提交的网络令牌的处理方式取决于支付网关。请参阅特定网关的文档,了解更多详情。
此处省略了一个代码示例,因为与网关令牌方法的差异只体现在 PaymentRequest 对象的构建上。