react-native-macos开发Mac App如何实现In App Purchases?
解决React Native macOS订阅类内购问题的可行方案
一、先排查react-native-iap的配置问题
如果已经尝试过react-native-iap但失败,先确认以下配置是否到位:
- 确保MacOS项目已启用In-App Purchases能力:打开Xcode的MacOS target,进入
Signing & Capabilities,点击+ Capability添加该能力。 - 验证Pod依赖:在项目的
macos目录下执行npx pod-install,确保react-native-iap的MacOS端依赖正确安装,Podfile中需包含对应的pod条目。 - 核对产品ID与Bundle ID:App Store Connect中创建的订阅产品ID,必须和RN代码中传入的完全一致;项目Bundle ID也要和开发者后台配置的匹配。
- 沙箱测试环境:测试必须使用App Store沙箱账号,MacOS端可通过
系统设置 -> App Store -> 登录沙箱账号切换环境。
二、原生封装StoreKit 2实现订阅(推荐)
如果react-native-iap对MacOS的适配存在问题,直接基于StoreKit 2封装原生模块是更可靠的方案:
1. 创建Swift原生模块
在MacOS项目中新建Swift文件(比如RNIAPMac.swift),实现核心内购逻辑:
import Foundation import StoreKit @objc(RNIAPMac) class RNIAPMac: NSObject { // 获取订阅产品列表 @objc func getSubscriptions(_ productIds: [String], resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { Task { do { let products = try await Product.products(for: productIds) let formattedProducts = products.map { product in [ "id": product.id, "title": product.displayName, "description": product.description, "price": product.price.formatted(), "priceAmount": product.price.doubleValue ] } resolve(formattedProducts) } catch { reject("PRODUCT_FETCH_FAILED", "Failed to fetch products: \(error.localizedDescription)", error) } } } // 发起订阅购买 @objc func purchaseSubscription(_ productId: String, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { Task { do { guard let product = try await Product.products(for: [productId]).first else { reject("PRODUCT_NOT_FOUND", "Product not found", nil) return } let result = try await product.purchase() switch result { case .success(let verificationResult): switch verificationResult { case .verified(let transaction): // 验证成功,处理交易 await transaction.finish() resolve(["status": "success", "transactionId": transaction.id.uuidString]) case .unverified(_, let error): reject("TRANSACTION_UNVERIFIED", "Transaction verification failed: \(error?.localizedDescription ?? "Unknown error")", error) } case .userCancelled: resolve(["status": "cancelled"]) case .pending: resolve(["status": "pending"]) @unknown default: reject("UNKNOWN_STATUS", "Unknown purchase status", nil) } } catch { reject("PURCHASE_FAILED", "Purchase failed: \(error.localizedDescription)", error) } } } // 查询当前订阅状态 @objc func checkSubscriptionStatus(resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { Task { do { let statuses = try await AppStore.current.subscriptionStatuses let activeSubscriptions = statuses.filter { $0.state == .active || $0.state == .inGracePeriod } resolve(["hasActiveSubscription": !activeSubscriptions.isEmpty]) } catch { reject("STATUS_CHECK_FAILED", "Failed to check subscription status: \(error.localizedDescription)", error) } } } } // 注册模块 @objc(RNIAPMacModule) class RNIAPMacModule: RCTBridgeModule { static func moduleName() -> String! { return "RNIAPMac" } static func requiresMainQueueSetup() -> Bool { return true } override class func supportedEvents() -> [String]! { return [] } }
2. 配置RN端调用
在RN中创建工具类调用原生模块:
import { NativeModules } from 'react-native'; const { RNIAPMac } = NativeModules; export const IAPMac = { getSubscriptions: async (productIds) => { return await RNIAPMac.getSubscriptions(productIds); }, purchaseSubscription: async (productId) => { return await RNIAPMac.purchaseSubscription(productId); }, checkSubscriptionStatus: async () => { return await RNIAPMac.checkSubscriptionStatus(); } };
三、关键注意事项
- 订阅状态验证:生产环境必须将交易凭证发送到苹果服务器验证,避免客户端伪造订阅状态。
- 沙箱测试:沙箱订阅会自动续期,测试时需注意状态变化;如果遇到无法购买,检查沙箱账号是否有效、产品是否处于“准备提交”状态。
- 权限处理:确保MacOS App的权限配置正确,内购不需要额外权限,但需保证App能访问App Store服务。
内容的提问来源于stack exchange,提问作者Usama Daood
相关产品推荐
相关产品推荐

