关于使用Flutter 编写 IOS 内购的完整流程(Flutter+in_app_purchase+PHP验证)

in with 0 comment

关于使用Flutter 编写 IOS 内购的完整流程(Flutter+in_app_purchase)

初始化购买流程

import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'dart:async';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';

class BuyEngine {
  static final BuyEngine _instance = BuyEngine._internal();
  final InAppPurchase _inAppPurchase = InAppPurchase.instance;
  late StreamSubscription<List<PurchaseDetails>> _subscription;

  factory BuyEngine() {
    return _instance;
  }

  BuyEngine._internal() {
    // 初始化购买监听
    final Stream<List<PurchaseDetails>> purchaseUpdated =
        _inAppPurchase.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _onDone,
      onError: _onError,
    );
  }

  Future<void> buyNonConsumable(String productId) async {
    await clearPendingPurchases();
    // 检查商店是否可用
    final bool available = await _inAppPurchase.isAvailable();
    if (!available) {
      throw Exception('商店不可用');
    }

    // 加载商品信息
    final ProductDetailsResponse response =
        await _inAppPurchase.queryProductDetails({productId});

    if (response.notFoundIDs.isNotEmpty) {
      throw Exception('商品未找到');
    }

    // 检查是否有待处理的交易
    // final List<PurchaseDetails> pending =
    //     await _inAppPurchase.purchaseStream.first;
    // final bool hasPendingTransaction = pending.any((purchase) =>
    //     purchase.productID == productId &&
    //     purchase.status == PurchaseStatus.pending);

    // if (hasPendingTransaction) {
    //   throw Exception('已有待处理的购买交易,请等待完成或手动完成交易');
    // }

    // 发起购买
    final PurchaseParam purchaseParam =
        PurchaseParam(productDetails: response.productDetails.first);

    await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
  }

  void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
    purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
      if (purchaseDetails.status == PurchaseStatus.pending) {
        // 处理待处理状态
      } else if (purchaseDetails.status == PurchaseStatus.error) {
        // 处理错误状态
      } else if (purchaseDetails.status == PurchaseStatus.purchased ||
          purchaseDetails.status == PurchaseStatus.restored) {
        // 验证购买凭证
        bool isValid = await _verifyPurchase(purchaseDetails);
        if (!isValid) {
          // 处理验证失败情况
          throw Exception('购买验证失败');
        }
        // 完成购买
        if (purchaseDetails.pendingCompletePurchase) {
          await _inAppPurchase.completePurchase(purchaseDetails);
        }
      }
    });
  }

  Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
    // 获取购买凭证
    String? receipt = purchaseDetails.verificationData.serverVerificationData;
    String? localVerificationData =
        purchaseDetails.verificationData.localVerificationData;

    // TODO: 实现服务器端验证
    // 1. 将 receipt 发送到您的服务器
    // 2. 服务器与 App Store/Google Play 验证购买
    // 3. 返回验证结果

    // 示例验证逻辑
    return receipt != null && receipt.isNotEmpty;
  }

  Future<void> clearPendingPurchases() async {
    try {
      print("clearPendingPurchases");
      final transactions = await SKPaymentQueueWrapper().transactions();
      print(transactions);
      for (final transaction in transactions) {
        try {
          await SKPaymentQueueWrapper().finishTransaction(transaction);
        } catch (e) {
          debugPrint("Error clearing pending purchases::in::loop");
          debugPrint(e.toString());
          rethrow;
        }
      }
    } catch (e) {
      debugPrint("Error clearing pending purchases");
      debugPrint(e.toString());
      rethrow;
    }
  }

  void _onDone() {
    _subscription.cancel();
  }

  void _onError(error) {
    // 处理错误
  }

  void dispose() {
    _subscription.cancel();
  }
}
请注意,我这里使用的是 throw Exception 抛出对应错误结果,请务必在使用过程中try catch 捕获当前问题

其中包含遇到问题

flutter: PlatformException(storekit_duplicate_product_object, There is a pending transaction for the same product identifier. Please either wait for it to be finished or finish it manually using `completePurchase` to avoid edge cases., {applicationUsername: null, quantity: 1, simulatesAskToBuyInSandbox: false, paymentDiscount: null, productIdentifier: testerweek, requestData: null}, null)

请参阅:

Future<void> clearPendingPurchases() async {
    try {
      print("clearPendingPurchases");
      final transactions = await SKPaymentQueueWrapper().transactions();
      print(transactions);
      for (final transaction in transactions) {
        try {
          await SKPaymentQueueWrapper().finishTransaction(transaction);
        } catch (e) {
          debugPrint("Error clearing pending purchases::in::loop");
          debugPrint(e.toString());
          rethrow;
        }
      }
    } catch (e) {
      debugPrint("Error clearing pending purchases");
      debugPrint(e.toString());
      rethrow;
    }
  }

这个是由于,发起购买,然后支付过程中取消支付了。然而,苹果的机制是一直在监听支付的动作,导致如果不流转已取消的状态,那么一直会提示此问题,表示当前 包含等待支付中的任务

调用支付

/// 苹果支付
  startApplePay(productId) async {
    ToastUtil.showLoading();
    try {
      final buyEngine = BuyEngine();
      await buyEngine.buyNonConsumable('testerweek');
    } catch (e) {
      print(e);[请输入链接描述][1]
      ToastUtil.toast("支付失败,请重试");
    } finally {
      ToastUtil.dismiss();
    }

    GlobalUtil.appController.showOrderSuccessModal();
  }

testerweek 是我的商品id,你可自由决定

GlobalUtil.appController.showOrderSuccessModal(); 是我的支付模态框提示,可忽略

后端验证是否有效

回到之前的代码行,查看 _verifyPurchase 函数

然后调用后端 Api

PHP 后端流程

请先阅读下方参考文章

  1. https://developer.apple.com/documentation/appstoreservernotifications/app_store_server_notifications_v2
  2. https://vastzh.com/blog/apple_pay/
  3. https://developer.apple.com/documentation/appstoreserverapi

https://developer.apple.com/cn/help/app-store-connect/configure-in-app-purchase-settings/generate-a-shared-secret-to-verify-receipts
https://developer.apple.com/cn/help/app-store-connect/configure-in-app-purchase-settings/enter-server-urls-for-app-store-server-notifications

图片描述...

Responses