mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
InAppPurchases: Add support for Android BillingClient 5.0.0
This commit is contained in:
parent
7c66dc8e15
commit
6375f640db
7 changed files with 402 additions and 185 deletions
|
|
@ -192,20 +192,23 @@ private:
|
|||
{
|
||||
purchaseInProgress = false;
|
||||
|
||||
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
for (const auto productId : info.purchase.productIds)
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
auto idx = findVoiceIndexFromIdentifier (productId);
|
||||
|
||||
voiceProduct.isPurchased = success;
|
||||
voiceProduct.purchaseInProgress = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// On failure Play Store will not tell us which purchase failed
|
||||
for (auto& voiceProduct : voiceProducts)
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = success;
|
||||
voiceProduct.purchaseInProgress = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// On failure Play Store will not tell us which purchase failed
|
||||
for (auto& voiceProduct : voiceProducts)
|
||||
voiceProduct.purchaseInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
|
|
@ -217,13 +220,16 @@ private:
|
|||
{
|
||||
for (auto& info : infos)
|
||||
{
|
||||
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
for (const auto productId : info.purchase.productIds)
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
auto idx = findVoiceIndexFromIdentifier (productId);
|
||||
|
||||
voiceProduct.isPurchased = true;
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -866,7 +866,7 @@ private:
|
|||
mo << " implementation files('libs/" << File (d).getFileName() << "')" << newLine;
|
||||
|
||||
if (isInAppBillingEnabled())
|
||||
mo << " implementation 'com.android.billingclient:billing:2.1.0'" << newLine;
|
||||
mo << " implementation 'com.android.billingclient:billing:5.0.0'" << newLine;
|
||||
|
||||
if (areRemoteNotificationsEnabled())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public class JuceActivity extends Activity
|
|||
{
|
||||
//==============================================================================
|
||||
private native void appNewIntent (Intent intent);
|
||||
private native void appOnResume();
|
||||
|
||||
@Override
|
||||
protected void onNewIntent (Intent intent)
|
||||
|
|
@ -42,4 +43,12 @@ public class JuceActivity extends Activity
|
|||
|
||||
appNewIntent (intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
appOnResume();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,12 +126,18 @@ static const uint8 javaComponentPeerView[] =
|
|||
|
||||
//==============================================================================
|
||||
#if JUCE_PUSH_NOTIFICATIONS && JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
extern bool juce_handleNotificationIntent (void*);
|
||||
extern void juce_firebaseDeviceNotificationsTokenRefreshed (void*);
|
||||
extern void juce_firebaseRemoteNotificationReceived (void*);
|
||||
extern void juce_firebaseRemoteMessagesDeleted();
|
||||
extern void juce_firebaseRemoteMessageSent(void*);
|
||||
extern void juce_firebaseRemoteMessageSendError (void*, void*);
|
||||
bool juce_handleNotificationIntent (void*);
|
||||
void juce_firebaseDeviceNotificationsTokenRefreshed (void*);
|
||||
void juce_firebaseRemoteNotificationReceived (void*);
|
||||
void juce_firebaseRemoteMessagesDeleted();
|
||||
void juce_firebaseRemoteMessageSent(void*);
|
||||
void juce_firebaseRemoteMessageSendError (void*, void*);
|
||||
#endif
|
||||
|
||||
#if JUCE_IN_APP_PURCHASES && JUCE_MODULE_AVAILABLE_juce_product_unlocking
|
||||
void juce_handleOnResume();
|
||||
#else
|
||||
static void juce_handleOnResume() {}
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -2091,7 +2097,8 @@ const int KeyPress::rewindKey = extendedKeyModifier + 72;
|
|||
struct JuceActivityNewIntentListener
|
||||
{
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
CALLBACK (appNewIntent, "appNewIntent", "(Landroid/content/Intent;)V")
|
||||
CALLBACK (appNewIntent, "appNewIntent", "(Landroid/content/Intent;)V") \
|
||||
CALLBACK (appOnResume, "appOnResume", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (JavaActivity, JUCE_PUSH_NOTIFICATIONS_ACTIVITY)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
|
@ -2100,6 +2107,11 @@ const int KeyPress::rewindKey = extendedKeyModifier + 72;
|
|||
{
|
||||
juce_handleNotificationIntent (static_cast<void*> (intentData));
|
||||
}
|
||||
|
||||
static void JNICALL appOnResume (JNIEnv*, jobject)
|
||||
{
|
||||
juce_handleOnResume();
|
||||
}
|
||||
};
|
||||
|
||||
JuceActivityNewIntentListener::JavaActivity_Class JuceActivityNewIntentListener::JavaActivity;
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ public:
|
|||
/** A unique order identifier for the transaction (generated by the store). */
|
||||
String orderId;
|
||||
|
||||
/** A unique identifier of in-app product that was purchased. */
|
||||
String productId;
|
||||
/** Unique identifiers of the products that were purchased. */
|
||||
StringArray productIds;
|
||||
|
||||
/** This will be bundle ID on iOS and package name on Android, of the application for which this
|
||||
in-app product was purchased. */
|
||||
|
|
@ -147,7 +147,7 @@ public:
|
|||
virtual void productPurchaseFinished (const PurchaseInfo&, bool /*success*/, const String& /*statusDescription*/) {}
|
||||
|
||||
/** Called when a list of all purchases is restored. This can be used to figure out to
|
||||
which products a user is entitled to.
|
||||
which products a user is entitled.
|
||||
|
||||
NOTE: It is possible to receive this callback for the same purchase multiple times. If that happens,
|
||||
only the newest set of downloads and the newest orderId will be valid, the old ones should be not used anymore!
|
||||
|
|
@ -210,7 +210,7 @@ public:
|
|||
const String& upgradeOrDowngradeFromSubscriptionWithProductIdentifier = {},
|
||||
bool creditForUnusedSubscription = true);
|
||||
|
||||
/** Asynchronously asks about a list of products that a user has already bought. Upon completion, Listener::purchasesListReceived()
|
||||
/** Asynchronously asks about a list of products that a user has already bought. Upon completion, Listener::purchasesListRestored()
|
||||
callback will be invoked. The user may be prompted to login first.
|
||||
|
||||
@param includeDownloadInfo (iOS only) if true, then after restoration is successful, the downloads array passed to
|
||||
|
|
@ -258,8 +258,8 @@ public:
|
|||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated ("On Android, it is no longer necessary to specify whether the product being purchased is a subscription "
|
||||
"and only a single subscription can be upgraded/downgraded. Use the updated purchaseProduct method "
|
||||
"which takes a single String argument.")]]
|
||||
"and only a single subscription can be upgraded/downgraded. Use the updated purchaseProduct method "
|
||||
"which takes a single String argument.")]]
|
||||
void purchaseProduct (const String& productIdentifier,
|
||||
bool isSubscription,
|
||||
const StringArray& upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers = {},
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ package com.rmsl.juce;
|
|||
|
||||
import com.android.billingclient.api.*;
|
||||
|
||||
public class JuceBillingClient implements PurchasesUpdatedListener, BillingClientStateListener {
|
||||
private native void skuDetailsQueryCallback(long host, java.util.List<SkuDetails> skuDetails);
|
||||
public class JuceBillingClient implements PurchasesUpdatedListener,
|
||||
BillingClientStateListener {
|
||||
private native void productDetailsQueryCallback(long host, java.util.List<ProductDetails> productDetails);
|
||||
private native void purchasesListQueryCallback(long host, java.util.List<Purchase> purchases);
|
||||
private native void purchaseCompletedCallback(long host, Purchase purchase, int responseCode);
|
||||
private native void purchaseConsumedCallback(long host, String productIdentifier, int responseCode);
|
||||
|
|
@ -57,36 +58,39 @@ public class JuceBillingClient implements PurchasesUpdatedListener, BillingClien
|
|||
== BillingClient.BillingResponseCode.OK;
|
||||
}
|
||||
|
||||
public void querySkuDetails(final String[] skusToQuery) {
|
||||
public QueryProductDetailsParams getProductListParams(final String[] productsToQuery, String type) {
|
||||
java.util.ArrayList<QueryProductDetailsParams.Product> productList = new java.util.ArrayList<>();
|
||||
|
||||
for (String product : productsToQuery)
|
||||
productList.add(QueryProductDetailsParams.Product.newBuilder().setProductId(product).setProductType(type).build());
|
||||
|
||||
return QueryProductDetailsParams.newBuilder().setProductList(productList).build();
|
||||
}
|
||||
|
||||
public void queryProductDetailsImpl(final String[] productsToQuery, java.util.List<String> productTypes, java.util.List<ProductDetails> details) {
|
||||
if (productTypes == null || productTypes.isEmpty()) {
|
||||
productDetailsQueryCallback(host, details);
|
||||
} else {
|
||||
billingClient.queryProductDetailsAsync(getProductListParams(productsToQuery, productTypes.get(0)), new ProductDetailsResponseListener() {
|
||||
@Override
|
||||
public void onProductDetailsResponse(BillingResult billingResult, java.util.List<ProductDetails> newDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
details.addAll(newDetails);
|
||||
queryProductDetailsImpl(productsToQuery, productTypes.subList(1, productTypes.size()), details);
|
||||
} else {
|
||||
queryProductDetailsImpl(productsToQuery, null, details);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void queryProductDetails(final String[] productsToQuery) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final java.util.List<String> skuList = java.util.Arrays.asList(skusToQuery);
|
||||
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(skuList)
|
||||
.setType(BillingClient.SkuType.INAPP);
|
||||
|
||||
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
|
||||
@Override
|
||||
public void onSkuDetailsResponse(BillingResult billingResult, final java.util.List<SkuDetails> inAppSkuDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(skuList)
|
||||
.setType(BillingClient.SkuType.SUBS);
|
||||
|
||||
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
|
||||
@Override
|
||||
public void onSkuDetailsResponse(BillingResult billingResult, java.util.List<SkuDetails> subsSkuDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
subsSkuDetails.addAll(inAppSkuDetails);
|
||||
skuDetailsQueryCallback(host, subsSkuDetails);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
String[] toCheck = {BillingClient.ProductType.INAPP, BillingClient.ProductType.SUBS};
|
||||
queryProductDetailsImpl(productsToQuery, java.util.Arrays.asList(toCheck), new java.util.ArrayList<ProductDetails>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -100,23 +104,30 @@ public class JuceBillingClient implements PurchasesUpdatedListener, BillingClien
|
|||
});
|
||||
}
|
||||
|
||||
private void queryPurchasesImpl(java.util.List<String> toCheck, java.util.ArrayList<Purchase> purchases) {
|
||||
if (toCheck == null || toCheck.isEmpty()) {
|
||||
purchasesListQueryCallback(host, purchases);
|
||||
} else {
|
||||
billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(toCheck.get(0)).build(), new PurchasesResponseListener() {
|
||||
@Override
|
||||
public void onQueryPurchasesResponse(BillingResult billingResult, java.util.List<Purchase> list) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
purchases.addAll(list);
|
||||
queryPurchasesImpl(toCheck.subList(1, toCheck.size()), purchases);
|
||||
} else {
|
||||
queryPurchasesImpl(null, purchases);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void queryPurchases() {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Purchase.PurchasesResult inAppPurchases = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
|
||||
Purchase.PurchasesResult subsPurchases = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
|
||||
|
||||
if (inAppPurchases.getResponseCode() == BillingClient.BillingResponseCode.OK
|
||||
&& subsPurchases.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
java.util.List<Purchase> purchaseList = inAppPurchases.getPurchasesList();
|
||||
purchaseList.addAll(subsPurchases.getPurchasesList());
|
||||
|
||||
purchasesListQueryCallback(host, purchaseList);
|
||||
return;
|
||||
}
|
||||
|
||||
purchasesListQueryCallback(host, null);
|
||||
String[] toCheck = {BillingClient.ProductType.INAPP, BillingClient.ProductType.SUBS};
|
||||
queryPurchasesImpl(java.util.Arrays.asList(toCheck), new java.util.ArrayList<Purchase>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -211,5 +222,5 @@ public class JuceBillingClient implements PurchasesUpdatedListener, BillingClien
|
|||
}
|
||||
|
||||
private long host = 0;
|
||||
private BillingClient billingClient;
|
||||
private final BillingClient billingClient;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,13 +27,46 @@ namespace juce
|
|||
{
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getSku, "getSku", "()Ljava/lang/String;") \
|
||||
METHOD (getTitle, "getTitle", "()Ljava/lang/String;") \
|
||||
METHOD (getDescription, "getDescription", "()Ljava/lang/String;") \
|
||||
METHOD (getPrice, "getPrice", "()Ljava/lang/String;") \
|
||||
METHOD (getPriceCurrencyCode, "getPriceCurrencyCode", "()Ljava/lang/String;")
|
||||
METHOD (getProductId, "getProductId", "()Ljava/lang/String;") \
|
||||
METHOD (getTitle, "getTitle", "()Ljava/lang/String;") \
|
||||
METHOD (getDescription, "getDescription", "()Ljava/lang/String;") \
|
||||
METHOD (getOneTimePurchaseOfferDetails, "getOneTimePurchaseOfferDetails", "()Lcom/android/billingclient/api/ProductDetails$OneTimePurchaseOfferDetails;") \
|
||||
METHOD (getSubscriptionOfferDetails, "getSubscriptionOfferDetails", "()Ljava/util/List;")
|
||||
|
||||
DECLARE_JNI_CLASS (SkuDetails, "com/android/billingclient/api/SkuDetails")
|
||||
DECLARE_JNI_CLASS (ProductDetails, "com/android/billingclient/api/ProductDetails")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getFormattedPrice, "getFormattedPrice", "()Ljava/lang/String;") \
|
||||
METHOD (getPriceCurrencyCode, "getPriceCurrencyCode", "()Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (OneTimePurchaseOfferDetails, "com/android/billingclient/api/ProductDetails$OneTimePurchaseOfferDetails")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getFormattedPrice, "getFormattedPrice", "()Ljava/lang/String;") \
|
||||
METHOD (getPriceCurrencyCode, "getPriceCurrencyCode", "()Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (PricingPhase, "com/android/billingclient/api/ProductDetails$PricingPhase")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getOfferToken, "getOfferToken", "()Ljava/lang/String;") \
|
||||
METHOD (getPricingPhases, "getPricingPhases", "()Lcom/android/billingclient/api/ProductDetails$PricingPhases;")
|
||||
|
||||
DECLARE_JNI_CLASS (SubscriptionOfferDetails, "com/android/billingclient/api/ProductDetails$SubscriptionOfferDetails")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getPricingPhaseList, "getPricingPhaseList", "()Ljava/util/List;")
|
||||
|
||||
DECLARE_JNI_CLASS (PricingPhases, "com/android/billingclient/api/ProductDetails$PricingPhases")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (newBuilder, "newBuilder", "()Lcom/android/billingclient/api/BillingFlowParams$ProductDetailsParams$Builder;")
|
||||
|
||||
DECLARE_JNI_CLASS (BillingFlowParamsProductDetailsParams, "com/android/billingclient/api/BillingFlowParams$ProductDetailsParams")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
|
|
@ -43,33 +76,81 @@ DECLARE_JNI_CLASS (BillingFlowParams, "com/android/billingclient/api/BillingFlow
|
|||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (build, "build", "()Lcom/android/billingclient/api/BillingFlowParams;") \
|
||||
METHOD (setOldSku, "setOldSku", "(Ljava/lang/String;Ljava/lang/String;)Lcom/android/billingclient/api/BillingFlowParams$Builder;") \
|
||||
METHOD (setReplaceSkusProrationMode, "setReplaceSkusProrationMode", "(I)Lcom/android/billingclient/api/BillingFlowParams$Builder;") \
|
||||
METHOD (setSkuDetails, "setSkuDetails", "(Lcom/android/billingclient/api/SkuDetails;)Lcom/android/billingclient/api/BillingFlowParams$Builder;")
|
||||
STATICMETHOD (newBuilder, "newBuilder", "()Lcom/android/billingclient/api/BillingFlowParams$SubscriptionUpdateParams$Builder;")
|
||||
|
||||
DECLARE_JNI_CLASS (BillingFlowParamsSubscriptionUpdateParams, "com/android/billingclient/api/BillingFlowParams$SubscriptionUpdateParams")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (build, "build", "()Lcom/android/billingclient/api/BillingFlowParams;") \
|
||||
METHOD (setSubscriptionUpdateParams, "setSubscriptionUpdateParams", "(Lcom/android/billingclient/api/BillingFlowParams$SubscriptionUpdateParams;)Lcom/android/billingclient/api/BillingFlowParams$Builder;") \
|
||||
METHOD (setProductDetailsParamsList, "setProductDetailsParamsList", "(Ljava/util/List;)Lcom/android/billingclient/api/BillingFlowParams$Builder;")
|
||||
|
||||
DECLARE_JNI_CLASS (BillingFlowParamsBuilder, "com/android/billingclient/api/BillingFlowParams$Builder")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (build, "build", "()Lcom/android/billingclient/api/BillingFlowParams$SubscriptionUpdateParams;") \
|
||||
METHOD (setOldPurchaseToken, "setOldPurchaseToken", "(Ljava/lang/String;)Lcom/android/billingclient/api/BillingFlowParams$SubscriptionUpdateParams$Builder;") \
|
||||
METHOD (setReplaceProrationMode, "setReplaceProrationMode", "(I)Lcom/android/billingclient/api/BillingFlowParams$SubscriptionUpdateParams$Builder;")
|
||||
|
||||
DECLARE_JNI_CLASS (BillingFlowParamsSubscriptionUpdateParamsBuilder, "com/android/billingclient/api/BillingFlowParams$SubscriptionUpdateParams$Builder")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (build, "build", "()Lcom/android/billingclient/api/BillingFlowParams$ProductDetailsParams;") \
|
||||
METHOD (setOfferToken, "setOfferToken", "(Ljava/lang/String;)Lcom/android/billingclient/api/BillingFlowParams$ProductDetailsParams$Builder;") \
|
||||
METHOD (setProductDetails, "setProductDetails", "(Lcom/android/billingclient/api/ProductDetails;)Lcom/android/billingclient/api/BillingFlowParams$ProductDetailsParams$Builder;")
|
||||
|
||||
DECLARE_JNI_CLASS (BillingFlowParamsProductDetailsParamsBuilder, "com/android/billingclient/api/BillingFlowParams$ProductDetailsParams$Builder")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getOrderId, "getOrderId", "()Ljava/lang/String;") \
|
||||
METHOD (getSku, "getSku", "()Ljava/lang/String;") \
|
||||
METHOD (getPurchaseState, "getPurchaseState", "()I") \
|
||||
METHOD (getProducts, "getProducts", "()Ljava/util/List;") \
|
||||
METHOD (getPackageName, "getPackageName", "()Ljava/lang/String;") \
|
||||
METHOD (getPurchaseTime, "getPurchaseTime", "()J") \
|
||||
METHOD (getPurchaseTime, "getPurchaseTime", "()J") \
|
||||
METHOD (getPurchaseToken, "getPurchaseToken", "()Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidPurchase, "com/android/billingclient/api/Purchase")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
template <typename Fn>
|
||||
static void callOnMainThread (Fn&& fn)
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
fn();
|
||||
else
|
||||
MessageManager::callAsync (std::forward<Fn> (fn));
|
||||
}
|
||||
|
||||
inline StringArray javaListOfStringToJuceStringArray (const LocalRef<jobject>& javaArray)
|
||||
{
|
||||
if (javaArray.get() == nullptr)
|
||||
return {};
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
StringArray result;
|
||||
|
||||
const auto size = env->CallIntMethod (javaArray, JavaList.size);
|
||||
|
||||
for (int i = 0; i < size; ++i)
|
||||
result.add (juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (javaArray, JavaList.get, i) }.get()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct InAppPurchases::Pimpl
|
||||
{
|
||||
Pimpl (InAppPurchases& parent)
|
||||
: owner (parent),
|
||||
billingClient (LocalRef<jobject> (getEnv()->NewObject (JuceBillingClient,
|
||||
JuceBillingClient.constructor,
|
||||
getAppContext().get(),
|
||||
(jlong) this)))
|
||||
billingClient (LocalRef<jobject> { getEnv()->NewObject (JuceBillingClient,
|
||||
JuceBillingClient.constructor,
|
||||
getAppContext().get(),
|
||||
(jlong) this) })
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -86,46 +167,52 @@ struct InAppPurchases::Pimpl
|
|||
|
||||
void getProductsInformation (const StringArray& productIdentifiers)
|
||||
{
|
||||
skuDetailsQueryCallbackQueue.emplace ([this] (LocalRef<jobject> skuDetailsList)
|
||||
productDetailsQueryCallbackQueue.emplace ([this] (LocalRef<jobject> productDetailsList)
|
||||
{
|
||||
if (skuDetailsList != nullptr)
|
||||
if (productDetailsList != nullptr)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
Array<InAppPurchases::Product> products;
|
||||
|
||||
for (int i = 0; i < env->CallIntMethod (skuDetailsList, JavaList.size); ++i)
|
||||
products.add (buildProduct (LocalRef<jobject> (env->CallObjectMethod (skuDetailsList, JavaList.get, i))));
|
||||
for (int i = 0; i < env->CallIntMethod (productDetailsList, JavaList.size); ++i)
|
||||
products.add (buildProduct (LocalRef<jobject> { env->CallObjectMethod (productDetailsList, JavaList.get, i) }));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.productsInfoReturned (products); });
|
||||
callMemberOnMainThread ([this, products]
|
||||
{
|
||||
owner.listeners.call ([&] (Listener& l) { l.productsInfoReturned (products); });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
querySkuDetailsAsync (convertToLowerCase (productIdentifiers));
|
||||
queryProductDetailsAsync (convertToLowerCase (productIdentifiers));
|
||||
}
|
||||
|
||||
void purchaseProduct (const String& productIdentifier,
|
||||
const String& subscriptionIdentifier,
|
||||
bool creditForUnusedSubscription)
|
||||
{
|
||||
skuDetailsQueryCallbackQueue.emplace ([=] (LocalRef<jobject> skuDetailsList)
|
||||
productDetailsQueryCallbackQueue.emplace ([=] (LocalRef<jobject> productDetailsList)
|
||||
{
|
||||
if (skuDetailsList != nullptr)
|
||||
if (productDetailsList != nullptr)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
if (env->CallIntMethod (skuDetailsList, JavaList.size) > 0)
|
||||
if (env->CallIntMethod (productDetailsList, JavaList.size) > 0)
|
||||
{
|
||||
LocalRef<jobject> skuDetails (env->CallObjectMethod (skuDetailsList, JavaList.get, 0));
|
||||
GlobalRef productDetails (LocalRef<jobject> { env->CallObjectMethod (productDetailsList, JavaList.get, 0) });
|
||||
|
||||
if (subscriptionIdentifier.isNotEmpty())
|
||||
changeExistingSubscription (skuDetails, subscriptionIdentifier, creditForUnusedSubscription);
|
||||
else
|
||||
purchaseProductWithSkuDetails (skuDetails);
|
||||
callMemberOnMainThread ([this, productDetails, subscriptionIdentifier, creditForUnusedSubscription]
|
||||
{
|
||||
if (subscriptionIdentifier.isNotEmpty())
|
||||
changeExistingSubscription (productDetails, subscriptionIdentifier, creditForUnusedSubscription);
|
||||
else
|
||||
purchaseProductWithProductDetails (productDetails);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
querySkuDetailsAsync (convertToLowerCase ({ productIdentifier }));
|
||||
queryProductDetailsAsync (convertToLowerCase ({ productIdentifier }));
|
||||
}
|
||||
|
||||
void restoreProductsBoughtList (bool, const juce::String&)
|
||||
|
|
@ -139,15 +226,21 @@ struct InAppPurchases::Pimpl
|
|||
|
||||
for (int i = 0; i < env->CallIntMethod (purchasesList, JavaArrayList.size); ++i)
|
||||
{
|
||||
LocalRef<jobject> purchase (env->CallObjectMethod (purchasesList, JavaArrayList.get, i));
|
||||
const LocalRef<jobject> purchase { env->CallObjectMethod (purchasesList, JavaArrayList.get, i) };
|
||||
purchases.add ({ buildPurchase (purchase), {} });
|
||||
}
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored (purchases, true, NEEDS_TRANS ("Success")); });
|
||||
callMemberOnMainThread ([this, purchases]
|
||||
{
|
||||
owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored (purchases, true, NEEDS_TRANS ("Success")); });
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Failure")); });
|
||||
callMemberOnMainThread ([this]
|
||||
{
|
||||
owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Failure")); });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -158,17 +251,17 @@ struct InAppPurchases::Pimpl
|
|||
{
|
||||
if (purchaseToken.isEmpty())
|
||||
{
|
||||
skuDetailsQueryCallbackQueue.emplace ([=] (LocalRef<jobject> skuDetailsList)
|
||||
productDetailsQueryCallbackQueue.emplace ([=] (LocalRef<jobject> productDetailsList)
|
||||
{
|
||||
if (skuDetailsList != nullptr)
|
||||
if (productDetailsList != nullptr)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
if (env->CallIntMethod (skuDetailsList, JavaList.size) > 0)
|
||||
if (env->CallIntMethod (productDetailsList, JavaList.size) > 0)
|
||||
{
|
||||
LocalRef<jobject> sku (env->CallObjectMethod (skuDetailsList, JavaList.get, 0));
|
||||
const LocalRef<jobject> product { env->CallObjectMethod (productDetailsList, JavaList.get, 0) };
|
||||
|
||||
auto token = juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (sku, AndroidPurchase.getSku)));
|
||||
auto token = juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (product, ProductDetails.getProductId) });
|
||||
|
||||
if (token.isNotEmpty())
|
||||
{
|
||||
|
|
@ -178,10 +271,13 @@ struct InAppPurchases::Pimpl
|
|||
}
|
||||
}
|
||||
|
||||
notifyListenersAboutConsume (productIdentifier, false, NEEDS_TRANS ("Item unavailable"));
|
||||
callMemberOnMainThread ([this, productIdentifier]
|
||||
{
|
||||
notifyListenersAboutConsume (productIdentifier, false, NEEDS_TRANS ("Item unavailable"));
|
||||
});
|
||||
});
|
||||
|
||||
querySkuDetailsAsync (convertToLowerCase ({ productIdentifier }));
|
||||
queryProductDetailsAsync (convertToLowerCase ({ productIdentifier }));
|
||||
}
|
||||
|
||||
consumePurchaseWithToken (productIdentifier, purchaseToken);
|
||||
|
|
@ -218,27 +314,27 @@ struct InAppPurchases::Pimpl
|
|||
|
||||
private:
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(Landroid/content/Context;J)V") \
|
||||
METHOD (endConnection, "endConnection", "()V") \
|
||||
METHOD (isReady, "isReady", "()Z") \
|
||||
METHOD (isBillingSupported, "isBillingSupported", "()Z") \
|
||||
METHOD (querySkuDetails, "querySkuDetails", "([Ljava/lang/String;)V") \
|
||||
METHOD (launchBillingFlow, "launchBillingFlow", "(Landroid/app/Activity;Lcom/android/billingclient/api/BillingFlowParams;)V") \
|
||||
METHOD (queryPurchases, "queryPurchases", "()V") \
|
||||
METHOD (consumePurchase, "consumePurchase", "(Ljava/lang/String;Ljava/lang/String;)V") \
|
||||
\
|
||||
CALLBACK (skuDetailsQueryCallback, "skuDetailsQueryCallback", "(JLjava/util/List;)V") \
|
||||
CALLBACK (purchasesListQueryCallback, "purchasesListQueryCallback", "(JLjava/util/List;)V") \
|
||||
CALLBACK (purchaseCompletedCallback, "purchaseCompletedCallback", "(JLcom/android/billingclient/api/Purchase;I)V") \
|
||||
CALLBACK (purchaseConsumedCallback, "purchaseConsumedCallback", "(JLjava/lang/String;I)V")
|
||||
METHOD (constructor, "<init>", "(Landroid/content/Context;J)V") \
|
||||
METHOD (endConnection, "endConnection", "()V") \
|
||||
METHOD (isReady, "isReady", "()Z") \
|
||||
METHOD (isBillingSupported, "isBillingSupported", "()Z") \
|
||||
METHOD (queryProductDetails, "queryProductDetails", "([Ljava/lang/String;)V") \
|
||||
METHOD (launchBillingFlow, "launchBillingFlow", "(Landroid/app/Activity;Lcom/android/billingclient/api/BillingFlowParams;)V") \
|
||||
METHOD (queryPurchases, "queryPurchases", "()V") \
|
||||
METHOD (consumePurchase, "consumePurchase", "(Ljava/lang/String;Ljava/lang/String;)V") \
|
||||
\
|
||||
CALLBACK (productDetailsQueryCallback, "productDetailsQueryCallback", "(JLjava/util/List;)V") \
|
||||
CALLBACK (purchasesListQueryCallback, "purchasesListQueryCallback", "(JLjava/util/List;)V") \
|
||||
CALLBACK (purchaseCompletedCallback, "purchaseCompletedCallback", "(JLcom/android/billingclient/api/Purchase;I)V") \
|
||||
CALLBACK (purchaseConsumedCallback, "purchaseConsumedCallback", "(JLjava/lang/String;I)V")
|
||||
|
||||
DECLARE_JNI_CLASS (JuceBillingClient, "com/rmsl/juce/JuceBillingClient")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static void JNICALL skuDetailsQueryCallback (JNIEnv*, jobject, jlong host, jobject skuDetailsList)
|
||||
static void JNICALL productDetailsQueryCallback (JNIEnv*, jobject, jlong host, jobject productDetailsList)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<Pimpl*> (host))
|
||||
myself->updateSkuDetails (skuDetailsList);
|
||||
myself->updateProductDetails (productDetailsList);
|
||||
}
|
||||
|
||||
static void JNICALL purchasesListQueryCallback (JNIEnv*, jobject, jlong host, jobject purchasesList)
|
||||
|
|
@ -289,7 +385,7 @@ private:
|
|||
return lowerCase;
|
||||
}
|
||||
|
||||
void querySkuDetailsAsync (const StringArray& productIdentifiers)
|
||||
void queryProductDetailsAsync (const StringArray& productIdentifiers)
|
||||
{
|
||||
Thread::launch ([=]
|
||||
{
|
||||
|
|
@ -299,7 +395,7 @@ private:
|
|||
MessageManager::callAsync ([=]
|
||||
{
|
||||
getEnv()->CallVoidMethod (billingClient,
|
||||
JuceBillingClient.querySkuDetails,
|
||||
JuceBillingClient.queryProductDetails,
|
||||
juceStringArrayToJava (productIdentifiers).get());
|
||||
});
|
||||
});
|
||||
|
|
@ -331,23 +427,15 @@ private:
|
|||
owner.listeners.call ([&] (Listener& l) { l.productConsumed (productIdentifier, success, statusDescription); });
|
||||
}
|
||||
|
||||
LocalRef<jobject> createBillingFlowParamsBuilder (LocalRef<jobject> skuDetails)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
auto builder = LocalRef<jobject> (env->CallStaticObjectMethod (BillingFlowParams, BillingFlowParams.newBuilder));
|
||||
|
||||
return LocalRef<jobject> (env->CallObjectMethod (builder.get(),
|
||||
BillingFlowParamsBuilder.setSkuDetails,
|
||||
skuDetails.get()));
|
||||
}
|
||||
|
||||
void launchBillingFlowWithParameters (LocalRef<jobject> params)
|
||||
{
|
||||
LocalRef<jobject> activity (getCurrentActivity());
|
||||
const auto activity = []
|
||||
{
|
||||
if (auto current = getCurrentActivity())
|
||||
return current;
|
||||
|
||||
if (activity == nullptr)
|
||||
activity = getMainActivity();
|
||||
return getMainActivity();
|
||||
}();
|
||||
|
||||
getEnv()->CallVoidMethod (billingClient,
|
||||
JuceBillingClient.launchBillingFlow,
|
||||
|
|
@ -355,7 +443,7 @@ private:
|
|||
params.get());
|
||||
}
|
||||
|
||||
void changeExistingSubscription (LocalRef<jobject> skuDetails, const String& subscriptionIdentifier, bool creditForUnusedSubscription)
|
||||
void changeExistingSubscription (GlobalRef productDetails, const String& subscriptionIdentifier, bool creditForUnusedSubscription)
|
||||
{
|
||||
if (! isReady())
|
||||
{
|
||||
|
|
@ -371,35 +459,47 @@ private:
|
|||
|
||||
for (int i = 0; i < env->CallIntMethod (purchasesList, JavaArrayList.size); ++i)
|
||||
{
|
||||
auto purchase = buildPurchase (LocalRef<jobject> (env->CallObjectMethod (purchasesList.get(), JavaArrayList.get, i)));
|
||||
auto purchase = buildPurchase (LocalRef<jobject> { env->CallObjectMethod (purchasesList.get(), JavaArrayList.get, i) });
|
||||
|
||||
if (purchase.productId == subscriptionIdentifier)
|
||||
if (purchase.productIds.contains (subscriptionIdentifier))
|
||||
{
|
||||
auto builder = createBillingFlowParamsBuilder (skuDetails);
|
||||
|
||||
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(),
|
||||
BillingFlowParamsBuilder.setOldSku,
|
||||
javaString (subscriptionIdentifier).get(),
|
||||
javaString (purchase.purchaseToken).get()));
|
||||
const LocalRef<jobject> subscriptionBuilder { getEnv()->CallStaticObjectMethod (BillingFlowParamsSubscriptionUpdateParams,
|
||||
BillingFlowParamsSubscriptionUpdateParams.newBuilder) };
|
||||
env->CallObjectMethod (subscriptionBuilder.get(),
|
||||
BillingFlowParamsSubscriptionUpdateParamsBuilder.setOldPurchaseToken,
|
||||
javaString (purchase.purchaseToken).get());
|
||||
|
||||
if (! creditForUnusedSubscription)
|
||||
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(),
|
||||
BillingFlowParamsBuilder.setReplaceSkusProrationMode,
|
||||
3 /*IMMEDIATE_WITHOUT_PRORATION*/));
|
||||
{
|
||||
env->CallObjectMethod (subscriptionBuilder.get(),
|
||||
BillingFlowParamsSubscriptionUpdateParamsBuilder.setReplaceProrationMode,
|
||||
3 /*IMMEDIATE_WITHOUT_PRORATION*/);
|
||||
}
|
||||
|
||||
launchBillingFlowWithParameters (LocalRef<jobject> (env->CallObjectMethod (builder.get(),
|
||||
BillingFlowParamsBuilder.build)));
|
||||
const LocalRef<jobject> subscriptionParams { env->CallObjectMethod (subscriptionBuilder.get(),
|
||||
BillingFlowParamsSubscriptionUpdateParamsBuilder.build) };
|
||||
|
||||
const LocalRef<jobject> builder { env->CallStaticObjectMethod (BillingFlowParams, BillingFlowParams.newBuilder) };
|
||||
env->CallObjectMethod (builder.get(),
|
||||
BillingFlowParamsBuilder.setSubscriptionUpdateParams,
|
||||
subscriptionParams.get());
|
||||
const LocalRef<jobject> params { env->CallObjectMethod (builder.get(), BillingFlowParamsBuilder.build) };
|
||||
|
||||
launchBillingFlowWithParameters (params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyListenersAboutPurchase ({}, false, NEEDS_TRANS ("Unable to get subscription details"));
|
||||
callMemberOnMainThread ([this]
|
||||
{
|
||||
notifyListenersAboutPurchase ({}, false, NEEDS_TRANS ("Unable to get subscription details"));
|
||||
});
|
||||
});
|
||||
|
||||
getProductsBoughtAsync();
|
||||
}
|
||||
|
||||
void purchaseProductWithSkuDetails (LocalRef<jobject> skuDetails)
|
||||
void purchaseProductWithProductDetails (GlobalRef productDetails)
|
||||
{
|
||||
if (! isReady())
|
||||
{
|
||||
|
|
@ -407,22 +507,48 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
launchBillingFlowWithParameters (LocalRef<jobject> (getEnv()->CallObjectMethod (createBillingFlowParamsBuilder (skuDetails).get(),
|
||||
BillingFlowParamsBuilder.build)));
|
||||
auto* env = getEnv();
|
||||
const LocalRef<jobject> billingFlowParamsProductDetailsParamsBuilder { env->CallStaticObjectMethod (BillingFlowParamsProductDetailsParams, BillingFlowParamsProductDetailsParams.newBuilder) };
|
||||
env->CallObjectMethod (billingFlowParamsProductDetailsParamsBuilder, BillingFlowParamsProductDetailsParamsBuilder.setProductDetails, productDetails.get());
|
||||
|
||||
if (const LocalRef<jobject> subscriptionDetailsList { env->CallObjectMethod (productDetails, ProductDetails.getSubscriptionOfferDetails) })
|
||||
{
|
||||
if (env->CallIntMethod (subscriptionDetailsList, JavaList.size) > 0)
|
||||
{
|
||||
const LocalRef<jobject> subscriptionDetails { env->CallObjectMethod (subscriptionDetailsList, JavaList.get, 0) };
|
||||
const LocalRef<jobject> offerToken { env->CallObjectMethod (subscriptionDetails, SubscriptionOfferDetails.getOfferToken) };
|
||||
env->CallObjectMethod (billingFlowParamsProductDetailsParamsBuilder, BillingFlowParamsProductDetailsParamsBuilder.setOfferToken, offerToken.get());
|
||||
}
|
||||
}
|
||||
|
||||
const LocalRef<jobject> billingFlowParamsProductDetailsParams { env->CallObjectMethod (billingFlowParamsProductDetailsParamsBuilder, BillingFlowParamsProductDetailsParamsBuilder.build) };
|
||||
|
||||
const LocalRef<jobject> list { env->NewObject (JavaArrayList, JavaArrayList.constructor, 0) };
|
||||
env->CallBooleanMethod (list, JavaArrayList.add, billingFlowParamsProductDetailsParams.get());
|
||||
|
||||
const LocalRef<jobject> billingFlowParamsBuilder { env->CallStaticObjectMethod (BillingFlowParams, BillingFlowParams.newBuilder) };
|
||||
env->CallObjectMethod (billingFlowParamsBuilder, BillingFlowParamsBuilder.setProductDetailsParamsList, list.get());
|
||||
const LocalRef<jobject> params { env->CallObjectMethod (billingFlowParamsBuilder, BillingFlowParamsBuilder.build) };
|
||||
|
||||
launchBillingFlowWithParameters (params);
|
||||
}
|
||||
|
||||
void consumePurchaseWithToken (const String& productIdentifier, const String& purchaseToken)
|
||||
{
|
||||
if (! isReady())
|
||||
{
|
||||
notifyListenersAboutConsume (productIdentifier, false, NEEDS_TRANS ("In-App purchases unavailable"));
|
||||
callMemberOnMainThread ([this, productIdentifier]
|
||||
{
|
||||
notifyListenersAboutConsume (productIdentifier, false, NEEDS_TRANS ("In-App purchases unavailable"));
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
getEnv()->CallObjectMethod (billingClient,
|
||||
JuceBillingClient.consumePurchase,
|
||||
LocalRef<jstring> (javaString (productIdentifier)).get(),
|
||||
LocalRef<jstring> (javaString (purchaseToken)).get());
|
||||
LocalRef<jstring> { javaString (productIdentifier) }.get(),
|
||||
LocalRef<jstring> { javaString (purchaseToken) }.get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -433,25 +559,59 @@ private:
|
|||
|
||||
auto* env = getEnv();
|
||||
|
||||
return { juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (purchase, AndroidPurchase.getOrderId))),
|
||||
juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (purchase, AndroidPurchase.getSku))),
|
||||
juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (purchase, AndroidPurchase.getPackageName))),
|
||||
if (env->CallIntMethod(purchase, AndroidPurchase.getPurchaseState) != 1 /* PURCHASED */)
|
||||
return {};
|
||||
|
||||
return { juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (purchase, AndroidPurchase.getOrderId) }),
|
||||
javaListOfStringToJuceStringArray (LocalRef<jobject> { env->CallObjectMethod (purchase, AndroidPurchase.getProducts) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (purchase, AndroidPurchase.getPackageName) }),
|
||||
Time (env->CallLongMethod (purchase, AndroidPurchase.getPurchaseTime)).toString (true, true, true, true),
|
||||
juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (purchase, AndroidPurchase.getPurchaseToken))) };
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (purchase, AndroidPurchase.getPurchaseToken) }) };
|
||||
}
|
||||
|
||||
static InAppPurchases::Product buildProduct (LocalRef<jobject> productSkuDetails)
|
||||
static InAppPurchases::Product buildProduct (LocalRef<jobject> productDetails)
|
||||
{
|
||||
if (productSkuDetails == nullptr)
|
||||
if (productDetails == nullptr)
|
||||
return {};
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
return { juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (productSkuDetails, SkuDetails.getSku))),
|
||||
juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (productSkuDetails, SkuDetails.getTitle))),
|
||||
juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (productSkuDetails, SkuDetails.getDescription))),
|
||||
juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (productSkuDetails, SkuDetails.getPrice))),
|
||||
juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (productSkuDetails, SkuDetails.getPriceCurrencyCode))) };
|
||||
if (LocalRef<jobject> oneTimePurchase { env->CallObjectMethod (productDetails, ProductDetails.getOneTimePurchaseOfferDetails) })
|
||||
{
|
||||
return { juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (productDetails, ProductDetails.getProductId) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (productDetails, ProductDetails.getTitle) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (productDetails, ProductDetails.getDescription) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (oneTimePurchase, OneTimePurchaseOfferDetails.getFormattedPrice) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (oneTimePurchase, OneTimePurchaseOfferDetails.getPriceCurrencyCode) }) };
|
||||
}
|
||||
|
||||
LocalRef<jobject> subscription { env->CallObjectMethod (productDetails, ProductDetails.getSubscriptionOfferDetails) };
|
||||
|
||||
if (env->CallIntMethod (subscription, JavaList.size) == 0)
|
||||
return {};
|
||||
|
||||
// We can only return a single subscription price for this subscription,
|
||||
// but the subscription has more than one pricing scheme.
|
||||
jassert (env->CallIntMethod (subscription, JavaList.size) == 1);
|
||||
|
||||
const LocalRef<jobject> offerDetails { env->CallObjectMethod (subscription, JavaList.get, 0) };
|
||||
const LocalRef<jobject> pricingPhases { env->CallObjectMethod (offerDetails, SubscriptionOfferDetails.getPricingPhases) };
|
||||
const LocalRef<jobject> phaseList { env->CallObjectMethod (pricingPhases, PricingPhases.getPricingPhaseList) };
|
||||
|
||||
if (env->CallIntMethod (phaseList, JavaList.size) == 0)
|
||||
return {};
|
||||
|
||||
// We can only return a single subscription price for this subscription,
|
||||
// but the pricing scheme for this subscription has more than one phase.
|
||||
jassert (env->CallIntMethod (phaseList, JavaList.size) == 1);
|
||||
|
||||
const LocalRef<jobject> phase { env->CallObjectMethod (phaseList, JavaList.get, 0) };
|
||||
|
||||
return { juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (productDetails, ProductDetails.getProductId) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (productDetails, ProductDetails.getTitle) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (productDetails, ProductDetails.getDescription) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (phase, PricingPhase.getFormattedPrice) }),
|
||||
juceString (LocalRef<jstring> { (jstring) env->CallObjectMethod (phase, PricingPhase.getPriceCurrencyCode) }) };
|
||||
}
|
||||
|
||||
static String getStatusDescriptionFromResponseCode (int responseCode)
|
||||
|
|
@ -478,29 +638,29 @@ private:
|
|||
|
||||
void purchaseCompleted (jobject purchase, int responseCode)
|
||||
{
|
||||
notifyListenersAboutPurchase (buildPurchase (LocalRef<jobject> (purchase)),
|
||||
notifyListenersAboutPurchase (buildPurchase (LocalRef<jobject> { purchase }),
|
||||
wasSuccessful (responseCode),
|
||||
getStatusDescriptionFromResponseCode (responseCode));
|
||||
}
|
||||
|
||||
void purchaseConsumed (jstring productIdentifier, int responseCode)
|
||||
{
|
||||
notifyListenersAboutConsume (juceString (LocalRef<jstring> (productIdentifier)),
|
||||
notifyListenersAboutConsume (juceString (LocalRef<jstring> { productIdentifier }),
|
||||
wasSuccessful (responseCode),
|
||||
getStatusDescriptionFromResponseCode (responseCode));
|
||||
}
|
||||
|
||||
void updateSkuDetails (jobject skuDetailsList)
|
||||
void updateProductDetails (jobject productDetailsList)
|
||||
{
|
||||
jassert (! skuDetailsQueryCallbackQueue.empty());
|
||||
skuDetailsQueryCallbackQueue.front() (LocalRef<jobject> (skuDetailsList));
|
||||
skuDetailsQueryCallbackQueue.pop();
|
||||
jassert (! productDetailsQueryCallbackQueue.empty());
|
||||
productDetailsQueryCallbackQueue.front() (LocalRef<jobject> { productDetailsList });
|
||||
productDetailsQueryCallbackQueue.pop();
|
||||
}
|
||||
|
||||
void updatePurchasesList (jobject purchasesList)
|
||||
{
|
||||
jassert (! purchasesListQueryCallbackQueue.empty());
|
||||
purchasesListQueryCallbackQueue.front() (LocalRef<jobject> (purchasesList));
|
||||
purchasesListQueryCallbackQueue.front() (LocalRef<jobject> { purchasesList });
|
||||
purchasesListQueryCallbackQueue.pop();
|
||||
}
|
||||
|
||||
|
|
@ -508,13 +668,32 @@ private:
|
|||
InAppPurchases& owner;
|
||||
GlobalRef billingClient;
|
||||
|
||||
std::queue<std::function<void (LocalRef<jobject>)>> skuDetailsQueryCallbackQueue,
|
||||
std::queue<std::function<void (LocalRef<jobject>)>> productDetailsQueryCallbackQueue,
|
||||
purchasesListQueryCallbackQueue;
|
||||
|
||||
//==============================================================================
|
||||
void callMemberOnMainThread (std::function<void()> callback)
|
||||
{
|
||||
callOnMainThread ([ref = WeakReference<Pimpl> (this), callback]
|
||||
{
|
||||
if (ref != nullptr)
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE(Pimpl)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
void juce_handleOnResume()
|
||||
{
|
||||
callOnMainThread ([]
|
||||
{
|
||||
InAppPurchases::getInstance()->restoreProductsBoughtList (false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
InAppPurchases::Pimpl::JuceBillingClient_Class InAppPurchases::Pimpl::JuceBillingClient;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue