capacitor-native-purchases 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,14 +3,17 @@ package jok.purchases.capacitor;
3
3
  import android.app.Activity;
4
4
  import android.util.Log;
5
5
 
6
+ import com.android.billingclient.api.AcknowledgePurchaseParams;
6
7
  import com.android.billingclient.api.BillingClient;
7
-
8
8
  import com.android.billingclient.api.BillingClientStateListener;
9
+ import com.android.billingclient.api.ConsumeParams;
9
10
  import com.android.billingclient.api.BillingFlowParams;
10
11
  import com.android.billingclient.api.BillingResult;
12
+ import com.android.billingclient.api.PendingPurchasesParams;
11
13
  import com.android.billingclient.api.ProductDetails;
12
14
  import com.android.billingclient.api.Purchase;
13
15
  import com.android.billingclient.api.PurchaseHistoryRecord;
16
+ import com.android.billingclient.api.PurchasesUpdatedListener;
14
17
  import com.android.billingclient.api.QueryProductDetailsParams;
15
18
  import com.android.billingclient.api.QueryPurchaseHistoryParams;
16
19
  import com.android.billingclient.api.QueryPurchasesParams;
@@ -20,8 +23,7 @@ import com.getcapacitor.PluginCall;
20
23
 
21
24
  import android.content.Context;
22
25
 
23
-
24
- import androidx.annotation.NonNull;
26
+ import androidx.appcompat.app.AlertDialog;
25
27
 
26
28
  import java.io.BufferedReader;
27
29
  import java.io.InputStreamReader;
@@ -31,370 +33,718 @@ import java.nio.charset.StandardCharsets;
31
33
  import java.text.SimpleDateFormat;
32
34
  import java.util.ArrayList;
33
35
  import java.util.Calendar;
36
+ import java.util.HashMap;
34
37
  import java.util.List;
35
38
  import java.util.Locale;
39
+ import java.util.Map;
36
40
  import java.util.Objects;
37
41
 
38
42
  public class Purchases {
39
43
 
40
- private final Activity activity;
41
- public Context context;
42
- private final BillingClient billingClient;
43
- private int billingClientIsConnected = 0;
44
+ private final Activity activity;
45
+ public Context context;
46
+ private BillingClient billingClient;
47
+ private int billingClientIsConnected = 0;
44
48
 
45
- private String googleVerifyEndpoint = "";
46
- private String googleBid = "";
49
+ // Reconnection configuration
50
+ private static final int MAX_RECONNECT_ATTEMPTS = 5;
51
+ private static final long RECONNECT_BASE_DELAY_MS = 1000; // 1 second
52
+ private int reconnectAttempts = 0;
53
+ private final android.os.Handler reconnectHandler = new android.os.Handler(android.os.Looper.getMainLooper());
47
54
 
48
- public Purchases(PurchasesPlugin plugin, BillingClient billingClient) {
55
+ private String googleVerifyEndpoint = "";
56
+ private String googleBid = "";
49
57
 
50
- this.billingClient = billingClient;
51
- this.billingClient.startConnection(new BillingClientStateListener() {
52
- @Override
53
- public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
54
- if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
55
- billingClientIsConnected = 1;
56
- } else {
57
- billingClientIsConnected = billingResult.getResponseCode();
58
- }
59
- }
58
+ // Cache to store product types (SUBS or INAPP) by productIdentifier
59
+ private final Map<String, String> productTypeCache = new HashMap<>();
60
60
 
61
- @Override
62
- public void onBillingServiceDisconnected() {
63
- // Try to restart the connection on the next request to
64
- // Google Play by calling the startConnection() method.
65
- }
66
- });
67
- this.activity = plugin.getActivity();
68
- this.context = plugin.getContext();
61
+ // Store pending purchase call to resolve after acknowledgement
62
+ private PluginCall pendingPurchaseCall = null;
69
63
 
70
- }
71
64
 
