增加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::AutoHandleScope hs;
jsb_run_script("js/jsb-adapter/jsb-builtin.js");
jsb_run_script("js/jcwallet.js");
jsb_run_script("js/platform.js");
jsb_run_script("js/main.js");
jsb_run_script("js/wallet.js");
jsb_run_script("Data/js/jsb-adapter/jsb-builtin.js");
jsb_run_script("Data/js/jcwallet.js");
jsb_run_script("Data/js/platform.js");
jsb_run_script("Data/js/main.js");
jsb_run_script("Data/js/wallet.js");
se->addAfterCleanupHook([]() {
JSBClassType::destroy();
});

View File

@ -17,6 +17,7 @@
#include "AppDelegate.h"
#import "UIViewController+Wallet.h"
#import "UIViewController+QR.h"
#import "UIViewController+Purchase.h"
#endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
@ -460,6 +461,110 @@ bool jsb_showWebPage(se::State& s) {
}
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) {
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("showQRCode", _SE(JSB_showQRCode));
__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_beginBuyJNI
return true;

View File

@ -1,5 +1,5 @@
//
// UIViewController+Logger.h
// UIViewController+Purchase.h
// Unity-iPhone
//
// Created by Hl Zhang on 2023/3/21.
@ -9,8 +9,10 @@
#import <UIKit/UIKit.h>
@interface UIViewController (Purchase)
- (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

View File

@ -1,116 +1,203 @@
//
// UIViewController+Logger.cpp
// UIViewController+Purchase.cpp
// Unity-iPhone
//
// Created by Hl Zhang on 2023/3/21.
//
#import "UIViewController+Purchase.h"
#import "UIViewController+Wallet.h"
#import "Utilities.h"
#import "StoreManager.h"
#import "StoreObserver.h"
#import "AppConfiguration.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)
-(void)initPurchaseEnv {
utility = [[Utilities alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleProductRequestNotification:)
name:PCSProductRequestNotification
object:[StoreManager sharedInstance]];
// [[NSNotificationCenter defaultCenter] addObserver:self
// selector:@selector(handleProductRequestNotification:)
// name:PCSProductRequestNotification
// object:[StoreManager sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handlePurchaseNotification:)
name:PCSPurchaseNotification
object:[StoreObserver sharedInstance]];
}
#pragma mark - Query products
/// 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.
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.
[[StoreManager sharedInstance] startProductRequestWithIdentifiers:identifiers];
} else {
// Warn the user that the resource file does not contain anything.
[self alertWithTitle:PCSMessagesStatus message:[NSString stringWithFormat:@"%@.%@ %@", PCSProductIdsPlistName, PCSProductIdsPlistFileExtension, PCSMessagesEmptyResource]];
// Fetch the product information.
[[StoreManager sharedInstance] fetchProductsMatchingIdentifiers:identifiers completionBlock: ^(PCSProductRequestStatus status, NSMutableArray *storeResponse){
if (status == PCSStoreResponse) {
// define json array to return
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 if ([section.name isEqualToString:PCSProductsInvalidIdentifiers]) {
// if there are invalid product identifiers, show them.
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 {
// Warn the user that the resource file could not be found.
[self alertWithTitle:PCSMessagesStatus message:[NSString stringWithFormat:@"%@ %@.%@.", PCSMessagesResourceNotFound, PCSProductIdsPlistName, PCSProductIdsPlistFileExtension]];
}
}];
} else {
// 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.
-(void)alertWithTitle:(NSString *)title message:(NSString *)message {
UIAlertController *alertController = [utility alertWithTitle:title message:message];
[self.navigationController presentViewController:alertController animated:YES completion:nil];
UIAlertController *alertController = [utility alertWithTitle:title message:message];
[self.navigationController presentViewController:alertController animated:YES completion:nil];
}
#pragma mark - Handle PCSProductRequest Notification
/// Updates the UI according to the product request notification result.
-(void)handleProductRequestNotification:(NSNotification *)notification {
StoreManager *productRequestNotification = (StoreManager*)notification.object;
PCSProductRequestStatus status = (PCSProductRequestStatus)productRequestNotification.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];
}
StoreManager *productRequestNotification = (StoreManager*)notification.object;
PCSProductRequestStatus status = (PCSProductRequestStatus)productRequestNotification.status;
NSLog(@"handleProductRequestNotification status: %ld", (long)status);
}
#pragma mark - Handle PCSPurchase Notification
/// Updates the UI according to the purchase request notification result.
-(void)handlePurchaseNotification:(NSNotification *)notification {
StoreObserver *purchasesNotification = (StoreObserver *)notification.object;
PCSPurchaseStatus status = (PCSPurchaseStatus)purchasesNotification.status;
switch (status) {
case PCSNoRestorablePurchases:
case PCSPurchaseFailed:
case PCSRestoreFailed: [self alertWithTitle:PCSMessagesPurchaseStatus message:purchasesNotification.message];
break;
// Switch to the Purchases view when receiving a successful restore notification.
case PCSRestoreSucceeded: [self handleRestoredSucceededTransaction];
break;
default: break;
}
NSDictionary* dic = notification.userInfo;
NSNumber *errcode = dic[@"errcode"];
NSLog(@"handlePurchaseNotification status: %@", errcode);
// check if errcode is zero or not
self.currentFunId = nil;
if (errcode != nil && [errcode intValue] != 0) {
[self nativeCb:self.currentFunId hasErr:YES dataStr: [NSString stringWithFormat:@"%@", dic[@"errmsg"]]];
} else {
NSString *tid = dic[@"dataid"];
NSMutableArray *transactions = [[NSMutableArray alloc] init];
[transactions addObject:tid];
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];
}
}
#pragma mark - Handle Restored Transactions
/// Handles succesful restored transactions. Switches to the Purchases view.
-(void)handleRestoredSucceededTransaction {
utility.restoreWasCalled = YES;
// Refresh the Purchases view with the restored purchases.
// [self switchToViewController:ParentViewControllerSegmentPurchases];
// [self.purchases reloadWithData:self.utility.dataSourceForPurchasesUI];
// self.segmentedControl.selectedSegmentIndex = ParentViewControllerSegmentPurchases;
utility.restoreWasCalled = YES;
// Refresh the Purchases view with the restored purchases.
// [self switchToViewController:ParentViewControllerSegmentPurchases];
// [self.purchases reloadWithData:self.utility.dataSourceForPurchasesUI];
// self.segmentedControl.selectedSegmentIndex = ParentViewControllerSegmentPurchases;
}
@end
@end

