使用 Flutter Pay 插件实现 Apple Pay 和 Google Pay 的完整指南

前言

pay 是 Google 官方维护的 Flutter 插件,提供了统一的 API 来集成 Apple Pay 和 Google Pay。相比其他支付插件,它更加轻量且易于配置。本文将详细介绍如何使用这个插件在 Flutter 应用中实现原生支付功能。

插件介绍

为什么选择 pay 插件?

  • 官方维护: 由 Google 官方团队维护,更新及时
  • 统一 API: 一套代码同时支持 Apple Pay 和 Google Pay
  • 配置简单: 使用 JSON 配置文件,无需复杂的原生代码
  • 灵活性高: 支持多种支付网关(Stripe、Braintree、Adyen 等)
  • 内置 UI: 提供标准的支付按钮组件

插件架构

ASCII 复制代码
┌─────────────────────────────────────────────────────┐
│                   Flutter App                       │
│                   (Payment UI)                      │
├─────────────────────────────────────────────────────┤
│                       pay                           │
│                     (Plugin)                        │
├────────────────────────┬────────────────────────────┤
│       Apple Pay        │        Google Pay          │
│        (iOS)           │        (Android)           │
├────────────────────────┴────────────────────────────┤
│                  Payment Gateway                    │
│      Stripe  |  Braintree  |  Adyen  |  Others...   │
└─────────────────────────────────────────────────────┘

环境准备

前置要求

  • Flutter SDK >= 3.0.0
  • Dart >= 2.17.0
  • iOS 12.0+ (Apple Pay)
  • Android API 19+ (Google Pay)
  • 支付网关账户(如 Stripe)

开发环境

  • Xcode 14+ (iOS 开发)
  • Android Studio / VS Code
  • 真机设备(模拟器支持有限)

安装与配置

1. 添加依赖

pubspec.yaml 中添加:

yaml 复制代码
dependencies:
  pay: ^3.3.0

运行:

bash 复制代码
flutter pub get

2. iOS 配置 (Apple Pay)

2.1 修改 ios/Podfile

ruby 复制代码
platform :ios, '12.0'

