Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/ios/IAPManager.mm
3185 views
#import "IAPManager.h"
#import <UIKit/UIKit.h>
#import "ViewControllerCommon.h"
#include "Common/System/Request.h"
#include "Common/Log.h"

#include "../ppsspp_config.h"

// Only one operation can be in progress at once.
@implementation IAPManager {
#ifdef USE_IAP
	SKProduct *_goldProduct;
#endif
	int _pendingRequestID;
}

+ (instancetype)sharedIAPManager {
	static IAPManager *shared = nil;
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		shared = [[self alloc] init];
	});
	return shared;
}

- (instancetype)init {
#ifdef USE_IAP
	if (self = [super init]) {
		[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
	}
#endif
	return self;
}

- (void)startObserving {
#ifdef USE_IAP
	[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
#endif
}

- (BOOL)isGoldUnlocked {
#ifdef USE_IAP
	return [[NSUserDefaults standardUserDefaults] boolForKey:@"isGold"];
#else
	return false;
#endif
}

- (void)buyGoldWithRequestID:(int)requestID {
#ifdef USE_IAP
	if (_pendingRequestID) {
		ERROR_LOG(Log::IAP, "A transaction is pending. Failing the new request.");
		g_requestManager.PostSystemFailure(requestID);
		return;
	}
	_pendingRequestID = requestID;

	if ([SKPaymentQueue canMakePayments]) {
		NSLog(@"[IAPManager] Starting buy request (requestID: %d)", requestID);
		NSSet *productIds = [NSSet setWithObject:@"org.ppsspp.gold"];
		SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIds];
		request.delegate = self;
		[request start];
	} else {
		NSLog(@"[IAPManager] In-App Purchases are disabled (requestID: %d)", requestID);
		g_requestManager.PostSystemFailure(requestID);
	}
#else
	g_requestManager.PostSystemFailure(requestID);
#endif
}

- (void)restorePurchasesWithRequestID:(int)requestID {
#ifdef USE_IAP
	if (_pendingRequestID) {
		ERROR_LOG(Log::IAP, "A transaction is pending. Failing the new request.");
		g_requestManager.PostSystemFailure(requestID);
		return;
	}
	_pendingRequestID = requestID;

	NSLog(@"Restoring purchases (id=%d)", requestID);
	// NOTE: This is deprecated, but StoreKit 2 is swift only. We'll keep using it until
	// there's a replacement.
	[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
#else
	g_requestManager.PostSystemFailure(requestID);
#endif
}

#ifdef USE_IAP

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
	NSLog(@"Restore completed successfully (requestID: %d)", _pendingRequestID);
	// Probably not necessary, we already got the updatedTransactions notification
	// and posted success. Though maybe we shouldn't do it there, but here instead.
	if (_pendingRequestID != 0) {
		g_requestManager.PostSystemSuccess(_pendingRequestID, "", 0);
		_pendingRequestID = 0;
	}
	// Notify your app/UI here
}

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
	NSLog(@"Restore failed (requestID: %d): %@", _pendingRequestID, error.localizedDescription);
	// Notify failure to game layer
	if (_pendingRequestID != 0) {
		g_requestManager.PostSystemFailure(_pendingRequestID);
		_pendingRequestID = 0;
	}
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
	_goldProduct = response.products.firstObject;
	if (_goldProduct) {
		// Received a valid product. Send a payment.
		SKPayment *payment = [SKPayment paymentWithProduct:_goldProduct];
		[[SKPaymentQueue defaultQueue] addPayment:payment];
	} else {
		NSLog(@"[IAPManager] Gold product not found (requestID: %d)", _pendingRequestID);
		g_requestManager.PostSystemFailure(_pendingRequestID);
		_pendingRequestID = 0;
	}
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
	for (SKPaymentTransaction *transaction in transactions) {
		switch (transaction.transactionState) {
			case SKPaymentTransactionStatePurchased:
			case SKPaymentTransactionStateRestored:
				NSLog(transaction.transactionState == SKPaymentTransactionStatePurchased ? @"IAP Purchase" : @"IAP Restore");
				// Perform the unlock (updaing the variable and switching the icon).
				[self unlockGold];
				[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
				g_requestManager.PostSystemSuccess(_pendingRequestID, "", 0);
				_pendingRequestID = 0;
				break;
			case SKPaymentTransactionStateFailed:
				NSLog(@"[IAPManager] Purchase failed (requestID: %d): %@", _pendingRequestID, transaction.error.localizedDescription);
				[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
				// Optionally post failure callback here
				g_requestManager.PostSystemFailure(_pendingRequestID);
				_pendingRequestID = 0;
				break;
			default:
				break;
		}
	}
}

#endif

- (void)unlockGold {
#ifdef USE_IAP
	INFO_LOG(Log::UI, "Unlocking gold reward!");

	// Write to user defaults to store the status.
	[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isGold"];
	[[NSUserDefaults standardUserDefaults] synchronize];

	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
		[self updateIcon:true];
	});

	NSLog(@"[IAPManager] Gold unlocked!");
#endif
}

static bool SafeStringEqual(NSString *a, NSString *b) {
    return (a == b) || [a isEqualToString:b];
}

- (void)updateIcon:(bool)force {
	NSString *desiredIcon = nil;
	if ([self isGoldUnlocked]) {
		desiredIcon = @"PPSSPPGold";
	}

	NSLog(@"updateIcon called with %@", desiredIcon);

	if (![UIApplication sharedApplication]) {
		NSLog(@"IAPManager: Application not initialized");
		return;
	}

	NSLog(@"Current icon name: %@", [[UIApplication sharedApplication] alternateIconName]);

	if (desiredIcon) {
		NSLog(@"IAPManager about to update icon to %@ (force=%d)", desiredIcon, (int)force);
	} else {
		NSLog(@"IAPManager about to reset the icon (force=%d)", (int)force);
	}

	if ([[UIApplication sharedApplication] supportsAlternateIcons]) {
		if (force || !SafeStringEqual([UIApplication sharedApplication].alternateIconName, desiredIcon)) {
			// Name not matching, do the update.
			[[UIApplication sharedApplication] setAlternateIconName:desiredIcon
												  completionHandler:^(NSError * _Nullable error) {
				if (error) {
					NSLog(@"[IAPManager] Failed to set Gold icon to %@: %@", desiredIcon, error.localizedDescription);
					[sharedViewController hideKeyboard];
				} else {
					NSLog(@"Icon update succeeded.");
					NSLog(@"Current icon name: %@", [[UIApplication sharedApplication] alternateIconName]);
					// Here we need to call hideKeyboard.
					[sharedViewController hideKeyboard];
				}
			}];
			NSLog(@"Icon update to %@ dispatched, waiting for response.", desiredIcon);
		} else {
			NSLog(@"Icon is already correct: %@", desiredIcon);
		}
	} else {
		NSLog(@"Application doesn't support alternate icons.");
	}
}

@end