capacitor-native-purchases 0.2.1 → 0.3.0

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