2.2 在 Xcode 中添加 Apple Pay 能力

  1. 打开 ios/Runner.xcworkspace
  2. 选择 Runner 项目 → Signing & Capabilities
  3. 点击 "+ Capability" → 添加 "Apple Pay"
  4. 添加你的 Merchant ID(格式:merchant.com.yourcompany.yourapp

2.3 Apple Developer 配置

  1. 登录 Apple Developer Portal
  2. 进入 Certificates, Identifiers & Profiles
  3. 选择 Identifiers → Merchant IDs
  4. 创建新的 Merchant ID
  5. 为你的 App ID 启用 Apple Pay 并关联 Merchant ID

3. Android 配置 (Google Pay)

3.1 修改 android/app/build.gradle.kts

kotlin 复制代码
android {
    compileSdk = 34
    
    defaultConfig {
        minSdk = 19
        targetSdk = 34
    }
}

3.2 修改 android/app/src/main/AndroidManifest.xml

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <!-- 启用 Google Pay API -->
        <meta-data
            android:name="com.google.android.gms.wallet.api.enabled"
            android:value="true" />
    </application>
</manifest>

支付配置文件

pay 插件使用 JSON 配置文件来定义支付参数。这是该插件的核心特性。

1. 创建配置文件目录

复制代码
assets/
├── apple_pay_config.json
└── google_pay_config.json

2. Apple Pay 配置文件

创建 assets/apple_pay_config.json

json 复制代码
{
  "provider": "apple_pay",
  "data": {
    "merchantIdentifier": "merchant.com.yourcompany.yourapp",
    "displayName": "Your Store Name",
    "merchantCapabilities": [
      "3DS",
      "debit",
      "credit"
    ],
    "supportedNetworks": [
      "amex",
      "discover",
      "masterCard",
      "visa"
    ],
    "countryCode": "US",
    "currencyCode": "USD",
    "requiredBillingContactFields": [
      "emailAddress",
      "name",
      "phoneNumber",
      "postalAddress"
    ],
    "requiredShippingContactFields": [
      "emailAddress",
      "name",
      "phoneNumber",
      "postalAddress"
    ],
    "shippingMethods": [
      {
        "amount": "0.00",
        "detail": "3-5 business days",
        "identifier": "standard",
        "label": "Standard Shipping"
      },
      {
        "amount": "9.99",
        "detail": "1-2 business days",
        "identifier": "express",
        "label": "Express Shipping"
      }
    ]
  }
}

配置参数说明

参数 说明
merchantIdentifier Apple Developer 中创建的 Merchant ID
displayName 支付表单中显示的商户名称
merchantCapabilities 支持的支付能力(3DS、借记卡、信用卡)
supportedNetworks 支持的卡网络
countryCode 商户所在国家代码
currencyCode 交易货币代码

3. Google Pay 配置文件

创建 assets/google_pay_config.json

json 复制代码
{
  "provider": "google_pay",
  "data": {
    "environment": "TEST",
    "apiVersion": 2,
    "apiVersionMinor": 0,
    "allowedPaymentMethods": [
      {
        "type": "CARD",
        "tokenizationSpecification": {
          "type": "PAYMENT_GATEWAY",
          "parameters": {
            "gateway": "stripe",
            "stripe:version": "2020-08-27",
            "stripe:publishableKey": "pk_test_your_publishable_key"
          }
        },
        "parameters": {
          "allowedCardNetworks": [
            "VISA",
            "MASTERCARD",
            "AMEX",
            "DISCOVER"
          ],
          "allowedAuthMethods": [
            "PAN_ONLY",
            "CRYPTOGRAM_3DS"
          ],
          "billingAddressRequired": true,
          "billingAddressParameters": {
            "format": "FULL",
            "phoneNumberRequired": true
          }
        }
      }
    ],
    "merchantInfo": {
      "merchantId": "BCR2DN4XXXXX",
      "merchantName": "Your Store Name"
    },
    "transactionInfo": {
      "countryCode": "US",
      "currencyCode": "USD"
    }
  }
}

Google Pay 配置参数说明

参数 说明
environment TESTPRODUCTION
gateway 支付网关名称(stripe、braintree 等)
merchantId Google Pay 商户 ID(生产环境需要)
allowedCardNetworks 支持的卡网络
allowedAuthMethods 支持的认证方式

4. 引用资源文件

pubspec.yaml 中添加:

yaml 复制代码
flutter:
  assets:
    - assets/apple_pay_config.json
    - assets/google_pay_config.json

Apple Pay 实现

基础实现

dart 复制代码
import 'package:pay/pay.dart';
import 'package:flutter/material.dart';

class ApplePayExample extends StatefulWidget {
  const ApplePayExample({super.key});

  @override
  State<ApplePayExample> createState() => _ApplePayExampleState();
}

class _ApplePayExampleState extends State<ApplePayExample> {
  late final Pay _payClient;

  // 定义支付项目
  final List<PaymentItem> _paymentItems = [
    const PaymentItem(
      label: '商品小计',
      amount: '99.99',
      status: PaymentItemStatus.final_price,
    ),
    const PaymentItem(
      label: '运费',
      amount: '5.99',
      status: PaymentItemStatus.final_price,
    ),
    const PaymentItem(
      label: '总计',
      amount: '105.98',
      status: PaymentItemStatus.final_price,
    ),
  ];

  @override
  void initState() {
    super.initState();
    // 从配置文件初始化
    _payClient = Pay({
      PayProvider.apple_pay: PaymentConfiguration.fromJsonString(
        _applePayConfigString,
      ),
    });
  }

  // 处理支付结果
  void _onApplePayResult(Map<String, dynamic> result) {
    // result 包含支付 token 和其他信息
    debugPrint('Apple Pay 结果: $result');
    
    // 将 token 发送到后端处理
    _processPayment(result);
  }

  Future<void> _processPayment(Map<String, dynamic> paymentResult) async {
    // 发送到后端
    // final response = await http.post(...);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ApplePayButton(
          paymentConfiguration: PaymentConfiguration.fromJsonString(
            _applePayConfigString,
          ),
          paymentItems: _paymentItems,
          style: ApplePayButtonStyle.black,
          type: ApplePayButtonType.buy,
          onPaymentResult: _onApplePayResult,
          onError: (error) {
            debugPrint('Apple Pay 错误: $error');
          },
          loadingIndicator: const CircularProgressIndicator(),
        ),
      ),
    );
  }
}