72
- public String echo(String value) {
73
- Log.i("Echo", value);
74
- return value;
75
- }
65
+ // This listener is fired upon completing the billing flow, it is vital to call the acknowledgePurchase
66
+ // method on the billingClient for SUBS, or consumeAsync for INAPP, otherwise Google will automatically
67
+ // cancel the subscription or refund the purchase shortly after
68
+ private final PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
69
+ JSObject response = new JSObject();
70
+ try {
71
+ if (purchases != null && billingResult.getResponseCode() == 0) {
72
+ for (int i = 0; i < purchases.size(); i++) {
76
73
 
77
- public void setGoogleVerificationDetails(String googleVerifyEndpoint, String bid) {
78
- this.googleVerifyEndpoint = googleVerifyEndpoint;
79
- this.googleBid = bid;
74
+ Purchase currentPurchase = purchases.get(i);
75
+ String purchaseToken = currentPurchase.getPurchaseToken();
80
76
 
81
- Log.i("SET-VERIFY", "Verification values updated");
82
- }
77
+ // Query INAPP purchases to check if this transaction is there
78
+ QueryPurchasesParams inAppQueryParams = QueryPurchasesParams.newBuilder()
79
+ .setProductType(BillingClient.ProductType.INAPP)
80
+ .build();
83
81
 
84
- public void getProductDetails(String productIdentifier, PluginCall call) {
82
+ billingClient.queryPurchasesAsync(inAppQueryParams, (inAppResult, inAppPurchases) -> {
83
+ boolean foundInInApp = false;
85
84
 
86
- JSObject response = new JSObject();
85
+ if (inAppPurchases != null) {
86
+ for (Purchase p : inAppPurchases) {
87
+ if (p.getPurchaseToken().equals(purchaseToken)) {
88
+ foundInInApp = true;
89
+ break;
90
+ }
91
+ }
92
+ }
87
93
 
88
- if (billingClientIsConnected == 1) {
94
+ if (foundInInApp) {
95
+ // Found in INAPP, use consumeAsync
96
+ ConsumeParams consumeParams = ConsumeParams.newBuilder()
97
+ .setPurchaseToken(purchaseToken)
98
+ .build();
99
+
100
+ billingClient.consumeAsync(consumeParams, (consumeResult, consumedToken) -> {
101
+ Log.i("Purchase consumed", currentPurchase.getOriginalJson());
102
+
103
+ if (pendingPurchaseCall != null) {
104
+ response.put("responseCode", consumeResult.getResponseCode());
105
+ if (consumeResult.getResponseCode() == 0) {
106
+ response.put("responseMessage", "Purchase consumed successfully");
107
+ } else {
108
+ response.put("responseMessage", "Failed to consume purchase: " + consumeResult.getDebugMessage());
109
+ }
110
+ pendingPurchaseCall.resolve(response);
111
+ pendingPurchaseCall = null;
112
+ }
113
+ });
114
+ } else {
115
+ // Not found in INAPP, query SUBS purchases
116
+ QueryPurchasesParams subsQueryParams = QueryPurchasesParams.newBuilder()
117
+ .setProductType(BillingClient.ProductType.SUBS)
118
+ .build();
119
+
120
+ billingClient.queryPurchasesAsync(subsQueryParams, (subsResult, subsPurchases) -> {
121
+ boolean foundInSubs = false;
122
+
123
+ if (subsPurchases != null) {
124
+ for (Purchase p : subsPurchases) {
125
+ if (p.getPurchaseToken().equals(purchaseToken)) {
126
+ foundInSubs = true;
127
+ break;
128
+ }
129
+ }
130
+ }
89
131
 
90
- QueryProductDetailsParams.Product productToFind = QueryProductDetailsParams.Product.newBuilder()
91
- .setProductId(productIdentifier)
92
- .setProductType(BillingClient.ProductType.SUBS)
132
+ if (foundInSubs) {
133
+ // Found in SUBS, use acknowledgePurchase
134
+ AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
135
+ .setPurchaseToken(purchaseToken)
93
136
  .build();
94
137
 
95
- QueryProductDetailsParams queryProductDetailsParams =
96
- QueryProductDetailsParams.newBuilder()
97
- .setProductList(List.of(productToFind))
98
- .build();
138
+ billingClient.acknowledgePurchase(acknowledgePurchaseParams, ackResult -> {
139
+ Log.i("Purchase ack", currentPurchase.getOriginalJson());
140
+
141
+ if (pendingPurchaseCall != null) {
142
+ response.put("responseCode", ackResult.getResponseCode());
143
+ if (ackResult.getResponseCode() == 0) {
144
+ response.put("responseMessage", "Purchase acknowledged successfully");
145
+ } else {
146
+ response.put("responseMessage", "Failed to acknowledge purchase: " + ackResult.getDebugMessage());
147
+ }
148
+ pendingPurchaseCall.resolve(response);
149
+ pendingPurchaseCall = null;
150
+ }
151
+ });
152
+ } else {
153
+ // Not found in either INAPP or SUBS
154
+ Log.w("PurchasesUpdatedListener", "Purchase not found in INAPP or SUBS queries");
155
+ if (pendingPurchaseCall != null) {
156
+ response.put("responseCode", -1);
157
+ response.put("responseMessage", "Purchase not found in active purchases");
158
+ pendingPurchaseCall.resolve(response);
159
+ pendingPurchaseCall = null;
160
+ }
161
+ }
162
+ });
163
+ }
164
+ });
165
+ }
166
+ } else {
167
+ // Purchase failed or was cancelled
168
+ if (pendingPurchaseCall != null) {
169
+ response.put("responseCode", billingResult.getResponseCode());
170
+ response.put("responseMessage", billingResult.getDebugMessage());
171
+ pendingPurchaseCall.resolve(response);
172
+ pendingPurchaseCall = null;
173
+ }
174
+ }
99
175
 
100
- billingClient.queryProductDetailsAsync(
101
- queryProductDetailsParams,
102
- (billingResult, productDetailsList) -> {
103
176
 
104
- try {
177
+ // if (pendingPurchaseCall != null) {
178
+ // response.put("responseCode", -2);
179
+ // response.put("responseMessage", "Not processed");
180
+ // pendingPurchaseCall.resolve(response);
181
+ // pendingPurchaseCall = null;
182
+ // }
183
+ }
184
+ catch(Error err){
185
+ new AlertDialog.Builder(this.context)
186
+ .setMessage("Error: " + err.toString())
187
+ .setPositiveButton("OK", null)
188
+ .show();
189
+
190
+ if (pendingPurchaseCall != null) {
191
+ response.put("responseCode", -1);
192
+ response.put("responseMessage", err.toString());
193
+ pendingPurchaseCall.resolve(response);
194
+ pendingPurchaseCall = null;
195
+ }
196
+ }
197
+ };
198
+
199
+ Context savedContext;
200
+
201
+ public Purchases(PurchasesPlugin plugin) {
202
+ this.context = plugin.getContext();
203
+ this.activity = plugin.getActivity();
204
+
205
+ this.billingClient = BillingClient.newBuilder(context)
206
+ .setListener(purchasesUpdatedListener)
207
+ .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().enablePrepaidPlans().build())
208
+ .build();
209
+
210
+ // Start initial connection
211
+ startBillingConnection();
212
+ }
213
+
214
+ /**
215
+ * Schedule a reconnection attempt with exponential backoff.
216
+ * Delay increases: 1s, 2s, 4s, 8s, 16s (max 5 attempts)
217
+ */
218
+ private void scheduleReconnect() {
219
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
220
+ Log.e("Purchases", "Max reconnection attempts reached. Please restart the app.");
221
+ return;
222
+ }
105
223
 
106
- ProductDetails productDetails = productDetailsList.get(0);
107
- String productId = productDetails.getProductId();
108
- String title = productDetails.getTitle();
109
- String desc = productDetails.getDescription();
110
- Log.i("productIdentifier", productId);
111
- Log.i("displayName", title);
112
- Log.i("desc", desc);
224
+ long delayMs = RECONNECT_BASE_DELAY_MS * (1L << reconnectAttempts); // Exponential backoff
225
+ reconnectAttempts++;
113
226
 
114
- List<ProductDetails.SubscriptionOfferDetails> subscriptionOfferDetails = productDetails.getSubscriptionOfferDetails();
227
+ Log.i("Purchases", "Scheduling reconnect attempt " + reconnectAttempts + " in " + delayMs + "ms");
115
228
 
116
- String price = Objects.requireNonNull(subscriptionOfferDetails).get(0).getPricingPhases().getPricingPhaseList().get(0).getFormattedPrice();
229
+ reconnectHandler.postDelayed(this::startBillingConnection, delayMs);
230
+ }
117
231
 
118
- JSObject data = new JSObject();
119
- data.put("productIdentifier", productId);
120
- data.put("displayName", title);
121
- data.put("description", desc);
122
- data.put("price", price);
232
+ /**
233
+ * Start or restart the billing client connection.
234
+ */
235
+ private void startBillingConnection() {
236
+ if (billingClient.isReady()) {
237
+ Log.i("Purchases", "BillingClient is already connected");
238
+ billingClientIsConnected = 1;
239
+ reconnectAttempts = 0;
240
+ return;
241
+ }
123
242
 
124
- response.put("responseCode", 0);
125
- response.put("responseMessage", "Successfully found the product details for given productIdentifier");
126
- response.put("data", data);
243
+ billingClient.startConnection(new BillingClientStateListener() {
244
+ @Override
245
+ public void onBillingSetupFinished(BillingResult billingResult) {
246
+ if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
247
+ billingClientIsConnected = 1;
248
+ reconnectAttempts = 0; // Reset on successful connection
249
+ Log.i("Purchases", "BillingClient connected successfully");
250
+ } else {
251
+ billingClientIsConnected = billingResult.getResponseCode();
252
+ Log.e("Purchases", "BillingClient connection failed: " + billingResult.getDebugMessage());
253
+ // Try again with backoff
254
+ scheduleReconnect();
255
+ }
256
+ }
257
+
258
+ @Override
259
+ public void onBillingServiceDisconnected() {
260
+ billingClientIsConnected = -1;
261
+ Log.w("Purchases", "BillingClient disconnected");
262
+ scheduleReconnect();
263
+ }
264
+ });
265
+ }
266
+
267
+ /**
268
+ * Ensure billing client is connected before executing an operation.
269
+ * Returns true if connected, false if reconnection was triggered.
270
+ */
271
+ public boolean ensureConnected() {
272
+ if (billingClient.isReady()) {
273
+ return true;
274
+ }
275
+ // Trigger reconnection
276
+ reconnectAttempts = 0; // Reset for on-demand reconnection
277
+ startBillingConnection();
278
+ return false;
279
+ }
127
280
 
128
- } catch (Exception e) {
129
- Log.e("Err", e.toString());
130
- response.put("responseCode", 1);
131
- response.put("responseMessage", "Could not find a product matching the given productIdentifier");
132
- }
281
+ public String echo(String value) {
282
+ Log.i("Echo", value);
283
+ return value;
284
+ }
133
285
 
