IAPWrapper.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. #import "IAPWrapper.h"
  2. #import <StoreKit/StoreKit.h>
  3. #import "platform/apple/JsbBridgeWrapper.h"
  4. #include "application/ApplicationManager.h"
  5. #include "application/BaseApplication.h"
  6. #include "base/Scheduler.h"
  7. #include "engine/BaseEngine.h"
  8. @interface IAPWrapper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
  9. @property(nonatomic, strong) NSMutableDictionary<NSString *, SKProduct *> *products;
  10. @property(nonatomic, strong) SKProductsRequest *productsRequest;
  11. @property(nonatomic, strong) NSDictionary *catalogRequest;
  12. @property(nonatomic, strong) NSDictionary *purchaseRequest;
  13. @property(nonatomic, strong) NSDictionary *restoreRequest;
  14. @property(nonatomic, strong) NSMutableArray *restored;
  15. @end
  16. @implementation IAPWrapper
  17. + (instancetype)shared {
  18. static IAPWrapper *instance;
  19. static dispatch_once_t once;
  20. dispatch_once(&once, ^{ instance = [IAPWrapper new]; instance.products = [NSMutableDictionary new]; });
  21. return instance;
  22. }
  23. - (void)start {
  24. static dispatch_once_t once;
  25. dispatch_once(&once, ^{
  26. // Global, capture-free listener survives Creator 3.8.8 MRC ownership.
  27. static OnScriptEventListener const listener = ^(NSString *payload) {
  28. dispatch_async(dispatch_get_main_queue(), ^{ [[IAPWrapper shared] handle:payload]; });
  29. };
  30. [[JsbBridgeWrapper sharedInstance] addScriptEventListener:@"iap.request" listener:listener];
  31. [SKPaymentQueue.defaultQueue addTransactionObserver:self];
  32. });
  33. }
  34. - (void)reply:(NSDictionary *)request data:(id)data error:(NSString *)error {
  35. if (!request) return;
  36. NSMutableDictionary *response = [@{@"requestId": request[@"requestId"] ?: @"",
  37. @"method": request[@"method"] ?: @"", @"success": @(!error)} mutableCopy];
  38. if (data) response[@"data"] = data;
  39. if (error) response[@"error"] = error;
  40. NSData *json = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil];
  41. NSString *payload = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
  42. auto app = CC_CURRENT_APPLICATION();
  43. if (!app || !app->getEngine() || !payload) return;
  44. app->getEngine()->getScheduler()->performFunctionInCocosThread([payload]() {
  45. [[JsbBridgeWrapper sharedInstance] dispatchEventToScript:@"iap.response" arg:payload];
  46. });
  47. }
  48. - (void)purchaseFailure:(NSDictionary *)request code:(NSString *)code reason:(NSString *)reason nativeError:(NSError *)error {
  49. id productID = request[@"params"][@"productId"];
  50. NSMutableDictionary *data = [@{@"status": @"error", @"errorCode": code, @"reason": reason,
  51. @"productId": [productID isKindOfClass:NSString.class] ? productID : @""} mutableCopy];
  52. if (error) data[@"nativeError"] = @{@"domain": error.domain, @"code": @(error.code)};
  53. // The bridge request completed; the purchase itself failed (data.status).
  54. [self reply:request data:data error:nil];
  55. }
  56. - (NSDictionary *)dataFor:(SKPaymentTransaction *)transaction {
  57. NSURL *url = NSBundle.mainBundle.appStoreReceiptURL;
  58. NSData *receipt = url ? [NSData dataWithContentsOfURL:url] : nil;
  59. NSString *status = transaction.transactionState == SKPaymentTransactionStateRestored ? @"restored" :
  60. transaction.transactionState == SKPaymentTransactionStateDeferred ? @"deferred" : @"purchased";
  61. return @{@"status": status, @"productId": transaction.payment.productIdentifier ?: @"",
  62. @"transactionId": transaction.transactionIdentifier ?: @"",
  63. @"originalTransactionId": transaction.originalTransaction.transactionIdentifier ?: transaction.transactionIdentifier ?: @"",
  64. @"receipt": [receipt base64EncodedStringWithOptions:0] ?: @""};
  65. }
  66. - (NSArray *)pending {
  67. NSMutableArray *items = [NSMutableArray new];
  68. for (SKPaymentTransaction *t in SKPaymentQueue.defaultQueue.transactions) {
  69. if (t.transactionState == SKPaymentTransactionStatePurchased || t.transactionState == SKPaymentTransactionStateRestored ||
  70. t.transactionState == SKPaymentTransactionStateDeferred) [items addObject:[self dataFor:t]];
  71. }
  72. return items;
  73. }
  74. - (void)handle:(NSString *)payload {
  75. id decoded = [NSJSONSerialization JSONObjectWithData:[payload dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
  76. if (![decoded isKindOfClass:NSDictionary.class]) return;
  77. NSDictionary *r = decoded;
  78. if (![r[@"requestId"] isKindOfClass:NSString.class] || ![r[@"method"] isKindOfClass:NSString.class]) return;
  79. NSString *method = r[@"method"];
  80. NSDictionary *p = [r[@"params"] isKindOfClass:NSDictionary.class] ? r[@"params"] : @{};
  81. if ([method isEqualToString:@"init"]) {
  82. if (self.productsRequest) { [self reply:r data:nil error:@"正在查询商品,请稍后重试。"]; return; }
  83. NSArray *ids = p[@"productIds"];
  84. if (![ids isKindOfClass:NSArray.class] || !ids.count) { [self reply:r data:nil error:@"商品列表不能为空。"]; return; }
  85. for (id value in ids) if (![value isKindOfClass:NSString.class] || ![value length]) {
  86. [self reply:r data:nil error:@"商品 ID 必须为非空字符串。"]; return;
  87. }
  88. self.catalogRequest = r;
  89. self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:ids]];
  90. self.productsRequest.delegate = self;
  91. [self.productsRequest start];
  92. } else if ([method isEqualToString:@"purchase"]) {
  93. if (self.purchaseRequest || self.restoreRequest) {
  94. [self purchaseFailure:r code:@"OPERATION_IN_PROGRESS" reason:@"已有购买或恢复操作进行中。" nativeError:nil]; return;
  95. }
  96. if (SKPaymentQueue.defaultQueue.transactions.count) {
  97. [self purchaseFailure:r code:@"PENDING_TRANSACTIONS" reason:@"存在未完成或待审批交易,请先处理或等待。" nativeError:nil]; return;
  98. }
  99. NSString *productID = p[@"productId"];
  100. SKProduct *product = [productID isKindOfClass:NSString.class] ? self.products[productID] : nil;
  101. if (![productID isKindOfClass:NSString.class] || !productID.length) {
  102. [self purchaseFailure:r code:@"INVALID_PRODUCT_ID" reason:@"商品 ID 不能为空。" nativeError:nil]; return;
  103. }
  104. if (!product) {
  105. [self purchaseFailure:r code:@"PRODUCT_NOT_LOADED" reason:@"尚未加载此商品,请检查初始化返回的 products 和 invalidProductIds。" nativeError:nil]; return;
  106. }
  107. if (![SKPaymentQueue canMakePayments]) {
  108. [self purchaseFailure:r code:@"PAYMENTS_NOT_ALLOWED" reason:@"设备不允许内购,请检查购买限制。" nativeError:nil]; return;
  109. }
  110. self.purchaseRequest = r;
  111. [SKPaymentQueue.defaultQueue addPayment:[SKPayment paymentWithProduct:product]];
  112. } else if ([method isEqualToString:@"restore"]) {
  113. if (self.restoreRequest || self.purchaseRequest) { [self reply:r data:nil error:@"已有购买或恢复操作进行中。"]; return; }
  114. self.restoreRequest = r;
  115. self.restored = [NSMutableArray new];
  116. [SKPaymentQueue.defaultQueue restoreCompletedTransactions];
  117. } else if ([method isEqualToString:@"pending"]) {
  118. [self reply:r data:[self pending] error:nil];
  119. } else if ([method isEqualToString:@"finish"]) {
  120. NSString *identifier = p[@"transactionId"];
  121. if (![identifier isKindOfClass:NSString.class] || !identifier.length) { [self reply:r data:nil error:@"无效的交易 ID。"]; return; }
  122. for (SKPaymentTransaction *t in SKPaymentQueue.defaultQueue.transactions) {
  123. if ([t.transactionIdentifier isEqualToString:identifier] &&
  124. (t.transactionState == SKPaymentTransactionStatePurchased || t.transactionState == SKPaymentTransactionStateRestored)) {
  125. [SKPaymentQueue.defaultQueue finishTransaction:t];
  126. [self reply:r data:@{} error:nil]; return;
  127. }
  128. }
  129. [self reply:r data:nil error:@"未找到可完成的交易,请查询未完成交易。"];
  130. } else { [self reply:r data:nil error:@"不支持的内购方法。"]; }
  131. }
  132. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
  133. dispatch_async(dispatch_get_main_queue(), ^{
  134. if (request != self.productsRequest) return;
  135. NSMutableArray *items = [NSMutableArray new];
  136. for (SKProduct *product in response.products) {
  137. self.products[product.productIdentifier] = product;
  138. NSNumberFormatter *formatter = [NSNumberFormatter new];
  139. formatter.numberStyle = NSNumberFormatterCurrencyStyle;
  140. formatter.locale = product.priceLocale;
  141. [items addObject:@{@"productId": product.productIdentifier, @"title": product.localizedTitle ?: @"",
  142. @"description": product.localizedDescription ?: @"", @"price": product.price.stringValue,
  143. @"localizedPrice": [formatter stringFromNumber:product.price] ?: @"", @"currencyCode": formatter.currencyCode ?: @""}];
  144. }
  145. [self reply:self.catalogRequest data:@{@"products": items, @"invalidProductIds": response.invalidProductIdentifiers} error:nil];
  146. self.catalogRequest = nil; self.productsRequest = nil;
  147. });
  148. }
  149. - (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
  150. dispatch_async(dispatch_get_main_queue(), ^{
  151. if (request != self.productsRequest) return;
  152. [self reply:self.catalogRequest data:nil error:error.localizedDescription];
  153. self.catalogRequest = nil; self.productsRequest = nil;
  154. });
  155. }
  156. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
  157. dispatch_async(dispatch_get_main_queue(), ^{
  158. for (SKPaymentTransaction *t in transactions) {
  159. if (t.transactionState == SKPaymentTransactionStatePurchasing) continue;
  160. BOOL matches = [self.purchaseRequest[@"params"][@"productId"] isEqualToString:t.payment.productIdentifier];
  161. if (t.transactionState == SKPaymentTransactionStateFailed) {
  162. BOOL cancelled = t.error.code == SKErrorPaymentCancelled && [t.error.domain isEqualToString:SKErrorDomain];
  163. NSDictionary *data = @{@"status": @"cancelled", @"productId": t.payment.productIdentifier ?: @""};
  164. if (matches) {
  165. if (cancelled) [self reply:self.purchaseRequest data:data error:nil];
  166. else [self purchaseFailure:self.purchaseRequest code:@"PAYMENT_FAILED" reason:t.error.localizedDescription ?: @"支付失败。" nativeError:t.error];
  167. self.purchaseRequest = nil;
  168. }
  169. [queue finishTransaction:t];
  170. continue;
  171. }
  172. NSDictionary *data = [self dataFor:t];
  173. if (t.transactionState == SKPaymentTransactionStateRestored && self.restoreRequest) [self.restored addObject:data];
  174. if (matches && t.transactionState != SKPaymentTransactionStateRestored) {
  175. [self reply:self.purchaseRequest data:data error:nil]; self.purchaseRequest = nil;
  176. }
  177. // Successful transactions remain in StoreKit until backend delivery is acknowledged.
  178. [self reply:@{@"method": @"transaction"} data:data error:nil];
  179. }
  180. });
  181. }
  182. - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
  183. dispatch_async(dispatch_get_main_queue(), ^{
  184. [self reply:self.restoreRequest data:self.restored ?: @[] error:nil];
  185. self.restoreRequest = nil; self.restored = nil;
  186. });
  187. }
  188. - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
  189. dispatch_async(dispatch_get_main_queue(), ^{
  190. [self reply:self.restoreRequest data:nil error:error.localizedDescription];
  191. self.restoreRequest = nil; self.restored = nil;
  192. });
  193. }
  194. @end