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

InAppPurchases: fix a bug on Android when failed purchases would not be propagated to IAP listeners. Make InAppPurchases a Singleton.

This commit is contained in:
Lukasz Kozakiewicz 2018-02-21 17:28:48 +01:00
parent 4be6679aab
commit 3e66bc69fb
6 changed files with 84 additions and 63 deletions

View file

@ -1,5 +1,42 @@
JUCE breaking changes
=====================
=====================
Develop
=======
Change
------
InAppPurchases class is now a JUCE Singleton. This means that you need
to get an instance via InAppPurchases::getInstance(), instead of storing a
InAppPurchases object yourself.
Possible Issues
---------------
Any code using InAppPurchases needs to be updated to retrieve a singleton pointer
to InAppPurchases.
Workaround
----------
Instead of holding a InAppPurchase member yourself, you should get an instance
via InAppPurchases::getInstance(), e.g.
instead of:
InAppPurchases iap;
iap.purchaseProduct (…);
call:
InAppPurchases::getInstance()->purchaseProduct (…);
Rationale
---------
This change was required to fix an issue on Android where on failed transaction
a listener would not get called.
Develop
=======

View file

@ -109,6 +109,7 @@ public:
void updateDisplay()
{
voiceListBox.updateContent();
voiceListBox.setEnabled (! getInstance()->getPurchases().isPurchaseInProgress());
voiceListBox.repaint();
}

View file

@ -52,7 +52,7 @@ public:
~VoicePurchases()
{
inAppPurchases.removeListener (this);
InAppPurchases::getInstance()->removeListener (this);
}
//==============================================================================
@ -61,9 +61,9 @@ public:
if (! havePurchasesBeenRestored)
{
havePurchasesBeenRestored = true;
inAppPurchases.addListener (this);
InAppPurchases::getInstance()->addListener (this);
inAppPurchases.restoreProductsBoughtList (true);
InAppPurchases::getInstance()->restoreProductsBoughtList (true);
}
return voiceProducts[voiceIndex];
@ -77,8 +77,12 @@ public:
if (! product.isPurchased)
{
purchaseInProgress = true;
product.purchaseInProgress = true;
inAppPurchases.purchaseProduct (product.identifier, false);
InAppPurchases::getInstance()->purchaseProduct (product.identifier, false);
guiUpdater.triggerAsyncUpdate();
}
}
}
@ -93,11 +97,13 @@ public:
return names;
}
bool isPurchaseInProgress() const noexcept { return purchaseInProgress; }
private:
//==============================================================================
void productsInfoReturned (const Array<InAppPurchases::Product>& products) override
{
if (! inAppPurchases.isInAppPurchasesSupported())
if (! InAppPurchases::getInstance()->isInAppPurchasesSupported())
{
for (auto idx = 1; idx < voiceProducts.size(); ++idx)
{
@ -142,6 +148,8 @@ private:
void productPurchaseFinished (const PurchaseInfo& info, bool success, const String&) override
{
purchaseInProgress = false;
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
if (isPositiveAndBelow (idx, voiceProducts.size()))
@ -152,6 +160,15 @@ private:
voiceProduct.purchaseInProgress = false;
guiUpdater.triggerAsyncUpdate();
}
else
{
// On failure Play Store will not tell us which purchase failed
for (auto& voiceProduct : voiceProducts)
voiceProduct.purchaseInProgress = false;
guiUpdater.triggerAsyncUpdate();
}
}
void purchasesListRestored (const Array<PurchaseInfo>& infos, bool success, const String&) override
@ -181,7 +198,7 @@ private:
for (auto& voiceProduct : voiceProducts)
identifiers.add (voiceProduct.identifier);
inAppPurchases.getProductsInformation(identifiers);
InAppPurchases::getInstance()->getProductsInformation (identifiers);
}
}
@ -199,8 +216,7 @@ private:
//==============================================================================
AsyncUpdater& guiUpdater;
bool havePurchasesBeenRestored = false, havePricesBeenFetched = false;
InAppPurchases inAppPurchases;
bool havePurchasesBeenRestored = false, havePricesBeenFetched = false, purchaseInProgress = false;
Array<VoiceProduct> voiceProducts;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoicePurchases)

View file