134
- call.resolve(response);
135
- }
136
- );
286
+ public void setGoogleVerificationDetails(String googleVerifyEndpoint, String bid) {
287
+ this.googleVerifyEndpoint = googleVerifyEndpoint;
288
+ this.googleBid = bid;
137
289
 
138
- } else if (billingClientIsConnected == 2) {
290
+ Log.i("SET-VERIFY", "Verification values updated");
291
+ }
139
292
 
140
- response.put("responseCode", 500);
141
- response.put("responseMessage", "Android: BillingClient failed to initialise");
142
- call.resolve(response);
293
+ public void getProductDetails(String productIdentifier, PluginCall call) {
143
294
 
144
- } else {
295
+ JSObject response = new JSObject();
145
296
 
146
- response.put("responseCode", billingClientIsConnected);
147
- response.put("responseMessage", "Android: BillingClient failed to initialise");
297
+ if (!ensureConnected()) {
298
+ response.put("responseCode", 503);
299
+ response.put("responseMessage", "Android: BillingClient is reconnecting, please retry");
300
+ call.resolve(response);
301
+ return;
302
+ }
148
303
 
149
- response.put("responseCode", 503);
150
- response.put("responseMessage", "Android: BillingClient is still initialising");
304
+ // Check cache first
305
+ String cachedType = productTypeCache.get(productIdentifier);
306
+
307
+ if (cachedType != null) {
308
+ // Use cached type directly
309
+ if (cachedType.equals(BillingClient.ProductType.SUBS)) {
310
+ querySubsProduct(productIdentifier, call, response);
311
+ } else {
312
+ queryInAppProduct(productIdentifier, call, response);
313
+ }
314
+ } else {
315
+ // Not cached - try SUBS first, then INAPP
316
+ querySubsProduct(productIdentifier, call, response);
317
+ }
318
+ }
319
+
320
+ private void querySubsProduct(String productIdentifier, PluginCall call, JSObject response) {
321
+ QueryProductDetailsParams.Product subsProductToFind = QueryProductDetailsParams.Product.newBuilder()
322
+ .setProductId(productIdentifier)
323
+ .setProductType(BillingClient.ProductType.SUBS)
324
+ .build();
325
+
326
+ QueryProductDetailsParams subsQueryParams =
327
+ QueryProductDetailsParams.newBuilder()
328
+ .setProductList(List.of(subsProductToFind))
329
+ .build();
330
+
331
+ billingClient.queryProductDetailsAsync(
332
+ subsQueryParams,
333
+ (billingResult, productDetailsList) -> {
334
+
335
+ if (productDetailsList != null && !productDetailsList.isEmpty()) {
336
+ // Found as SUBS
337
+ try {
338
+ ProductDetails productDetails = productDetailsList.get(0);
339
+ String productId = productDetails.getProductId();
340
+ String title = productDetails.getTitle();
341
+ String desc = productDetails.getDescription();
342
+ Log.i("productIdentifier", productId);
343
+ Log.i("displayName", title);
344
+ Log.i("desc", desc);
345
+
346
+ List<ProductDetails.SubscriptionOfferDetails> subscriptionOfferDetails = productDetails.getSubscriptionOfferDetails();
347
+ String price = Objects.requireNonNull(subscriptionOfferDetails).get(0).getPricingPhases().getPricingPhaseList().get(0).getFormattedPrice();
348
+
349
+ // Cache product type as SUBS
350
+ productTypeCache.put(productId, BillingClient.ProductType.SUBS);
351
+
352
+ JSObject data = new JSObject();
353
+ data.put("productIdentifier", productId);
354
+ data.put("displayName", title);
355
+ data.put("description", desc);
356
+ data.put("price", price);
357
+ data.put("productType", "SUBS");
358
+
359
+ response.put("responseCode", 0);
360
+ response.put("responseMessage", "Successfully found the product details for given productIdentifier");
361
+ response.put("data", data);
151
362
  call.resolve(response);
152
363
 
364
+ } catch (Exception e) {
365
+ Log.e("Err", e.toString());
366
+ // If error processing SUBS, try INAPP
367
+ queryInAppProduct(productIdentifier, call, response);
368
+ }
369
+ } else {
370
+ // Not found as SUBS, try INAPP
371
+ queryInAppProduct(productIdentifier, call, response);
372
+ }
373
+ }
374
+ );
375
+ }
376
+
377
+ private void queryInAppProduct(String productIdentifier, PluginCall call, JSObject response) {
378
+ QueryProductDetailsParams.Product inAppProductToFind = QueryProductDetailsParams.Product.newBuilder()
379
+ .setProductId(productIdentifier)
380
+ .setProductType(BillingClient.ProductType.INAPP)
381
+ .build();
382
+
383
+ QueryProductDetailsParams inAppQueryParams =
384
+ QueryProductDetailsParams.newBuilder()
385
+ .setProductList(List.of(inAppProductToFind))
386
+ .build();
387
+
388
+ billingClient.queryProductDetailsAsync(
389
+ inAppQueryParams,
390
+ (billingResult, productDetailsList) -> {
391
+ try {
392
+ if (productDetailsList != null && !productDetailsList.isEmpty()) {
393
+ ProductDetails productDetails = productDetailsList.get(0);
394
+ String productId = productDetails.getProductId();
395
+ String title = productDetails.getTitle();
396
+ String desc = productDetails.getDescription();
397
+ Log.i("productIdentifier", productId);
398
+ Log.i("displayName", title);
399
+ Log.i("desc", desc);
400
+
401
+ ProductDetails.OneTimePurchaseOfferDetails oneTimeOfferDetails = productDetails.getOneTimePurchaseOfferDetails();
402
+ String price = Objects.requireNonNull(oneTimeOfferDetails).getFormattedPrice();
403
+
404
+ // Cache product type as INAPP
405
+ productTypeCache.put(productId, BillingClient.ProductType.INAPP);
406
+
407
+ JSObject data = new JSObject();
408
+ data.put("productIdentifier", productId);
409
+ data.put("displayName", title);
410
+ data.put("description", desc);
411
+ data.put("price", price);
412
+ data.put("productType", "INAPP");
413
+
414
+ response.put("responseCode", 0);
415
+ response.put("responseMessage", "Successfully found the product details for given productIdentifier");
416
+ response.put("data", data);
417
+ } else {
418
+ response.put("responseCode", 1);
419
+ response.put("responseMessage", "Could not find a product matching the given productIdentifier");
420
+ }
421
+ } catch (Exception e) {
422
+ Log.e("Err", e.toString());
423
+ response.put("responseCode", 1);
424
+ response.put("responseMessage", "Could not find a product matching the given productIdentifier");
153
425
  }
154
- }
155
426
 
156
- public void getLatestTransaction(String productIdentifier, PluginCall call) {
427
+ call.resolve(response);
428
+ }
429
+ );
430
+ }
157
431
 