View File

@ -6,6 +6,7 @@
//
#import "UIViewController+QR.h"
#import "UIViewController+Wallet.h"
#import "QRCodeReaderViewController.h"
#import "QRCodeReader.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

View File

@ -22,5 +22,5 @@
-(void)saveKey:(NSString *) account key:(NSString *) key;
-(NSString *)loadKey:(NSString *) account;
-(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

View File

@ -176,14 +176,26 @@ static WebPageViewController *webpageVC = nil;
return;
}
std::string methodName = "nativeCallBack";
NSString *paramStr;
NSMutableDictionary *json = [[NSMutableDictionary alloc] init];
if (hasErr) {
paramStr = [NSString stringWithFormat:@"{\"errcode\": 1, \"errmessage\": \"%@\"}", dataStr];
json[@"errcode"] = @1;
json[@"errmessage"] = dataStr;
} 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 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());
}

View File

@ -322,6 +322,7 @@
D59AB8522A68FEB700433200 /* Section.m in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8502A68FEB700433200 /* Section.m */; };
D59AB8552A690BC300433200 /* UIViewController+Purchase.mm in Sources */ = {isa = PBXBuildFile; fileRef = D59AB8542A690BC300433200 /* UIViewController+Purchase.mm */; };
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 */; };
D5BF397829C9B8C000EC6351 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D5BF397729C9B8C000EC6351 /* GoogleService-Info.plist */; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -1469,6 +1473,7 @@
29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
isa = PBXGroup;
children = (
D59AB85A2A6A387A00433200 /* test-in-app-purchase.storekit */,
D59AB8482A68FC2900433200 /* purchase */,
D5C03B6F2A49A808002E758D /* Data */,
D526FA3C299498E3002A2290 /* cocos2d_libs.xcodeproj */,
@ -3500,6 +3505,8 @@
D59AB8482A68FC2900433200 /* purchase */ = {
isa = PBXGroup;
children = (
D59AB85D2A6A500900433200 /* SKProduct+SKProductAdditions.h */,
D59AB85C2A6A500900433200 /* SKProduct+SKProductAdditions.m */,
D59AB8492A68FC5200433200 /* StoreObserver.h */,
D59AB8462A68FC2200433200 /* StoreObserver.m */,
D59AB84A2A68FC7200433200 /* AppConfiguration.h */,
@ -3817,6 +3824,7 @@
D55759902A5FAC16009369FC /* Generics5.cpp in Sources */,
D59AB84F2A68FE8E00433200 /* StoreManager.m in Sources */,
D55759D12A5FAC16009369FC /* UnityEngine.InputLegacyModule.cpp in Sources */,
D59AB85E2A6A500900433200 /* SKProduct+SKProductAdditions.m in Sources */,
D507C9412994A0A800CF3953 /* UIView+Toast.m in Sources */,
D55759E12A5FAC16009369FC /* UnityEngine.TextCoreTextEngineModule.cpp in Sources */,
D507D5E92994C62A00CF3953 /* UnityAdsUtilities.m in Sources */,

View File

@ -142,8 +142,8 @@
filePath = "Classes_cocos/JcWallet.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "182"
endingLineNumber = "182"
startingLineNumber = "183"
endingLineNumber = "183"
landmarkName = "JcWallet::jsToUnity(funId, msg)"
landmarkType = "7">
</BreakpointContent>
@ -238,8 +238,8 @@
filePath = "Classes_cocos/JcWallet.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "188"
endingLineNumber = "188"
startingLineNumber = "189"
endingLineNumber = "189"
landmarkName = "initEnv()"
landmarkType = "9">
</BreakpointContent>
@ -254,11 +254,267 @@
filePath = "Classes_cocos/JcWallet.mm"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "114"
endingLineNumber = "114"
startingLineNumber = "115"
endingLineNumber = "115"
landmarkName = "JcWallet::initEnv()"
landmarkType = "7">
</BreakpointContent>
</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>
</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) {
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
@ -493,20 +507,34 @@ function nftMallBuy(funId, currency, addresses, ids, amounts, values, signature,
// 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
function queryGoogleProducts(funId, productIds) {
let ids = JSON.parse(productIds);
console.log('queryGoogleProducts:: ' + productIds);
promiseCb(funId, jc.wallet.paySvr.queryGoogleProducts(ids));
console.log('queryAppleProducts:: ' + productIds);
promiseCb(funId, jc.wallet.paySvr.queryIOSProducts(ids));
}
function queryGooglePurchases(funId) {
promiseCb(funId, jc.wallet.paySvr.queryGooglePurchases());
promiseCb(funId, jc.wallet.paySvr.queryIOSPurchases());
}
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

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;
/// 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.
-(NSString *)titleMatchingIdentifier:(NSString *)identifier;
-(SKProduct *)productMatchingIdentifier:(NSString *)identifier;
/// - returns: Existing product's title associated with the specified payment transaction.
-(NSString *)titleMatchingPaymentTransaction:(SKPaymentTransaction *)transaction;
@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.
@property (strong) SKProductsRequest *productRequest;
@property (copy, nonatomic) void (^completionBlock) (PCSProductRequestStatus, NSMutableArray *);
@end
@implementation StoreManager
@ -47,13 +50,10 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
#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.
-(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.
NSSet *productIdentifiers = [NSSet setWithArray:identifiers];
@ -89,7 +89,9 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
if (self.storeResponse.count > 0) {
self.status = PCSStoreResponse;
if (_completionBlock) {
_completionBlock(self.status, self.storeResponse);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:PCSProductRequestNotification object:self];
});
@ -123,6 +125,16 @@ Retrieves product information from the App Store using SKRequestDelegate, SKProd
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.
-(NSString *)titleMatchingPaymentTransaction:(SKPaymentTransaction *) transaction {
NSString *title = [self titleMatchingIdentifier:transaction.payment.productIdentifier];

View File

@ -16,24 +16,34 @@ Implements the SKPaymentTransactionObserver protocol. Handles purchasing and res
- returns: true if the user is allowed to make payments and false, otherwise. Tell StoreManager to query the App Store when the user is allowed to make payments and there are product identifiers to be
queried.
*/
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL isAuthorizedForPayments;
@property(NS_NONATOMIC_IOSONLY, readonly) BOOL isAuthorizedForPayments;
/// Indicates the cause of the purchase failure.
@property (nonatomic, copy) NSString *message;
@property(nonatomic, copy) NSString *message;
/// Keeps track of all purchases.
@property (strong) NSMutableArray *productsPurchased;
@property(strong) NSMutableArray *productsPurchased;
/// 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.
@property (nonatomic) PCSPurchaseStatus status;
@property(nonatomic) PCSPurchaseStatus status;
/// 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.
-(void)restore;
@end
- (void)restore;
- (NSString *)getAppReceipt;
@end

View File

@ -1,9 +1,9 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
Implements the SKPaymentTransactionObserver protocol. Handles purchasing and restoring products using paymentQueue:updatedTransactions: .
*/
See LICENSE folder for this samples licensing information.
Abstract:
Implements the SKPaymentTransactionObserver protocol. Handles purchasing and restoring products using paymentQueue:updatedTransactions: .
*/
#import "StoreObserver.h"
@ -14,155 +14,262 @@ Implements the SKPaymentTransactionObserver protocol. Handles purchasing and res
@implementation StoreObserver
+ (StoreObserver *)sharedInstance {
static dispatch_once_t onceToken;
static StoreObserver * storeObserverSharedInstance;
dispatch_once(&onceToken, ^{
storeObserverSharedInstance = [[StoreObserver alloc] init];
});
return storeObserverSharedInstance;
static dispatch_once_t onceToken;
static StoreObserver * storeObserverSharedInstance;
dispatch_once(&onceToken, ^{
storeObserverSharedInstance = [[StoreObserver alloc] init];
});
return storeObserverSharedInstance;
}
- (instancetype)init {
self = [super init];
if (self != nil) {
_hasRestorablePurchases = NO;
_productsPurchased = [[NSMutableArray alloc] initWithCapacity:0];
_productsRestored = [[NSMutableArray alloc] initWithCapacity:0];
_status = PCSPurchaseStatusNone;
}
return self;
self = [super init];
if (self != nil) {
_hasRestorablePurchases = NO;
_productsRestored = [[NSMutableArray alloc] initWithCapacity:0];
_pendingTransactions = [[NSMutableDictionary alloc] init];
_finishedTransactions = [[NSMutableSet alloc] init];
_status = PCSPurchaseStatusNone;
}
return self;
}
/**
Indicates whether the user is allowed to make payments.
- returns: true if the user is allowed to make payments and false, otherwise. Tell StoreManager to query the App Store when the user is allowed to make payments and there are product identifiers to be
queried.
Indicates whether the user is allowed to make payments.
- returns: true if the user is allowed to make payments and false, otherwise. Tell StoreManager to query the App Store when the user is allowed to make payments and there are product identifiers to be
queried.
*/
-(BOOL)isAuthorizedForPayments {
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
/// Creates and adds a payment request to the payment queue.
-(void)buy:(SKProduct *)product {
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
-(void)buy:(SKProduct *)product orderId: (NSString *) orderId {
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];
}
#pragma mark - Restore All Restorable Purchases
/// Restores all previously completed purchases.
-(void)restore {
if (self.productsRestored.count > 0) {
[self.productsRestored removeAllObjects];
}
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
if (self.productsRestored.count > 0) {
[self.productsRestored removeAllObjects];
}
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
#pragma mark - SKPaymentTransactionObserver Methods
/// Called when there are transactions in the payment queue.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for(SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing: break;
// Do not block your UI. Allow the user to continue using your app.
case SKPaymentTransactionStateDeferred: NSLog(@"%@", PCSMessagesDeferred);
break;
// The purchase was successful.
case SKPaymentTransactionStatePurchased: [self handlePurchasedTransaction:transaction];
break;
// The transaction failed.
case SKPaymentTransactionStateFailed: [self handleFailedTransaction:transaction];
break;
// There are restored products.
case SKPaymentTransactionStateRestored: [self handleRestoredTransaction:transaction];
break;
default: break;
}
for(SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing: break;
// Do not block your UI. Allow the user to continue using your app.
case SKPaymentTransactionStateDeferred: NSLog(@"%@", PCSMessagesDeferred);
break;
// The purchase was successful.
case SKPaymentTransactionStatePurchased: [self handleTransactionPurchased:transaction];
break;
// The transaction failed.
case SKPaymentTransactionStateFailed: [self handleTransactionFailed:transaction];
break;
// There are restored products.
case SKPaymentTransactionStateRestored: [self handleTransactionRestored:transaction];
break;
default: break;
}
}
}
/// Logs all transactions that have been removed from the payment queue.
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions {
for(SKPaymentTransaction *transaction in transactions) {
NSLog(@"%@ %@", transaction.payment.productIdentifier, PCSMessagesRemove);
}
for(SKPaymentTransaction *transaction in transactions) {
NSLog(@"%@ %@", transaction.payment.productIdentifier, PCSMessagesRemove);
}
}
/// Called when an error occur while restoring purchases. Notifies the user about the error.
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
if (error.code != SKErrorPaymentCancelled) {
self.status = PCSRestoreFailed;
self.message = error.localizedDescription;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self];
});
}
NSLog(@"%@\n%@", PCSMessagesFailed, error.localizedDescription);
// if (error.code != SKErrorPaymentCancelled) {
// self.status = PCSRestoreFailed;
// self.message = error.localizedDescription;
//
// dispatch_async(dispatch_get_main_queue(), ^{
// [[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self];
// });
// }
}
/// Called when all restorable transactions have been processed by the payment queue.
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
NSLog(@"%@", PCSMessagesRestorable);
if (!self.hasRestorablePurchases) {
self.status = PCSNoRestorablePurchases;
self.message = [NSString stringWithFormat:@"%@\n%@", PCSMessagesNoRestorablePurchases, PCSMessagesPreviouslyBought];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self];
});
}
NSLog(@"%@\n%@", PCSMessagesNoRestorablePurchases, PCSMessagesPreviouslyBought);
// if (!self.hasRestorablePurchases) {
// self.status = PCSNoRestorablePurchases;
// self.message = [NSString stringWithFormat:@"%@\n%@", PCSMessagesNoRestorablePurchases, PCSMessagesPreviouslyBought];
//
// dispatch_async(dispatch_get_main_queue(), ^{
// [[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object:self];
// });
// }
}
#pragma mark - Handle Payment Transactions
/// Handles successful purchase transactions.
-(void)handlePurchasedTransaction:(SKPaymentTransaction*)transaction {
[self.productsPurchased addObject:transaction];
NSLog(@"%@ %@.", PCSMessagesDeliverContent, transaction.payment.productIdentifier);
// Finish the successful transaction.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
/// Handles failed purchase transactions.
-(void)handleFailedTransaction:(SKPaymentTransaction*)transaction {
self.status = PCSPurchaseFailed;
self.message = [NSString stringWithFormat:@"%@ %@ %@",PCSMessagesPurchaseOf, transaction.payment.productIdentifier, PCSMessagesFailed];
if (transaction.error) {
[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.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
-(void)handleTransactionPurchased:(SKPaymentTransaction*)transaction {
NSString *transactionId = transaction.transactionIdentifier;
NSLog(@"%@ %@.", PCSMessagesDeliverContent, transaction.payment.productIdentifier);
NSLog(@"original transaction id: %@", transactionId);
NSLog(@"order id: %@", transaction.payment.applicationUsername);
[self onTransactionSucceeded: transaction];
}
/// Handles restored purchase transactions.
-(void)handleRestoredTransaction:(SKPaymentTransaction*)transaction {
self.status = PCSRestoreSucceeded;
self.hasRestorablePurchases = true;
[self.productsRestored addObject:transaction];
NSLog(@"%@ %@.", PCSMessagesRestoreContent, transaction.payment.productIdentifier);
-(void)handleTransactionRestored:(SKPaymentTransaction*)transaction {
self.status = PCSRestoreSucceeded;
self.hasRestorablePurchases = true;
[self onTransactionSucceeded: transaction];
}
/// Handles failed purchase transactions.
-(void)handleTransactionFailed:(SKPaymentTransaction*)transaction {
self.status = PCSPurchaseFailed;
self.message = [NSString stringWithFormat:@"%@ %@ %@",PCSMessagesPurchaseOf, transaction.payment.productIdentifier, PCSMessagesFailed];
[self onPurchaseResult: transaction.payment.productIdentifier errorCode: 200 + transaction.error.code errorDescription: [NSString stringWithFormat:@"\n%@ %@", PCSMessagesError, transaction.error.localizedDescription]];
// Finish the failed transaction.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
// Handle a new or restored purchase transaction by informing Unity.
- (void)onTransactionSucceeded:(SKPaymentTransaction*)transaction
{
NSString* transactionId = transaction.transactionIdentifier;
// This should never happen according to Apple's docs, but it does!
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!"];
}
// 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];
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];
[[NSNotificationCenter defaultCenter] postNotificationName:PCSPurchaseNotification object: self userInfo:dic];
});
// Finish the restored transaction.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
@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
}
}