1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +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();
}
void productPurchaseFinished (const PurchaseInfo& info, bool success, const String&) override
void productPurchaseFinished (const PurchaseInfo& info, bool success, const String& error) override
{
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();
}

View file

@ -73,14 +73,14 @@ struct InAppPurchases::Pimpl
};
Type type;
std::unique_ptr<SKProductsRequest, NSObjectDeleter> request;
NSUniquePtr<SKProductsRequest> request;
};
/** Represents a pending request started from [SKReceiptRefreshRequest start]. */
struct PendingReceiptRefreshRequest
{
String subscriptionsSharedSecret;
std::unique_ptr<SKReceiptRefreshRequest, NSObjectDeleter> request;
NSUniquePtr<SKReceiptRefreshRequest> request;
};
/** 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)]];
pendingProductInfoRequests.add (new PendingProductInfoRequest { PendingProductInfoRequest::Type::query,
std::unique_ptr<SKProductsRequest, NSObjectDeleter> (productsRequest) });
pendingProductInfoRequests.emplace_back (new PendingProductInfoRequest { PendingProductInfoRequest::Type::query,
NSUniquePtr<SKProductsRequest> (productsRequest) });
productsRequest.delegate = delegate.get();
[productsRequest start];
@ -160,8 +160,8 @@ struct InAppPurchases::Pimpl
auto productIdentifiers = [NSArray arrayWithObject: juceStringToNS (productIdentifier)];
auto productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]];
pendingProductInfoRequests.add (new PendingProductInfoRequest { PendingProductInfoRequest::Type::purchase,
std::unique_ptr<SKProductsRequest, NSObjectDeleter> (productsRequest) });
pendingProductInfoRequests.emplace_back (new PendingProductInfoRequest { PendingProductInfoRequest::Type::purchase,
NSUniquePtr<SKProductsRequest> (productsRequest) });
productsRequest.delegate = delegate.get();
[productsRequest start];
@ -177,8 +177,8 @@ struct InAppPurchases::Pimpl
{
auto receiptRequest = [[SKReceiptRefreshRequest alloc] init];
pendingReceiptRefreshRequests.add (new PendingReceiptRefreshRequest { subscriptionsSharedSecret,
std::unique_ptr<SKReceiptRefreshRequest, NSObjectDeleter> ([receiptRequest retain]) });
pendingReceiptRefreshRequests.emplace_back (new PendingReceiptRefreshRequest { subscriptionsSharedSecret,
NSUniquePtr<SKReceiptRefreshRequest> ([receiptRequest retain]) });
receiptRequest.delegate = delegate.get();
[receiptRequest start];
}
@ -615,8 +615,23 @@ struct InAppPurchases::Pimpl
return (ObjCType*) o;
}
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>>
{
//==============================================================================
@ -629,42 +644,40 @@ private:
{
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];
if (pendingRequest.request.get() == request)
switch (iter->get()->type)
{
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;
case PendingProductInfoRequest::Type::query:
t.notifyProductsInfoReceived (response.products);
break;
case PendingProductInfoRequest::Type::purchase:
t.startPurchase (response.products);
break;
}
}
// Unknown request received!
jassertfalse;
t.pendingProductInfoRequests.erase (iter);
}
else
{
// Unknown request received!
jassertfalse;
}
});
addMethod (@selector (requestDidFinish:), [] (id self, SEL, SKRequest* request)
{
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)
{
auto& pendingRequest = *t.pendingReceiptRefreshRequests[i];
t.processReceiptRefreshResponseWithSubscriptionsSharedSecret (iter->get()->subscriptionsSharedSecret);
t.pendingReceiptRefreshRequests.erase (iter);
}
if (pendingRequest.request.get() == receiptRefreshRequest)
{
t.processReceiptRefreshResponseWithSubscriptionsSharedSecret (pendingRequest.subscriptionsSharedSecret);
t.pendingReceiptRefreshRequests.remove (i);
return;
}
}
if (const auto iter = t.findPendingProductInfoRequest (request); iter != t.pendingProductInfoRequests.end())
{
t.pendingProductInfoRequests.erase (iter);
}
});
@ -672,20 +685,49 @@ private:
{
auto& t = getThis (self);
if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request))
const auto errorDetails = [&]
{
for (auto i = 0; i < t.pendingReceiptRefreshRequests.size(); ++i)
{
auto& pendingRequest = *t.pendingReceiptRefreshRequests[i];
if (error == nil)
return String();
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;
}
const auto description = nsStringToJuce ([error localizedDescription]);
if (description.isEmpty())
return String();
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;
std::unique_ptr<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>, NSObjectDeleter> delegate { [Class::get().createInstance() init] };
NSUniquePtr<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>> delegate { [Class::get().createInstance() init] };
OwnedArray<PendingProductInfoRequest> pendingProductInfoRequests;
OwnedArray<PendingReceiptRefreshRequest> pendingReceiptRefreshRequests;
std::vector<std::unique_ptr<PendingProductInfoRequest>> pendingProductInfoRequests;
std::vector<std::unique_ptr<PendingReceiptRefreshRequest>> pendingReceiptRefreshRequests;
OwnedArray<PendingDownloadsTransaction> pendingDownloadsTransactions;
Array<Listener::PurchaseInfo> restoredPurchases;