158
- JSObject response = new JSObject();
432
+ /**
433
+ * Get product type from cache, defaults to SUBS if not cached
434
+ */
435
+ private String getProductType(String productIdentifier) {
436
+ return productTypeCache.getOrDefault(productIdentifier, null);
437
+ }
159
438
 
160
- if (billingClientIsConnected == 1) {
161
-
162
- QueryPurchaseHistoryParams queryPurchaseHistoryParams =
163
- QueryPurchaseHistoryParams.newBuilder()
164
- .setProductType(BillingClient.ProductType.SUBS)
165
- .build();
166
-
167
-
168
- billingClient.queryPurchaseHistoryAsync(queryPurchaseHistoryParams, (BillingResult billingResult, List<PurchaseHistoryRecord> list) -> {
169
-
170
- // Try to loop through the list until we find a purchase history record associated with the passed in productIdentifier.
171
- // If we do, then set found to true to break out of the loop, then compile a response with necessary data. Otherwise compile
172
- // a response saying that the there were not transactions for the given productIdentifier.
173
- int i = 0;
174
- boolean found = false;
175
- while (list != null && (i < list.size() && !found)) {
176
- try {
177
-
178
- JSObject currentPurchaseHistoryRecord = new JSObject(list.get(i).getOriginalJson());
179
- Log.i("PurchaseHistory", currentPurchaseHistoryRecord.toString());
180
-
181
- if (currentPurchaseHistoryRecord.get("productId").equals(productIdentifier)) {
182
-
183
- found = true;
184
-
185
- JSObject data = new JSObject();
186
- String expiryDate = getExpiryDateFromGoogle(productIdentifier, currentPurchaseHistoryRecord.get("purchaseToken").toString());
187
- if (expiryDate != null) {
188
- data.put("expiryDate", expiryDate);
189
- }
190
- Calendar calendar = Calendar.getInstance();
191
- calendar.setTimeInMillis(Long.parseLong((currentPurchaseHistoryRecord.get("purchaseTime").toString())));
192
- String orderId = currentPurchaseHistoryRecord.optString("orderId", ""); // Usamos optString para obtener un valor por defecto si la clave no existe
193
- data.put("productIdentifier", currentPurchaseHistoryRecord.get("productId"));
194
- data.put("originalId", orderId);
195
- data.put("transactionId", orderId);
196
- data.put("developerPayload",currentPurchaseHistoryRecord.optString("developerPayload", "")); // Usamos optString para obtener un valor por defecto si la clave no existe
197
- data.put("purchaseToken", currentPurchaseHistoryRecord.get("purchaseToken").toString());
198
-
199
- response.put("responseCode", 0);
200
- response.put("responseMessage", "Successfully found the latest transaction matching given productIdentifier");
201
- response.put("data", data);
202
- }
203
- } catch (Exception e) {
204
- Logger.error(e.getMessage());
205
- }
439
+ public void getLatestTransaction(String productIdentifier, PluginCall call) {
206
440
 
207
- i++;
441
+ JSObject response = new JSObject();
208
442
 
209
- }
443
+ if (!ensureConnected()) {
444
+ response.put("responseCode", 503);
445
+ response.put("responseMessage", "Android: BillingClient is reconnecting, please retry");
446
+ call.resolve(response);
447
+ return;
448
+ }
210
449
 
211
- // If after looping through the list of purchase history records, no records are found to be associated with
212
- // the given product identifier, return a response saying no transactions found
213
- if (!found) {
214
- response.put("responseCode", 3);
215
- response.put("responseMessage", "No transaction for given productIdentifier, or it could not be verified");
216
- }
450
+ // Use cached product type, defaults to SUBS
451
+ String productType = getProductType(productIdentifier);
452
+ if (productType == null) {
453
+ response.put("responseCode", 1);
454
+ response.put("responseMessage", "Please load products first so type will be identified");
455
+ call.resolve(response);
456
+ return;
457
+ }
217
458
 
218
- call.resolve(response);
459
+ QueryPurchaseHistoryParams queryPurchaseHistoryParams =
460
+ QueryPurchaseHistoryParams.newBuilder()
461
+ .setProductType(productType)
462
+ .build();
219
463
 
220
- });
464
+ billingClient.queryPurchaseHistoryAsync(queryPurchaseHistoryParams, (BillingResult billingResult, List<PurchaseHistoryRecord> list) -> {
221
465
 
222
- }
466
+ // Try to loop through the list until we find a purchase history record associated with the passed in productIdentifier.
467
+ // If we do, then set found to true to break out of the loop, then compile a response with necessary data. Otherwise compile
468
+ // a response saying that the there were not transactions for the given productIdentifier.
469
+ int i = 0;
470
+ boolean found = false;
471
+ while (list != null && (i < list.size() && !found)) {
472
+ try {
223
473
 
224
- }
474
+ JSObject currentPurchaseHistoryRecord = new JSObject(list.get(i).getOriginalJson());
475
+ Log.i("PurchaseHistory", currentPurchaseHistoryRecord.toString());
225
476
 
226
- public void getCurrentEntitlements(PluginCall call) {
477
+ if (currentPurchaseHistoryRecord.get("productId").equals(productIdentifier)) {
227
478
 
228
- JSObject response = new JSObject();
479
+ found = true;
229
480
 
230
- if (billingClientIsConnected == 1) {
481
+ JSObject data = new JSObject();
482
+ String expiryDate = getExpiryDateFromGoogle(productIdentifier, currentPurchaseHistoryRecord.get("purchaseToken").toString());
483
+ if (expiryDate != null) {
484
+ data.put("expiryDate", expiryDate);
485
+ }
486
+ Calendar calendar = Calendar.getInstance();
487
+ calendar.setTimeInMillis(Long.parseLong((currentPurchaseHistoryRecord.get("purchaseTime").toString())));
488
+ String orderId = currentPurchaseHistoryRecord.optString("orderId", ""); // Usamos optString para obtener un valor por defecto si la clave no existe
489
+ data.put("productIdentifier", currentPurchaseHistoryRecord.get("productId"));
490
+ data.put("originalId", orderId);
491
+ data.put("transactionId", orderId);
492
+ data.put("developerPayload", currentPurchaseHistoryRecord.optString("developerPayload", "")); // Usamos optString para obtener un valor por defecto si la clave no existe
493
+ data.put("purchaseToken", currentPurchaseHistoryRecord.get("purchaseToken").toString());
494
+
495
+ response.put("responseCode", 0);
496
+ response.put("responseMessage", "Successfully found the latest transaction matching given productIdentifier");
497
+ response.put("data", data);
498
+ }
499
+ } catch (Exception e) {
500
+ Logger.error(e.getMessage());
501
+ }
231
502
 
232
- QueryPurchasesParams queryPurchasesParams =
233
- QueryPurchasesParams.newBuilder()
234
- .setProductType(BillingClient.ProductType.SUBS)
235
- .build();
503
+ i++;
236
504
 
237
- billingClient.queryPurchasesAsync(
238
- queryPurchasesParams,
239
- (billingResult, purchaseList) -> {
505
+ }
240
506
 
241
- try {
507
+ // If after looping through the list of purchase history records, no records are found to be associated with
508
+ // the given product identifier, return a response saying no transactions found
509
+ if (!found) {
510
+ response.put("responseCode", 3);
511
+ response.put("responseMessage", "No transaction for given productIdentifier, or it could not be verified");
512
+ }
242
513
 
243
- int amountOfPurchases = purchaseList.size();
514
+ call.resolve(response);
244
515
 
245
- if (amountOfPurchases > 0) {
516
+ });
517
+ }
246
518
 
247
- ArrayList<JSObject> entitlements = new ArrayList<>();
248
- for (int i = 0; i < purchaseList.size(); i++) {
519
+ public void getCurrentEntitlements(PluginCall call) {
249
520
 
250
- Purchase currentPurchase = purchaseList.get(i);
521
+ JSObject response = new JSObject();
251
522
 
252
- String expiryDate = this.getExpiryDateFromGoogle(currentPurchase.getProducts().get(0), currentPurchase.getPurchaseToken());
253
- String orderId = currentPurchase.getOrderId();
523
+ if (!ensureConnected()) {
524
+ response.put("responseCode", 503);
525
+ response.put("responseMessage", "Android: BillingClient is reconnecting, please retry");
526
+ call.resolve(response);
527
+ return;
528
+ }
254
529
 
255
- String dateFormat = "dd-MM-yyyy hh:mm";
256
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.getDefault());
257
- Calendar calendar = Calendar.getInstance();
258
- calendar.setTimeInMillis(Long.parseLong((String.valueOf(currentPurchase.getPurchaseTime()))));
530
+ ArrayList<JSObject> allEntitlements = new ArrayList<>();
531
+
532
+ // First query SUBS
533
+ QueryPurchasesParams subsQueryParams =
534
+ QueryPurchasesParams.newBuilder()
535
+ .setProductType(BillingClient.ProductType.SUBS)
536
+ .build();
537
+
538
+ billingClient.queryPurchasesAsync(
539
+ subsQueryParams,
540
+ (billingResult, subsPurchaseList) -> {
541
+
542
+ // Process SUBS purchases
543
+ processEntitlements(subsPurchaseList, allEntitlements, "SUBS");
544
+
545
+ // Then query INAPP
546
+ QueryPurchasesParams inAppQueryParams =
547
+ QueryPurchasesParams.newBuilder()
548
+ .setProductType(BillingClient.ProductType.INAPP)
549
+ .build();
550
+
551
+ billingClient.queryPurchasesAsync(
552
+ inAppQueryParams,
553
+ (billingResult2, inAppPurchaseList) -> {
554
+
555
+ // Process INAPP purchases
556
+ processEntitlements(inAppPurchaseList, allEntitlements, "INAPP");
557
+
558
+ try {
559
+ if (!allEntitlements.isEmpty()) {
560
+ response.put("responseCode", 0);
561
+ response.put("responseMessage", "Successfully found all entitlements across all product types");
562
+ response.put("data", allEntitlements);
563
+ } else {
564
+ Log.i("No Purchases", "No active purchases found");
565
+ response.put("responseCode", 1);
566
+ response.put("responseMessage", "No entitlements were found");
567
+ }
568
+ } catch (Exception e) {
569
+ Log.e("Error", e.toString());
570
+ response.put("responseCode", 2);
571
+ response.put("responseMessage", e.toString());
572
+ }
259
573
 
260
- entitlements.add(
261
- new JSObject()
262
- .put("productIdentifier", currentPurchase.getProducts().get(0))
263
- .put("expiryDate", expiryDate)
264
- .put("originalStartDate", simpleDateFormat.format(calendar.getTime()))
265
- .put("originalId", orderId)
266
- .put("transactionId", orderId)
267
- .put("purchaseToken", currentPurchase.getPurchaseToken())
268
- );
269
- }
574
+ call.resolve(response);
575
+ }
576
+ );
577
+ }
578
+ );
579
+ }
580
+
581
+ private void processEntitlements(List<Purchase> purchaseList, ArrayList<JSObject> entitlements, String productType) {
582
+ if (purchaseList == null) return;
583
+
584
+ for (Purchase currentPurchase : purchaseList) {
585
+ try {
586
+ String productId = currentPurchase.getProducts().get(0);
587
+ String orderId = currentPurchase.getOrderId();
588
+
589
+ String dateFormat = "dd-MM-yyyy hh:mm";
590
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.getDefault());
591
+ Calendar calendar = Calendar.getInstance();
592
+ calendar.setTimeInMillis(currentPurchase.getPurchaseTime());
593
+
594
+ JSObject entitlement = new JSObject()
595
+ .put("productIdentifier", productId)
596
+ .put("originalStartDate", simpleDateFormat.format(calendar.getTime()))
597
+ .put("originalId", orderId)
598
+ .put("transactionId", orderId)
599
+ .put("purchaseToken", currentPurchase.getPurchaseToken())
600
+ .put("productType", productType);
601
+
602
+ // Only get expiry date for subscriptions
603
+ if (productType.equals("SUBS")) {
604
+ String expiryDate = this.getExpiryDateFromGoogle(productId, currentPurchase.getPurchaseToken());
605
+ entitlement.put("expiryDate", expiryDate);
606
+ }
270
607
 
271
- response.put("responseCode", 0);
272
- response.put("responseMessage", "Successfully found all entitlements across all product types");
273
- response.put("data", entitlements);
608
+ // Cache the product type
609
+ // productTypeCache.put(productId, productType.equals("SUBS") ? BillingClient.ProductType.SUBS : BillingClient.ProductType.INAPP);
274
610
 
611
+ entitlements.add(entitlement);
612
+ } catch (Exception e) {
613
+ Log.e("Error", "Error processing entitlement: " + e.toString());
614
+ }
615
+ }
616
+ }
275
617
 
276
- } else {
277
- Log.i("No Purchases", "No active subscriptions found");
278
- response.put("responseCode", 1);
279
- response.put("responseMessage", "No entitlements were found");
280
- }
618
+ public void purchaseProduct(String productIdentifier, String accountToken, PluginCall call) {
619
+ JSObject response = new JSObject();
281
620
 
621
+ if (!ensureConnected()) {
622
+ response.put("responseCode", 503);
623
+ response.put("responseMessage", "Android: BillingClient is reconnecting, please retry");
624
+ call.resolve(response);
625
+ return;
626
+ }
282
627
 
283
- call.resolve(response);
628
+ // Use cached product type, defaults to SUBS
629
+ String productType = getProductType(productIdentifier);
284
630
 
285
- } catch (Exception e) {
286
- Log.e("Error", e.toString());
287
- response.put("responseCode", 2);
288
- response.put("responseMessage", e.toString());
289
- }
631
+ if (productType == null) {
632
+ response.put("responseCode", 1);
633
+ response.put("responseMessage", "Please load products first so type will be identified");
634
+ call.resolve(response);
635
+ return;
636
+ }
290
637
 
291
- call.resolve(response);
638
+ QueryProductDetailsParams.Product productToFind = QueryProductDetailsParams.Product.newBuilder()
639
+ .setProductId(productIdentifier)
640
+ .setProductType(productType)
641
+ .build();
292
642
 
293
- }
294
- );
643
+ QueryProductDetailsParams queryProductDetailsParams =
644
+ QueryProductDetailsParams.newBuilder()
645
+ .setProductList(List.of(productToFind))
646
+ .build();
295
647
 