Apple Pay 按钮样式

dart 复制代码
// 按钮样式选项
ApplePayButtonStyle.black      // 黑色背景
ApplePayButtonStyle.white      // 白色背景
ApplePayButtonStyle.whiteOutline  // 白色带边框

// 按钮类型选项
ApplePayButtonType.buy         // "使用 Apple Pay 购买"
ApplePayButtonType.plain       // Apple Pay 图标
ApplePayButtonType.setUp       // "设置 Apple Pay"
ApplePayButtonType.inStore     // "店内使用 Apple Pay"
ApplePayButtonType.donate      // "使用 Apple Pay 捐赠"
ApplePayButtonType.checkout    // "使用 Apple Pay 结账"
ApplePayButtonType.book        // "使用 Apple Pay 预订"
ApplePayButtonType.subscribe   // "使用 Apple Pay 订阅"

Google Pay 实现

基础实现

dart 复制代码
import 'package:pay/pay.dart';
import 'package:flutter/material.dart';

class GooglePayExample extends StatefulWidget {
  const GooglePayExample({super.key});

  @override
  State<GooglePayExample> createState() => _GooglePayExampleState();
}

class _GooglePayExampleState extends State<GooglePayExample> {
  // 定义支付项目
  final List<PaymentItem> _paymentItems = [
    const PaymentItem(
      label: '商品总计',
      amount: '99.99',
      status: PaymentItemStatus.final_price,
    ),
    const PaymentItem(
      label: '运费',
      amount: '5.99',
      status: PaymentItemStatus.final_price,
    ),
    const PaymentItem(
      label: '总计',
      amount: '105.98',
      status: PaymentItemStatus.final_price,
    ),
  ];

  // 处理支付结果
  void _onGooglePayResult(Map<String, dynamic> result) {
    debugPrint('Google Pay 结果: $result');
    
    // 从结果中提取 token
    final token = result['paymentMethodData']['tokenizationData']['token'];
    debugPrint('支付 Token: $token');
    
    // 将 token 发送到后端处理
    _processPayment(token);
  }

  Future<void> _processPayment(String token) async {
    // 发送到后端
    // final response = await http.post(...);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GooglePayButton(
          paymentConfiguration: PaymentConfiguration.fromAsset(
            'assets/google_pay_config.json',
          ),
          paymentItems: _paymentItems,
          type: GooglePayButtonType.buy,
          onPaymentResult: _onGooglePayResult,
          onError: (error) {
            debugPrint('Google Pay 错误: $error');
          },
          loadingIndicator: const CircularProgressIndicator(),
        ),
      ),
    );
  }
}

Google Pay 按钮样式

dart 复制代码
// 按钮类型选项
GooglePayButtonType.buy        // "使用 Google Pay 购买"
GooglePayButtonType.book       // "使用 Google Pay 预订"
GooglePayButtonType.checkout   // "使用 Google Pay 结账"
GooglePayButtonType.donate     // "使用 Google Pay 捐赠"
GooglePayButtonType.order      // "使用 Google Pay 下单"
GooglePayButtonType.pay        // "使用 Google Pay 支付"
GooglePayButtonType.plain      // Google Pay 图标
GooglePayButtonType.subscribe  // "使用 Google Pay 订阅"

完整代码示例

支付服务类

dart 复制代码
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:pay/pay.dart';

class NativePayService {
  static NativePayService? _instance;
  late Pay _payClient;
  
  PaymentConfiguration? _applePayConfig;
  PaymentConfiguration? _googlePayConfig;

  NativePayService._();

  static NativePayService get instance {
    _instance ??= NativePayService._();
    return _instance!;
  }

