google_pay.md 10 KB

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 申请
Merchant ID Google Pay & Wallet Console 中的商户 ID(用于生产环境配置)
支付方式 Google Pay 本身不收款,需绑定 收单网关(PSP)或自行解密后走银行
商户国家/地区代码 US(决定可用的卡片网络与结算)

开发阶段可用测试环境testEnv: true 或使用测试 Merchant),无需真实签约即可联调。

2.2 网关(Gateway)配置

通过 Stripe 时使用 gateway: "stripe" 与 Stripe 的 gatewayMerchantIdpk_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_..."
      }
    }
  }]
}

3. 支付流程

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:

// 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)是加密的银行卡令牌。直连模式需解密:

// 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(推荐)

# 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);
  }
}

5.2 方式二:使用 pay 插件(直连令牌模式)

# 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);
  }
}

6. 接口定义

6.1 创建支付意图(PSP 模式)

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_..."
}

6.2 提交支付令牌(直连模式)

POST /v1/payment/google/token

请求:

{ "order_id": "pay_8f3a2b", "token": "base64加密令牌", "amount": 1100 }

响应:

{ "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 未生效