| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- #import "IAPWrapper.h"
- #import <StoreKit/StoreKit.h>
- #import "platform/apple/JsbBridgeWrapper.h"
- #include "application/ApplicationManager.h"
- #include "application/BaseApplication.h"
- #include "base/Scheduler.h"
- #include "engine/BaseEngine.h"
- @interface IAPWrapper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
- @property(nonatomic, strong) NSMutableDictionary<NSString *, SKProduct *> *products;
- @property(nonatomic, strong) SKProductsRequest *productsRequest;
- @property(nonatomic, strong) NSDictionary *catalogRequest;
- @property(nonatomic, strong) NSDictionary *purchaseRequest;
- @property(nonatomic, strong) NSDictionary *restoreRequest;
- @property(nonatomic, strong) NSMutableArray *restored;
- @end
- @implementation IAPWrapper
- + (instancetype)shared {
- static IAPWrapper *instance;
- static dispatch_once_t once;
- dispatch_once(&once, ^{ instance = [IAPWrapper new]; instance.products = [NSMutableDictionary new]; });
- return instance;
- }
- - (void)start {
- static dispatch_once_t once;
- dispatch_once(&once, ^{
- // Global, capture-free listener survives Creator 3.8.8 MRC ownership.
- static OnScriptEventListener const listener = ^(NSString *payload) {
- dispatch_async(dispatch_get_main_queue(), ^{ [[IAPWrapper shared] handle:payload]; });
- };
- [[JsbBridgeWrapper sharedInstance] addScriptEventListener:@"iap.request" listener:listener];
- [SKPaymentQueue.defaultQueue addTransactionObserver:self];
- });
- }
- - (void)reply:(NSDictionary *)request data:(id)data error:(NSString *)error {
- if (!request) return;
- NSMutableDictionary *response = [@{@"requestId": request[@"requestId"] ?: @"",
- @"method": request[@"method"] ?: @"", @"success": @(!error)} mutableCopy];
- if (data) response[@"data"] = data;
- if (error) response[@"error"] = error;
- NSData *json = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil];
- NSString *payload = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
- auto app = CC_CURRENT_APPLICATION();
- if (!app || !app->getEngine() || !payload) return;
- app->getEngine()->getScheduler()->performFunctionInCocosThread([payload]() {
- [[JsbBridgeWrapper sharedInstance] dispatchEventToScript:@"iap.response" arg:payload];
- });
- }
- - (NSDictionary *)dataFor:(SKPaymentTransaction *)transaction {
- NSURL *url = NSBundle.mainBundle.appStoreReceiptURL;
- NSData *receipt = url ? [NSData dataWithContentsOfURL:url] : nil;
- NSString *status = transaction.transactionState == SKPaymentTransactionStateRestored ? @"restored" :
- transaction.transactionState == SKPaymentTransactionStateDeferred ? @"deferred" : @"purchased";
- return @{@"status": status, @"productId": transaction.payment.productIdentifier ?: @"",
- @"transactionId": transaction.transactionIdentifier ?: @"",
- @"originalTransactionId": transaction.originalTransaction.transactionIdentifier ?: transaction.transactionIdentifier ?: @"",
- @"receipt": [receipt base64EncodedStringWithOptions:0] ?: @""};
- }
- - (NSArray *)pending {
- NSMutableArray *items = [NSMutableArray new];
- for (SKPaymentTransaction *t in SKPaymentQueue.defaultQueue.transactions) {
- if (t.transactionState == SKPaymentTransactionStatePurchased || t.transactionState == SKPaymentTransactionStateRestored ||
- t.transactionState == SKPaymentTransactionStateDeferred) [items addObject:[self dataFor:t]];
- }
- return items;
- }
- - (void)handle:(NSString *)payload {
- id decoded = [NSJSONSerialization JSONObjectWithData:[payload dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
- if (![decoded isKindOfClass:NSDictionary.class]) return;
- NSDictionary *r = decoded;
- if (![r[@"requestId"] isKindOfClass:NSString.class] || ![r[@"method"] isKindOfClass:NSString.class]) return;
- NSString *method = r[@"method"];
- NSDictionary *p = [r[@"params"] isKindOfClass:NSDictionary.class] ? r[@"params"] : @{};
- if ([method isEqualToString:@"init"]) {
- if (self.productsRequest) { [self reply:r data:nil error:@"正在查询商品,请稍后重试。"]; return; }
- NSArray *ids = p[@"productIds"];
- if (![ids isKindOfClass:NSArray.class] || !ids.count) { [self reply:r data:nil error:@"商品列表不能为空。"]; return; }
- for (id value in ids) if (![value isKindOfClass:NSString.class] || ![value length]) {
- [self reply:r data:nil error:@"商品 ID 必须为非空字符串。"]; return;
- }
- self.catalogRequest = r;
- self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:ids]];
- self.productsRequest.delegate = self;
- [self.productsRequest start];
- } else if ([method isEqualToString:@"purchase"]) {
- if (self.purchaseRequest || self.restoreRequest || SKPaymentQueue.defaultQueue.transactions.count) {
- [self reply:r data:nil error:@"存在进行中或未完成交易,请先处理未完成交易。"]; return;
- }
- NSString *productID = p[@"productId"];
- SKProduct *product = [productID isKindOfClass:NSString.class] ? self.products[productID] : nil;
- if (!product || ![SKPaymentQueue canMakePayments]) {
- [self reply:r data:nil error:@"商品未初始化或此设备不允许内购。"]; return;
- }
- self.purchaseRequest = r;
- [SKPaymentQueue.defaultQueue addPayment:[SKPayment paymentWithProduct:product]];
- } else if ([method isEqualToString:@"restore"]) {
- if (self.restoreRequest || self.purchaseRequest) { [self reply:r data:nil error:@"已有购买或恢复操作进行中。"]; return; }
- self.restoreRequest = r;
- self.restored = [NSMutableArray new];
- [SKPaymentQueue.defaultQueue restoreCompletedTransactions];
- } else if ([method isEqualToString:@"pending"]) {
- [self reply:r data:[self pending] error:nil];
- } else if ([method isEqualToString:@"finish"]) {
- NSString *identifier = p[@"transactionId"];
- if (![identifier isKindOfClass:NSString.class] || !identifier.length) { [self reply:r data:nil error:@"无效的交易 ID。"]; return; }
- for (SKPaymentTransaction *t in SKPaymentQueue.defaultQueue.transactions) {
- if ([t.transactionIdentifier isEqualToString:identifier] &&
- (t.transactionState == SKPaymentTransactionStatePurchased || t.transactionState == SKPaymentTransactionStateRestored)) {
- [SKPaymentQueue.defaultQueue finishTransaction:t];
- [self reply:r data:@{} error:nil]; return;
- }
- }
- [self reply:r data:nil error:@"未找到可完成的交易,请查询未完成交易。"];
- } else { [self reply:r data:nil error:@"不支持的内购方法。"]; }
- }
- - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
- dispatch_async(dispatch_get_main_queue(), ^{
- if (request != self.productsRequest) return;
- NSMutableArray *items = [NSMutableArray new];
- for (SKProduct *product in response.products) {
- self.products[product.productIdentifier] = product;
- NSNumberFormatter *formatter = [NSNumberFormatter new];
- formatter.numberStyle = NSNumberFormatterCurrencyStyle;
- formatter.locale = product.priceLocale;
- [items addObject:@{@"productId": product.productIdentifier, @"title": product.localizedTitle ?: @"",
- @"description": product.localizedDescription ?: @"", @"price": product.price.stringValue,
- @"localizedPrice": [formatter stringFromNumber:product.price] ?: @"", @"currencyCode": formatter.currencyCode ?: @""}];
- }
- [self reply:self.catalogRequest data:@{@"products": items, @"invalidProductIds": response.invalidProductIdentifiers} error:nil];
- self.catalogRequest = nil; self.productsRequest = nil;
- });
- }
- - (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
- dispatch_async(dispatch_get_main_queue(), ^{
- if (request != self.productsRequest) return;
- [self reply:self.catalogRequest data:nil error:error.localizedDescription];
- self.catalogRequest = nil; self.productsRequest = nil;
- });
- }
- - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
- dispatch_async(dispatch_get_main_queue(), ^{
- for (SKPaymentTransaction *t in transactions) {
- if (t.transactionState == SKPaymentTransactionStatePurchasing) continue;
- BOOL matches = [self.purchaseRequest[@"params"][@"productId"] isEqualToString:t.payment.productIdentifier];
- if (t.transactionState == SKPaymentTransactionStateFailed) {
- BOOL cancelled = t.error.code == SKErrorPaymentCancelled && [t.error.domain isEqualToString:SKErrorDomain];
- NSDictionary *data = @{@"status": @"cancelled", @"productId": t.payment.productIdentifier ?: @""};
- if (matches) { [self reply:self.purchaseRequest data:cancelled ? data : nil error:cancelled ? nil : t.error.localizedDescription ?: @"支付失败。"]; self.purchaseRequest = nil; }
- [queue finishTransaction:t];
- continue;
- }
- NSDictionary *data = [self dataFor:t];
- if (t.transactionState == SKPaymentTransactionStateRestored && self.restoreRequest) [self.restored addObject:data];
- if (matches && t.transactionState != SKPaymentTransactionStateRestored) {
- [self reply:self.purchaseRequest data:data error:nil]; self.purchaseRequest = nil;
- }
- // Successful transactions remain in StoreKit until backend delivery is acknowledged.
- [self reply:@{@"method": @"transaction"} data:data error:nil];
- }
- });
- }
- - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self reply:self.restoreRequest data:self.restored ?: @[] error:nil];
- self.restoreRequest = nil; self.restored = nil;
- });
- }
- - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self reply:self.restoreRequest data:nil error:error.localizedDescription];
- self.restoreRequest = nil; self.restored = nil;
- });
- }
- @end
|