  /// 初始化支付服务
  Future<void> initialize() async {
    final configurations = <PayProvider, PaymentConfiguration>{};

    if (Platform.isIOS) {
      final applePayConfigString = await rootBundle.loadString(
        'assets/apple_pay_config.json',
      );
      _applePayConfig = PaymentConfiguration.fromJsonString(
        applePayConfigString,
      );
      configurations[PayProvider.apple_pay] = _applePayConfig!;
    }

    if (Platform.isAndroid) {
      final googlePayConfigString = await rootBundle.loadString(
        'assets/google_pay_config.json',
      );
      _googlePayConfig = PaymentConfiguration.fromJsonString(
        googlePayConfigString,
      );
      configurations[PayProvider.google_pay] = _googlePayConfig!;
    }

    _payClient = Pay(configurations);
  }

  /// 获取当前平台的支付配置
  PaymentConfiguration? get currentConfig {
    if (Platform.isIOS) return _applePayConfig;
    if (Platform.isAndroid) return _googlePayConfig;
    return null;
  }

  /// 检查原生支付是否可用
  Future<bool> isAvailable() async {
    try {
      if (Platform.isIOS) {
        return await _payClient.userCanPay(PayProvider.apple_pay);
      } else if (Platform.isAndroid) {
        return await _payClient.userCanPay(PayProvider.google_pay);
      }
      return false;
    } catch (e) {
      return false;
    }
  }

  /// 显示支付表单
  Future<Map<String, dynamic>> showPaymentSheet({
    required List<PaymentItem> paymentItems,
  }) async {
    if (Platform.isIOS) {
      return await _payClient.showPaymentSelector(
        PayProvider.apple_pay,
        paymentItems,
      );
    } else if (Platform.isAndroid) {
      return await _payClient.showPaymentSelector(
        PayProvider.google_pay,
        paymentItems,
      );
    }
    throw UnsupportedError('不支持的平台');
  }
}

跨平台支付页面

dart 复制代码
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:pay/pay.dart';
import '../services/native_pay_service.dart';

class CheckoutScreen extends StatefulWidget {
  const CheckoutScreen({super.key});

  @override
  State<CheckoutScreen> createState() => _CheckoutScreenState();
}

class _CheckoutScreenState extends State<CheckoutScreen> {
  bool _isLoading = true;
  bool _isPayAvailable = false;
  String? _errorMessage;

  // 购物车商品
  final List<CartItem> _cartItems = [
    CartItem(name: 'Flutter 开发指南', price: 49.99),
    CartItem(name: 'Dart 编程实战', price: 39.99),
  ];

  // 计算总价
  double get _subtotal => _cartItems.fold(0, (sum, item) => sum + item.price);
  double get _shipping => 5.99;
  double get _total => _subtotal + _shipping;

  // 支付项目列表
  List<PaymentItem> get _paymentItems => [
    PaymentItem(
      label: '商品小计',
      amount: _subtotal.toStringAsFixed(2),
      status: PaymentItemStatus.final_price,
    ),
    PaymentItem(
      label: '运费',
      amount: _shipping.toStringAsFixed(2),
      status: PaymentItemStatus.final_price,
    ),
    PaymentItem(
      label: '总计',
      amount: _total.toStringAsFixed(2),
      status: PaymentItemStatus.final_price,
    ),
  ];

  @override
  void initState() {
    super.initState();
    _initializePayment();
  }

  Future<void> _initializePayment() async {
    try {
      await NativePayService.instance.initialize();
      final isAvailable = await NativePayService.instance.isAvailable();
      
      setState(() {
        _isPayAvailable = isAvailable;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _errorMessage = e.toString();
        _isLoading = false;
      });
    }
  }