648
+ billingClient.queryProductDetailsAsync(
649
+ queryProductDetailsParams,
650
+ (billingResult1, productDetailsList) -> {
651
+
652
+ if (productDetailsList != null && !productDetailsList.isEmpty()) {
653
+ launchBillingFlow(productDetailsList.get(0), accountToken, call);
654
+ } else {
655
+ JSObject resp = new JSObject();
656
+ resp.put("responseCode", 1);
657
+ resp.put("responseMessage", "Product not found. " + productIdentifier + " " + productType + " " + productTypeCache.size());
658
+ call.resolve(resp);
296
659
  }
660
+ });
661
+ }
297
662
 
298
- }
663
+ private void launchBillingFlow(ProductDetails productDetails, String accountToken, PluginCall call) {
664
+ try {
665
+ BillingFlowParams.ProductDetailsParams.Builder productParamsBuilder =
666
+ BillingFlowParams.ProductDetailsParams.newBuilder()
667
+ .setProductDetails(productDetails);
299
668
 
300
- public void purchaseProduct(String productIdentifier, String accountToken, PluginCall call) {
669
+ if (productDetails.getProductType().equals(BillingClient.ProductType.SUBS)) {
670
+ productParamsBuilder.setOfferToken(Objects.requireNonNull(productDetails.getSubscriptionOfferDetails()).get(0).getOfferToken());
671
+ }
301
672
 
302
- JSObject response = new JSObject();
673
+ BillingFlowParams.Builder paramsBuilder = BillingFlowParams.newBuilder()
674
+ .setProductDetailsParamsList(List.of(productParamsBuilder.build()));
303
675
 
304
- if (billingClientIsConnected == 1) {
676
+ if (accountToken != null && !accountToken.isEmpty()) {
677
+ paramsBuilder.setObfuscatedAccountId(accountToken);
678
+ }
305
679
 
306
- QueryProductDetailsParams.Product productToFind = QueryProductDetailsParams.Product.newBuilder()
307
- .setProductId(productIdentifier)
308
- .setProductType(BillingClient.ProductType.SUBS)
309
- .build();
680
+ BillingFlowParams billingFlowParams = paramsBuilder.build();
310
681
 
311
- QueryProductDetailsParams queryProductDetailsParams =
312
- QueryProductDetailsParams.newBuilder()
313
- .setProductList(List.of(productToFind))
314
- .build();
315
-
316
- billingClient.queryProductDetailsAsync(
317
- queryProductDetailsParams,
318
- (billingResult1, productDetailsList) -> {
319
-
320
- try {
321
- ProductDetails productDetails = productDetailsList.get(0);
322
- BillingFlowParams.Builder paramsBuilder = BillingFlowParams.newBuilder()
323
- .setProductDetailsParamsList(
324
- List.of(
325
- BillingFlowParams.ProductDetailsParams.newBuilder()
326
- .setProductDetails(productDetails)
327
- .setOfferToken(Objects.requireNonNull(productDetails.getSubscriptionOfferDetails()).get(0).getOfferToken())
328
- .build()
329
- )
330
- );
331
-
332
- if (accountToken != null && !accountToken.isEmpty()) {
333
- paramsBuilder.setObfuscatedAccountId(accountToken);
334
- }
335
-
336
- BillingFlowParams billingFlowParams = paramsBuilder.build();
337
-
338
- BillingResult result = billingClient.launchBillingFlow(this.activity, billingFlowParams);
339
-
340
- Log.i("RESULT", result.toString());
341
- response.put("responseCode", 0);
342
- response.put("responseMessage", "Successfully opened native popover");
343
-
344
- } catch (Exception e) {
345
- Logger.error(e.getMessage());
346
- response.put("responseCode", 1);
347
- response.put("responseMessage", "Failed to open native popover");
348
- }
349
-
350
- call.resolve(response);
351
- });
352
- }
682
+ BillingResult result = billingClient.launchBillingFlow(this.activity, billingFlowParams);
683
+
684
+ Log.i("RESULT", result.toString());
353
685
 
686
+ if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
687
+ // Save the call to resolve later in purchasesUpdatedListener
688
+ pendingPurchaseCall = call;
689
+ } else {
690
+ JSObject response = new JSObject();
691
+ // Billing flow failed to launch
692
+ response.put("responseCode", result.getResponseCode());
693
+ response.put("responseMessage", "Failed to launch billing flow: " + result.getDebugMessage());
694
+ call.resolve(response);
695
+ }
696
+
697
+ } catch (Exception e) {
698
+ Logger.error(e.getMessage());
699
+ JSObject response = new JSObject();
700
+ response.put("responseCode", 1);
701
+ response.put("responseMessage", "Failed to open native popover. " + e.getMessage() + " " + productDetails.getProductType());
702
+ call.resolve(response);
354
703
  }
704
+ }
355
705
 
