1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

InAppPurchases: Ensure that the iOS implementation notifies on failure

This commit is contained in:
reuk 2023-02-28 14:04:33 +00:00
parent f21bc3f4ae
commit cb54044c1c
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
2 changed files with 96 additions and 48 deletions

View file

@ -190,7 +190,7 @@ private:
guiUpdater.triggerAsyncUpdate(); guiUpdater.triggerAsyncUpdate();
} }
void productPurchaseFinished (const PurchaseInfo& info, bool success, const String&) override void productPurchaseFinished (const PurchaseInfo& info, bool success, const String& error) override
{ {
purchaseInProgress = false; purchaseInProgress = false;
@ -213,6 +213,12 @@ private:
} }
} }
if (! success)
{
auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon, "Purchase failed", error);
messageBox = AlertWindow::showScopedAsync (options, nullptr);
}
guiUpdater.triggerAsyncUpdate(); guiUpdater.triggerAsyncUpdate();
} }

View file

@ -73,14 +73,14 @@ struct InAppPurchases::Pimpl
}; };
Type type; Type type;
std::unique_ptr<SKProductsRequest, NSObjectDeleter> request; NSUniquePtr<SKProductsRequest> request;
}; };
/** Represents a pending request started from [SKReceiptRefreshRequest start]. */ /** Represents a pending request started from [SKReceiptRefreshRequest start]. */
struct PendingReceiptRefreshRequest struct PendingReceiptRefreshRequest
{ {
String subscriptionsSharedSecret; String subscriptionsSharedSecret;
std::unique_ptr<SKReceiptRefreshRequest, NSObjectDeleter> request; NSUniquePtr<SKReceiptRefreshRequest> request;
}; };
/** Represents a transaction with pending downloads. Only after all downloads /** Represents a transaction with pending downloads. Only after all downloads
@ -142,8 +142,8 @@ struct InAppPurchases::Pimpl
{ {
auto productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray: createNSArrayFromStringArray (productIdentifiers)]]; auto productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray: createNSArrayFromStringArray (productIdentifiers)]];
pendingProductInfoRequests.add (new PendingProductInfoRequest { PendingProductInfoRequest::Type::query, pendingProductInfoRequests.emplace_back (new PendingProductInfoRequest { PendingProductInfoRequest::Type::query,
std::unique_ptr<SKProductsRequest, NSObjectDeleter> (productsRequest) }); NSUniquePtr<SKProductsRequest> (productsRequest) });
productsRequest.delegate = delegate.get(); productsRequest.delegate = delegate.get();
[productsRequest start]; [productsRequest start];
@ -160,8 +160,8 @@ struct InAppPurchases::Pimpl
auto productIdentifiers = [NSArray arrayWithObject: juceStringToNS (productIdentifier)]; auto productIdentifiers = [NSArray arrayWithObject: juceStringToNS (productIdentifier)];
auto productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]]; auto productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]];
pendingProductInfoRequests.add (new PendingProductInfoRequest { PendingProductInfoRequest::Type::purchase, pendingProductInfoRequests.emplace_back (new PendingProductInfoRequest { PendingProductInfoRequest::Type::purchase,
std::unique_ptr<SKProductsRequest, NSObjectDeleter> (productsRequest) }); NSUniquePtr<SKProductsRequest> (productsRequest) });
productsRequest.delegate = delegate.get(); productsRequest.delegate = delegate.get();
[productsRequest start]; [productsRequest start];
@ -177,8 +177,8 @@ struct InAppPurchases::Pimpl
{ {
auto receiptRequest = [[SKReceiptRefreshRequest alloc] init]; auto receiptRequest = [[SKReceiptRefreshRequest alloc] init];
pendingReceiptRefreshRequests.add (new PendingReceiptRefreshRequest { subscriptionsSharedSecret, pendingReceiptRefreshRequests.emplace_back (new PendingReceiptRefreshRequest { subscriptionsSharedSecret,
std::unique_ptr<SKReceiptRefreshRequest, NSObjectDeleter> ([receiptRequest retain]) }); NSUniquePtr<SKReceiptRefreshRequest> ([receiptRequest retain]) });
receiptRequest.delegate = delegate.get(); receiptRequest.delegate = delegate.get();
[receiptRequest start]; [receiptRequest start];
} }
@ -615,8 +615,23 @@ struct InAppPurchases::Pimpl
return (ObjCType*) o; return (ObjCType*) o;
} }
private: private:
auto findPendingProductInfoRequest (SKRequest* r) const
{
const auto request = getAs<SKProductsRequest> (r);
return std::find_if (pendingProductInfoRequests.begin(),
pendingProductInfoRequests.end(),
[&] (const auto& pending) { return pending->request.get() == request; });
}
auto findPendingReceiptRefreshRequest (SKRequest* r) const
{
const auto request = getAs<SKReceiptRefreshRequest> (r);
return std::find_if (pendingReceiptRefreshRequests.begin(),
pendingReceiptRefreshRequests.end(),
[&] (const auto& pending) { return pending->request.get() == request; });
}
struct Class : public ObjCClass<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>> struct Class : public ObjCClass<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>>
{ {
//============================================================================== //==============================================================================
@ -629,42 +644,40 @@ private:
{ {
auto& t = getThis (self); auto& t = getThis (self);
for (auto i = 0; i < t.pendingProductInfoRequests.size(); ++i) if (const auto iter = t.findPendingProductInfoRequest (request); iter != t.pendingProductInfoRequests.end())
{ {
auto& pendingRequest = *t.pendingProductInfoRequests[i]; switch (iter->get()->type)
if (pendingRequest.request.get() == request)
{ {
if (pendingRequest.type == PendingProductInfoRequest::Type::query) t.notifyProductsInfoReceived (response.products); case PendingProductInfoRequest::Type::query:
else if (pendingRequest.type == PendingProductInfoRequest::Type::purchase) t.startPurchase (response.products); t.notifyProductsInfoReceived (response.products);
else break; break;
case PendingProductInfoRequest::Type::purchase:
t.pendingProductInfoRequests.remove (i); t.startPurchase (response.products);
return; break;
} }
}
// Unknown request received! t.pendingProductInfoRequests.erase (iter);
jassertfalse; }
else
{
// Unknown request received!
jassertfalse;
}
}); });
addMethod (@selector (requestDidFinish:), [] (id self, SEL, SKRequest* request) addMethod (@selector (requestDidFinish:), [] (id self, SEL, SKRequest* request)
{ {
auto& t = getThis (self); auto& t = getThis (self);
if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request)) if (const auto iter = t.findPendingReceiptRefreshRequest (request); iter != t.pendingReceiptRefreshRequests.end())
{ {
for (auto i = 0; i < t.pendingReceiptRefreshRequests.size(); ++i) t.processReceiptRefreshResponseWithSubscriptionsSharedSecret (iter->get()->subscriptionsSharedSecret);
{ t.pendingReceiptRefreshRequests.erase (iter);
auto& pendingRequest = *t.pendingReceiptRefreshRequests[i]; }
if (pendingRequest.request.get() == receiptRefreshRequest) if (const auto iter = t.findPendingProductInfoRequest (request); iter != t.pendingProductInfoRequests.end())
{ {
t.processReceiptRefreshResponseWithSubscriptionsSharedSecret (pendingRequest.subscriptionsSharedSecret); t.pendingProductInfoRequests.erase (iter);
t.pendingReceiptRefreshRequests.remove (i);
return;
}
}
} }
}); });
@ -672,20 +685,49 @@ private:
{ {
auto& t = getThis (self); auto& t = getThis (self);
if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request)) const auto errorDetails = [&]
{ {
for (auto i = 0; i < t.pendingReceiptRefreshRequests.size(); ++i) if (error == nil)
{ return String();
auto& pendingRequest = *t.pendingReceiptRefreshRequests[i];
if (pendingRequest.request.get() == receiptRefreshRequest) const auto description = nsStringToJuce ([error localizedDescription]);
{
auto errorDetails = error != nil ? (", " + nsStringToJuce ([error localizedDescription])) : String(); if (description.isEmpty())
t.owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Receipt fetch failed") + errorDetails); }); return String();
t.pendingReceiptRefreshRequests.remove (i);
return; return ": " + description;
} }();
if (const auto iter = t.findPendingReceiptRefreshRequest (request); iter != t.pendingReceiptRefreshRequests.end())
{
t.owner.listeners.call ([&] (Listener& l)
{
l.purchasesListRestored ({}, false, NEEDS_TRANS ("Receipt fetch failed") + errorDetails);
});
t.pendingReceiptRefreshRequests.erase (iter);
return;
}
if (const auto iter = t.findPendingProductInfoRequest (request); iter != t.pendingProductInfoRequests.end())
{
switch (iter->get()->type)
{
case PendingProductInfoRequest::Type::query:
t.owner.listeners.call ([&] (Listener& l)
{
l.purchasesListRestored ({}, false, NEEDS_TRANS ("Product query failed") + errorDetails);
});
break;
case PendingProductInfoRequest::Type::purchase:
t.owner.listeners.call ([&] (Listener& l)
{
l.productPurchaseFinished ({}, false, NEEDS_TRANS ("Purchase request failed") + errorDetails);
});
break;
} }
t.pendingProductInfoRequests.erase (iter);
return;
} }
}); });
@ -779,10 +821,10 @@ private:
//============================================================================== //==============================================================================
InAppPurchases& owner; InAppPurchases& owner;
std::unique_ptr<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>, NSObjectDeleter> delegate { [Class::get().createInstance() init] }; NSUniquePtr<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>> delegate { [Class::get().createInstance() init] };
OwnedArray<PendingProductInfoRequest> pendingProductInfoRequests; std::vector<std::unique_ptr<PendingProductInfoRequest>> pendingProductInfoRequests;
OwnedArray<PendingReceiptRefreshRequest> pendingReceiptRefreshRequests; std::vector<std::unique_ptr<PendingReceiptRefreshRequest>> pendingReceiptRefreshRequests;
OwnedArray<PendingDownloadsTransaction> pendingDownloadsTransactions; OwnedArray<PendingDownloadsTransaction> pendingDownloadsTransactions;
Array<Listener::PurchaseInfo> restoredPurchases; Array<Listener::PurchaseInfo> restoredPurchases;