  void _onPaymentResult(Map<String, dynamic> result) {
    debugPrint('支付结果: $result');
    
    // 显示成功提示
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('支付成功!'),
        backgroundColor: Colors.green,
      ),
    );

    // 处理支付结果
    _processPaymentOnServer(result);
  }

  Future<void> _processPaymentOnServer(Map<String, dynamic> result) async {
    // 提取 token 并发送到后端
    String token;
    
    if (Platform.isIOS) {
      // Apple Pay token
      token = result['token'] ?? '';
    } else {
      // Google Pay token
      token = result['paymentMethodData']?['tokenizationData']?['token'] ?? '';
    }

    // 发送到后端处理
    // await PaymentApi.processPayment(token: token, amount: _total);
  }

  void _onPaymentError(Object? error) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('支付失败: $error'),
        backgroundColor: Colors.red,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('结账'),
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _buildContent(),
    );
  }

  Widget _buildContent() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // 购物车商品列表
          _buildCartItems(),
          
          const SizedBox(height: 16),
          
          // 价格明细
          _buildPriceSummary(),
          
          const SizedBox(height: 24),
          
          // 支付按钮
          _buildPaymentButton(),
          
          const SizedBox(height: 16),
          
          // 或者使用其他支付方式
          _buildAlternativePayment(),
        ],
      ),
    );
  }

  Widget _buildCartItems() {
    return Card(
      child: Column(
        children: _cartItems.map((item) => ListTile(
          title: Text(item.name),
          trailing: Text('\$${item.price.toStringAsFixed(2)}'),
        )).toList(),
      ),
    );
  }

  Widget _buildPriceSummary() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            _buildPriceRow('小计', _subtotal),
            _buildPriceRow('运费', _shipping),
            const Divider(),
            _buildPriceRow('总计', _total, isBold: true),
          ],
        ),
      ),
    );
  }

  Widget _buildPriceRow(String label, double amount, {bool isBold = false}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            label,
            style: TextStyle(
              fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
              fontSize: isBold ? 18 : 14,
            ),
          ),
          Text(
            '\$${amount.toStringAsFixed(2)}',
            style: TextStyle(
              fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
              fontSize: isBold ? 18 : 14,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildPaymentButton() {
    if (!_isPayAvailable) {
      return const Text(
        '此设备不支持 Apple Pay / Google Pay',
        textAlign: TextAlign.center,
        style: TextStyle(color: Colors.grey),
      );
    }

    final config = NativePayService.instance.currentConfig;
    if (config == null) return const SizedBox.shrink();

    if (Platform.isIOS) {
      return ApplePayButton(
        paymentConfiguration: config,
        paymentItems: _paymentItems,
        style: ApplePayButtonStyle.black,
        type: ApplePayButtonType.checkout,
        onPaymentResult: _onPaymentResult,
        onError: _onPaymentError,
        loadingIndicator: const Center(
          child: CircularProgressIndicator(),
        ),
      );
    } else {
      return GooglePayButton(
        paymentConfiguration: config,
        paymentItems: _paymentItems,
        type: GooglePayButtonType.checkout,
        onPaymentResult: _onPaymentResult,
        onError: _onPaymentError,
        loadingIndicator: const Center(
          child: CircularProgressIndicator(),
        ),
      );
    }
  }

  Widget _buildAlternativePayment() {
    return Column(
      children: [
        const Row(
          children: [
            Expanded(child: Divider()),
            Padding(
              padding: EdgeInsets.symmetric(horizontal: 16),
              child: Text('或', style: TextStyle(color: Colors.grey)),
            ),
            Expanded(child: Divider()),
          ],
        ),
        const SizedBox(height: 16),
        OutlinedButton(
          onPressed: () {
            // 跳转到信用卡支付页面
          },
          child: const Text('使用信用卡支付'),
        ),
      ],
    );
  }
}

class CartItem {
  final String name;
  final double price;

  CartItem({required this.name, required this.price});
}

应用入口

dart 复制代码
import 'package:flutter/material.dart';
import 'screens/checkout_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pay Plugin Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const CheckoutScreen(),
    );
  }
}

与支付网关集成

Stripe 集成

后端代码 (Node.js)

javascript 复制代码
const express = require('express');
const stripe = require('stripe')('sk_test_your_secret_key');
const app = express();

app.use(express.json());

