Browse Source

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

tags/2021-05-28
Lukasz Kozakiewicz 8 years ago
parent
commit
3e66bc69fb
6 changed files with 84 additions and 63 deletions
  1. +38
    -1
      BREAKING-CHANGES.txt
  2. +1
    -0
      examples/InAppPurchase/Source/Main.cpp
  3. +24
    -8
      examples/InAppPurchase/Source/VoicePurchases.h
  4. +4
    -1
      modules/juce_product_unlocking/in_app_purchases/juce_InAppPurchases.cpp
  5. +6
    -2
      modules/juce_product_unlocking/in_app_purchases/juce_InAppPurchases.h
  6. +11
    -51
      modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp

+ 38
- 1
BREAKING-CHANGES.txt 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
=======


+ 1
- 0
examples/InAppPurchase/Source/Main.cpp View File

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


+ 24
- 8
examples/InAppPurchase/Source/VoicePurchases.h 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)


+ 4
- 1
modules/juce_product_unlocking/in_app_purchases/juce_InAppPurchases.cpp 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
{


+ 6
- 2
modules/juce_product_unlocking/in_app_purchases/juce_InAppPurchases.h 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;


+ 11
- 51
modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp 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);
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()));
auto purchaseTimeString = Time (purchaseTime.toString().getLargeIntValue())
.toString (true, true, true, true);
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

Loading…
Cancel
Save