@ -27,13 +27,16 @@
namespace juce
{
//==============================================================================
JUCE_IMPLEMENT_SINGLETON (InAppPurchases)
InAppPurchases::InAppPurchases()
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
: pimpl (new Pimpl (*this))
#endif
{}
InAppPurchases::~InAppPurchases() {}
InAppPurchases::~InAppPurchases() { clearSingletonInstance(); }
bool InAppPurchases::isInAppPurchasesSupported() const
{

View file

@ -36,9 +36,13 @@ namespace juce
Once an InAppPurchases object is created, call addListener() to attach listeners.
*/
class JUCE_API InAppPurchases
class JUCE_API InAppPurchases : private DeletedAtShutdown
{
public:
#ifndef DOXYGEN
JUCE_DECLARE_SINGLETON (InAppPurchases, false)
#endif
//==============================================================================
/** Represents a product available in the store. */
struct Product
@ -253,13 +257,13 @@ public:
/** iOS only: Cancels downloads of hosted content from the store. */
void cancelDownloads (const Array<Download*>& downloads);
private:
//==============================================================================
#ifndef DOXYGEN
InAppPurchases();
~InAppPurchases();
#endif
private:
//==============================================================================
ListenerList<Listener> listeners;

View file

@ -79,8 +79,6 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
{
Pimpl (InAppPurchases& parent) : owner (parent)
{
getInAppPurchaseInstances().add (this);
auto* env = getEnv();
auto intent = env->NewObject (AndroidIntent, AndroidIntent.constructWithString,
javaString ("com.android.vending.billing.InAppBillingService.BIND").get());
@ -103,8 +101,6 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
android.activity.callVoidMethod (JuceAppActivity.unbindService, serviceConnection.get());
serviceConnection.clear();
}
getInAppPurchaseInstances().removeFirstMatchingValue (this);
}
//==============================================================================
@ -221,7 +217,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
auto skuString = javaString (productIdentifier);
auto productTypeString = javaString (isSubscription ? "subs" : "inapp");
auto devString = javaString (getDeveloperExtraData());
auto devString = javaString ("");
if (subscriptionIdentifiers.isEmpty())
return LocalRef<jobject> (inAppBillingService.callObjectMethod (IInAppBillingService.getBuyIntent, 3,
@ -769,13 +765,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
}
//==============================================================================
static Array<Pimpl*>& getInAppPurchaseInstances() noexcept
{
static Array<Pimpl*> instances;
return instances;
}
static void inAppPurchaseCompleted (jobject intentData)
void inAppPurchaseCompleted (jobject intentData)
{
auto* env = getEnv();
@ -811,47 +801,16 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
var purchaseToken = props[purchaseTokenIdentifier];
var developerPayload = props[developerPayloadIdentifier];
if (auto* target = getPimplFromDeveloperExtraData (developerPayload))
{
auto purchaseTimeString = Time (purchaseTime.toString().getLargeIntValue())
.toString (true, true, true, true);
auto purchaseTimeString = Time (purchaseTime.toString().getLargeIntValue())
.toString (true, true, true, true);
target->notifyAboutPurchaseResult ({ orderId.toString(), productId.toString(), packageName.toString(),
purchaseTimeString, purchaseToken.toString() },
true, statusCodeUserString);
}
}
}
//==============================================================================
String getDeveloperExtraData()
{
static const Identifier inAppPurchaseInstance ("inAppPurchaseInstance");
DynamicObject::Ptr developerString (new DynamicObject());
developerString->setProperty (inAppPurchaseInstance,
"0x" + String::toHexString (reinterpret_cast<pointer_sized_int> (this)));
return JSON::toString (var (developerString));
}
static Pimpl* getPimplFromDeveloperExtraData (const String& developerExtra)
{
static const Identifier inAppPurchaseInstance ("inAppPurchaseInstance");
if (DynamicObject::Ptr developerData = JSON::fromString (developerExtra).getDynamicObject())
{
String hexAddr = developerData->getProperty (inAppPurchaseInstance);
if (hexAddr.startsWith ("0x"))
hexAddr = hexAddr.fromFirstOccurrenceOf ("0x", false, false);
auto* target = reinterpret_cast<Pimpl*> (static_cast<pointer_sized_int> (hexAddr.getHexValue64()));
if (getInAppPurchaseInstances().contains (target))
return target;
notifyAboutPurchaseResult ({ orderId.toString(), productId.toString(), packageName.toString(),
purchaseTimeString, purchaseToken.toString() },
true, statusCodeUserString);
return;
}
return nullptr;
notifyAboutPurchaseResult ({}, false, statusCodeUserString);
}
//==============================================================================
@ -890,7 +849,8 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
//==============================================================================
void juce_inAppPurchaseCompleted (void* intentData)
{
InAppPurchases::Pimpl::inAppPurchaseCompleted (static_cast<jobject> (intentData));
if (auto* instance = InAppPurchases::getInstance())
instance->pimpl->inAppPurchaseCompleted (static_cast<jobject> (intentData));
}
} // namespace juce