// 处理 Apple Pay / Google Pay token
app.post('/process-payment', async (req, res) => {
  try {
    const { token, amount, currency } = req.body;

    // 创建支付
    const charge = await stripe.charges.create({
      amount: Math.round(amount * 100), // 转换为分
      currency: currency || 'usd',
      source: token,
      description: 'App Purchase',
    });

    res.json({
      success: true,
      chargeId: charge.id,
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message,
    });
  }
});

// 或者使用 Payment Intent(推荐)
app.post('/create-payment-intent', async (req, res) => {
  try {
    const { amount, currency } = req.body;

    const paymentIntent = await stripe.paymentIntents.create({
      amount: Math.round(amount * 100),
      currency: currency || 'usd',
      automatic_payment_methods: {
        enabled: true,
      },
    });

    res.json({
      clientSecret: paymentIntent.client_secret,
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);

其他支付网关配置

Braintree

json 复制代码
{
  "provider": "google_pay",
  "data": {
    "environment": "TEST",
    "apiVersion": 2,
    "apiVersionMinor": 0,
    "allowedPaymentMethods": [
      {
        "type": "CARD",
        "tokenizationSpecification": {
          "type": "PAYMENT_GATEWAY",
          "parameters": {
            "gateway": "braintree",
            "braintree:apiVersion": "v1",
            "braintree:sdkVersion": "3.0.0",
            "braintree:merchantId": "your_merchant_id",
            "braintree:clientKey": "your_client_key"
          }
        },
        "parameters": {
          "allowedCardNetworks": ["VISA", "MASTERCARD"],
          "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"]
        }
      }
    ]
  }
}

Adyen

json 复制代码
{
  "provider": "google_pay",
  "data": {
    "environment": "TEST",
    "apiVersion": 2,
    "apiVersionMinor": 0,
    "allowedPaymentMethods": [
      {
        "type": "CARD",
        "tokenizationSpecification": {
          "type": "PAYMENT_GATEWAY",
          "parameters": {
            "gateway": "adyen",
            "gatewayMerchantId": "your_merchant_account"
          }
        },
        "parameters": {
          "allowedCardNetworks": ["VISA", "MASTERCARD", "AMEX"],
          "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"]
        }
      }
    ]
  }
}

测试与调试

测试环境设置

  1. Google Pay 测试模式

  2. Apple Pay 沙盒测试

    • 使用 Sandbox 测试账户
    • 在设置中添加测试卡

调试技巧

dart 复制代码
// 详细日志输出
void _onPaymentResult(Map<String, dynamic> result) {
  // 打印完整结果
  debugPrint('完整支付结果:');
  debugPrint(const JsonEncoder.withIndent('  ').convert(result));
  
  // Apple Pay 结果结构
  if (Platform.isIOS) {
    debugPrint('Token: ${result['token']}');
    debugPrint('Transaction ID: ${result['transactionIdentifier']}');
    debugPrint('Payment Method: ${result['paymentMethod']}');
  }
  
  // Google Pay 结果结构
  if (Platform.isAndroid) {
    final paymentMethodData = result['paymentMethodData'];
    debugPrint('Type: ${paymentMethodData['type']}');
    debugPrint('Token: ${paymentMethodData['tokenizationData']['token']}');
    debugPrint('Card Network: ${paymentMethodData['info']['cardNetwork']}');
  }
}

常见测试场景

dart 复制代码
// 测试不同金额
final testAmounts = [
  PaymentItem(label: '小额测试', amount: '1.00', status: PaymentItemStatus.final_price),
  PaymentItem(label: '标准测试', amount: '99.99', status: PaymentItemStatus.final_price),
  PaymentItem(label: '大额测试', amount: '999.99', status: PaymentItemStatus.final_price),
];

// 测试待定价格(如运费计算中)
final pendingItems = [
  PaymentItem(label: '商品', amount: '50.00', status: PaymentItemStatus.final_price),
  PaymentItem(label: '运费', amount: '0.00', status: PaymentItemStatus.pending), // 待计算
];

常见问题

Q1: Apple Pay 按钮不显示?

可能原因与解决方案:

  1. 设备未添加支付卡

    • 在设置 → 钱包与 Apple Pay 中添加卡片
  2. Merchant ID 配置错误

    • 检查 Xcode 中的 Merchant ID 是否与配置文件一致
    • 确认 Apple Developer 中已正确配置
  3. 模拟器限制

    • Apple Pay 在模拟器上支持有限,建议使用真机测试

Q2: Google Pay 返回 "Request Failed"?

解决方案:

  1. 检查 AndroidManifest.xml 中的 meta-data 配置
  2. 确认 Google Play Services 已更新
  3. 检查配置文件中的 gateway 参数是否正确
  4. 确保设备已安装 Google Pay 应用

Q3: 如何处理用户取消支付?

dart 复制代码
void _onPaymentError(Object? error) {
  if (error is PlatformException) {
    if (error.code == 'cancelled') {
      // 用户取消了支付
      debugPrint('用户取消支付');
      return;
    }
  }
  // 其他错误
  debugPrint('支付错误: $error');
}

Q4: 如何自定义支付按钮?

dart 复制代码
// 使用 Pay 客户端手动触发支付
class CustomPayButton extends StatelessWidget {
  final List<PaymentItem> paymentItems;
  final Function(Map<String, dynamic>) onResult;

  const CustomPayButton({
    super.key,
    required this.paymentItems,
    required this.onResult,
  });

  Future<void> _handlePayment() async {
    try {
      final result = await NativePayService.instance.showPaymentSheet(
        paymentItems: paymentItems,
      );
      onResult(result);
    } catch (e) {
      debugPrint('支付错误: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _handlePayment,
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.black,
        foregroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 32),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Platform.isIOS ? Icons.apple : Icons.g_mobiledata),
          const SizedBox(width: 8),
          Text(Platform.isIOS ? 'Apple Pay' : 'Google Pay'),
        ],
      ),
    );
  }
}