356
- private String getExpiryDateFromGoogle(String productIdentifier, String purchaseToken) {
706
+ private String getExpiryDateFromGoogle(String productIdentifier, String purchaseToken) {
357
707
 
358
- try {
708
+ try {
359
709
 
360
- // Compile request to verify purchase token
361
- URL obj = new URL(this.googleVerifyEndpoint + "?bid=" + this.googleBid + "&subId=" + productIdentifier + "&purchaseToken=" + purchaseToken);
362
- HttpURLConnection con = (HttpURLConnection) obj.openConnection();
363
- con.setRequestMethod("GET");
710
+ // Compile request to verify purchase token
711
+ URL obj = new URL(this.googleVerifyEndpoint + "?bid=" + this.googleBid + "&subId=" + productIdentifier + "&purchaseToken=" + purchaseToken);
712
+ HttpURLConnection con = (HttpURLConnection) obj.openConnection();
713
+ con.setRequestMethod("GET");
364
714
 
365
- // Try to receive response from server
366
- try (BufferedReader br = new BufferedReader(
367
- new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
715
+ // Try to receive response from server
716
+ try (BufferedReader br = new BufferedReader(
717
+ new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
368
718
 
369
- StringBuilder googleResponse = new StringBuilder();
370
- String responseLine;
371
- while ((responseLine = br.readLine()) != null) {
372
- googleResponse.append(responseLine.trim());
373
- Log.i("Response Line", responseLine);
374
- }
375
-
376
- // If the response was successful, extract expiryDate and put it in our response data property
377
- if (con.getResponseCode() == 200) {
378
- JSObject postResponseJSON = new JSObject(googleResponse.toString());
379
- JSObject googleResponseJSON = new JSObject(postResponseJSON.get("googleResponse").toString()); // <-- note the typo in response object from server
380
- JSObject payloadJSON = new JSObject(googleResponseJSON.get("payload").toString());
381
- String dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
382
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.getDefault());
383
- Calendar calendar = Calendar.getInstance();
384
- calendar.setTimeInMillis(Long.parseLong(payloadJSON.get("expiryTimeMillis").toString()));
385
- return simpleDateFormat.format(calendar.getTime());
386
- } else {
387
- return null;
388
- }
389
- } catch (Exception e) {
390
- Logger.error(e.getMessage());
391
- }
392
- } catch (Exception e) {
393
- Logger.error(e.getMessage());
719
+ StringBuilder googleResponse = new StringBuilder();
720
+ String responseLine;
721
+ while ((responseLine = br.readLine()) != null) {
722
+ googleResponse.append(responseLine.trim());
723
+ Log.i("Response Line", responseLine);
394
724
  }
395
725
 
396
- // If the method manages to each this far before already returning, just return null
397
- // because something went wrong
398
- return null;
726
+ // If the response was successful, extract expiryDate and put it in our response data property
727
+ if (con.getResponseCode() == 200) {
728
+ JSObject postResponseJSON = new JSObject(googleResponse.toString());
729
+ JSObject googleResponseJSON = new JSObject(postResponseJSON.get("googleResponse").toString()); // <-- note the typo in response object from server
730
+ JSObject payloadJSON = new JSObject(googleResponseJSON.get("payload").toString());
731
+ String dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
732
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.getDefault());
733
+ Calendar calendar = Calendar.getInstance();
734
+ calendar.setTimeInMillis(Long.parseLong(payloadJSON.get("expiryTimeMillis").toString()));
735
+ return simpleDateFormat.format(calendar.getTime());
736
+ } else {
737
+ return null;
738
+ }
739
+ } catch (Exception e) {
740
+ Logger.error(e.getMessage());
741
+ }
742
+ } catch (Exception e) {
743
+ Logger.error(e.getMessage());
399
744
  }
745
+
746
+ // If the method manages to each this far before already returning, just return null
747
+ // because something went wrong
748
+ return null;
749
+ }
400
750
  }
@@ -8,162 +8,106 @@ import com.getcapacitor.annotation.CapacitorPlugin;
8
8
 
9
9
  import android.content.Intent;
10
10
  import android.net.Uri;
11
- import android.util.Log;
12
-
13
- import com.android.billingclient.api.AcknowledgePurchaseParams;
14
- import com.android.billingclient.api.BillingClient;
15
- import com.android.billingclient.api.Purchase;
16
- import com.android.billingclient.api.PurchasesUpdatedListener;
17
-
18
11
 
19
12
  @CapacitorPlugin(name = "Purchases")
20
13
  public class PurchasesPlugin extends Plugin {
21
14
 
22
- private Purchases implementation;
23
-
24
- private BillingClient billingClient;
25
-
26
- public PurchasesPlugin () {
27
-
28
- }
29
-
30
- // This listener is fired upon completing the billing flow, it is vital to call the acknowledgePurchase
31
- // method on the billingClient, with the purchase token otherwise Google will automatically cancel the subscription
32
- // shortly after the purchase
33
- private final PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
34
-
35
- JSObject response = new JSObject();
36
-
37
- if(purchases != null) {
38
- for (int i = 0; i < purchases.size(); i++) {
39
-
40
- Purchase currentPurchase = purchases.get(i);
41
- if (!currentPurchase.isAcknowledged() && billingResult.getResponseCode() == 0 && currentPurchase.getPurchaseState() != 2) {
42
-
43
- AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
44
- .setPurchaseToken(currentPurchase.getPurchaseToken())
45
- .build();
15
+ private Purchases implementation;
46
16
 
47
- billingClient.acknowledgePurchase(acknowledgePurchaseParams, billingResult1 -> {
48
- Log.i("Purchase ack", currentPurchase.getOriginalJson());
49
- billingResult1.getResponseCode();
50
17
 
51
- response.put("successful", billingResult1.getResponseCode());
18
+ public PurchasesPlugin () {
19
+ }
52
20
 
53
- // WARNING: Changed the notifyListeners method from protected to public in order to get the method call to work
54
- // This may be a security issue in the future - in order to fix it, it may be best to move this listener + the billingClient
55
- // initiation into the SubscriptionsPlugin.java, then pass it into this implementation class so we can still access the
56
- // billingClient.
57
- notifyListeners("ANDROID-PURCHASE-RESPONSE", response);
58
- });
59
- } else {
60
- response.put("successful", false);
61
- notifyListeners("ANDROID-PURCHASE-RESPONSE", response);
62
- }
63
21
 
64
- }
65
- } else {
66
- response.put("successful", false);
67
- notifyListeners("ANDROID-PURCHASE-RESPONSE", response);
68
- }
22
+ @Override
23
+ public void load() {
24
+ implementation = new Purchases(this);
25
+ }
69
26
 
70
- };
71
-
72
- @Override
73
- public void load() {
74
-
75
- this.billingClient = BillingClient.newBuilder(getContext())
76
- .setListener(purchasesUpdatedListener)
77
- .enablePendingPurchases()
78
- .build();
79
- implementation = new Purchases(this, billingClient);
27
+ @PluginMethod
28
+ public void setGoogleVerificationDetails(PluginCall call) {
29
+ String googleVerifyEndpoint = call.getString("googleVerifyEndpoint");
30
+ String bid = call.getString("bid");
80
31
 
32
+ if(googleVerifyEndpoint != null && bid != null) {
33
+ implementation.setGoogleVerificationDetails(googleVerifyEndpoint, bid);
34
+ } else {
35
+ call.reject("Missing required parameters");
81
36
  }
37
+ }
82
38
 
83
- @PluginMethod
84
- public void setGoogleVerificationDetails(PluginCall call) {
85
- String googleVerifyEndpoint = call.getString("googleVerifyEndpoint");
86
- String bid = call.getString("bid");
39
+ @PluginMethod
40
+ public void echo(PluginCall call) {
41
+ String value = call.getString("value");
87
42
 
88
- if(googleVerifyEndpoint != null && bid != null) {
89
- implementation.setGoogleVerificationDetails(googleVerifyEndpoint, bid);
90
- } else {
91
- call.reject("Missing required parameters");
92
- }
93
- }
43
+ JSObject ret = new JSObject();
44
+ ret.put("value", implementation.echo(value));
45
+ call.resolve(ret);
46
+ }
94
47
 
95
- @PluginMethod
96
- public void echo(PluginCall call) {
97
- String value = call.getString("value");
48
+ @PluginMethod
49
+ public void getProductDetails(PluginCall call) {
98
50
 
99
- JSObject ret = new JSObject();
100
- ret.put("value", implementation.echo(value));
101
- call.resolve(ret);
51
+ String productIdentifier = call.getString("productIdentifier");
52
+
53
+ if (productIdentifier == null) {
54
+ call.reject("Must provide a productID");
102
55
  }
103
56
 
104
- @PluginMethod
105
- public void getProductDetails(PluginCall call) {
57
+ implementation.getProductDetails(productIdentifier, call);
106
58
 
107
- String productIdentifier = call.getString("productIdentifier");
59
+ }
108
60
 
109
- if (productIdentifier == null) {
110
- call.reject("Must provide a productID");
111
- }
61
+ @PluginMethod
62
+ public void purchaseProduct(PluginCall call) {
112
63
 
113
- implementation.getProductDetails(productIdentifier, call);
64
+ String productIdentifier = call.getString("productIdentifier");
65
+ String accountToken = call.getString("accountToken", "");
114
66
 
67
+ if(productIdentifier == null) {
68
+ call.reject("Must provide a productID");
115
69
  }
116
70
 
117
- @PluginMethod
118
- public void purchaseProduct(PluginCall call) {
71
+ implementation.purchaseProduct(productIdentifier, accountToken, call);
119
72
 
120
- String productIdentifier = call.getString("productIdentifier");
121
- String accountToken = call.getString("accountToken", "");
73
+ }
122
74
 
123
- if(productIdentifier == null) {
124
- call.reject("Must provide a productID");
125
- }
75
+ @PluginMethod
76
+ public void getLatestTransaction(PluginCall call) {
126
77
 
127
- implementation.purchaseProduct(productIdentifier, accountToken, call);
78
+ String productIdentifier = call.getString("productIdentifier");
128
79
 
80
+ if(productIdentifier == null) {
81
+ call.reject("Must provide a productID");
129
82
  }
130
83
 
131
- @PluginMethod
132
- public void getLatestTransaction(PluginCall call) {
84
+ implementation.getLatestTransaction(productIdentifier, call);
133
85
 
134
- String productIdentifier = call.getString("productIdentifier");
86
+ }
135
87
 
136
- if(productIdentifier == null) {
137
- call.reject("Must provide a productID");
138
- }
88
+ @PluginMethod
89
+ public void getCurrentEntitlements(PluginCall call) {
139
90
 
140
- implementation.getLatestTransaction(productIdentifier, call);
91
+ implementation.getCurrentEntitlements(call);
141
92
 
142
- }
93
+ }
143
94
 
144
- @PluginMethod
145
- public void getCurrentEntitlements(PluginCall call) {
95
+ @PluginMethod
96
+ public void manageSubscriptions(PluginCall call) {
146
97
 
147
- implementation.getCurrentEntitlements(call);
98
+ String productIdentifier = call.getString("productIdentifier");
99
+ String bid = call.getString("bid");
148
100
 
101
+ if(productIdentifier == null) {
102
+ call.reject("Must provide a productID");
149
103
  }
150
104
 
151
- @PluginMethod
152
- public void manageSubscriptions(PluginCall call) {
153
-
154
- String productIdentifier = call.getString("productIdentifier");
155
- String bid = call.getString("bid");
156
-
157
- if(productIdentifier == null) {
158
- call.reject("Must provide a productID");
159
- }
160
-
161
- if(bid == null) {
162
- call.reject("Must provide a bundleID");
163
- }
164
-
165
- Intent browserIntent = new Intent(Intent.ACTION_VIEW,
166
- Uri.parse("https://play.google.com/store/account/subscriptions?sku=" + productIdentifier + "&package=" + bid));
167
- getActivity().startActivity(browserIntent);
105
+ if(bid == null) {
106
+ call.reject("Must provide a bundleID");
168
107
  }
108
+
109
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW,
110
+ Uri.parse("https://play.google.com/store/account/subscriptions?sku=" + productIdentifier + "&package=" + bid));
111
+ getActivity().startActivity(browserIntent);
112
+ }
169
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-native-purchases",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "IAP Purchases integration for iOS and Android",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",