Google Pay 是 Google 生态内的免密快捷支付方式,基于 Token 化银行卡。适用于 Android App 与 Web(Chrome/Edge 等 Chromium 内核)。中国大陆不支持 Google Pay,本文面向海外市场。
| 模式 | 说明 | 适用场景 |
|---|---|---|
| PSP 模式(推荐) | 通过 Stripe / Adyen 等支付服务商处理 | 有 Stripe 账户,流程简单 |
| 直连商户模式 | 商户自行解密 PaymentData 中的 token 后走收单银行 |
已签约 Google Pay 直连商户 |
与 Apple Pay 一样,推荐走 Stripe(PSP):Stripe 支持
isPlatformPaySupported+confirmPlatformPayPaymentIntent一键接入。
| 项目 | 说明 |
|---|---|
| Google Pay API 权限 | 通过 Google Pay & Wallet Console 申请 |
| Merchant ID | Google Pay & Wallet Console 中的商户 ID(用于生产环境配置) |
| 支付方式 | Google Pay 本身不收款,需绑定 收单网关(PSP)或自行解密后走银行 |
| 商户国家/地区代码 | 如 US(决定可用的卡片网络与结算) |
开发阶段可用测试环境(
testEnv: true或使用测试 Merchant),无需真实签约即可联调。
通过 Stripe 时使用 gateway: "stripe" 与 Stripe 的 gatewayMerchantId(pk_test_... 前的商户 id)。直连模式可配置 direct: {} + 网关证书。
{
"merchantInfo": {
"merchantName": "Example Merchant",
"merchantId": "BCR2DN4TEST123456"
},
"transactionInfo": {
"countryCode": "US",
"currencyCode": "USD",
"totalPriceStatus": "FINAL",
"totalPrice": "99.99"
},
"allowedPaymentMethods": [{
"type": "CARD",
"parameters": {
"allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"],
"allowedCardNetworks": ["VISA", "MASTERCARD", "AMEX", "DISCOVER"]
},
"tokenizationSpecification": {
"type": "PAYMENT_GATEWAY",
"parameters": {
"gateway": "stripe",
"stripe:version": "2018-10-31",
"stripe:publishableKey": "pk_test_..."
}
}
}]
}
sequenceDiagram
participant App as Flutter App (Android)
participant Backend as 业务后端
participant PSP as 支付服务商(如Stripe)
participant Google as Google Pay
App->>Backend: 1. 创建订单 / 获取client_secret
Backend->>PSP: 2. 创建 PaymentIntent
PSP-->>Backend: client_secret
Backend-->>App: client_secret
App->>Google: 3. 拉起 Google Pay 支付面板
Google-->>App: PaymentData(token)
App->>PSP: 4. 确认支付(confirmPlatformPayPaymentIntent)
PSP->>PSP: 校验令牌、扣款
PSP->>Backend: 5. Webhook 通知(payment_intent.succeeded)
Backend->>Backend: 验签 + 幂等更新订单 + 发货
App->>Backend: 6. 查询最终状态
与 Apple Pay 完全一致,仅 payment_method_types 仍为 card,Stripe 会根据确认参数识别 Google Pay:
// Node.js + stripe SDK
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function createGooglePayPaymentIntent({ outTradeNo, amount, currency }) {
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount * 100),
currency,
payment_method_types: ['card'],
metadata: { out_trade_no: outTradeNo },
});
return {
clientSecret: paymentIntent.client_secret,
paymentId: paymentIntent.id,
};
}
PaymentData 中的 paymentMethodData.tokenizationData.token(Base64)是加密的银行卡令牌。直连模式需解密:
// src/services/googlepay_service.js
const crypto = require('crypto');
/**
* 解密 Google Pay 支付令牌(直连模式)
* 令牌 = encryptedMessage(签名+内容) + ephemeralPublicKey(EC) + tag
* 需要 Google 商户证书私钥,用 ECDH + AES-CTR 解密
*/
function decryptPaymentToken(token, merchantPrivateKeyPem) {
// 1. 解析 token: { encryptedMessage, ephemeralPublicKey, tag }
// 2. ECDH:ephemeralPublicKey(EC P-256) × 商户私钥 → 共享密钥
// 3. HKDF(sharedSecret, 'Google' salt) → AES-256-CTR 密钥
// 4. AES-256-CTR 解密 encryptedMessage → 明文JSON(卡号/有效期/3DS信息)
// 5. 校验签名与金额后提交收单渠道扣款
}
// 业务入口
app.post('/v1/payment/google/token', async (req, res) => {
const { token, orderId } = req.body;
const cardInfo = decryptPaymentToken(token, MERCHANT_PRIVATE_KEY);
const result = await acquiringGateway.charge({ orderId, amount: order.amount, card: cardInfo });
res.json({ success: result.success });
});
直连模式同样涉及 PAN 明文,需符合 PCI DSS;大多数商户建议走 PSP。
# pubspec.yaml
dependencies:
flutter_stripe: ^10.0.0
// lib/service/pay_service.dart
import 'package:flutter_stripe/flutter_stripe.dart';
/// 使用 Stripe 拉起 Google Pay
Future<void> googlePayWithStripe() async {
try {
// 1. 检查支持性
final supported = await Stripe.instance.isPlatformPaySupported(
googlePay: IsGooglePaySupportedParams(
testEnv: true, // 生产改为 false
existingPaymentMethodRequired: false,
),
);
if (!supported) {
showErrorMessage('当前设备不支持 Google Pay');
return;
}
// 2. 后端创建 PaymentIntent
final created = await APIServer().createStripePayment(
productId: product.id,
source: paymentSource(),
);
// 3. 拉起 Google Pay
await Stripe.instance.confirmPlatformPayPaymentIntent(
clientSecret: created.paymentIntent,
confirmParams: PlatformPayConfirmParams.googlePay(
googlePay: GooglePayParams(
testEnv: true, // 生产 false
merchantName: 'Example Merchant',
merchantCountryCode: 'US',
currencyCode: 'USD',
),
),
);
// 4. 后端查询确认
final resp = await APIServer().queryPaymentStatus(created.paymentId);
if (resp.success) showSuccessMessage(resp.note ?? '支付成功');
} on StripeException catch (e) {
showErrorMessage(e.error.localizedMessage ?? 'Google Pay 支付失败');
} on Exception catch (e) {
showErrorMessageEnhanced(context, e);
}
}
# pubspec.yaml
dependencies:
pay: ^2.0.0
// lib/components/google_pay_button.dart
import 'package:pay/pay.dart';
const googlePayConfig = '''
{
"provider": "google_pay",
"environment": "TEST",
"merchantName": "Example Merchant",
"merchantId": "BCR2DN4TEST123456",
"allowedCardNetworks": ["VISA", "MASTERCARD", "AMEX", "DISCOVER"],
"allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"],
"gateway": "stripe",
"gatewayMerchantId": "pk_test_..."
}
''';
class GooglePayButtonDemo extends StatelessWidget {
final PaymentProduct product;
GooglePayButtonDemo({required this.product});
@override
Widget build(BuildContext context) {
return GooglePayButton(
paymentConfiguration: PaymentConfiguration.fromJsonString(googlePayConfig),
paymentItems: [
PaymentItem(
label: product.name,
amount: (product.retailPriceUSD / 100).toStringAsFixed(2),
status: PaymentItemStatus.final_price,
),
],
type: GooglePayButtonType.buy,
onPaymentResult: (result) {
// result['paymentMethodData']['tokenizationData']['token'] = 加密令牌
final token = (result['paymentMethodData'] as Map)['tokenizationData']['token'];
_sendTokenToBackend(token);
},
onError: (error) => showErrorMessage('Google Pay 失败: $error'),
);
}
/// 直连模式:令牌发后端解密
Future<void> _sendTokenToBackend(String token) async {
await APIServer().submitGooglePayToken(orderId: product.id, token: token);
}
}
POST /v1/payment/google/
请求:
{ "product_id": "p001", "currency": "usd" }
响应:
{
"payment_id": "pi_3Qx...",
"payment_intent": "pi_3Qx..._secret_xxx",
"publishable_key": "pk_test_...",
"ephemeral_key": "ek_test_..."
}
POST /v1/payment/google/token
请求:
{ "order_id": "pay_8f3a2b", "token": "base64加密令牌", "amount": 1100 }
响应:
{ "success": true, "payment_id": "pay_8f3a2b" }
testEnv/environment: TEST 仅联调用,上线切换为生产,且 Production 需真实 Merchant ID。totalPrice 仅展示。| 项 | 说明 |
|---|---|
| 测试环境 | Google Pay 提供 TEST 环境(测试 Merchant ID BCR2DN4TEST...),无需签约 |
| 真机要求 | Android 5.0+ 且支持 NFC 或 Chrome(Web) |
| Stripe 测试 | 测试模式完整可用,测试卡 4242 4242 4242 4242 |
| 模拟器 | Android 模拟器支持 Google Pay 测试环境 |
| 问题 | 原因/解决 |
|---|---|
| 按钮不显示 | 测试环境未用 TEST Merchant ID;设备/浏览器不支持;网络被限制 |
isPlatformPaySupported 返回 false |
测试环境配置错误,或地区不支持 |
| 支付面板崩溃 | PaymentData 配置的网关与后端不匹配(如 gateway 与 publishableKey 不一致) |
| 扣款失败 | currencyCode/countryCode 与商户签约范围不符 |
| 生产环境报 unauthorized | 未在 Google Pay Console 完成生产资质审核,Merchant ID 未生效 |