提示:
- 本文主要面向需要在应用内提供付费内容或增值服务的开发者,即基于 StoreKit 的 In-App Purchase 测试流程。如果你想要测试 Apple Pay(使用信用卡、借记卡等直接支付),流程会有所不同,但核心测试思路类似:都需在苹果沙盒环境进行测试。
- 在 Flutter 中进行内购,一般会使用类似于
in_app_purchase
插件,它在 iOS 端底层依赖 StoreKit 完成支付流程。
一、前置准备
1. 注册苹果开发者账号并加入开发者计划
- 前往 Apple Developer 注册或登陆你的 Apple ID。
- 如果你尚未加入 Apple Developer Program,需要加入付费的开发者计划,才能创建并配置 In-App Purchase 产品。
2. 配置 Xcode 项目
-
打开你的 Flutter 项目的 iOS 子项目:在 Flutter 项目根目录下执行
open ios/Runner.xcworkspace
,使用 Xcode 打开。 -
选择你的项目 target,进入
Signing & Capabilities
:- 在
Team
一栏选择你的开发者团队 (Team)。 - 确保
Bundle Identifier
与苹果开发者后台应用的Bundle ID
一致。
- 在
-
Capabilities: 确保你的应用中已经启用了
In-App Purchases
。
3. App Store Connect 配置
-
在 “我的 App” 或 “My Apps” 中,创建一个 App(如果已经有,直接编辑即可)。
Bundle ID
与 Xcode 项目一致。- 其他信息可以先随意填写,后续上架再补充完善。
-
进入你新建/已有的 App,切换到 “功能” (Features) 或 “App 内购买项目” (In-App Purchases) 标签页,创建新的
In-App Purchase
项目:- Consumable (消耗型): 用户每次使用都需要再次购买(如游戏内道具、点券)。
- Non-Consumable (非消耗型): 只需购买一次,永久有效(如解锁高级功能)。
- Auto-Renewable Subscription (自动续期订阅): 按月或年等周期自动续期。
- Non-Renewing Subscription (非续期订阅): 手动续订的订阅类型。
-
在创建时,填写:
- Reference Name:仅管理用,你可随意填写。
- Product ID:与应用中硬编码引用的
productId
一致,通常类似com.yourcompany.app.iap_item_001
。 - Pricing:可以选择适当的价格档位。
- 语言描述:提供展示给用户看的名称、描述等。
-
状态:创建完成后,这些商品最初会是 “Ready to Submit” 或 “Missing Metadata” 状态。当所有必须的描述、截图(如果有)都填写完后,商品会变成 “Ready to Submit”。只要 App 处于 “Ready for Sale” 或者在测试中就可以在沙盒环境购买。
二、沙盒测试账号配置
为了在开发调试阶段测试支付流程,需要使用苹果沙盒 (Sandbox) 环境下的测试账号,而不是你的真实 Apple ID。
-
点击右上角的 “+” 号创建 Sandbox Tester:
- Email:随意写一个未被苹果注册过的邮箱(可以是子邮箱或临时邮箱)。
- Password:设置一个符合苹果要求的密码。
- Country/Region:选择与测试市场一致的地区。一般与创建商品时的定价区域对应。
-
创建完成后,不要在真实设备的系统设置中直接登录这个沙盒账号到 iCloud!正确流程是:
- 在 iOS 设备或模拟器上,打开
Settings -> App Store
,下拉到 “沙盒账户”,点击登入(一般只有在安装了测试包后或者 debug 包后,实际操作才能看到)。 - 如果在模拟器中,则需要手动执行购买流程后,iOS 会在弹窗中提示你输入沙盒账号。
- 在 iOS 设备或模拟器上,打开
三、在 Xcode 中使用 StoreKit Configuration 文件进行本地测试(可选)
Xcode 从 12 版本开始,支持通过 StoreKit Configuration File 进行本地测试,无需创建沙盒账号也能模拟购买流程。这对于快速测试非常有用。
-
在 Xcode 中,右击项目根目录,选择
New File...
-
选择
StoreKit Configuration File
,命名为StoreKitTest.storekit
(可自定义)。 -
打开新建的
StoreKitTest.storekit
文件,点击 “+” 创建一个新的产品。- Product Identifier 必须与 App Store Connect 上配置的 In-App Purchase
Product ID
一致。 - 设置价格、类型等信息。
- Product Identifier 必须与 App Store Connect 上配置的 In-App Purchase
-
在
Scheme
设置中,选择Edit Scheme -> Run -> Options
,将StoreKit Configuration
选择为你刚刚创建的StoreKitTest.storekit
。 -
运行应用后,进行购买操作时,将使用本地 StoreKit 文件进行模拟购买,并可在 Debug 控制台查看详细的测试日志。
注意:本地 StoreKit Configuration 测试并不需要沙盒账号,它与沙盒环境是两种不同的测试方式。
四、Flutter 端的插件与代码编写
以下以官方维护的 in_app_purchase
插件为例,演示在 Flutter 中如何集成并调用 iOS 端的 StoreKit 进行支付测试。你也可以使用其他插件,但思路基本一致。
1. 安装依赖
在 pubspec.yaml
中添加依赖:
dependencies:
flutter:
sdk: flutter
in_app_purchase: ^5.0.0 # 版本号仅举例,请查看最新版本
然后执行 flutter pub get
。
2. iOS 端配置
在 ios/Runner/Info.plist
中,添加 In-App Purchase 权限描述(一般来说不需要额外添加,但有时需要声明用途):
<key>SKAdNetworkItems</key>
<array/>
通常只需要在 Xcode “Signing & Capabilities” 中勾选
In-App Purchases
即可,Info.plist
中并不一定需要额外的权限描述,不过如果项目有使用到其他权限或网络请求,可能在Info.plist
中统一配置。
3. 初始化与获取商品信息
在你的 Flutter 代码中(比如 main.dart
),进行初始化与商品信息请求。
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 检查平台是否支持内购
final bool isAvailable = await InAppPurchase.instance.isAvailable();
if (!isAvailable) {
// 如果不支持,可能是模拟器不支持购买,或网络问题
print('In-app purchases are NOT available');
} else {
print('In-app purchases are available');
}
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// 假设我们要测试的商品 ID
static const String _testProductId = 'com.yourcompany.app.iap_item_001';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'StoreKit Test Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('StoreKit Test Demo'),
),
body: FutureBuilder<ProductDetailsResponse>(
future: InAppPurchase.instance.queryProductDetails({_testProductId}),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final response = snapshot.data!;
if (response.error != null) {
return Center(child: Text('Error: ${response.error}'));
}
if (response.productDetails.isEmpty) {
return const Center(child: Text('No products found.'));
}
final product = response.productDetails.first;
return Center(
child: ElevatedButton(
child: Text('购买 ${product.title} - ${product.price}'),
onPressed: () {
// 创建购买请求
final purchaseParam = PurchaseParam(
productDetails: product,
);
InAppPurchase.instance.buyConsumable(
purchaseParam: purchaseParam,
);
},
),
);
},
),
),
);
}
}
4. 监听购买状态并处理结果
为了正确地处理购买成功、失败等情况,我们需要监听 InAppPurchase.instance.purchaseStream
。这通常在一个全局或独立的 Provider
/Bloc
中实现,这里简化示例:
import 'package:in_app_purchase/in_app_purchase.dart';
class PurchaseHandler {
// 单例
static final PurchaseHandler _instance = PurchaseHandler._internal();
factory PurchaseHandler() => _instance;
PurchaseHandler._internal();
void init() {
InAppPurchase.instance.purchaseStream.listen((purchases) {
_handlePurchaseUpdate(purchases);
}, onDone: () {
// 流结束了
}, onError: (error) {
// 购买出错
print('Purchase Stream error: $error');
});
}
Future<void> _handlePurchaseUpdate(List<PurchaseDetails> purchases) async {
for (final purchase in purchases) {
switch (purchase.status) {
case PurchaseStatus.purchased:
// 购买成功
print('Purchase Success: ${purchase.productID}');
// TODO: 向自己的服务器验证收据 或 直接完成交易
InAppPurchase.instance.completePurchase(purchase);
break;
case PurchaseStatus.pending:
// 用户还在支付确认中
print('Purchase Pending...');
break;
case PurchaseStatus.error:
// 购买失败
print('Purchase Error: ${purchase.error}');
break;
case PurchaseStatus.restored:
// 恢复购买
print('Purchase Restored: ${purchase.productID}');
// 完成交易
InAppPurchase.instance.completePurchase(purchase);
break;
case PurchaseStatus.canceled:
// 用户取消
print('Purchase Canceled');
break;
}
}
}
}
在 main.dart
或应用入口处调用 PurchaseHandler().init()
初始化即可。
五、如何进行测试
1. 使用沙盒环境测试
-
真机 / 模拟器上运行:
- 建议使用真机,模拟器有时在支付相关的功能不一定完全可行,尤其是苹果支付弹窗。
- 在 Xcode 的
Runner
目标中,选择真机或者 iOS 模拟器,点击Run
。
-
应用启动后:
- 调用
InAppPurchase.instance.queryProductDetails
获取商品信息,看能否正确返回。
- 调用
-
进行购买:
- 点击 “购买” 按钮时,会弹出苹果的支付弹窗。
- 输入之前在 App Store Connect -> Sandbox 里配置的 沙盒测试账号。
- 如果弹出 “需要登录” 的窗口,就使用你创建的沙盒账号。
-
观察控制台输出,或在你的 UI 中检查购买回调是否成功。
-
如果购买成功,可以在沙盒环境中多次进行相同操作来模拟消耗型商品,或切换到恢复购买逻辑测试非消耗型商品。
2. 使用本地 StoreKit Configuration 测试
- 在 Xcode 中设置
Run -> Options -> StoreKit Configuration
为创建的StoreKitTest.storekit
文件。 - 运行应用后,点击 “购买” 按钮。
- 由于是本地模拟,会直接回调购买成功或失败,不会出现真正的支付弹窗或 Apple ID 输入窗口。
- 你可以在
StoreKitTest.storekit
界面里对每个商品的状态(如订阅周期、价格等)进行自定义,并可快速进行测试。
六、常见问题与注意事项
-
沙盒账号登录
- 千万不要把沙盒账号登录到系统 iCloud 中,而是让它在支付弹窗时弹出,然后登录。
- 如果出现无法弹出沙盒登录窗口、提示账号不存在等,可以在 “设置 -> 退出 Apple ID” 并重新以真实 Apple ID 登录,再重试购买流程。
-
商品拉取不到
- 确认 Product ID 与 App Store Connect 上的设置一致;
- 确认在 App Store Connect 上的商品状态是 “Ready to Submit” 或以上,且 App 已经有过至少一次构建并提交测试;
- 有时候需要等待苹果服务器同步,最多可达 24 小时。
-
收据验证
- 沙盒环境下的收据验证 URL 与正式环境不同,需要在服务器端根据环境做区分;
- 使用本地 StoreKit Configuration 测试时,没有真正的收据验证流程。
-
测试订阅
- 如果你是测试订阅类型 (Auto-Renewable),沙盒环境中某些周期会被加速,如 1 个月订阅在沙盒中可能只持续几分钟,用于模拟续订。
七、完整流程回顾
-
苹果开发者账号:注册并加入付费开发者计划。
-
Xcode 项目配置:设置
Bundle ID
、启用In-App Purchase
Capabilities。 -
App Store Connect:创建应用、添加内购商品,配置商品信息与价格,记录
Product ID
。 -
沙盒账号:创建沙盒测试账号,用于在真机/模拟器中测试购买。
-
可选 StoreKit Configuration:在 Xcode 中创建本地配置文件,无需沙盒账号即可模拟购买。
-
Flutter 端:
- 添加
in_app_purchase
插件; - 初始化
InAppPurchase
,查询ProductDetails
; - 发起购买请求
buyConsumable
或buyNonConsumable
; - 监听
purchaseStream
,根据回调处理成功/失败等逻辑; - 完成交易
completePurchase
。
- 添加
-
测试:
- 在真机或模拟器上测试,使用沙盒账号进行真实沙盒购买;
- 或在 Xcode 使用本地 StoreKit 配置文件快速模拟购买。
可参考: