增加ios内购

This commit is contained in:
CounterFire2023 2023-08-03 13:14:23 +08:00
parent 13bd51e38e
commit 1c5faa5afd
20 changed files with 931 additions and 234 deletions

View File

@ -66,11 +66,11 @@ bool AppDelegate::applicationDidFinishLaunching()
se->start(); se->start();
se::AutoHandleScope hs; se::AutoHandleScope hs;
jsb_run_script("js/jsb-adapter/jsb-builtin.js"); jsb_run_script("Data/js/jsb-adapter/jsb-builtin.js");
jsb_run_script("js/jcwallet.js"); jsb_run_script("Data/js/jcwallet.js");
jsb_run_script("js/platform.js"); jsb_run_script("Data/js/platform.js");
jsb_run_script("js/main.js"); jsb_run_script("Data/js/main.js");
jsb_run_script("js/wallet.js"); jsb_run_script("Data/js/wallet.js");
se->addAfterCleanupHook([]() { se->addAfterCleanupHook([]() {
JSBClassType::destroy(); JSBClassType::destroy();
}); });

View File

@ -17,6 +17,7 @@
#include "AppDelegate.h" #include "AppDelegate.h"
#import "UIViewController+Wallet.h" #import "UIViewController+Wallet.h"
#import "UIViewController+QR.h" #import "UIViewController+QR.h"
#import "UIViewController+Purchase.h"
#endif #endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
@ -460,6 +461,110 @@ bool jsb_showWebPage(se::State& s) {
} }
SE_BIND_FUNC(jsb_showWebPage) SE_BIND_FUNC(jsb_showWebPage)
static bool JSB_queryProducts(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc > 1) {
std::string funid;
ok = seval_to_std_string(args[0], &funid);
SE_PRECONDITION2(ok, false, "funid is invalid!");
std::string param0;
ok = seval_to_std_string(args[1], &param0);
SE_PRECONDITION2(ok, false, "param0 is invalid!");
NSString *nParams = [NSString stringWithCString:param0.c_str() encoding: NSUTF8StringEncoding];
NSString *nfunid = [NSString stringWithCString:funid.c_str() encoding: NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
[window.rootViewController queryProducts:nfunid products: nParams];
});
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
return false;
}
SE_BIND_FUNC(JSB_queryProducts)
static bool JSB_queryPurchase(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc > 0) {
std::string funid;
ok = seval_to_std_string(args[0], &funid);
SE_PRECONDITION2(ok, false, "funid is invalid!");
NSString *nfunid = [NSString stringWithCString:funid.c_str() encoding: NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
[window.rootViewController queryPurchase:nfunid ];
});
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
return false;
}
SE_BIND_FUNC(JSB_queryPurchase)
static bool JSB_finishTransaction(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc > 1) {
std::string funid;
ok = seval_to_std_string(args[0], &funid);
SE_PRECONDITION2(ok, false, "funid is invalid!");
std::string transactionid;
ok = seval_to_std_string(args[1], &transactionid);
SE_PRECONDITION2(ok, false, "transactionid is invalid!");
NSString *nfunid = [NSString stringWithCString:funid.c_str() encoding: NSUTF8StringEncoding];
NSString *ntransactionid = [NSString stringWithCString:transactionid.c_str() encoding: NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
[window.rootViewController finishTransaction:nfunid transactionId:ntransactionid ];
});
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
return false;
}
SE_BIND_FUNC(JSB_finishTransaction)
static bool JSB_beginBuy(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc > 2) {
std::string funid;
ok = seval_to_std_string(args[0], &funid);
SE_PRECONDITION2(ok, false, "funid is invalid!");
std::string param0;
ok = seval_to_std_string(args[1], &param0);
SE_PRECONDITION2(ok, false, "param0 is invalid!");
std::string param1;
ok = seval_to_std_string(args[2], &param1);
SE_PRECONDITION2(ok, false, "param1 is invalid!");
NSString *nParam0 = [NSString stringWithCString:param0.c_str() encoding: NSUTF8StringEncoding];
NSString *nParam1 = [NSString stringWithCString:param1.c_str() encoding: NSUTF8StringEncoding];
NSString *nfunid = [NSString stringWithCString:funid.c_str() encoding: NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
[window.rootViewController beginBuy:nfunid productId: nParam0 orderId:nParam1];
});
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3);
return false;
}
SE_BIND_FUNC(JSB_beginBuy)
bool jsb_register_walletevent_modules(se::Object* global) { bool jsb_register_walletevent_modules(se::Object* global) {
getOrCreatePlainObject_r("jsb", global, &__jsbObj); getOrCreatePlainObject_r("jsb", global, &__jsbObj);
@ -473,6 +578,10 @@ bool jsb_register_walletevent_modules(se::Object* global) {
__jsbObj->defineFunction("signOutGoogle", _SE(jsb_signOutGoogle)); __jsbObj->defineFunction("signOutGoogle", _SE(jsb_signOutGoogle));
__jsbObj->defineFunction("showQRCode", _SE(JSB_showQRCode)); __jsbObj->defineFunction("showQRCode", _SE(JSB_showQRCode));
__jsbObj->defineFunction("showWebPage", _SE(jsb_showWebPage)); __jsbObj->defineFunction("showWebPage", _SE(jsb_showWebPage));
__jsbObj->defineFunction("queryProducts", _SE(JSB_queryProducts));
__jsbObj->defineFunction("queryPurchase", _SE(JSB_queryPurchase));
__jsbObj->defineFunction("finishTransaction", _SE(JSB_finishTransaction));
__jsbObj->defineFunction("beginBuy", _SE(JSB_beginBuy));
// JSB_signWithEmail // JSB_signWithEmail
// JSB_beginBuyJNI // JSB_beginBuyJNI
return true; return true;

View File

@ -1,5 +1,5 @@
// //
// UIViewController+Logger.h // UIViewController+Purchase.h
// Unity-iPhone // Unity-iPhone
// //
// Created by Hl Zhang on 2023/3/21. // Created by Hl Zhang on 2023/3/21.
@ -9,8 +9,10 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface UIViewController (Purchase) @interface UIViewController (Purchase)
- (void)initPurchaseEnv; - (void)initPurchaseEnv;
- (void)queryProducts:(NSString *)funId products:(NSString *)products;
- (void)queryPurchase:(NSString *)funId;
- (void)finishTransaction:(NSString *)funId transactionId: (NSString *) transactionId;
- (void)beginBuy:(NSString *)funId productId:(NSString *)productId orderId:(NSString *)orderId;
@end @end

View File

@ -1,64 +1,157 @@
// //
// UIViewController+Logger.cpp // UIViewController+Purchase.cpp
// Unity-iPhone // Unity-iPhone
// //
// Created by Hl Zhang on 2023/3/21. // Created by Hl Zhang on 2023/3/21.
// //
#import "UIViewController+Purchase.h" #import "UIViewController+Purchase.h"
#import "UIViewController+Wallet.h"
#import "Utilities.h" #import "Utilities.h"
#import "StoreManager.h" #import "StoreManager.h"
#import "StoreObserver.h" #import "StoreObserver.h"
#import "AppConfiguration.h" #import "AppConfiguration.h"
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "SKProduct+SKProductAdditions.h"
#include <string>
#import "NSString+Customer.h"
#include "JcWallet.h"
static Utilities *utility; static Utilities *utility = nil;
@interface UIViewController (Purchase)
@property (nonatomic) NSString *currentFunId;
@end
@implementation UIViewController (Purchase) @implementation UIViewController (Purchase)
-(void)initPurchaseEnv { -(void)initPurchaseEnv {
utility = [[Utilities alloc] init]; utility = [[Utilities alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self // [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleProductRequestNotification:) // selector:@selector(handleProductRequestNotification:)
name:PCSProductRequestNotification // name:PCSProductRequestNotification
object:[StoreManager sharedInstance]]; // object:[StoreManager sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handlePurchaseNotification:) selector:@selector(handlePurchaseNotification:)
name:PCSPurchaseNotification name:PCSPurchaseNotification
object:[StoreObserver sharedInstance]]; object:[StoreObserver sharedInstance]];
} }
#pragma mark - Query products
/// Retrieves product information from the App Store. /// Retrieves product information from the App Store.
-(void)fetchProductInformation { - (void)queryProducts:(NSString *) funId products: (NSString *) products {
if (utility == nil) {
[self initPurchaseEnv];
}
if (products == nil || [products isEqualToString:@""]) {
NSLog(@"queryProducts with empty products: %@", products);
[self nativeCb:funId hasErr:YES dataStr:@"queryProducts with empty products"];
return;
}
// split products to array
NSArray *identifiers = [products componentsSeparatedByString:@","];
// First, let's check whether the user is allowed to make purchases. Proceed if they are allowed. Display an alert, otherwise. // First, let's check whether the user is allowed to make purchases. Proceed if they are allowed. Display an alert, otherwise.
if ([StoreObserver sharedInstance].isAuthorizedForPayments) { if ([StoreObserver sharedInstance].isAuthorizedForPayments) {
NSArray *identifiers = utility.identifiers;
if (identifiers != nil) {
if (identifiers.count > 0) {
Section *section = [[Section alloc] initWithName:PCSProductsInvalidIdentifiers elements:identifiers];
// Refresh the UI with identifiers to be queried.
// [self switchToViewController:ParentViewControllerSegmentProducts];
// [self.products reloadWithData:[NSMutableArray arrayWithObject:section]];
// Fetch the product information. // Fetch the product information.
[[StoreManager sharedInstance] startProductRequestWithIdentifiers:identifiers]; [[StoreManager sharedInstance] fetchProductsMatchingIdentifiers:identifiers completionBlock: ^(PCSProductRequestStatus status, NSMutableArray *storeResponse){
} else { if (status == PCSStoreResponse) {
// Warn the user that the resource file does not contain anything. // define json array to return
[self alertWithTitle:PCSMessagesStatus message:[NSString stringWithFormat:@"%@.%@ %@", PCSProductIdsPlistName, PCSProductIdsPlistFileExtension, PCSMessagesEmptyResource]]; NSMutableArray *jsonArray = [[NSMutableArray alloc] init];
for (Section *section in storeResponse) {
NSArray *content = section.elements;
if ([section.name isEqualToString:PCSProductsAvailableProducts]) {
for (SKProduct *product in content) {
NSMutableDictionary *json = [[NSMutableDictionary alloc] init];
[json setObject:product.productIdentifier forKey:@"id"];
[json setObject:product.localizedTitle forKey:@"name"];
[json setObject:product.localizedDescription forKey:@"description"];
[json setObject:product.priceLocale.currencyCode forKey:@"currencyCode"];
[json setObject:product.price.stringValue forKey:@"priceValue"];
[json setObject:product.regularPrice forKey:@"priceShow"];
[jsonArray addObject:json];
} }
} else { } else if ([section.name isEqualToString:PCSProductsInvalidIdentifiers]) {
// Warn the user that the resource file could not be found. // if there are invalid product identifiers, show them.
[self alertWithTitle:PCSMessagesStatus message:[NSString stringWithFormat:@"%@ %@.%@.", PCSMessagesResourceNotFound, PCSProductIdsPlistName, PCSProductIdsPlistFileExtension]]; NSLog(@"Invalid product identifiers: %@", section.name);
// [self nativeCb:funId hasErr:YES dataStr: section.name];
} }
}
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonArray options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
if (!jsonData) {
NSLog(@"Got an error: %@", error);
[self nativeCb:funId hasErr:YES dataStr: [NSString stringWithFormat:@"%@", error]];
} else {
NSLog(@"jsonString: %@", jsonString);
[self nativeCb:funId hasErr:NO dataStr: jsonString];
}
} else if (status == PCSRequestFailed) {
NSLog(@"Product request failed with message: %@", [StoreManager sharedInstance].message);
[self nativeCb:funId hasErr:YES dataStr: [StoreManager sharedInstance].message];
}
}];
} else { } else {
// Warn the user that they are not allowed to make purchases. // Warn the user that they are not allowed to make purchases.
[self alertWithTitle:PCSMessagesStatus message:[NSString stringWithFormat:@"%@", PCSMessagesCannotMakePayments]]; NSLog(@"User is not allowed to make payments (Payments are disabled in Settings).");
[self nativeCb:funId hasErr:YES dataStr: [NSString stringWithFormat:@"%@", PCSMessagesCannotMakePayments]];
} }
} }
#pragma mark - Query Purchase
- (void)queryPurchase:(NSString *)funId {
if (utility == nil) {
[self initPurchaseEnv];
}
NSArray *transations = [[StoreObserver sharedInstance] queryPendingTransactions];
// convert to json string
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:transations options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[self nativeCb:funId hasErr:NO dataStr:jsonString];
}
#pragma mark - Begin Buy Product
- (void)beginBuy:(NSString *)funId productId:(NSString *)productId orderId:(NSString *)orderId {
if (utility == nil) {
[self initPurchaseEnv];
}
if (productId == nil || [productId isEqualToString:@""]) {
NSLog(@"beginBy with empty productId: %@", productId);
[self nativeCb:funId hasErr:YES dataStr:@"beginBy with empty productId"];
return;
}
// check if currentFunId is empty
if (self.currentFunId != nil && ![self.currentFunId isEqualToString:@""]) {
NSLog(@"beginBy with currentFunId: %@", self.currentFunId);
[self nativeCb:funId hasErr:YES dataStr:@"other purchase is processing"];
return;
}
self.currentFunId = funId;
SKProduct *product = [[StoreManager sharedInstance] productMatchingIdentifier: productId];
if (product == nil) {
NSLog(@"beginBy with empty product: %@", productId);
self.currentFunId = nil;
[self nativeCb:funId hasErr:YES dataStr:@"beginBy with empty product"];
return;
}
if (orderId == nil || [orderId isEqualToString:@""]) {
NSLog(@"beginBy with empty orderId: %@", orderId);
self.currentFunId = nil;
[self nativeCb:funId hasErr:YES dataStr:@"beginBy with empty orderId"];
return;
}
[[StoreObserver sharedInstance] buy:product orderId: orderId];
}
#pragma mark - Finish Transaction
- (void)finishTransaction:(NSString *)funId transactionId: (NSString *) transactionId {
[[StoreObserver sharedInstance] finishTransaction: transactionId];
[self nativeCb:funId hasErr:NO dataStr:@""];
}
/// Creates and displays an alert. /// Creates and displays an alert.
-(void)alertWithTitle:(NSString *)title message:(NSString *)message { -(void)alertWithTitle:(NSString *)title message:(NSString *)message {
UIAlertController *alertController = [utility alertWithTitle:title message:message]; UIAlertController *alertController = [utility alertWithTitle:title message:message];
@ -71,33 +164,27 @@ static Utilities *utility;
-(void)handleProductRequestNotification:(NSNotification *)notification { -(void)handleProductRequestNotification:(NSNotification *)notification {
StoreManager *productRequestNotification = (StoreManager*)notification.object; StoreManager *productRequestNotification = (StoreManager*)notification.object;
PCSProductRequestStatus status = (PCSProductRequestStatus)productRequestNotification.status; PCSProductRequestStatus status = (PCSProductRequestStatus)productRequestNotification.status;
NSLog(@"handleProductRequestNotification status: %ld", (long)status);
if (status == PCSStoreResponse) {
// Switch to the Products view controller.
// [self switchToViewController:ParentViewControllerSegmentProducts];
// [self.products reloadWithData:productRequestNotification.storeResponse];
// self.segmentedControl.selectedSegmentIndex = ParentViewControllerSegmentProducts;
} else if (status == PCSRequestFailed) {
[self alertWithTitle:PCSMessagesProductRequestStatus message:productRequestNotification.message];
}
} }
#pragma mark - Handle PCSPurchase Notification #pragma mark - Handle PCSPurchase Notification
/// Updates the UI according to the purchase request notification result. /// Updates the UI according to the purchase request notification result.
-(void)handlePurchaseNotification:(NSNotification *)notification { -(void)handlePurchaseNotification:(NSNotification *)notification {
StoreObserver *purchasesNotification = (StoreObserver *)notification.object; NSDictionary* dic = notification.userInfo;
PCSPurchaseStatus status = (PCSPurchaseStatus)purchasesNotification.status; NSNumber *errcode = dic[@"errcode"];
NSLog(@"handlePurchaseNotification status: %@", errcode);
switch (status) { // check if errcode is zero or not
case PCSNoRestorablePurchases: self.currentFunId = nil;
case PCSPurchaseFailed: if (errcode != nil && [errcode intValue] != 0) {
case PCSRestoreFailed: [self alertWithTitle:PCSMessagesPurchaseStatus message:purchasesNotification.message]; [self nativeCb:self.currentFunId hasErr:YES dataStr: [NSString stringWithFormat:@"%@", dic[@"errmsg"]]];
break; } else {
// Switch to the Purchases view when receiving a successful restore notification. NSString *tid = dic[@"dataid"];
case PCSRestoreSucceeded: [self handleRestoredSucceededTransaction]; NSMutableArray *transactions = [[NSMutableArray alloc] init];
break; [transactions addObject:tid];
default: break; NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:transactions options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[self nativeCb:self.currentFunId hasErr:NO dataStr: jsonString];
} }
} }
@ -112,5 +199,5 @@ static Utilities *utility;
// [self.purchases reloadWithData:self.utility.dataSourceForPurchasesUI]; // [self.purchases reloadWithData:self.utility.dataSourceForPurchasesUI];
// self.segmentedControl.selectedSegmentIndex = ParentViewControllerSegmentPurchases; // self.segmentedControl.selectedSegmentIndex = ParentViewControllerSegmentPurchases;
} }
@end
@end

View File

@ -6,6 +6,7 @@
// //
#import "UIViewController+QR.h" #import "UIViewController+QR.h"
#import "UIViewController+Wallet.h"
#import "QRCodeReaderViewController.h" #import "QRCodeReaderViewController.h"
#import "QRCodeReader.h" #import "QRCodeReader.h"
#import "QRCodeReaderDelegate.h" #import "QRCodeReaderDelegate.h"
@ -132,21 +133,5 @@ static SimpleQRViewController *sqrVC = nil;
}]; }];
} }
-(void)nativeCb:(NSString *)funid hasErr: (BOOL) hasErr dataStr:(NSString *) dataStr {
if ([NSString isStringEmpty:funid]) {
NSLog(@"nativeCallBack with empty funid: %@", funid);
return;
}
std::string methodName = "nativeCallBack";
NSString *paramStr;
if (hasErr) {
paramStr = [NSString stringWithFormat:@"{\"errcode\": 1, \"errmessage\": \"%@\"}", dataStr];
} else {
paramStr = [NSString stringWithFormat:@"{\"errcode\": 0, \"data\": \"%@\"}", dataStr];
}
std::string sfunid = std::string([funid UTF8String], [funid lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
std::string sparam = std::string([paramStr UTF8String], [paramStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
cocos2d::nativeCallBack(sfunid.c_str(), methodName.c_str(), sparam.c_str());
}
@end @end

View File

@ -22,5 +22,5 @@
-(void)saveKey:(NSString *) account key:(NSString *) key; -(void)saveKey:(NSString *) account key:(NSString *) key;
-(NSString *)loadKey:(NSString *) account; -(NSString *)loadKey:(NSString *) account;
-(void)showPage:(NSString *)url; -(void)showPage:(NSString *)url;
//-(void)nativeCb:(NSString *)funid hasErr: (BOOL) hasErr dataStr:(NSString *) dataStr; -(void)nativeCb:(NSString *)funid hasErr: (BOOL) hasErr dataStr:(NSString *) dataStr;
@end @end

View File

@ -176,14 +176,26 @@ static WebPageViewController *webpageVC = nil;
return; return;
} }
std::string methodName = "nativeCallBack"; std::string methodName = "nativeCallBack";
NSString *paramStr; NSMutableDictionary *json = [[NSMutableDictionary alloc] init];
if (hasErr) { if (hasErr) {
paramStr = [NSString stringWithFormat:@"{\"errcode\": 1, \"errmessage\": \"%@\"}", dataStr]; json[@"errcode"] = @1;
json[@"errmessage"] = dataStr;
} else { } else {
paramStr = [NSString stringWithFormat:@"{\"errcode\": 0, \"data\": \"%@\"}", dataStr]; json[@"errcode"] = @0;
json[@"data"] = dataStr;
} }
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:json options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
std::string sfunid = std::string([funid UTF8String], [funid lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); std::string sfunid = std::string([funid UTF8String], [funid lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
std::string sparam = std::string([paramStr UTF8String], [paramStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); if (error) {
NSLog(@"Got an error: %@", error);
NSString *errorStr = [NSString stringWithFormat:@"{\"errcode\": 1, \"errmessage\": \"%@\"}", error];
std::string sparam = std::string([errorStr UTF8String], [errorStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
cocos2d::nativeCallBack(sfunid.c_str(), methodName.c_str(), sparam.c_str());
return;
}
std::string sparam = std::string([jsonString UTF8String], [jsonString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
cocos2d::nativeCallBack(sfunid.c_str(), methodName.c_str(), sparam.c_str()); cocos2d::nativeCallBack(sfunid.c_str(), methodName.c_str(), sparam.c_str());
} }

View File

@ -322,6 +322,7 @@
D59AB8522A68FEB700433200 /* Section.m in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8502A68FEB700433200 /* Section.m */; }; D59AB8522A68FEB700433200 /* Section.m in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8502A68FEB700433200 /* Section.m */; };
D59AB8552A690BC300433200 /* UIViewController+Purchase.mm in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8542A690BC300433200 /* UIViewController+Purchase.mm */; }; D59AB8552A690BC300433200 /* UIViewController+Purchase.mm in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8542A690BC300433200 /* UIViewController+Purchase.mm */; };
D59AB8582A690C9900433200 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8572A690C9900433200 /* Utilities.m */; }; D59AB8582A690C9900433200 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8572A690C9900433200 /* Utilities.m */; };
D59AB85E2A6A500900433200 /* SKProduct+SKProductAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D59AB85C2A6A500900433200 /* SKProduct+SKProductAdditions.m */; };
D5BF397629C9B79400EC6351 /* UIViewController+Logger.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5BF397529C9B79400EC6351 /* UIViewController+Logger.mm */; }; D5BF397629C9B79400EC6351 /* UIViewController+Logger.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5BF397529C9B79400EC6351 /* UIViewController+Logger.mm */; };
D5BF397829C9B8C000EC6351 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D5BF397729C9B8C000EC6351 /* GoogleService-Info.plist */; }; D5BF397829C9B8C000EC6351 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D5BF397729C9B8C000EC6351 /* GoogleService-Info.plist */; };
D5C03B702A49A808002E758D /* Data in Resources */ = {isa = PBXBuildFile; fileRef = D5C03B6F2A49A808002E758D /* Data */; }; D5C03B702A49A808002E758D /* Data in Resources */ = {isa = PBXBuildFile; fileRef = D5C03B6F2A49A808002E758D /* Data */; };
@ -1369,6 +1370,9 @@
D59AB8542A690BC300433200 /* UIViewController+Purchase.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIViewController+Purchase.mm"; sourceTree = "<group>"; }; D59AB8542A690BC300433200 /* UIViewController+Purchase.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIViewController+Purchase.mm"; sourceTree = "<group>"; };
D59AB8562A690C9900433200 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = "<group>"; }; D59AB8562A690C9900433200 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = "<group>"; };
D59AB8572A690C9900433200 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = "<group>"; }; D59AB8572A690C9900433200 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = "<group>"; };
D59AB85A2A6A387A00433200 /* test-in-app-purchase.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = "test-in-app-purchase.storekit"; sourceTree = "<group>"; };
D59AB85C2A6A500900433200 /* SKProduct+SKProductAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SKProduct+SKProductAdditions.m"; sourceTree = "<group>"; };
D59AB85D2A6A500900433200 /* SKProduct+SKProductAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SKProduct+SKProductAdditions.h"; sourceTree = "<group>"; };
D5BF397429C9B77E00EC6351 /* UIViewController+Logger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Logger.h"; sourceTree = "<group>"; }; D5BF397429C9B77E00EC6351 /* UIViewController+Logger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Logger.h"; sourceTree = "<group>"; };
D5BF397529C9B79400EC6351 /* UIViewController+Logger.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIViewController+Logger.mm"; sourceTree = "<group>"; }; D5BF397529C9B79400EC6351 /* UIViewController+Logger.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIViewController+Logger.mm"; sourceTree = "<group>"; };
D5BF397729C9B8C000EC6351 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; D5BF397729C9B8C000EC6351 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
@ -1469,6 +1473,7 @@
29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D59AB85A2A6A387A00433200 /* test-in-app-purchase.storekit */,
D59AB8482A68FC2900433200 /* purchase */, D59AB8482A68FC2900433200 /* purchase */,
D5C03B6F2A49A808002E758D /* Data */, D5C03B6F2A49A808002E758D /* Data */,
D526FA3C299498E3002A2290 /* cocos2d_libs.xcodeproj */, D526FA3C299498E3002A2290 /* cocos2d_libs.xcodeproj */,
@ -3500,6 +3505,8 @@
D59AB8482A68FC2900433200 /* purchase */ = { D59AB8482A68FC2900433200 /* purchase */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D59AB85D2A6A500900433200 /* SKProduct+SKProductAdditions.h */,
D59AB85C2A6A500900433200 /* SKProduct+SKProductAdditions.m */,
D59AB8492A68FC5200433200 /* StoreObserver.h */, D59AB8492A68FC5200433200 /* StoreObserver.h */,
D59AB8462A68FC2200433200 /* StoreObserver.m */, D59AB8462A68FC2200433200 /* StoreObserver.m */,
D59AB84A2A68FC7200433200 /* AppConfiguration.h */, D59AB84A2A68FC7200433200 /* AppConfiguration.h */,
@ -3817,6 +3824,7 @@
D55759902A5FAC16009369FC /* Generics5.cpp in Sources */, D55759902A5FAC16009369FC /* Generics5.cpp in Sources */,
D59AB84F2A68FE8E00433200 /* StoreManager.m in Sources */, D59AB84F2A68FE8E00433200 /* StoreManager.m in Sources */,
D55759D12A5FAC16009369FC /* UnityEngine.InputLegacyModule.cpp in Sources */, D55759D12A5FAC16009369FC /* UnityEngine.InputLegacyModule.cpp in Sources */,
D59AB85E2A6A500900433200 /* SKProduct+SKProductAdditions.m in Sources */,
D507C9412994A0A800CF3953 /* UIView+Toast.m in Sources */, D507C9412994A0A800CF3953 /* UIView+Toast.m in Sources */,
D55759E12A5FAC16009369FC /* UnityEngine.TextCoreTextEngineModule.cpp in Sources */, D55759E12A5FAC16009369FC /* UnityEngine.TextCoreTextEngineModule.cpp in Sources */,
D507D5E92994C62A00CF3953 /* UnityAdsUtilities.m in Sources */, D507D5E92994C62A00CF3953 /* UnityAdsUtilities.m in Sources */,

View File

@ -142,8 +142,8 @@
filePath = "Classes_cocos/JcWallet.mm" filePath = "Classes_cocos/JcWallet.mm"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "182" startingLineNumber = "183"
endingLineNumber = "182" endingLineNumber = "183"
landmarkName = "JcWallet::jsToUnity(funId, msg)" landmarkName = "JcWallet::jsToUnity(funId, msg)"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
@ -238,8 +238,8 @@
filePath = "Classes_cocos/JcWallet.mm" filePath = "Classes_cocos/JcWallet.mm"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "188" startingLineNumber = "189"
endingLineNumber = "188" endingLineNumber = "189"
landmarkName = "initEnv()" landmarkName = "initEnv()"
landmarkType = "9"> landmarkType = "9">
</BreakpointContent> </BreakpointContent>
@ -254,11 +254,267 @@
filePath = "Classes_cocos/JcWallet.mm" filePath = "Classes_cocos/JcWallet.mm"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "114" startingLineNumber = "115"
endingLineNumber = "114" endingLineNumber = "115"
landmarkName = "JcWallet::initEnv()" landmarkName = "JcWallet::initEnv()"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "4D59F0BA-B8D8-4997-82C4-0E290B339761"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "44"
endingLineNumber = "44"
landmarkName = "-queryProducts:products:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "EFF0CD4A-8153-4757-B4A4-357559BA0D1B"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "97"
endingLineNumber = "97"
landmarkName = "-queryProducts:products:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "4FBF6B08-08A0-4695-995A-211586F5A6CF"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "25"
endingLineNumber = "25"
landmarkName = "unknown"
landmarkType = "0">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "6CDA02A2-8D28-4324-BFE6-7B2F4037BCB7"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "74"
endingLineNumber = "74"
landmarkName = "-queryProducts:products:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "E24AA442-B796-47C7-B2EC-28713FB595F3"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "46"
endingLineNumber = "46"
landmarkName = "-queryProducts:products:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "41E5D281-1A9D-4031-AF76-9BDCF83C9611"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "80"
endingLineNumber = "80"
landmarkName = "-queryProducts:products:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "79F18C2C-0D3F-41EE-BCEC-0A1B0D6DDC2F"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Wallet.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "174"
endingLineNumber = "174"
landmarkName = "-nativeCb:hasErr:dataStr:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "8B87105F-C5EE-489F-BF1E-00FDF62602D0"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "purchase/StoreObserver.m"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "131"
endingLineNumber = "131"
landmarkName = "-paymentQueue:updatedTransactions:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "6E8F3D37-5AC6-454A-8526-57B1C343F168"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "111"
endingLineNumber = "111"
landmarkName = "-queryPurchase:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "298A81F7-4B66-40D1-8EA3-BE7606FB25D2"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "purchase/StoreObserver.m"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "190"
endingLineNumber = "190"
landmarkName = "-handleTransactionPurchased:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "E989D350-F926-417F-BB88-2C17D5DDE9F2"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "purchase/StoreObserver.m"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "50"
endingLineNumber = "50"
landmarkName = "-getAppReceipt"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "69659E6C-1EDA-428A-A479-A7139FF808A3"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "purchase/StoreObserver.m"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "56"
endingLineNumber = "56"
landmarkName = "-getAppReceipt"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "E2F34C60-2F85-4D8F-8A0A-75BD5B33C607"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "158"
endingLineNumber = "158"
landmarkName = "-alertWithTitle:message:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "9119790E-EDB6-49DA-B186-43B0CE2E3876"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Classes_cocos/UIViewController+Purchase.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "159"
endingLineNumber = "159"
landmarkName = "-alertWithTitle:message:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "A9CA3DDC-6205-4BD4-8933-EA7A3B91A6F3"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "purchase/StoreObserver.m"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "206"
endingLineNumber = "206"
landmarkName = "-handleTransactionFailed:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "15FCA0D4-7E42-43F6-9DC2-FB0643322A2D"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "purchase/StoreObserver.m"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "264"
endingLineNumber = "264"
landmarkName = "-onPurchaseResult:errorCode:errorDescription:"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints> </Breakpoints>
</Bucket> </Bucket>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<VariablesViewState
version = "1.0">
<ContextStates>
<ContextState
contextName = "-[StoreObserver handlePurchasedTransaction:]:StoreObserver.m">
<PersistentStrings>
<PersistentString
value = "&#10;transaction.originalID">
</PersistentString>
</PersistentStrings>
</ContextState>
</ContextStates>
</VariablesViewState>

File diff suppressed because one or more lines are too long

View File

@ -363,6 +363,20 @@ function tokenPrice(funId, tokenName, amount) {
function fiatList(funId) { function fiatList(funId) {
promiseCb(funId, jc.wallet.paySvr.fetchFiatList()); promiseCb(funId, jc.wallet.paySvr.fetchFiatList());
} }
/**
* query price of crypto -> usd
* @param {string} crypto
* @param {number} chain chain id,
*/
function getCryptoPriceOfUSD(funId, crypto, chain) {
let chainData = jc.wallet.currentChain;
if (chain) {
chainData = jc.wallet.chainList.find((v) => v.chainId === +chain);
}
let network = chainData.type !== 'Testnet' ? chainData.network || chainData.symbol : 'ARBITRUM';
network = network || 'ARBITRUM';
promiseCb(funId, jc.wallet.paySvr.queryTokenPrice(network, crypto));
}
/** /**
* format price * format price
@ -493,20 +507,34 @@ function nftMallBuy(funId, currency, addresses, ids, amounts, values, signature,
// end of NFT mall // end of NFT mall
// begin of token mall
// buy ceg with usdt, usdc
function buyTokenWithErc20(funId, currency, amount, gas, estimate) {
promiseCb(
funId,
jc.wallet.jcStandard.buyTokenWithErc20({
currency,
amount,
estimate,
}),
(v) => JSON.stringify(v)
);
}
// begin of token mall
// begin of google pay // begin of google pay
function queryGoogleProducts(funId, productIds) { function queryGoogleProducts(funId, productIds) {
let ids = JSON.parse(productIds); let ids = JSON.parse(productIds);
console.log('queryGoogleProducts:: ' + productIds); console.log('queryAppleProducts:: ' + productIds);
promiseCb(funId, jc.wallet.paySvr.queryGoogleProducts(ids)); promiseCb(funId, jc.wallet.paySvr.queryIOSProducts(ids));
} }
function queryGooglePurchases(funId) { function queryGooglePurchases(funId) {
promiseCb(funId, jc.wallet.paySvr.queryGooglePurchases()); promiseCb(funId, jc.wallet.paySvr.queryIOSPurchases());
} }
function beginGoogleBuy(funId, productId, orderId) { function beginGoogleBuy(funId, productId, orderId) {
promiseCb(funId, jc.wallet.paySvr.buyGoogleProduct(productId, orderId)); promiseCb(funId, jc.wallet.paySvr.beginIOSPurchase(productId, orderId));
} }
// end of google pay // end of google pay

View File

@ -0,0 +1,13 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
Creates a category for the SKProduct class.
*/
@import StoreKit;
@interface SKProduct (SKProductAdditions)
/// - returns: The cost of the product formatted in the local currency.
-(NSString *)regularPrice;
@end

View File

@ -0,0 +1,19 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
Creates a category for the SKProduct class.
*/
#import "SKProduct+SKProductAdditions.h"
@implementation SKProduct (SKProductAdditions)
/// - returns: The cost of the product formatted in the local currency.
-(NSString *)regularPrice {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[formatter setLocale: self.priceLocale];
return [formatter stringFromNumber:self.price];
}
@end

View File

@ -23,11 +23,13 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
@property (strong) NSMutableArray *storeResponse; @property (strong) NSMutableArray *storeResponse;
/// Starts the product request with the specified identifiers. /// Starts the product request with the specified identifiers.
-(void)startProductRequestWithIdentifiers:(NSArray *)identifiers; -(void)fetchProductsMatchingIdentifiers:(NSArray *)identifiers completionBlock: (nullable void (^) (PCSProductRequestStatus status, NSMutableArray * _Nullable storeResponse))completionBlock;
/// - returns: Existing product's title matching the specified product identifier. /// - returns: Existing product's title matching the specified product identifier.
-(NSString *)titleMatchingIdentifier:(NSString *)identifier; -(NSString *)titleMatchingIdentifier:(NSString *)identifier;
-(SKProduct *)productMatchingIdentifier:(NSString *)identifier;
/// - returns: Existing product's title associated with the specified payment transaction. /// - returns: Existing product's title associated with the specified payment transaction.
-(NSString *)titleMatchingPaymentTransaction:(SKPaymentTransaction *)transaction; -(NSString *)titleMatchingPaymentTransaction:(SKPaymentTransaction *)transaction;
@end @end

View File

@ -19,6 +19,9 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
/// Keeps a strong reference to the product request. /// Keeps a strong reference to the product request.
@property (strong) SKProductsRequest *productRequest; @property (strong) SKProductsRequest *productRequest;
@property (copy, nonatomic) void (^completionBlock) (PCSProductRequestStatus, NSMutableArray *);
@end @end
@implementation StoreManager @implementation StoreManager
@ -47,13 +50,10 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
#pragma mark - Request Information #pragma mark - Request Information
/// Starts the product request with the specified identifiers.
-(void)startProductRequestWithIdentifiers:(NSArray *)identifiers {
[self fetchProductsMatchingIdentifiers:identifiers];
}
/// Fetches information about your products from the App Store. /// Fetches information about your products from the App Store.
-(void)fetchProductsMatchingIdentifiers:(NSArray *)identifiers { -(void)fetchProductsMatchingIdentifiers:(NSArray *)identifiers completionBlock: (void (^) (PCSProductRequestStatus status, NSMutableArray *resultAsString))completionBlock{
self.completionBlock = completionBlock;
// Create a set for the product identifiers. // Create a set for the product identifiers.
NSSet *productIdentifiers = [NSSet setWithArray:identifiers]; NSSet *productIdentifiers = [NSSet setWithArray:identifiers];
@ -89,7 +89,9 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
if (self.storeResponse.count > 0) { if (self.storeResponse.count > 0) {
self.status = PCSStoreResponse; self.status = PCSStoreResponse;
if (_completionBlock) {
_completionBlock(self.status, self.storeResponse);
}
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:PCSProductRequestNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:PCSProductRequestNotification object:self];
}); });
@ -123,6 +125,16 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
return title; return title;
} }
-(SKProduct *) productMatchingIdentifier:(NSString *)identifier {
SKProduct *result = nil;
for (SKProduct *product in self.availableProducts) {
if ([product.productIdentifier isEqualToString:identifier]) {
result = product;
}
}
return result;
}
/// - returns: Existing product's title associated with the specified payment transaction. /// - returns: Existing product's title associated with the specified payment transaction.
-(NSString *)titleMatchingPaymentTransaction:(SKPaymentTransaction *) transaction { -(NSString *)titleMatchingPaymentTransaction:(SKPaymentTransaction *) transaction {
NSString *title = [self titleMatchingIdentifier:transaction.payment.productIdentifier]; NSString *title = [self titleMatchingIdentifier:transaction.payment.productIdentifier];

View File

@ -27,13 +27,23 @@ Implements the SKPaymentTransactionObserver protocol. Handles purchasing and res
/// Keeps track of all restored purchases. /// Keeps track of all restored purchases.
@property(strong) NSMutableArray *productsRestored; @property(strong) NSMutableArray *productsRestored;
@property(strong) NSMutableDictionary *pendingTransactions;
@property(strong) NSMutableSet *finishedTransactions;
/// Indicates the purchase status. /// Indicates the purchase status.
@property(nonatomic) PCSPurchaseStatus status; @property(nonatomic) PCSPurchaseStatus status;
/// Implements the purchase of a product. /// Implements the purchase of a product.
-(void)buy:(SKProduct *)product; - (void)buy:(SKProduct *)product orderId:(NSString *)orderId;
- (void)finishTransaction:(NSString *)transactionIdentifier;
- (NSArray *)queryPendingTransactions;
/// Implements the restoration of previously completed purchases. /// Implements the restoration of previously completed purchases.
- (void)restore; - (void)restore;
@end
- (NSString *)getAppReceipt;
@end

View File

@ -28,8 +28,9 @@ Implements the SKPaymentTransactionObserver protocol. Handles purchasing and res
if (self != nil) { if (self != nil) {
_hasRestorablePurchases = NO; _hasRestorablePurchases = NO;
_productsPurchased = [[NSMutableArray alloc] initWithCapacity:0];
_productsRestored = [[NSMutableArray alloc] initWithCapacity:0]; _productsRestored = [[NSMutableArray alloc] initWithCapacity:0];
_pendingTransactions = [[NSMutableDictionary alloc] init];
_finishedTransactions = [[NSMutableSet alloc] init];
_status = PCSPurchaseStatusNone; _status = PCSPurchaseStatusNone;
} }
return self; return self;
@ -43,11 +44,73 @@ Implements the SKPaymentTransactionObserver protocol. Handles purchasing and res
return [SKPaymentQueue canMakePayments]; return [SKPaymentQueue canMakePayments];
} }
- (NSString*)getAppReceipt
{
NSBundle* bundle = [NSBundle mainBundle];
if ([bundle respondsToSelector: @selector(appStoreReceiptURL)])
{
NSURL *receiptURL = [bundle appStoreReceiptURL];
if ([[NSFileManager defaultManager] fileExistsAtPath: [receiptURL path]])
{
NSData *receipt = [NSData dataWithContentsOfURL: receiptURL];
#if MAC_APPSTORE
// The base64EncodedStringWithOptions method was only added in OSX 10.9.
NSString* result = [receipt mgb64_base64EncodedString];
#else
NSString* result = [receipt base64EncodedStringWithOptions: 0];
#endif
return result;
}
}
NSLog(@"No App Receipt found");
return @"";
}
#if !MAC_APPSTORE
- (BOOL)isiOS6OrEarlier
{
float version = [[[UIDevice currentDevice] systemVersion] floatValue];
return version < 7;
}
#endif
// Retrieve a receipt for the transaction, which will either
// be the old style transaction receipt on <= iOS 6,
// or the App Receipt in OSX and iOS 7+.
- (NSString*)selectReceipt:(SKPaymentTransaction*)transaction
{
#if MAC_APPSTORE
return [self getAppReceipt];
#else
if ([self isiOS6OrEarlier])
{
if (nil == transaction)
{
return @"";
}
NSString* receipt;
receipt = [[NSString alloc] initWithData: transaction.transactionReceipt encoding: NSUTF8StringEncoding];
return receipt;
}
else
{
return [self getAppReceipt];
}
#endif
}
#pragma mark - Submit Payment Request #pragma mark - Submit Payment Request
/// Creates and adds a payment request to the payment queue. /// Creates and adds a payment request to the payment queue.
-(void)buy:(SKProduct *)product { -(void)buy:(SKProduct *)product orderId: (NSString *) orderId {
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
// payment.applicationUsername = orderId;
// generate UUID for order id
NSString *testOrderId = [[NSUUID UUID] UUIDString];
[payment performSelector: @selector(setApplicationUsername:) withObject: testOrderId];
[[SKPaymentQueue defaultQueue] addPayment:payment]; [[SKPaymentQueue defaultQueue] addPayment:payment];
} }
@ -72,13 +135,13 @@ Implements the SKPaymentTransactionObserver protocol. Handles purchasing and res
case SKPaymentTransactionStateDeferred: NSLog(@"%@", PCSMessagesDeferred); case SKPaymentTransactionStateDeferred: NSLog(@"%@", PCSMessagesDeferred);
break; break;
// The purchase was successful. // The purchase was successful.
case SKPaymentTransactionStatePurchased: [self handlePurchasedTransaction:transaction]; case SKPaymentTransactionStatePurchased: [self handleTransactionPurchased:transaction];
break; break;
// The transaction failed. // The transaction failed.
case SKPaymentTransactionStateFailed: [self handleFailedTransaction:transaction]; case SKPaymentTransactionStateFailed: [self handleTransactionFailed:transaction];
break; break;
// There are restored products. // There are restored products.
case SKPaymentTransactionStateRestored: [self handleRestoredTransaction:transaction]; case SKPaymentTransactionStateRestored: [self handleTransactionRestored:transaction];
break; break;
default: break; default: break;
} }
@ -94,75 +157,119 @@ Implements the SKPaymentTransactionObserver protocol. Handles purchasing and res
/// Called when an error occur while restoring purchases. Notifies the user about the error. /// Called when an error occur while restoring purchases. Notifies the user about the error.
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
if (error.code != SKErrorPaymentCancelled) { NSLog(@"%@\n%@", PCSMessagesFailed, error.localizedDescription);
self.status = PCSRestoreFailed; // if (error.code != SKErrorPaymentCancelled) {
self.message = error.localizedDescription; // self.status = PCSRestoreFailed;
// self.message = error.localizedDescription;
dispatch_async(dispatch_get_main_queue(), ^{ //
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self]; // dispatch_async(dispatch_get_main_queue(), ^{
}); // [[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self];
} // });
// }
} }
/// Called when all restorable transactions have been processed by the payment queue. /// Called when all restorable transactions have been processed by the payment queue.
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
NSLog(@"%@", PCSMessagesRestorable); NSLog(@"%@\n%@", PCSMessagesNoRestorablePurchases, PCSMessagesPreviouslyBought);
// if (!self.hasRestorablePurchases) {
if (!self.hasRestorablePurchases) { // self.status = PCSNoRestorablePurchases;
self.status = PCSNoRestorablePurchases; // self.message = [NSString stringWithFormat:@"%@\n%@", PCSMessagesNoRestorablePurchases, PCSMessagesPreviouslyBought];
self.message = [NSString stringWithFormat:@"%@\n%@", PCSMessagesNoRestorablePurchases, PCSMessagesPreviouslyBought]; //
// dispatch_async(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{ // [[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self]; // });
}); // }
}
} }
#pragma mark - Handle Payment Transactions #pragma mark - Handle Payment Transactions
/// Handles successful purchase transactions. /// Handles successful purchase transactions.
-(void)handlePurchasedTransaction:(SKPaymentTransaction*)transaction { -(void)handleTransactionPurchased:(SKPaymentTransaction*)transaction {
[self.productsPurchased addObject:transaction]; NSString *transactionId = transaction.transactionIdentifier;
NSLog(@"%@ %@.", PCSMessagesDeliverContent, transaction.payment.productIdentifier); NSLog(@"%@ %@.", PCSMessagesDeliverContent, transaction.payment.productIdentifier);
NSLog(@"original transaction id: %@", transactionId);
NSLog(@"order id: %@", transaction.payment.applicationUsername);
[self onTransactionSucceeded: transaction];
}
// Finish the successful transaction. /// Handles restored purchase transactions.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction]; -(void)handleTransactionRestored:(SKPaymentTransaction*)transaction {
self.status = PCSRestoreSucceeded;
self.hasRestorablePurchases = true;
[self onTransactionSucceeded: transaction];
} }
/// Handles failed purchase transactions. /// Handles failed purchase transactions.
-(void)handleFailedTransaction:(SKPaymentTransaction*)transaction { -(void)handleTransactionFailed:(SKPaymentTransaction*)transaction {
self.status = PCSPurchaseFailed; self.status = PCSPurchaseFailed;
self.message = [NSString stringWithFormat:@"%@ %@ %@",PCSMessagesPurchaseOf, transaction.payment.productIdentifier, PCSMessagesFailed]; self.message = [NSString stringWithFormat:@"%@ %@ %@",PCSMessagesPurchaseOf, transaction.payment.productIdentifier, PCSMessagesFailed];
if (transaction.error) { [self onPurchaseResult: transaction.payment.productIdentifier errorCode: 200 + transaction.error.code errorDescription: [NSString stringWithFormat:@"\n%@ %@", PCSMessagesError, transaction.error.localizedDescription]];
[self.message stringByAppendingString:[NSString stringWithFormat:@"\n%@ %@", PCSMessagesError, transaction.error.localizedDescription]];
NSLog(@"%@ %@", PCSMessagesError, transaction.error.localizedDescription);
}
// Do not send any notifications when the user cancels the purchase.
if (transaction.error.code != SKErrorPaymentCancelled) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self];
});
}
// Finish the failed transaction. // Finish the failed transaction.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} }
/// Handles restored purchase transactions. // Handle a new or restored purchase transaction by informing Unity.
-(void)handleRestoredTransaction:(SKPaymentTransaction*)transaction { - (void)onTransactionSucceeded:(SKPaymentTransaction*)transaction
self.status = PCSRestoreSucceeded; {
self.hasRestorablePurchases = true; NSString* transactionId = transaction.transactionIdentifier;
[self.productsRestored addObject:transaction];
NSLog(@"%@ %@.", PCSMessagesRestoreContent, transaction.payment.productIdentifier);
dispatch_async(dispatch_get_main_queue(), ^{ // This should never happen according to Apple's docs, but it does!
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self]; if (nil == transactionId)
}); {
// Make something up, allowing us to identifiy the transaction when finishing it.
transactionId = [[NSUUID UUID] UUIDString];
NSLog(@"Missing transaction Identifier!");
[self onPurchaseResult: transactionId errorCode: 101 errorDescription: @"Missing transaction Identifier!"];
}
// Finish the restored transaction. // 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
if ([self.finishedTransactions containsObject: transactionId])
{
[[SKPaymentQueue defaultQueue] finishTransaction: transaction]; [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
NSLog(@"DuplicateTransaction error with product %@ and transactionId %@", transaction.payment.productIdentifier, transactionId);
[self onPurchaseResult: transaction.payment.productIdentifier errorCode: 100 errorDescription: @"Duplicate transaction occurred"];
return; // EARLY RETURN
}
// Item was successfully purchased or restored.
if (nil == [self.pendingTransactions objectForKey: transactionId])
{
[self.pendingTransactions setObject: transaction forKey: transactionId];
}
[self onPurchaseResult: transactionId errorCode: 0 errorDescription: @""];
}
- (NSArray *)queryPendingTransactions{
return [self.pendingTransactions allKeys];
}
- (void)finishTransaction:(NSString *)transactionIdentifier{
SKPaymentTransaction* transaction = [self.pendingTransactions objectForKey: transactionIdentifier];
if (nil != transaction) {
NSLog(@"Finishing transaction %@", transactionIdentifier);
[[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
[self.pendingTransactions removeObjectForKey: transactionIdentifier];
[self.finishedTransactions addObject: transactionIdentifier];
}
else {
NSLog(@"Transaction %@ not pending, nothing to finish here", transactionIdentifier);
}
}
- (void)onPurchaseResult:(NSString*)productId errorCode:(NSInteger)errorCode errorDescription:(NSString*)errorDescription
{
NSMutableDictionary* dic = [[NSMutableDictionary alloc] init];
[dic setObject: productId forKey: @"dataid"];
[dic setObject: [NSNumber numberWithInteger: errorCode] forKey: @"errcode"];
[dic setObject: errorDescription forKey: @"errmsg"];
// NSData* data = [NSJSONSerialization dataWithJSONObject: dic options: 0 error: nil];
// NSString* result = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object: self userInfo:dic];
});
} }
@end @end

View File

@ -0,0 +1,33 @@
{
"identifier" : "E7768E5D",
"nonRenewingSubscriptions" : [
],
"products" : [
{
"displayPrice" : "0.99",
"familyShareable" : false,
"internalID" : "07066426",
"localizations" : [
{
"description" : "buy 100ceg",
"displayName" : "100ceg",
"locale" : "en_US"
}
],
"productID" : "2999",
"referenceName" : "100d",
"type" : "Consumable"
}
],
"settings" : {
},
"subscriptionGroups" : [
],
"version" : {
"major" : 2,
"minor" : 0
}
}