Browse Source

iOS InAppPurchases: Tidying

v7.0.9
reuk 2 years ago
parent
commit
3b1b52b65e
No known key found for this signature in database GPG Key ID: FCB43929F012EE5C
1 changed files with 210 additions and 212 deletions
  1. +210
    -212
      modules/juce_product_unlocking/native/juce_ios_InAppPurchases.cpp

+ 210
- 212
modules/juce_product_unlocking/native/juce_ios_InAppPurchases.cpp View File

@@ -26,74 +26,13 @@
namespace juce
{
struct SKDelegateAndPaymentObserver
{
SKDelegateAndPaymentObserver() : delegate ([getClass().createInstance() init])
{
Class::setThis (delegate.get(), this);
}
virtual ~SKDelegateAndPaymentObserver() {}
virtual void didReceiveResponse (SKProductsRequest*, SKProductsResponse*) = 0;
virtual void requestDidFinish (SKRequest*) = 0;
virtual void requestDidFailWithError (SKRequest*, NSError*) = 0;
virtual void updatedTransactions (SKPaymentQueue*, NSArray<SKPaymentTransaction*>*) = 0;
virtual void restoreCompletedTransactionsFailedWithError (SKPaymentQueue*, NSError*) = 0;
virtual void restoreCompletedTransactionsFinished (SKPaymentQueue*) = 0;
virtual void updatedDownloads (SKPaymentQueue*, NSArray<SKDownload*>*) = 0;
protected:
std::unique_ptr<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>, NSObjectDeleter> delegate;
private:
struct Class : public ObjCClass<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>>
{
//==============================================================================
Class() : ObjCClass<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>> ("SKDelegateAndPaymentObserverBase_")
{
addIvar<SKDelegateAndPaymentObserver*> ("self");
addMethod (@selector (productsRequest:didReceiveResponse:), didReceiveResponse);
addMethod (@selector (requestDidFinish:), requestDidFinish);
addMethod (@selector (request:didFailWithError:), requestDidFailWithError);
addMethod (@selector (paymentQueue:updatedTransactions:), updatedTransactions);
addMethod (@selector (paymentQueue:restoreCompletedTransactionsFailedWithError:), restoreCompletedTransactionsFailedWithError);
addMethod (@selector (paymentQueueRestoreCompletedTransactionsFinished:), restoreCompletedTransactionsFinished);
addMethod (@selector (paymentQueue:updatedDownloads:), updatedDownloads);
registerClass();
}
//==============================================================================
static SKDelegateAndPaymentObserver& getThis (id self) { return *getIvar<SKDelegateAndPaymentObserver*> (self, "self"); }
static void setThis (id self, SKDelegateAndPaymentObserver* s) { object_setInstanceVariable (self, "self", s); }
//==============================================================================
static void didReceiveResponse (id self, SEL, SKProductsRequest* request, SKProductsResponse* response) { getThis (self).didReceiveResponse (request, response); }
static void requestDidFinish (id self, SEL, SKRequest* request) { getThis (self).requestDidFinish (request); }
static void requestDidFailWithError (id self, SEL, SKRequest* request, NSError* err) { getThis (self).requestDidFailWithError (request, err); }
static void updatedTransactions (id self, SEL, SKPaymentQueue* queue, NSArray<SKPaymentTransaction*>* trans) { getThis (self).updatedTransactions (queue, trans); }
static void restoreCompletedTransactionsFailedWithError (id self, SEL, SKPaymentQueue* q, NSError* err) { getThis (self).restoreCompletedTransactionsFailedWithError (q, err); }
static void restoreCompletedTransactionsFinished (id self, SEL, SKPaymentQueue* queue) { getThis (self).restoreCompletedTransactionsFinished (queue); }
static void updatedDownloads (id self, SEL, SKPaymentQueue* queue, NSArray<SKDownload*>* downloads) { getThis (self).updatedDownloads (queue, downloads); }
};
//==============================================================================
static Class& getClass()
{
static Class c;
return c;
}
};
//==============================================================================
struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
struct InAppPurchases::Pimpl
{
/** AppStore implementation of hosted content download. */
struct DownloadImpl : public Download
{
DownloadImpl (SKDownload* downloadToUse)
explicit DownloadImpl (SKDownload* downloadToUse)
: download (downloadToUse)
{
[download retain];
@@ -184,8 +123,17 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
};
//==============================================================================
Pimpl (InAppPurchases& p) : owner (p) { [[SKPaymentQueue defaultQueue] addTransactionObserver: delegate.get()]; }
~Pimpl() noexcept override { [[SKPaymentQueue defaultQueue] removeTransactionObserver: delegate.get()]; }
explicit Pimpl (InAppPurchases& p)
: owner (p)
{
Class::setThis (delegate.get(), this);
[[SKPaymentQueue defaultQueue] addTransactionObserver: delegate.get()];
}
~Pimpl()
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver: delegate.get()];
}
//==============================================================================
bool isInAppPurchasesSupported() const { return true; }
@@ -259,119 +207,6 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
[[SKPaymentQueue defaultQueue] cancelDownloads: downloadsToSKDownloads (removeInvalidDownloads (downloads))];
}
//==============================================================================
void didReceiveResponse (SKProductsRequest* request, SKProductsResponse* response) override
{
for (auto i = 0; i < pendingProductInfoRequests.size(); ++i)
{
auto& pendingRequest = *pendingProductInfoRequests[i];
if (pendingRequest.request.get() == request)
{
if (pendingRequest.type == PendingProductInfoRequest::Type::query) notifyProductsInfoReceived (response.products);
else if (pendingRequest.type == PendingProductInfoRequest::Type::purchase) startPurchase (response.products);
else break;
pendingProductInfoRequests.remove (i);
return;
}
}
// Unknown request received!
jassertfalse;
}
void requestDidFinish (SKRequest* request) override
{
if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request))
{
for (auto i = 0; i < pendingReceiptRefreshRequests.size(); ++i)
{
auto& pendingRequest = *pendingReceiptRefreshRequests[i];
if (pendingRequest.request.get() == receiptRefreshRequest)
{
processReceiptRefreshResponseWithSubscriptionsSharedSecret (pendingRequest.subscriptionsSharedSecret);
pendingReceiptRefreshRequests.remove (i);
return;
}
}
}
}
void requestDidFailWithError (SKRequest* request, NSError* error) override
{
if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request))
{
for (auto i = 0; i < pendingReceiptRefreshRequests.size(); ++i)
{
auto& pendingRequest = *pendingReceiptRefreshRequests[i];
if (pendingRequest.request.get() == receiptRefreshRequest)
{
auto errorDetails = error != nil ? (", " + nsStringToJuce ([error localizedDescription])) : String();
owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Receipt fetch failed") + errorDetails); });
pendingReceiptRefreshRequests.remove (i);
return;
}
}
}
}
void updatedTransactions (SKPaymentQueue*, NSArray<SKPaymentTransaction*>* transactions) override
{
for (SKPaymentTransaction* transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchasing: break;
case SKPaymentTransactionStateDeferred: break;
case SKPaymentTransactionStateFailed: processTransactionFinish (transaction, false); break;
case SKPaymentTransactionStatePurchased: processTransactionFinish (transaction, true); break;
case SKPaymentTransactionStateRestored: processTransactionFinish (transaction, true); break;
default: jassertfalse; break; // Unexpected transaction state
}
}
}
void restoreCompletedTransactionsFailedWithError (SKPaymentQueue*, NSError* error) override
{
owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, nsStringToJuce (error.localizedDescription)); });
}
void restoreCompletedTransactionsFinished (SKPaymentQueue*) override
{
owner.listeners.call ([this] (Listener& l) { l.purchasesListRestored (restoredPurchases, true, NEEDS_TRANS ("Success")); });
restoredPurchases.clear();
}
void updatedDownloads (SKPaymentQueue*, NSArray<SKDownload*>* downloads) override
{
for (SKDownload* download in downloads)
{
if (auto* pendingDownload = getPendingDownloadFor (download))
{
#if JUCE_IOS
switch (download.downloadState)
#else
switch (download.state)
#endif
{
case SKDownloadStateWaiting: break;
case SKDownloadStatePaused: owner.listeners.call ([&] (Listener& l) { l.productDownloadPaused (*pendingDownload); }); break;
case SKDownloadStateActive: owner.listeners.call ([&] (Listener& l) { l.productDownloadProgressUpdate (*pendingDownload,
download.progress,
RelativeTime (download.timeRemaining)); }); break;
case SKDownloadStateFinished:
case SKDownloadStateFailed:
case SKDownloadStateCancelled: processDownloadFinish (pendingDownload, download); break;
default: jassertfalse; break; // Unexpected download state
}
}
}
}
//==============================================================================
void notifyProductsInfoReceived (NSArray<SKProduct*>* products)
{
@@ -626,40 +461,41 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
for (id inAppPurchaseData in inAppPurchases)
{
if (auto* purchaseData = getAs<NSDictionary> (inAppPurchaseData))
{
// Ignore products that were cancelled.
if (purchaseData[@"cancellation_date"] != nil)
continue;
auto* purchaseData = getAs<NSDictionary> (inAppPurchaseData);
if (purchaseData == nullptr)
return sendReceiptFetchFailAsync();
// Ignore products that were cancelled.
if (purchaseData[@"cancellation_date"] != nil)
continue;
if (auto transactionId = getAs<NSString> (purchaseData[@"original_transaction_id"]))
if (auto transactionId = getAs<NSString> (purchaseData[@"original_transaction_id"]))
{
if (auto productId = getAs<NSString> (purchaseData[@"product_id"]))
{
if (auto productId = getAs<NSString> (purchaseData[@"product_id"]))
{
auto purchaseTime = getPurchaseDateMs (purchaseData[@"purchase_date_ms"]);
if (purchaseTime > 0)
{
purchases.add ({ { nsStringToJuce (transactionId),
nsStringToJuce (productId),
nsStringToJuce (bundleId),
Time (purchaseTime).toString (true, true, true, true),
{} }, {} });
}
else
{
return sendReceiptFetchFailAsync();
}
}
auto purchaseTime = getPurchaseDateMs (purchaseData[@"purchase_date_ms"]);
if (purchaseTime <= 0)
return sendReceiptFetchFailAsync();
purchases.add ({ { nsStringToJuce (transactionId),
nsStringToJuce (productId),
nsStringToJuce (bundleId),
Time (purchaseTime).toString (true, true, true, true),
{} }, {} });
}
}
else
{
return sendReceiptFetchFailAsync();
}
}
MessageManager::callAsync ([this, purchases] { owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored (purchases, true, NEEDS_TRANS ("Success")); }); });
MessageManager::callAsync ([this, purchases]
{
owner.listeners.call ([&] (Listener& l)
{
l.purchasesListRestored (purchases, true, NEEDS_TRANS ("Success"));
});
});
return;
}
}
@@ -681,14 +517,13 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
static int64 getPurchaseDateMs (id date)
{
if (auto dateAsNumber = getAs<NSNumber> (date))
{
return [dateAsNumber longLongValue];
}
else if (auto dateAsString = getAs<NSString> (date))
if (auto dateAsString = getAs<NSString> (date))
{
auto formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle: NSNumberFormatterDecimalStyle];
dateAsNumber = [formatter numberFromString: dateAsString];
auto dateAsNumber = [formatter numberFromString: dateAsString];
[formatter release];
return [dateAsNumber longLongValue];
}
@@ -724,7 +559,7 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
case SKPaymentTransactionStateFailed: return NEEDS_TRANS ("Failure");
case SKPaymentTransactionStateRestored: return NEEDS_TRANS ("Restored");
case SKPaymentTransactionStateDeferred: return NEEDS_TRANS ("Deferred");
default: jassertfalse; return NEEDS_TRANS ("Unknown status");
default: jassertfalse; return NEEDS_TRANS ("Unknown status");
}
}
@@ -739,7 +574,7 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
case SKDownloadStateFinished: return Download::Status::finished;
case SKDownloadStateFailed: return Download::Status::failed;
case SKDownloadStateCancelled: return Download::Status::cancelled;
default: jassertfalse; return Download::Status::waiting;
default: jassertfalse; return Download::Status::waiting;
}
}
@@ -763,9 +598,172 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
return (ObjCType*) o;
}
private:
struct Class : public ObjCClass<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>>
{
//==============================================================================
Class()
: ObjCClass ("SKDelegateAndPaymentObserverBase_")
{
addIvar<Pimpl*> ("self");
addMethod (@selector (productsRequest:didReceiveResponse:), [] (id self, SEL, SKProductsRequest* request, SKProductsResponse* response)
{
auto& t = getThis (self);
for (auto i = 0; i < t.pendingProductInfoRequests.size(); ++i)
{
auto& pendingRequest = *t.pendingProductInfoRequests[i];
if (pendingRequest.request.get() == request)
{
if (pendingRequest.type == PendingProductInfoRequest::Type::query) t.notifyProductsInfoReceived (response.products);
else if (pendingRequest.type == PendingProductInfoRequest::Type::purchase) t.startPurchase (response.products);
else break;
t.pendingProductInfoRequests.remove (i);
return;
}
}
// Unknown request received!
jassertfalse;
});
addMethod (@selector (requestDidFinish:), [] (id self, SEL, SKRequest* request)
{
auto& t = getThis (self);
if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request))
{
for (auto i = 0; i < t.pendingReceiptRefreshRequests.size(); ++i)
{
auto& pendingRequest = *t.pendingReceiptRefreshRequests[i];
if (pendingRequest.request.get() == receiptRefreshRequest)
{
t.processReceiptRefreshResponseWithSubscriptionsSharedSecret (pendingRequest.subscriptionsSharedSecret);
t.pendingReceiptRefreshRequests.remove (i);
return;
}
}
}
});
addMethod (@selector (request:didFailWithError:), [] (id self, SEL, SKRequest* request, NSError* error)
{
auto& t = getThis (self);
if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request))
{
for (auto i = 0; i < t.pendingReceiptRefreshRequests.size(); ++i)
{
auto& pendingRequest = *t.pendingReceiptRefreshRequests[i];
if (pendingRequest.request.get() == receiptRefreshRequest)
{
auto errorDetails = error != nil ? (", " + nsStringToJuce ([error localizedDescription])) : String();
t.owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Receipt fetch failed") + errorDetails); });
t.pendingReceiptRefreshRequests.remove (i);
return;
}
}
}
});
addMethod (@selector (paymentQueue:updatedTransactions:), [] (id self, SEL, SKPaymentQueue*, NSArray<SKPaymentTransaction*>* transactions)
{
auto& t = getThis (self);
for (SKPaymentTransaction* transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchasing: break;
case SKPaymentTransactionStateDeferred: break;
case SKPaymentTransactionStateFailed: t.processTransactionFinish (transaction, false); break;
case SKPaymentTransactionStatePurchased: t.processTransactionFinish (transaction, true); break;
case SKPaymentTransactionStateRestored: t.processTransactionFinish (transaction, true); break;
default: jassertfalse; break; // Unexpected transaction state
}
}
});
addMethod (@selector (paymentQueue:restoreCompletedTransactionsFailedWithError:), [] (id self, SEL, SKPaymentQueue*, NSError* error)
{
auto& t = getThis (self);
t.owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, nsStringToJuce (error.localizedDescription)); });
});
addMethod (@selector (paymentQueueRestoreCompletedTransactionsFinished:), [] (id self, SEL, SKPaymentQueue*)
{
auto& t = getThis (self);
t.owner.listeners.call ([&t] (Listener& l) { l.purchasesListRestored (t.restoredPurchases, true, NEEDS_TRANS ("Success")); });
t.restoredPurchases.clear();
});
addMethod (@selector (paymentQueue:updatedDownloads:), [] (id self, SEL, SKPaymentQueue*, NSArray<SKDownload*>* downloads)
{
auto& t = getThis (self);
for (SKDownload* download in downloads)
{
if (auto* pendingDownload = t.getPendingDownloadFor (download))
{
#if JUCE_IOS
switch (download.downloadState)
#else
switch (download.state)
#endif
{
case SKDownloadStateWaiting:
break;
case SKDownloadStatePaused:
t.owner.listeners.call ([&] (Listener& l) { l.productDownloadPaused (*pendingDownload); });
break;
case SKDownloadStateActive:
t.owner.listeners.call ([&] (Listener& l)
{
l.productDownloadProgressUpdate (*pendingDownload,
download.progress,
RelativeTime (download.timeRemaining));
});
break;
case SKDownloadStateFinished:
case SKDownloadStateFailed:
case SKDownloadStateCancelled:
t.processDownloadFinish (pendingDownload, download);
break;
default:
// Unexpected download state
jassertfalse;
break;
}
}
}
});
registerClass();
}
//==============================================================================
static Pimpl& getThis (id self) { return *getIvar<Pimpl*> (self, "self"); }
static void setThis (id self, Pimpl* s) { object_setInstanceVariable (self, "self", s); }
static Class& get()
{
static Class c;
return c;
}
};
//==============================================================================
InAppPurchases& owner;
std::unique_ptr<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>, NSObjectDeleter> delegate { [Class::get().createInstance() init] };
OwnedArray<PendingProductInfoRequest> pendingProductInfoRequests;
OwnedArray<PendingReceiptRefreshRequest> pendingReceiptRefreshRequests;


Loading…
Cancel
Save