Q5: 如何支持多种货币?

dart 复制代码
// 动态配置货币
PaymentItem createPaymentItem({
  required String label,
  required double amount,
  required String currencyCode,
}) {
  // 根据货币格式化金额
  String formattedAmount;
  switch (currencyCode) {
    case 'JPY': // 日元无小数
      formattedAmount = amount.toInt().toString();
      break;
    default:
      formattedAmount = amount.toStringAsFixed(2);
  }

  return PaymentItem(
    label: label,
    amount: formattedAmount,
    status: PaymentItemStatus.final_price,
  );
}

生产环境检查清单

通用检查

  • 替换测试配置为生产配置
  • 配置生产环境的支付网关密钥
  • 实现支付结果的服务端验证
  • 添加支付失败重试机制
  • 配置错误监控和日志
  • 实现订单状态同步

Apple Pay 检查

  • 在 Apple Developer 中完成商户认证
  • 上传支付处理证书到支付网关
  • 测试所有支持的卡网络
  • 验证 Merchant ID 配置

Google Pay 检查

  • 申请 Google Pay 生产环境访问权限
  • 设置 "environment": "PRODUCTION"
  • 配置正确的 merchantId
  • 完成 Google Pay API 集成检查清单

参考资源

相关推荐
麦客奥德彪3 小时前
Flutter riverpod 对应Android开发概念理解
flutter
tangweiguo030519873 小时前
Kotlin vs Dart vs Swift:语法对比全解
flutter
feelingHy3 小时前
GetX 状态管理实践
flutter
tangweiguo030519874 小时前
Flutter多品牌应用架构实战:从配置驱动到编译部署的完整解决方案
flutter
Bryce李小白5 小时前
FlutterBoost适配Flutter3.38.4版本生成补丁包
flutter
tangweiguo030519876 小时前
Flutter Packages 设计与实践:构建可维护的模块化应用
flutter
小a杰.6 小时前
Flutter 的编译技术核心
flutter
hudawei9968 小时前
flutter setState(() { … }) 作用
flutter
ujainu小8 小时前
Flutter 结合 local_auth 3.0.0 实现跨平台本地生物识别认证
flutter·local_auth