# Google Pay 技术文档 ## 1. 概述 Google Pay 是 Google 生态内的免密快捷支付方式,基于 **Token 化银行卡**。适用于 Android App 与 Web(Chrome/Edge 等 Chromium 内核)。中国大陆不支持 Google Pay,本文面向海外市场。 ### 1.1 两种集成模式 | 模式 | 说明 | 适用场景 | | --- | --- | --- | | **PSP 模式(推荐)** | 通过 Stripe / Adyen 等支付服务商处理 | 有 Stripe 账户,流程简单 | | **直连商户模式** | 商户自行解密 `PaymentData` 中的 token 后走收单银行 | 已签约 Google Pay 直连商户 | > 与 Apple Pay 一样,**推荐走 Stripe(PSP)**:Stripe 支持 `isPlatformPaySupported` + `confirmPlatformPayPaymentIntent` 一键接入。 ## 2. 申请与配置 ### 2.1 需要申请的内容 | 项目 | 说明 | | --- | --- | | Google Pay API 权限 | 通过 [Google Pay & Wallet Console](https://pay.google.com/business/console) 申请 | | **Merchant ID** | Google Pay & Wallet Console 中的商户 ID(用于生产环境配置) | | 支付方式 | Google Pay 本身不收款,需绑定 **收单网关**(PSP)或自行解密后走银行 | | 商户国家/地区代码 | 如 `US`(决定可用的卡片网络与结算) | > 开发阶段可用**测试环境**(`testEnv: true` 或使用测试 Merchant),无需真实签约即可联调。 ### 2.2 网关(Gateway)配置 通过 Stripe 时使用 `gateway: "stripe"` 与 Stripe 的 `gatewayMerchantId`(`pk_test_...` 前的商户 id)。直连模式可配置 `direct: {}` + 网关证书。 ```json { "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_..." } } }] } ``` ## 3. 支付流程 ```mermaid 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. 查询最终状态 ``` ## 4. 后端集成 ### 4.1 方式一:通过 Stripe(推荐) 与 Apple Pay 完全一致,仅 `payment_method_types` 仍为 `card`,Stripe 会根据确认参数识别 Google Pay: ```javascript // 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, }; } ``` ### 4.2 方式二:直连商户(解密 PaymentData) `PaymentData` 中的 `paymentMethodData.tokenizationData.token`(Base64)是加密的银行卡令牌。直连模式需解密: ```javascript // 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**。 ## 5. 前端集成(Flutter) ### 5.1 方式一:通过 flutter_stripe(推荐) ```yaml # pubspec.yaml dependencies: flutter_stripe: ^10.0.0 ``` ```dart // lib/service/pay_service.dart import 'package:flutter_stripe/flutter_stripe.dart'; /// 使用 Stripe 拉起 Google Pay Future 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); } } ``` ### 5.2 方式二:使用 pay 插件(直连令牌模式) ```yaml # pubspec.yaml dependencies: pay: ^2.0.0 ``` ```dart // 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 _sendTokenToBackend(String token) async { await APIServer().submitGooglePayToken(orderId: product.id, token: token); } } ``` ## 6. 接口定义 ### 6.1 创建支付意图(PSP 模式) `POST /v1/payment/google/` 请求: ```json { "product_id": "p001", "currency": "usd" } ``` 响应: ```json { "payment_id": "pi_3Qx...", "payment_intent": "pi_3Qx..._secret_xxx", "publishable_key": "pk_test_...", "ephemeral_key": "ek_test_..." } ``` ### 6.2 提交支付令牌(直连模式) `POST /v1/payment/google/token` 请求: ```json { "order_id": "pay_8f3a2b", "token": "base64加密令牌", "amount": 1100 } ``` 响应: ```json { "success": true, "payment_id": "pay_8f3a2b" } ``` ## 7. 安全注意事项 1. **测试环境与生产环境分离**:`testEnv`/`environment: TEST` 仅联调用,上线切换为生产,且 Production 需真实 Merchant ID。 2. **金额以服务端 PaymentIntent 为准**:前端 `totalPrice` 仅展示。 3. **令牌解密安全**:直连模式解出的 PAN 属敏感数据,符合 PCI DSS;不存储、不落日志。 4. **Webhook 验签**:PSP 模式必须校验 Stripe Webhook 签名。 5. **地区限制**:Google Pay 在中国大陆不可用,上线前确认目标市场。 ## 8. 沙箱与测试 | 项 | 说明 | | --- | --- | | 测试环境 | Google Pay 提供 `TEST` 环境(测试 Merchant ID `BCR2DN4TEST...`),无需签约 | | 真机要求 | Android 5.0+ 且支持 NFC 或 Chrome(Web) | | Stripe 测试 | 测试模式完整可用,测试卡 `4242 4242 4242 4242` | | 模拟器 | Android 模拟器支持 Google Pay 测试环境 | ## 9. 常见问题 | 问题 | 原因/解决 | | --- | --- | | 按钮不显示 | 测试环境未用 TEST Merchant ID;设备/浏览器不支持;网络被限制 | | `isPlatformPaySupported` 返回 false | 测试环境配置错误,或地区不支持 | | 支付面板崩溃 | PaymentData 配置的网关与后端不匹配(如 gateway 与 publishableKey 不一致) | | 扣款失败 | `currencyCode`/`countryCode` 与商户签约范围不符 | | 生产环境报 unauthorized | 未在 Google Pay Console 完成生产资质审核,Merchant ID 未生效 |