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:
parent
f21bc3f4ae
commit
cb54044c1c
2 changed files with 96 additions and 48 deletions
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue