277 lines
10 KiB
Objective-C
277 lines
10 KiB
Objective-C
/*
|
||
See LICENSE folder for this sample’s licensing information.
|
||
|
||
Abstract:
|
||
Implements the SKPaymentTransactionObserver protocol. Handles purchasing and restoring products using paymentQueue:updatedTransactions: .
|
||
*/
|
||
|
||
#import "StoreObserver.h"
|
||
|
||
@interface StoreObserver ()
|
||
/// Indicates whether there are restorable purchases.
|
||
@property (nonatomic) BOOL hasRestorablePurchases;
|
||
@end
|
||
|
||
@implementation StoreObserver
|
||
+ (StoreObserver *)sharedInstance {
|
||
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;
|
||
_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.
|
||
*/
|
||
-(BOOL)isAuthorizedForPayments {
|
||
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 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];
|
||
}
|
||
|
||
#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 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);
|
||
}
|
||
}
|
||
|
||
/// Called when an error occur while restoring purchases. Notifies the user about the error.
|
||
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
|
||
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(@"%@\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)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)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 userInfo:dic];
|
||
});
|
||
}
|
||
|
||
@end
|
||
|