UnityPurchasing.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. #import "UnityPurchasing.h"
  2. #if MAC_APPSTORE
  3. #import "Base64.h"
  4. #endif
  5. @implementation ProductDefinition
  6. @synthesize id;
  7. @synthesize storeSpecificId;
  8. @synthesize type;
  9. @end
  10. @implementation ReceiptRefresher
  11. -(id) initWithCallback:(void (^)(BOOL))callbackBlock {
  12. self.callback = callbackBlock;
  13. return [super init];
  14. }
  15. -(void) requestDidFinish:(SKRequest *)request {
  16. self.callback(true);
  17. }
  18. -(void) request:(SKRequest *)request didFailWithError:(NSError *)error {
  19. self.callback(false);
  20. }
  21. @end
  22. void UnityPurchasingLog(NSString *format, ...) {
  23. va_list args;
  24. va_start(args, format);
  25. NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
  26. va_end(args);
  27. NSLog(@"UnityIAP:%@", message);
  28. }
  29. @implementation UnityPurchasing
  30. // The max time we wait in between retrying failed SKProductRequests.
  31. static const int MAX_REQUEST_PRODUCT_RETRY_DELAY = 60;
  32. // Track our accumulated delay.
  33. int delayInSeconds = 2;
  34. -(NSString*) getAppReceipt {
  35. NSBundle* bundle = [NSBundle mainBundle];
  36. if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
  37. NSURL *receiptURL = [bundle appStoreReceiptURL];
  38. if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
  39. NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
  40. #if MAC_APPSTORE
  41. // The base64EncodedStringWithOptions method was only added in OSX 10.9.
  42. NSString* result = [receipt mgb64_base64EncodedString];
  43. #else
  44. NSString* result = [receipt base64EncodedStringWithOptions:0];
  45. #endif
  46. return result;
  47. }
  48. }
  49. UnityPurchasingLog(@"No App Receipt found");
  50. return @"";
  51. }
  52. -(void) UnitySendMessage:(NSString*) subject payload:(NSString*) payload {
  53. messageCallback(subject.UTF8String, payload.UTF8String, @"".UTF8String, @"".UTF8String);
  54. }
  55. -(void) UnitySendMessage:(NSString*) subject payload:(NSString*) payload receipt:(NSString*) receipt {
  56. messageCallback(subject.UTF8String, payload.UTF8String, receipt.UTF8String, @"".UTF8String);
  57. }
  58. -(void) UnitySendMessage:(NSString*) subject payload:(NSString*) payload receipt:(NSString*) receipt transactionId:(NSString*) transactionId {
  59. messageCallback(subject.UTF8String, payload.UTF8String, receipt.UTF8String, transactionId.UTF8String);
  60. }
  61. -(void) setCallback:(UnityPurchasingCallback)callback {
  62. messageCallback = callback;
  63. }
  64. #if !MAC_APPSTORE
  65. -(BOOL) isiOS6OrEarlier {
  66. float version = [[[UIDevice currentDevice] systemVersion] floatValue];
  67. return version < 7;
  68. }
  69. #endif
  70. // Retrieve a receipt for the transaction, which will either
  71. // be the old style transaction receipt on <= iOS 6,
  72. // or the App Receipt in OSX and iOS 7+.
  73. -(NSString*) selectReceipt:(SKPaymentTransaction*) transaction {
  74. #if MAC_APPSTORE
  75. return [self getAppReceipt];
  76. #else
  77. if ([self isiOS6OrEarlier]) {
  78. if (nil == transaction) {
  79. return @"";
  80. }
  81. NSString* receipt;
  82. receipt = [[NSString alloc] initWithData:transaction.transactionReceipt encoding: NSUTF8StringEncoding];
  83. return receipt;
  84. } else {
  85. return [self getAppReceipt];
  86. }
  87. #endif
  88. }
  89. -(void) refreshReceipt {
  90. #if !MAC_APPSTORE
  91. if ([self isiOS6OrEarlier]) {
  92. UnityPurchasingLog(@"RefreshReceipt not supported on iOS < 7!");
  93. return;
  94. }
  95. #endif
  96. self.receiptRefresher = [[ReceiptRefresher alloc] initWithCallback:^(BOOL success) {
  97. UnityPurchasingLog(@"RefreshReceipt status %d", success);
  98. if (success) {
  99. [self UnitySendMessage:@"onAppReceiptRefreshed" payload:[self getAppReceipt]];
  100. } else {
  101. [self UnitySendMessage:@"onAppReceiptRefreshFailed" payload:nil];
  102. }
  103. }];
  104. self.refreshRequest = [[SKReceiptRefreshRequest alloc] init];
  105. self.refreshRequest.delegate = self.receiptRefresher;
  106. [self.refreshRequest start];
  107. }
  108. // Handle a new or restored purchase transaction by informing Unity.
  109. - (void)onTransactionSucceeded:(SKPaymentTransaction*)transaction {
  110. NSString* transactionId = transaction.transactionIdentifier;
  111. // This should never happen according to Apple's docs, but it does!
  112. if (nil == transactionId) {
  113. // Make something up, allowing us to identifiy the transaction when finishing it.
  114. transactionId = [[NSUUID UUID] UUIDString];
  115. UnityPurchasingLog(@"Missing transaction Identifier!");
  116. }
  117. // This transaction was marked as finished, but was not cleared from the queue. Try to clear it now, then pass the error up the stack as a DuplicateTransaction
  118. if ([finishedTransactions containsObject:transactionId]) {
  119. [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
  120. UnityPurchasingLog(@"DuplicateTransaction error with product %@ and transactionId %@", transaction.payment.productIdentifier, transactionId);
  121. [self onPurchaseFailed:transaction.payment.productIdentifier reason:@"DuplicateTransaction"];
  122. return; // EARLY RETURN
  123. }
  124. // Item was successfully purchased or restored.
  125. if (nil == [pendingTransactions objectForKey:transactionId]) {
  126. [pendingTransactions setObject:transaction forKey:transactionId];
  127. }
  128. [self UnitySendMessage:@"OnPurchaseSucceeded" payload:transaction.payment.productIdentifier receipt:[self selectReceipt:transaction] transactionId:transactionId];
  129. }
  130. // Called back by managed code when the tranaction has been logged.
  131. -(void) finishTransaction:(NSString *)transactionIdentifier {
  132. SKPaymentTransaction* transaction = [pendingTransactions objectForKey:transactionIdentifier];
  133. if (nil != transaction) {
  134. UnityPurchasingLog(@"Finishing transaction %@", transactionIdentifier);
  135. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; // If this fails (user not logged into the store?), transaction is already removed from pendingTransactions, so future calls to finishTransaction will not retry
  136. [pendingTransactions removeObjectForKey:transactionIdentifier];
  137. [finishedTransactions addObject:transactionIdentifier];
  138. } else {
  139. UnityPurchasingLog(@"Transaction %@ not found!", transactionIdentifier);
  140. }
  141. }
  142. // Request information about our products from Apple.
  143. -(void) requestProducts:(NSSet*)paramIds
  144. {
  145. productIds = paramIds;
  146. UnityPurchasingLog(@"Requesting %lu products", (unsigned long) [productIds count]);
  147. // Start an immediate poll.
  148. [self initiateProductPoll:0];
  149. }
  150. // Execute a product metadata retrieval request via GCD.
  151. -(void) initiateProductPoll:(int) delayInSeconds
  152. {
  153. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
  154. dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
  155. UnityPurchasingLog(@"Requesting product data...");
  156. request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIds];
  157. request.delegate = self;
  158. [request start];
  159. });
  160. }
  161. // Called by managed code when a user requests a purchase.
  162. -(void) purchaseProduct:(ProductDefinition*)productDef
  163. {
  164. // Look up our corresponding product.
  165. SKProduct* requestedProduct = [validProducts objectForKey:productDef.storeSpecificId];
  166. if (requestedProduct != nil) {
  167. UnityPurchasingLog(@"PurchaseProduct: %@", requestedProduct.productIdentifier);
  168. if ([SKPaymentQueue canMakePayments]) {
  169. SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestedProduct];
  170. // Modify payment request for testing ask-to-buy
  171. if (_simulateAskToBuyEnabled) {
  172. if ([payment respondsToSelector:@selector(setSimulatesAskToBuyInSandbox:)]) {
  173. UnityPurchasingLog(@"Queueing payment request with simulatesAskToBuyInSandbox enabled");
  174. [payment performSelector:@selector(setSimulatesAskToBuyInSandbox:) withObject:@YES];
  175. //payment.simulatesAskToBuyInSandbox = YES;
  176. }
  177. }
  178. // Modify payment request with "applicationUsername" for fraud detection
  179. if (_applicationUsername != nil) {
  180. if ([payment respondsToSelector:@selector(setApplicationUsername:)]) {
  181. UnityPurchasingLog(@"Setting applicationUsername to %@", _applicationUsername);
  182. [payment performSelector:@selector(setApplicationUsername:) withObject:_applicationUsername];
  183. //payment.applicationUsername = _applicationUsername;
  184. }
  185. }
  186. [[SKPaymentQueue defaultQueue] addPayment:payment];
  187. } else {
  188. UnityPurchasingLog(@"PurchaseProduct: IAP Disabled");
  189. [self onPurchaseFailed:productDef.storeSpecificId reason:@"PurchasingUnavailable"];
  190. }
  191. } else {
  192. [self onPurchaseFailed:productDef.storeSpecificId reason:@"ItemUnavailable"];
  193. }
  194. }
  195. // Initiate a request to Apple to restore previously made purchases.
  196. -(void) restorePurchases
  197. {
  198. UnityPurchasingLog(@"RestorePurchase");
  199. [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
  200. }
  201. // A transaction observer should be added at startup (by managed code)
  202. // and maintained for the life of the app, since transactions can
  203. // be delivered at any time.
  204. -(void) addTransactionObserver {
  205. SKPaymentQueue* defaultQueue = [SKPaymentQueue defaultQueue];
  206. // Detect whether an existing transaction observer is in place.
  207. // An existing observer will have processed any transactions already pending,
  208. // so when we add our own storekit will not call our updatedTransactions handler.
  209. // We workaround this by explicitly processing any existing transactions if they exist.
  210. BOOL processExistingTransactions = false;
  211. if (defaultQueue != nil && defaultQueue.transactions != nil)
  212. {
  213. if ([[defaultQueue transactions] count] > 0) {
  214. processExistingTransactions = true;
  215. }
  216. }
  217. [defaultQueue addTransactionObserver:self];
  218. if (processExistingTransactions) {
  219. [self paymentQueue:defaultQueue updatedTransactions:defaultQueue.transactions];
  220. }
  221. }
  222. #pragma mark -
  223. #pragma mark SKProductsRequestDelegate Methods
  224. // Store Kit returns a response from an SKProductsRequest.
  225. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
  226. UnityPurchasingLog(@"Received %lu products", (unsigned long) [response.products count]);
  227. // Add the retrieved products to our set of valid products.
  228. NSDictionary* fetchedProducts = [NSDictionary dictionaryWithObjects:response.products forKeys:[response.products valueForKey:@"productIdentifier"]];
  229. [validProducts addEntriesFromDictionary:fetchedProducts];
  230. NSString* productJSON = [UnityPurchasing serializeProductMetadata:response.products];
  231. // Send the app receipt as a separate parameter to avoid JSON parsing a large string.
  232. [self UnitySendMessage:@"OnProductsRetrieved" payload:productJSON receipt:[self selectReceipt:nil] ];
  233. }
  234. #pragma mark -
  235. #pragma mark SKPaymentTransactionObserver Methods
  236. // A product metadata retrieval request failed.
  237. // We handle it by retrying at an exponentially increasing interval.
  238. - (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
  239. delayInSeconds = MIN(MAX_REQUEST_PRODUCT_RETRY_DELAY, 2 * delayInSeconds);
  240. UnityPurchasingLog(@"SKProductRequest::didFailWithError: %ld, %@. Unity Purchasing will retry in %i seconds", (long)error.code, error.description, delayInSeconds);
  241. [self initiateProductPoll:delayInSeconds];
  242. }
  243. - (void)requestDidFinish:(SKRequest *)req {
  244. request = nil;
  245. }
  246. - (void)onPurchaseFailed:(NSString*) productId reason:(NSString*)reason {
  247. NSMutableDictionary* dic = [[NSMutableDictionary alloc] init];
  248. [dic setObject:productId forKey:@"productId"];
  249. [dic setObject:reason forKey:@"reason"];
  250. NSData* data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
  251. NSString* result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  252. [self UnitySendMessage:@"OnPurchaseFailed" payload:result];
  253. }
  254. - (NSString*)purchaseErrorCodeToReason:(NSInteger) errorCode {
  255. switch (errorCode) {
  256. case SKErrorPaymentCancelled:
  257. return @"UserCancelled";
  258. case SKErrorPaymentInvalid:
  259. return @"PaymentDeclined";
  260. case SKErrorPaymentNotAllowed:
  261. return @"PurchasingUnavailable";
  262. }
  263. return @"Unknown";
  264. }
  265. // The transaction status of the SKPaymentQueue is sent here.
  266. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
  267. UnityPurchasingLog(@"UpdatedTransactions");
  268. for(SKPaymentTransaction *transaction in transactions) {
  269. switch (transaction.transactionState) {
  270. case SKPaymentTransactionStatePurchasing:
  271. // Item is still in the process of being purchased
  272. break;
  273. case SKPaymentTransactionStatePurchased:
  274. case SKPaymentTransactionStateRestored: {
  275. [self onTransactionSucceeded:transaction];
  276. break;
  277. }
  278. case SKPaymentTransactionStateDeferred:
  279. UnityPurchasingLog(@"PurchaseDeferred");
  280. [self UnitySendMessage:@"onProductPurchaseDeferred" payload:transaction.payment.productIdentifier];
  281. break;
  282. case SKPaymentTransactionStateFailed: {
  283. // Purchase was either cancelled by user or an error occurred.
  284. NSString* errorCode = [NSString stringWithFormat:@"%ld",(long)transaction.error.code];
  285. UnityPurchasingLog(@"PurchaseFailed: %@", errorCode);
  286. NSString* reason = [self purchaseErrorCodeToReason:transaction.error.code];
  287. [self onPurchaseFailed:transaction.payment.productIdentifier reason:reason];
  288. // Finished transactions should be removed from the payment queue.
  289. [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
  290. }
  291. break;
  292. }
  293. }
  294. }
  295. // Called when one or more transactions have been removed from the queue.
  296. - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
  297. {
  298. // Nothing to do here.
  299. }
  300. // Called when SKPaymentQueue has finished sending restored transactions.
  301. - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
  302. UnityPurchasingLog(@"PaymentQueueRestoreCompletedTransactionsFinished");
  303. [self UnitySendMessage:@"onTransactionsRestoredSuccess" payload:@""];
  304. }
  305. // Called if an error occurred while restoring transactions.
  306. - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
  307. {
  308. UnityPurchasingLog(@"restoreCompletedTransactionsFailedWithError");
  309. // Restore was cancelled or an error occurred, so notify user.
  310. [self UnitySendMessage:@"onTransactionsRestoredFail" payload:error.localizedDescription];
  311. }
  312. +(ProductDefinition*) decodeProductDefinition:(NSDictionary*) hash
  313. {
  314. ProductDefinition* product = [[ProductDefinition alloc] init];
  315. product.id = [hash objectForKey:@"id"];
  316. product.storeSpecificId = [hash objectForKey:@"storeSpecificId"];
  317. product.type = [hash objectForKey:@"type"];
  318. return product;
  319. }
  320. + (NSArray*) deserializeProductDefs:(NSString*)json
  321. {
  322. NSData* data = [json dataUsingEncoding:NSUTF8StringEncoding];
  323. NSArray* hashes = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
  324. NSMutableArray* result = [[NSMutableArray alloc] init];
  325. for (NSDictionary* hash in hashes) {
  326. [result addObject:[self decodeProductDefinition:hash]];
  327. }
  328. return result;
  329. }
  330. + (ProductDefinition*) deserializeProductDef:(NSString*)json
  331. {
  332. NSData* data = [json dataUsingEncoding:NSUTF8StringEncoding];
  333. NSDictionary* hash = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
  334. return [self decodeProductDefinition:hash];
  335. }
  336. + (NSString*) serializeProductMetadata:(NSArray*)appleProducts
  337. {
  338. NSMutableArray* hashes = [[NSMutableArray alloc] init];
  339. for (id product in appleProducts) {
  340. if (NULL == [product productIdentifier]) {
  341. UnityPurchasingLog(@"Product is missing an identifier!");
  342. continue;
  343. }
  344. NSMutableDictionary* hash = [[NSMutableDictionary alloc] init];
  345. [hashes addObject:hash];
  346. [hash setObject:[product productIdentifier] forKey:@"storeSpecificId"];
  347. NSMutableDictionary* metadata = [[NSMutableDictionary alloc] init];
  348. [hash setObject:metadata forKey:@"metadata"];
  349. if (NULL != [product price]) {
  350. [metadata setObject:[product price] forKey:@"localizedPrice"];
  351. }
  352. if (NULL != [product priceLocale]) {
  353. NSString *currencyCode = [[product priceLocale] objectForKey:NSLocaleCurrencyCode];
  354. [metadata setObject:currencyCode forKey:@"isoCurrencyCode"];
  355. }
  356. NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
  357. [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
  358. [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
  359. [numberFormatter setLocale:[product priceLocale]];
  360. NSString *formattedString = [numberFormatter stringFromNumber:[product price]];
  361. if (NULL == formattedString) {
  362. UnityPurchasingLog(@"Unable to format a localized price");
  363. [metadata setObject:@"" forKey:@"localizedPriceString"];
  364. } else {
  365. [metadata setObject:formattedString forKey:@"localizedPriceString"];
  366. }
  367. if (NULL == [product localizedTitle]) {
  368. UnityPurchasingLog(@"No localized title for: %@. Have your products been disapproved in itunes connect?", [product productIdentifier]);
  369. [metadata setObject:@"" forKey:@"localizedTitle"];
  370. } else {
  371. [metadata setObject:[product localizedTitle] forKey:@"localizedTitle"];
  372. }
  373. if (NULL == [product localizedDescription]) {
  374. UnityPurchasingLog(@"No localized description for: %@. Have your products been disapproved in itunes connect?", [product productIdentifier]);
  375. [metadata setObject:@"" forKey:@"localizedDescription"];
  376. } else {
  377. [metadata setObject:[product localizedDescription] forKey:@"localizedDescription"];
  378. }
  379. }
  380. NSData *data = [NSJSONSerialization dataWithJSONObject:hashes options:0 error:nil];
  381. return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  382. }
  383. #pragma mark - Internal Methods & Events
  384. - (id)init {
  385. if ( self = [super init] ) {
  386. validProducts = [[NSMutableDictionary alloc] init];
  387. pendingTransactions = [[NSMutableDictionary alloc] init];
  388. finishedTransactions = [[NSMutableSet alloc] init];
  389. }
  390. return self;
  391. }
  392. @end
  393. UnityPurchasing* UnityPurchasing_instance = NULL;
  394. UnityPurchasing* UnityPurchasing_getInstance() {
  395. if (NULL == UnityPurchasing_instance) {
  396. UnityPurchasing_instance = [[UnityPurchasing alloc] init];
  397. }
  398. return UnityPurchasing_instance;
  399. }
  400. // Make a heap allocated copy of a string.
  401. // This is suitable for passing to managed code,
  402. // which will free the string when it is garbage collected.
  403. // Stack allocated variables must not be returned as results
  404. // from managed to native calls.
  405. char* UnityPurchasingMakeHeapAllocatedStringCopy (NSString* string)
  406. {
  407. if (NULL == string) {
  408. return NULL;
  409. }
  410. char* res = (char*)malloc([string length] + 1);
  411. strcpy(res, [string UTF8String]);
  412. return res;
  413. }
  414. void setUnityPurchasingCallback(UnityPurchasingCallback callback) {
  415. [UnityPurchasing_getInstance() setCallback:callback];
  416. }
  417. void unityPurchasingRetrieveProducts(const char* json) {
  418. NSString* str = [NSString stringWithUTF8String:json];
  419. NSArray* productDefs = [UnityPurchasing deserializeProductDefs:str];
  420. NSMutableSet* productIds = [[NSMutableSet alloc] init];
  421. for (ProductDefinition* product in productDefs) {
  422. [productIds addObject:product.storeSpecificId];
  423. }
  424. [UnityPurchasing_getInstance() requestProducts:productIds];
  425. }
  426. void unityPurchasingPurchase(const char* json, const char* developerPayload) {
  427. NSString* str = [NSString stringWithUTF8String:json];
  428. ProductDefinition* product = [UnityPurchasing deserializeProductDef:str];
  429. [UnityPurchasing_getInstance() purchaseProduct:product];
  430. }
  431. void unityPurchasingFinishTransaction(const char* productJSON, const char* transactionId) {
  432. if (transactionId == NULL)
  433. return;
  434. NSString* tranId = [NSString stringWithUTF8String:transactionId];
  435. [UnityPurchasing_getInstance() finishTransaction:tranId];
  436. }
  437. void unityPurchasingRestoreTransactions() {
  438. UnityPurchasingLog(@"restoreTransactions");
  439. [UnityPurchasing_getInstance() restorePurchases];
  440. }
  441. void unityPurchasingAddTransactionObserver() {
  442. UnityPurchasingLog(@"addTransactionObserver");
  443. [UnityPurchasing_getInstance() addTransactionObserver];
  444. }
  445. void unityPurchasingRefreshAppReceipt() {
  446. UnityPurchasingLog(@"refreshAppReceipt");
  447. [UnityPurchasing_getInstance() refreshReceipt];
  448. }
  449. char* getUnityPurchasingAppReceipt () {
  450. NSString* receipt = [UnityPurchasing_getInstance() getAppReceipt];
  451. return UnityPurchasingMakeHeapAllocatedStringCopy(receipt);
  452. }
  453. BOOL getUnityPurchasingCanMakePayments () {
  454. return [SKPaymentQueue canMakePayments];
  455. }
  456. void setSimulateAskToBuy(BOOL enabled) {
  457. UnityPurchasingLog(@"setSimulateAskToBuy %@", enabled ? @"true" : @"false");
  458. UnityPurchasing_getInstance().simulateAskToBuyEnabled = enabled;
  459. }
  460. BOOL getSimulateAskToBuy() {
  461. return UnityPurchasing_getInstance().simulateAskToBuyEnabled;
  462. }
  463. void unityPurchasingSetApplicationUsername(const char *username) {
  464. if (username == NULL)
  465. return;
  466. UnityPurchasing_getInstance().applicationUsername = [NSString stringWithUTF8String:username];
  467. }