mirror of
https://github.com/id-Software/DOOM-IOS2.git
synced 2026-03-20 00:49:30 +01:00
684 lines
22 KiB
Plaintext
Executable File
684 lines
22 KiB
Plaintext
Executable File
/*
|
|
|
|
Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
/*
|
|
================================================================================================
|
|
|
|
Implentation of the In-App Purchase system for iOS devices.
|
|
|
|
|
|
This implementation conforms to the interface in InAppStore.h. It uses the NSUserDefaults object
|
|
provided by the system to keep track of the state of the products that the application offers.
|
|
|
|
================================================================================================
|
|
*/
|
|
|
|
|
|
#include "InAppStore.h"
|
|
|
|
#import <StoreKit/StoreKit.h>
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#include <map>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
// For ease of development, leave this defined to skip in-app purchase prompts.
|
|
//#define TEST_ALL_PRODUCTS_PURCHASED
|
|
|
|
@interface MyStoreObserver : NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver> {
|
|
|
|
}
|
|
|
|
// SKProductsRequestDelegate methods
|
|
- (void)productsRequest: (SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response;
|
|
|
|
// SKPaymentTransactionObserver methods
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions;
|
|
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error;
|
|
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue;
|
|
|
|
// MyStoreObserver methods
|
|
- (void)completeTransaction:(SKPaymentTransaction *)transaction;
|
|
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
|
|
- (void)restoreTransaction:(SKPaymentTransaction *)transaction;
|
|
- (void)finalizeTransaction:(SKPaymentTransaction *)transaction;
|
|
|
|
@end
|
|
|
|
// Unnamed-namespace for stuff that's "private" to this file (internal linkage).
|
|
namespace {
|
|
// The single instance of the store observer. This is created in Initialize and released
|
|
// in Shutdown.
|
|
MyStoreObserver * observer;
|
|
|
|
// The callback to invoke when an interesting event occurs.
|
|
idInAppStore::callback_t callbackObject;
|
|
|
|
/*
|
|
================================================
|
|
Each product identifier has an associated state to keep track of the progress of
|
|
in-app purchases. These states are stored in the NSUserDefaults for the corresponding
|
|
product identifier. These states are not in an enum because the the NSUserDefaults store
|
|
NSIntegers, and we can't define the type of an enum in C++03.
|
|
================================================
|
|
*/
|
|
typedef NSInteger productState_t;
|
|
|
|
// This is the state of a product if it not found in the user's defaults.
|
|
const productState_t PRODUCT_NOT_FOUND = 0;
|
|
|
|
// Information from this product has been requested, but has not been received yet.
|
|
const productState_t PRODUCT_WAIT_FOR_INFORMATION = 1;
|
|
|
|
// Information for this product is available, but a purchase has not been initiated.
|
|
const productState_t PRODUCT_HAS_INFORMATION = 2;
|
|
|
|
// A purchase was initiated, and the application is waiting for the App Store to
|
|
// process the purchase.
|
|
const productState_t PRODUCT_WAIT_FOR_PURCHASE = 3;
|
|
|
|
// The app store has fully processed the purchase and the product is available for the
|
|
// user/
|
|
const productState_t PRODUCT_PURCHASED = 4;
|
|
|
|
|
|
void RequestInformation( const std::vector<std::string> & productIdentifiers );
|
|
|
|
/*
|
|
================================================
|
|
Store product information in a C++ friendly way.
|
|
================================================
|
|
*/
|
|
struct productInformation_t {
|
|
std::string identifier;
|
|
std::string localizedPrice;
|
|
|
|
productInformation_t( const std::string & identifier_, const std::string & localizedPrice_ )
|
|
: identifier( identifier_ )
|
|
, localizedPrice( localizedPrice_ )
|
|
{
|
|
}
|
|
};
|
|
|
|
// Cache the product information that has been received from the App Store.
|
|
typedef std::map<std::string, productInformation_t> productMap_t;
|
|
productMap_t productCache;
|
|
|
|
/*
|
|
========================
|
|
GetProductState
|
|
|
|
Queries the NSUserDefaults for the state of the product corresponding to the
|
|
productIdentifier.
|
|
========================
|
|
*/
|
|
productState_t GetProductState( const char * const productIdentifier ) {
|
|
#if defined( TEST_ALL_PRODUCTS_PURCHASED )
|
|
return PRODUCT_PURCHASED;
|
|
#endif
|
|
|
|
// Convert the C string to an NSString for use with NSUserDefaults.
|
|
NSString *productKey = [NSString stringWithCString:productIdentifier
|
|
encoding:NSUTF8StringEncoding];
|
|
|
|
// Purchase records are stored in the standardUserDefaults, with a key equal to the
|
|
// product identifier.
|
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
NSInteger productState = [defaults integerForKey:productKey];
|
|
|
|
return productState;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
SetProductState
|
|
|
|
Sets the NSUserDefaults value corresponding to product identifier to the given state.
|
|
========================
|
|
*/
|
|
void SetProductState( NSString * productIdentifier, productState_t state ) {
|
|
// Purchase records are stored in the standardUserDefaults, with a key equal to the
|
|
// product identifier.
|
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
[defaults setInteger:state forKey:productIdentifier];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
SetProductState
|
|
|
|
Sets the NSUserDefaults value corresponding to product identifier to the given state.
|
|
========================
|
|
*/
|
|
void SetProductState( const char * const productIdentifier, productState_t state ) {
|
|
// Convert the C string to an NSString for use with NSUserDefaults.
|
|
NSString *productKey = [NSString stringWithCString:productIdentifier
|
|
encoding:NSUTF8StringEncoding];
|
|
|
|
SetProductState( productKey, state );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
RequestInformation
|
|
|
|
Queries the App Store for information about the all the identifiers in the vector.
|
|
When the App Store responds, calls the callback. The product identifiers are UTF8 encoded.
|
|
|
|
If in-app purchases are disabled, this function does nothing. The application may want to
|
|
show an alert in this case. The application can call IsEnabled() to check for this.
|
|
========================
|
|
*/
|
|
void RequestInformation( const std::vector<std::string> & productIdentifiers ) {
|
|
// Early exit if the system hasn't been initialized.
|
|
if ( observer == nil ) {
|
|
printf( "In-App Purchase system not initialized. Can't purchase anything!\n" );
|
|
return;
|
|
}
|
|
|
|
// Early exit if in-app purchases are disabled.
|
|
if ( [SKPaymentQueue canMakePayments] == FALSE ) {
|
|
printf( "In-App Purchases are disabled for this device. Can't purchase anything!\n" );
|
|
return;
|
|
}
|
|
|
|
// This will be the set of product identifiers to request from the App Store.
|
|
NSMutableSet * identifiersToRequest = [NSMutableSet setWithCapacity: productIdentifiers.size()];
|
|
|
|
#ifndef NDEBUG
|
|
// For debugging purposes, set this flag to 'true' and all in-app purchase products
|
|
// will be reset to the unpurchased state.
|
|
bool resetPurchases = false;
|
|
|
|
if ( resetPurchases ) {
|
|
for( std::vector<std::string>::const_iterator i = productIdentifiers.begin(); i != productIdentifiers.end(); ++i ) {
|
|
SetProductState( i->c_str(), PRODUCT_NOT_FOUND );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Check the NSUserDefaults for each identifier requested, to get the state of
|
|
// the product.
|
|
for( std::vector<std::string>::const_iterator i = productIdentifiers.begin(); i != productIdentifiers.end(); ++i ) {
|
|
// If the user has not purchased this product, we need to request information about
|
|
// it from the App Store.
|
|
productState_t productState = GetProductState( i->c_str() );
|
|
if ( productState != PRODUCT_PURCHASED ) {
|
|
|
|
SetProductState( i->c_str(), PRODUCT_WAIT_FOR_INFORMATION );
|
|
|
|
NSString * nsIdentifier = [NSString stringWithCString:i->c_str()
|
|
encoding:NSUTF8StringEncoding];
|
|
|
|
[identifiersToRequest addObject:nsIdentifier];
|
|
}
|
|
}
|
|
|
|
// Now we know which identifiers to request from the store, start the request object.
|
|
if ( [identifiersToRequest count] > 0 ) {
|
|
SKProductsRequest *request= [[SKProductsRequest alloc]
|
|
initWithProductIdentifiers:identifiersToRequest ];
|
|
|
|
request.delegate = observer;
|
|
[request start];
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace idInAppStore {
|
|
|
|
/*
|
|
========================
|
|
Initialize
|
|
|
|
Creates the StoreKit observer object and registers it with the payment queue. The
|
|
application must provide all the product IDs it has available for sale to this function,
|
|
so that it can request information from the app store.
|
|
========================
|
|
*/
|
|
void Initialize( const std::vector<std::string> & productIdentifiers ) {
|
|
if ( !observer ) {
|
|
observer = [[MyStoreObserver alloc] init];
|
|
|
|
// Add the observer here in case purchase requests were interrupted last time.
|
|
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
|
|
}
|
|
|
|
RequestInformation( productIdentifiers );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
Shutdown
|
|
|
|
Frees the StoreKit observer object.
|
|
========================
|
|
*/
|
|
void Shutdown() {
|
|
[observer release];
|
|
observer = nil;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
IsEnabled
|
|
|
|
Returns true if in-app purchases are enabled, false if they are not.
|
|
========================
|
|
*/
|
|
bool IsEnabled() {
|
|
return ( [SKPaymentQueue canMakePayments] )? true: false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
StartPurchase
|
|
|
|
Begins the purchase process with the App Store. The product id must have been
|
|
previously requested through RequestInformation. This will launch the OS-controlled
|
|
confirmation prompt after a moment, and it might take a while to actually complete the
|
|
purchase.
|
|
========================
|
|
*/
|
|
void StartPurchase( const char * const productIdentifier ) {
|
|
if ( ![SKPaymentQueue canMakePayments] ) {
|
|
return;
|
|
}
|
|
|
|
if ( !CanPurchase( productIdentifier ) ) {
|
|
return;
|
|
}
|
|
|
|
SetProductState( productIdentifier, PRODUCT_WAIT_FOR_PURCHASE );
|
|
|
|
NSString * nsProductIdentifier = [NSString stringWithCString:productIdentifier
|
|
encoding:NSUTF8StringEncoding];
|
|
|
|
SKPayment * payment = [SKPayment paymentWithProductIdentifier:nsProductIdentifier];
|
|
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
ShowDisabledAlert
|
|
|
|
If and only if in-app purchases are disabled, shows an alert notifying the user of this fact
|
|
and reminds him or her that he or she can enable in-app purchases in settings.
|
|
|
|
If in-app purchasees are enabled, this function does nothing.
|
|
|
|
The string parameters must be encoded in UTF8.
|
|
========================
|
|
*/
|
|
void ShowDisabledAlert( std::string title,
|
|
std::string description,
|
|
std::string okButton ) {
|
|
if ( ![SKPaymentQueue canMakePayments] ) {
|
|
// User has disabled In-App purchases in settings. Kindly remind him that he can
|
|
// enable purchases again.
|
|
|
|
if ( title.empty() ) {
|
|
title = "In-App Purchases are disabled";
|
|
}
|
|
|
|
if ( description.empty() ) {
|
|
description = "You can enable In-App purchases in your device's settings.";
|
|
}
|
|
|
|
if ( okButton.empty() ) {
|
|
title = "OK";
|
|
}
|
|
|
|
NSString * nsTitle = [NSString stringWithCString:title.c_str() encoding:NSUTF8StringEncoding];
|
|
NSString * nsDescription = [NSString stringWithCString:description.c_str() encoding:NSUTF8StringEncoding];
|
|
NSString * nsOkButton = [NSString stringWithCString:okButton.c_str() encoding:NSUTF8StringEncoding];
|
|
|
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nsTitle
|
|
message:nsDescription
|
|
delegate:nil
|
|
cancelButtonTitle:nsOkButton
|
|
otherButtonTitles:nil];
|
|
|
|
// Make sure the alert shows up on the main thread.
|
|
[alert show];
|
|
[alert release];
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
HasPurchased
|
|
|
|
Queries the standard NSUserDefaults object for the state of the product with the given
|
|
identifier. If the state is PRODUCT_PURCHASED, the user has purchased the product.
|
|
If the state is anything else, the user has not purchased the product.
|
|
========================
|
|
*/
|
|
bool HasPurchased( const char * const productIdentifier ) {
|
|
const productState_t state = GetProductState( productIdentifier );
|
|
const bool stateIsPurchased = (state == PRODUCT_PURCHASED);
|
|
return ( stateIsPurchased );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
CanPurchase
|
|
|
|
Returns true if it is safe for the application to start a purchase, false if the application
|
|
should not start a purchase at this time.
|
|
========================
|
|
*/
|
|
bool CanPurchase( const char * const productIdentifier ) {
|
|
const productState_t state = GetProductState( productIdentifier );
|
|
const bool stateHasInformaion = ( state == PRODUCT_HAS_INFORMATION );
|
|
return ( stateHasInformaion );
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
IsWaitingForInformation
|
|
|
|
Queries the standard NSUserDefaults object for the state of the product with the given
|
|
identifier. If the application is still waiting for the App Store to return information,
|
|
this function returns true. If the application has information, this function returns false.
|
|
========================
|
|
*/
|
|
bool IsWaitingForInformation( const char * const productIdentifier ) {
|
|
return ( GetProductState( productIdentifier ) == PRODUCT_WAIT_FOR_INFORMATION );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
IsWaitingForPurchase
|
|
|
|
Queries the standard NSUserDefaults object for the state of the product with the given
|
|
identifier. If the application is still waiting for the App Store to purchase the item,
|
|
this function returns true. If the application is not waiting on the app store, returns false.
|
|
========================
|
|
*/
|
|
bool IsWaitingForPurchase( const char * const productIdentifier ) {
|
|
return ( GetProductState( productIdentifier ) == PRODUCT_WAIT_FOR_PURCHASE );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
GetLocalizedPrice
|
|
|
|
Returns a UTF-8 encoded string that represents the price of the product. This string is
|
|
suitable for display to the user. If the string isn't available yet, for example, if the
|
|
App Store hasn't responded to an information request, the return value will be an empty
|
|
string.
|
|
========================
|
|
*/
|
|
std::string GetLocalizedPrice( const char * const productIdentifier ) {
|
|
productMap_t::iterator found = productCache.find( productIdentifier );
|
|
|
|
if ( found != productCache.end() ) {
|
|
return found->second.localizedPrice;
|
|
}
|
|
|
|
return std::string();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
SetCallback
|
|
|
|
Stores a callback function to be invoked when something happens relating to a product.
|
|
========================
|
|
*/
|
|
void SetCallback( callback_t callback ) {
|
|
callbackObject = callback;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
ClearCallback
|
|
|
|
Removes the callback function previously registered through SetCallback.
|
|
========================
|
|
*/
|
|
void ClearCallback() {
|
|
callbackObject = callback_t();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@implementation MyStoreObserver
|
|
|
|
- (void) showInvalidProductIdAlert
|
|
{
|
|
NSString * nsTitle = @"In-app purchase error";
|
|
NSString * nsDescription = @"Invalid product ID requested. In-app purchase will not work!";
|
|
NSString * nsOkButton = @"OK";
|
|
|
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nsTitle
|
|
message:nsDescription
|
|
delegate:nil
|
|
cancelButtonTitle:nsOkButton
|
|
otherButtonTitles:nil];
|
|
|
|
[alert show];
|
|
[alert release];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
productsRequest:didReceiveResponse
|
|
|
|
Called by the OS when the application gets product information about an In-App Purchase.
|
|
========================
|
|
*/
|
|
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
|
|
{
|
|
NSArray *myProduct = response.products;
|
|
NSArray *invalidProducts = response.invalidProductIdentifiers;
|
|
|
|
for ( SKProduct * product in myProduct )
|
|
{
|
|
NSLog( @"Valid product: %@", product.productIdentifier);
|
|
}
|
|
|
|
for ( NSString * productId in invalidProducts )
|
|
{
|
|
NSLog( @"Invalid product: %@", productId);
|
|
|
|
[self performSelectorOnMainThread:@selector(showInvalidProductIdAlert) withObject:nil waitUntilDone:NO];
|
|
return;
|
|
}
|
|
|
|
// We should handle the case of an empty array, this seems to occur if we send a request
|
|
// with a nonexistant product ID, but there may be other cases.
|
|
// TODO: Need a timeout to be even more robust.
|
|
if ( [myProduct count] == 0 ) {
|
|
//if ( clientCallback ) {
|
|
// clientCallback( IAP_FAILURE );
|
|
//}
|
|
return;
|
|
}
|
|
|
|
// We can use the same number formatter for all the products.
|
|
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
|
|
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
|
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
|
|
|
|
// Get the response and cache it.
|
|
for ( SKProduct * product in response.products )
|
|
{
|
|
// Convert the price to user-interface worthy representation.
|
|
[numberFormatter setLocale:product.priceLocale];
|
|
NSString *formattedString = [numberFormatter stringFromNumber:product.price];
|
|
|
|
const std::string priceString = [formattedString cStringUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
// Convert the NSString identifier to a C++ string.
|
|
const std::string identifierString = [product.productIdentifier cStringUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
productInformation_t productInformation( identifierString, priceString );
|
|
|
|
// Add the C++ product information to the cache.
|
|
productCache.insert( std::make_pair( identifierString, productInformation ) );
|
|
|
|
// Store the fact that we have information in the defaults.
|
|
SetProductState( identifierString.c_str(), PRODUCT_HAS_INFORMATION );
|
|
|
|
if ( callbackObject ) {
|
|
callbackObject( identifierString.c_str(), idInAppStore::PRODUCT_STATUS_RECEIVED_INFORMATION );
|
|
}
|
|
}
|
|
|
|
[numberFormatter release];
|
|
[request autorelease];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
paymentQueue:updatedTransactions
|
|
|
|
Called by the system when purchase requests are updated.
|
|
========================
|
|
*/
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
|
|
{
|
|
(void)queue;
|
|
for (SKPaymentTransaction *transaction in transactions)
|
|
{
|
|
switch (transaction.transactionState)
|
|
{
|
|
case SKPaymentTransactionStatePurchased:
|
|
[self completeTransaction:transaction];
|
|
break;
|
|
case SKPaymentTransactionStateFailed:
|
|
[self failedTransaction:transaction];
|
|
break;
|
|
case SKPaymentTransactionStateRestored:
|
|
[self restoreTransaction:transaction];
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
|
|
{
|
|
(void)queue; (void)transactions;
|
|
}
|
|
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
|
|
{
|
|
(void)queue; (void)error;
|
|
}
|
|
|
|
|
|
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
|
|
{
|
|
(void)queue;}
|
|
|
|
/*
|
|
========================
|
|
completeTransaction
|
|
|
|
Called by updatedTransactions when a request is completed.
|
|
========================
|
|
*/
|
|
- (void)completeTransaction:(SKPaymentTransaction *)transaction
|
|
{
|
|
[self finalizeTransaction:transaction];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
failedTransaction
|
|
|
|
Called by updatedTransactions when a request fails.
|
|
========================
|
|
*/
|
|
- (void)failedTransaction:(SKPaymentTransaction *)transaction
|
|
{
|
|
if (transaction.error.code != SKErrorPaymentCancelled)
|
|
{
|
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"In-App Purchase error"
|
|
message:[transaction.error localizedDescription]
|
|
delegate:nil
|
|
cancelButtonTitle:@"OK"
|
|
otherButtonTitles:nil];
|
|
|
|
[alert show];
|
|
[alert release];
|
|
}
|
|
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
|
|
|
|
const char * const cProductIdentifier = [transaction.payment.productIdentifier cStringUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
// Since we can get failed transactions after the user has already purchased a product,
|
|
// don't do anthing if the user has already purchased the product.
|
|
if ( GetProductState( cProductIdentifier ) != PRODUCT_PURCHASED ) {
|
|
SetProductState( transaction.payment.productIdentifier, PRODUCT_HAS_INFORMATION );
|
|
|
|
|
|
if ( callbackObject ) {
|
|
callbackObject( cProductIdentifier, idInAppStore::PRODUCT_STATUS_TRANSACTION_FAILED );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
restoreTransaction
|
|
|
|
Called by updatedTransactions when a request is restored. This can behave identically to
|
|
completeTransaction.
|
|
========================
|
|
*/
|
|
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
|
|
{
|
|
[self finalizeTransaction:transaction.originalTransaction];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
finalizeTransaction
|
|
|
|
This method actually delivers the purchased item to the user. Currently, this means setting
|
|
the state in NSUserDefaults to PRODUCT_PURCHASED.
|
|
========================
|
|
*/
|
|
- (void)finalizeTransaction:(SKPaymentTransaction *)transaction
|
|
{
|
|
SetProductState( transaction.payment.productIdentifier, PRODUCT_PURCHASED );
|
|
|
|
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
|
|
|
|
const char * const cProductIdentifier = [transaction.payment.productIdentifier cStringUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
if ( callbackObject ) {
|
|
callbackObject( cProductIdentifier, idInAppStore::PRODUCT_STATUS_PURCHASED );
|
|
}
|
|
}
|
|
|
|
@end
|
|
|