@capgo/native-purchases 7.1.27 → 7.1.30

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.
package/README.md CHANGED
@@ -17,6 +17,17 @@ npm install @capgo/native-purchases
17
17
  npx cap sync
18
18
  ```
19
19
 
20
+ ## 📚 Testing Guides
21
+
22
+ Complete visual testing guides for both platforms:
23
+
24
+ | Platform | Guide | Content |
25
+ |----------|-------|---------|
26
+ | 🍎 **iOS** | **[iOS Testing Guide](./docs/iOS_TESTING_GUIDE.md)** | StoreKit Local Testing, Sandbox Testing, Developer Mode setup |
27
+ | 🤖 **Android** | **[Android Testing Guide](./docs/ANDROID_TESTING_GUIDE.md)** | Internal Testing, License Testing, Internal App Sharing |
28
+
29
+ > 💡 **Quick Start**: Choose **StoreKit Local Testing** for iOS or **Internal Testing** for Android for the fastest development experience.
30
+
20
31
  ## Android
21
32
 
22
33
  Add this to manifest
@@ -27,6 +38,8 @@ Add this to manifest
27
38
 
28
39
  ### Testing with Google Play Console
29
40
 
41
+ > 📖 **[Complete Android Testing Guide](./docs/Android_TESTING_GUIDE.md)** - Comprehensive guide covering Internal Testing, License Testing, and Internal App Sharing methods with step-by-step instructions, troubleshooting, and best practices.
42
+
30
43
  For testing in-app purchases on Android:
31
44
 
32
45
  1. Upload your app to Google Play Console (internal testing track is sufficient)
@@ -47,7 +60,9 @@ Add the "In-App Purchase" capability to your Xcode project:
47
60
  4. Click the "+" button to add a capability
48
61
  5. Search for and add "In-App Purchase"
49
62
 
50
- > 📖 **[Complete Testing Guide](./docs/TESTING_GUIDE_IOS.md)** - Comprehensive guide covering both Sandbox and StoreKit local testing methods with step-by-step instructions, troubleshooting, and best practices.
63
+ > ⚠️ **App Store Requirement**: You MUST display product names and prices using data from the plugin (`product.title`, `product.priceString`). Hardcoded values will cause App Store rejection.
64
+
65
+ > 📖 **[Complete iOS Testing Guide](./docs/iOS_TESTING_GUIDE.md)** - Comprehensive guide covering both Sandbox and StoreKit local testing methods with step-by-step instructions, troubleshooting, and best practices.
51
66
 
52
67
  ### Testing with Sandbox
53
68
 
@@ -69,175 +84,270 @@ Import the plugin in your TypeScript file:
69
84
  import { NativePurchases } from '@capgo/native-purchases';
70
85
  ```
71
86
 
72
- ### Check if billing is supported
87
+ ### Complete Example: Get Product Info and Purchase
73
88
 
74
- Before attempting to make purchases, check if billing is supported on the device:
75
- We only support Storekit 2 on iOS (iOS 15+) and google play on Android
89
+ Here's a complete example showing how to get product information and make a purchase:
76
90
 
77
91
  ```typescript
78
- const checkBillingSupport = async () => {
79
- try {
80
- const { isBillingSupported } = await NativePurchases.isBillingSupported();
81
- if (isBillingSupported) {
82
- console.log('Billing is supported on this device');
92
+ import { NativePurchases } from '@capgo/native-purchases';
93
+
94
+ class PurchaseManager {
95
+ private productId = 'com.yourapp.premium.monthly';
96
+
97
+ async initializeStore() {
98
+ try {
99
+ // 1. Check if billing is supported
100
+ const { isBillingSupported } = await NativePurchases.isBillingSupported();
101
+ if (!isBillingSupported) {
102
+ throw new Error('Billing not supported on this device');
103
+ }
104
+
105
+ // 2. Get product information (REQUIRED by Apple - no hardcoded prices!)
106
+ const product = await this.getProductInfo();
107
+
108
+ // 3. Display product with dynamic info from store
109
+ this.displayProduct(product);
110
+
111
+ } catch (error) {
112
+ console.error('Store initialization failed:', error);
113
+ }
114
+ }
115
+
116
+ async getProductInfo() {
117
+ try {
118
+ const { product } = await NativePurchases.getProduct({
119
+ productIdentifier: this.productId
120
+ });
121
+
122
+ console.log('Product loaded:', {
123
+ id: product.identifier,
124
+ title: product.title, // Use this for display (required by Apple)
125
+ price: product.priceString, // Use this for display (required by Apple)
126
+ description: product.description
127
+ });
128
+
129
+ return product;
130
+ } catch (error) {
131
+ console.error('Failed to get product:', error);
132
+ throw error;
133
+ }
134
+ }
135
+
136
+ displayProduct(product: any) {
137
+ // ✅ CORRECT: Use dynamic product info (required by Apple)
138
+ document.getElementById('product-title')!.textContent = product.title;
139
+ document.getElementById('product-price')!.textContent = product.priceString;
140
+ document.getElementById('product-description')!.textContent = product.description;
141
+
142
+ // ❌ WRONG: Never hardcode prices - Apple will reject your app
143
+ // document.getElementById('product-price')!.textContent = '$9.99/month';
144
+ }
145
+
146
+ async purchaseProduct() {
147
+ try {
148
+ console.log('Starting purchase...');
149
+
150
+ const result = await NativePurchases.purchaseProduct({
151
+ productIdentifier: this.productId,
152
+ quantity: 1
153
+ });
154
+
155
+ console.log('Purchase successful!', result.transactionId);
156
+
157
+ // Handle successful purchase
158
+ await this.handleSuccessfulPurchase(result.transactionId);
159
+
160
+ } catch (error) {
161
+ console.error('Purchase failed:', error);
162
+ this.handlePurchaseError(error);
163
+ }
164
+ }
165
+
166
+ async handleSuccessfulPurchase(transactionId: string) {
167
+ // 1. Grant access to premium features
168
+ localStorage.setItem('premium_active', 'true');
169
+
170
+ // 2. Update UI
171
+ document.getElementById('subscription-status')!.textContent = 'Premium Active';
172
+
173
+ // 3. Optional: Verify purchase on your server
174
+ await this.verifyPurchaseOnServer(transactionId);
175
+ }
176
+
177
+ handlePurchaseError(error: any) {
178
+ // Handle different error scenarios
179
+ if (error.message.includes('User cancelled')) {
180
+ console.log('User cancelled the purchase');
181
+ } else if (error.message.includes('Network')) {
182
+ alert('Network error. Please check your connection and try again.');
83
183
  } else {
84
- console.log('Billing is not supported on this device');
184
+ alert('Purchase failed. Please try again.');
85
185
  }
86
- } catch (error) {
87
- console.error('Error checking billing support:', error);
88
186
  }
89
- };
187
+
188
+ async verifyPurchaseOnServer(transactionId: string) {
189
+ try {
190
+ // Send transaction to your server for verification
191
+ const response = await fetch('/api/verify-purchase', {
192
+ method: 'POST',
193
+ headers: { 'Content-Type': 'application/json' },
194
+ body: JSON.stringify({ transactionId })
195
+ });
196
+
197
+ const result = await response.json();
198
+ console.log('Server verification:', result);
199
+ } catch (error) {
200
+ console.error('Server verification failed:', error);
201
+ }
202
+ }
203
+
204
+ async restorePurchases() {
205
+ try {
206
+ await NativePurchases.restorePurchases();
207
+ console.log('Purchases restored successfully');
208
+
209
+ // Check if user has active premium after restore
210
+ const product = await this.getProductInfo();
211
+ // Update UI based on restored purchases
212
+
213
+ } catch (error) {
214
+ console.error('Failed to restore purchases:', error);
215
+ }
216
+ }
217
+ }
218
+
219
+ // Usage in your app
220
+ const purchaseManager = new PurchaseManager();
221
+
222
+ // Initialize when app starts
223
+ purchaseManager.initializeStore();
224
+
225
+ // Attach to UI buttons
226
+ document.getElementById('buy-button')?.addEventListener('click', () => {
227
+ purchaseManager.purchaseProduct();
228
+ });
229
+
230
+ document.getElementById('restore-button')?.addEventListener('click', () => {
231
+ purchaseManager.restorePurchases();
232
+ });
90
233
  ```
91
234
 
92
- ### Get available products
235
+ ### Quick Examples
93
236
 
94
- Retrieve information about available products:
237
+ #### Get Multiple Products
95
238
 
96
239
  ```typescript
97
- const getAvailableProducts = async () => {
240
+ // Get multiple products at once
241
+ const getProducts = async () => {
98
242
  try {
99
243
  const { products } = await NativePurchases.getProducts({
100
- productIdentifiers: ['product_id_1', 'product_id_2'],
101
- productType: PURCHASE_TYPE.INAPP // or PURCHASE_TYPE.SUBS for subscriptions
244
+ productIdentifiers: [
245
+ 'com.yourapp.premium.monthly',
246
+ 'com.yourapp.premium.yearly',
247
+ 'com.yourapp.remove_ads'
248
+ ]
249
+ });
250
+
251
+ products.forEach(product => {
252
+ console.log(`${product.title}: ${product.priceString}`);
102
253
  });
103
- console.log('Available products:', products);
254
+
255
+ return products;
104
256
  } catch (error) {
105
257
  console.error('Error getting products:', error);
106
258
  }
107
259
  };
108
260
  ```
109
261
 
110
- ### Purchase a product
111
-
112
- To initiate a purchase:
262
+ #### Simple Purchase Flow
113
263
 
114
264
  ```typescript
115
- const purchaseProduct = async (productId: string) => {
265
+ // Simple one-function purchase
266
+ const buyPremium = async () => {
116
267
  try {
117
- const transaction = await NativePurchases.purchaseProduct({
118
- productIdentifier: productId,
119
- productType: PURCHASE_TYPE.INAPP // or PURCHASE_TYPE.SUBS for subscriptions
268
+ // Check billing support
269
+ const { isBillingSupported } = await NativePurchases.isBillingSupported();
270
+ if (!isBillingSupported) {
271
+ alert('Purchases not supported on this device');
272
+ return;
273
+ }
274
+
275
+ // Get product (for price display)
276
+ const { product } = await NativePurchases.getProduct({
277
+ productIdentifier: 'com.yourapp.premium'
120
278
  });
121
- console.log('Purchase successful:', transaction);
122
- // Handle the successful purchase (e.g., unlock content, update UI)
123
- } catch (error) {
124
- console.error('Purchase failed:', error);
125
- }
126
- };
127
- ```
128
279
 
129
- ### Restore purchases
280
+ // Confirm with user (showing real price from store)
281
+ const confirmed = confirm(`Purchase ${product.title} for ${product.priceString}?`);
282
+ if (!confirmed) return;
130
283
 
131
- To restore previously purchased products:
284
+ // Make purchase
285
+ const result = await NativePurchases.purchaseProduct({
286
+ productIdentifier: 'com.yourapp.premium',
287
+ quantity: 1
288
+ });
132
289
 
133
- ```typescript
134
- const restorePurchases = async () => {
135
- try {
136
- const { customerInfo } = await NativePurchases.restorePurchases();
137
- console.log('Restored purchases:', customerInfo);
138
- // Update your app's state based on the restored purchases
290
+ alert('Purchase successful! Transaction ID: ' + result.transactionId);
291
+
139
292
  } catch (error) {
140
- console.error('Failed to restore purchases:', error);
293
+ alert('Purchase failed: ' + error.message);
141
294
  }
142
295
  };
143
296
  ```
144
297
 
145
- ## Example: Implementing a simple store
298
+ ### Check if billing is supported
146
299
 
147
- Here's a basic example of how you might implement a simple store in your app:
300
+ Before attempting to make purchases, check if billing is supported on the device:
301
+ We only support Storekit 2 on iOS (iOS 15+) and google play on Android
148
302
 
149
303
  ```typescript
150
- import { Capacitor } from '@capacitor/core';
151
- import { NativePurchases, PURCHASE_TYPE, Product } from '@capgo/native-purchases';
152
-
153
- class Store {
154
- private products: Product[] = [];
155
-
156
- async initialize() {
157
- if (Capacitor.isNativePlatform()) {
158
- try {
159
- await this.checkBillingSupport();
160
- await this.loadProducts();
161
- } catch (error) {
162
- console.error('Store initialization failed:', error);
163
- }
164
- }
165
- }
166
-
167
- private async checkBillingSupport() {
304
+ const checkBillingSupport = async () => {
305
+ try {
168
306
  const { isBillingSupported } = await NativePurchases.isBillingSupported();
169
- if (!isBillingSupported) {
170
- throw new Error('Billing is not supported on this device');
307
+ if (isBillingSupported) {
308
+ console.log('Billing is supported on this device');
309
+ } else {
310
+ console.log('Billing is not supported on this device');
171
311
  }
312
+ } catch (error) {
313
+ console.error('Error checking billing support:', error);
172
314
  }
315
+ };
316
+ ```
173
317
 
174
- private async loadProducts() {
175
- const productIds = ['premium_subscription', 'remove_ads', 'coin_pack'];
176
- const { products } = await NativePurchases.getProducts({
177
- productIdentifiers: productIds,
178
- productType: PURCHASE_TYPE.INAPP
179
- });
180
- this.products = products;
181
- }
182
-
183
- getProducts() {
184
- return this.products;
185
- }
318
+ ### API Reference
186
319
 
187
- async purchaseProduct(productId: string) {
188
- try {
189
- const transaction = await NativePurchases.purchaseProduct({
190
- productIdentifier: productId,
191
- productType: PURCHASE_TYPE.INAPP
192
- });
193
- console.log('Purchase successful:', transaction);
194
- // Handle the successful purchase
195
- return transaction;
196
- } catch (error) {
197
- console.error('Purchase failed:', error);
198
- throw error;
199
- }
200
- }
320
+ #### Core Methods
201
321
 
202
- async restorePurchases() {
203
- try {
204
- const { customerInfo } = await NativePurchases.restorePurchases();
205
- console.log('Restored purchases:', customerInfo);
206
- // Update app state based on restored purchases
207
- return customerInfo;
208
- } catch (error) {
209
- console.error('Failed to restore purchases:', error);
210
- throw error;
211
- }
212
- }
213
- }
322
+ ```typescript
323
+ // Check if in-app purchases are supported
324
+ await NativePurchases.isBillingSupported();
214
325
 
215
- // Usage
216
- const store = new Store();
217
- await store.initialize();
326
+ // Get single product information
327
+ await NativePurchases.getProduct({ productIdentifier: 'product_id' });
218
328
 
219
- // Display products
220
- const products = store.getProducts();
221
- console.log('Available products:', products);
329
+ // Get multiple products
330
+ await NativePurchases.getProducts({ productIdentifiers: ['id1', 'id2'] });
222
331
 
223
332
  // Purchase a product
224
- try {
225
- await store.purchaseProduct('premium_subscription');
226
- console.log('Purchase completed successfully');
227
- } catch (error) {
228
- console.error('Purchase failed:', error);
229
- }
333
+ await NativePurchases.purchaseProduct({
334
+ productIdentifier: 'product_id',
335
+ quantity: 1
336
+ });
230
337
 
231
- // Restore purchases
232
- try {
233
- await store.restorePurchases();
234
- console.log('Purchases restored successfully');
235
- } catch (error) {
236
- console.error('Failed to restore purchases:', error);
237
- }
338
+ // Restore previous purchases
339
+ await NativePurchases.restorePurchases();
340
+
341
+ // Get plugin version
342
+ await NativePurchases.getPluginVersion();
238
343
  ```
239
344
 
240
- This example provides a basic structure for initializing the store, loading products, making purchases, and restoring previous purchases. You'll need to adapt this to fit your specific app's needs, handle UI updates, and implement proper error handling and user feedback.
345
+ ### Important Notes
346
+
347
+ - **Apple Requirement**: Always display product names and prices from StoreKit data, never hardcode them
348
+ - **Error Handling**: Implement proper error handling for network issues and user cancellations
349
+ - **Server Verification**: Always verify purchases on your server for security
350
+ - **Testing**: Use the comprehensive testing guides for both iOS and Android platforms
241
351
 
242
352
  ## Backend Validation
243
353
 
@@ -40,19 +40,24 @@ public class NativePurchasesPlugin extends Plugin {
40
40
 
41
41
  @PluginMethod
42
42
  public void isBillingSupported(PluginCall call) {
43
+ Log.d(TAG, "isBillingSupported() called");
43
44
  JSObject ret = new JSObject();
44
45
  ret.put("isBillingSupported", true);
45
- call.resolve();
46
+ Log.d(TAG, "isBillingSupported() returning true");
47
+ call.resolve(ret);
46
48
  }
47
49
 
48
50
  @Override
49
51
  public void load() {
50
52
  super.load();
53
+ Log.d(TAG, "Plugin load() called");
51
54
  Log.i(NativePurchasesPlugin.TAG, "load");
52
55
  semaphoreDown();
56
+ Log.d(TAG, "Plugin load() completed");
53
57
  }
54
58
 
55
59
  private void semaphoreWait(Number waitTime) {
60
+ Log.d(TAG, "semaphoreWait() called with waitTime: " + waitTime);
56
61
  Log.i(NativePurchasesPlugin.TAG, "semaphoreWait " + waitTime);
57
62
  try {
58
63
  // Log.i(CapacitorUpdater.TAG, "semaphoreReady count " + CapacitorUpdaterPlugin.this.semaphoreReady.getCount());
@@ -67,20 +72,26 @@ public class NativePurchasesPlugin extends Plugin {
67
72
  "semaphoreReady count " +
68
73
  NativePurchasesPlugin.this.semaphoreReady.getPhase()
69
74
  );
75
+ Log.d(TAG, "semaphoreWait() completed successfully");
70
76
  } catch (InterruptedException e) {
77
+ Log.d(TAG, "semaphoreWait() InterruptedException: " + e.getMessage());
71
78
  Log.i(NativePurchasesPlugin.TAG, "semaphoreWait InterruptedException");
72
79
  e.printStackTrace();
73
80
  } catch (TimeoutException e) {
81
+ Log.d(TAG, "semaphoreWait() TimeoutException: " + e.getMessage());
74
82
  throw new RuntimeException(e);
75
83
  }
76
84
  }
77
85
 
78
86
  private void semaphoreUp() {
87
+ Log.d(TAG, "semaphoreUp() called");
79
88
  Log.i(NativePurchasesPlugin.TAG, "semaphoreUp");
80
89
  NativePurchasesPlugin.this.semaphoreReady.register();
90
+ Log.d(TAG, "semaphoreUp() completed");
81
91
  }
82
92
 
83
93
  private void semaphoreDown() {
94
+ Log.d(TAG, "semaphoreDown() called");
84
95
  Log.i(NativePurchasesPlugin.TAG, "semaphoreDown");
85
96
  Log.i(
86
97
  NativePurchasesPlugin.TAG,
@@ -88,57 +99,86 @@ public class NativePurchasesPlugin extends Plugin {
88
99
  NativePurchasesPlugin.this.semaphoreReady.getPhase()
89
100
  );
90
101
  NativePurchasesPlugin.this.semaphoreReady.arriveAndDeregister();
102
+ Log.d(TAG, "semaphoreDown() completed");
91
103
  }
92
104
 
93
105
  private void closeBillingClient() {
106
+ Log.d(TAG, "closeBillingClient() called");
94
107
  if (billingClient != null) {
108
+ Log.d(TAG, "Ending billing client connection");
95
109
  billingClient.endConnection();
96
110
  billingClient = null;
97
111
  semaphoreDown();
112
+ Log.d(TAG, "Billing client closed and set to null");
113
+ } else {
114
+ Log.d(TAG, "Billing client was already null");
98
115
  }
99
116
  }
100
117
 
101
118
  private void handlePurchase(Purchase purchase, PluginCall purchaseCall) {
119
+ Log.d(TAG, "handlePurchase() called");
120
+ Log.d(TAG, "Purchase details: " + purchase.toString());
102
121
  Log.i(NativePurchasesPlugin.TAG, "handlePurchase" + purchase);
103
122
  Log.i(
104
123
  NativePurchasesPlugin.TAG,
105
124
  "getPurchaseState" + purchase.getPurchaseState()
106
125
  );
126
+ Log.d(TAG, "Purchase state: " + purchase.getPurchaseState());
127
+ Log.d(TAG, "Purchase token: " + purchase.getPurchaseToken());
128
+ Log.d(TAG, "Is acknowledged: " + purchase.isAcknowledged());
129
+
107
130
  if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
131
+ Log.d(TAG, "Purchase state is PURCHASED");
108
132
  // Grant entitlement to the user, then acknowledge the purchase
109
133
  // if sub then acknowledgePurchase
110
134
  // if one time then consumePurchase
111
135
  if (purchase.isAcknowledged()) {
136
+ Log.d(TAG, "Purchase already acknowledged, consuming...");
112
137
  ConsumeParams consumeParams = ConsumeParams.newBuilder()
113
138
  .setPurchaseToken(purchase.getPurchaseToken())
114
139
  .build();
115
140
  billingClient.consumeAsync(consumeParams, this::onConsumeResponse);
116
141
  } else {
142
+ Log.d(TAG, "Purchase not acknowledged, acknowledging...");
117
143
  acknowledgePurchase(purchase.getPurchaseToken());
118
144
  }
119
145
 
120
146
  JSObject ret = new JSObject();
121
147
  ret.put("transactionId", purchase.getPurchaseToken());
148
+ Log.d(
149
+ TAG,
150
+ "Resolving purchase call with transactionId: " +
151
+ purchase.getPurchaseToken()
152
+ );
122
153
  if (purchaseCall != null) {
123
154
  purchaseCall.resolve(ret);
155
+ } else {
156
+ Log.d(TAG, "purchaseCall is null, cannot resolve");
124
157
  }
125
158
  } else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
159
+ Log.d(TAG, "Purchase state is PENDING");
126
160
  // Here you can confirm to the user that they've started the pending
127
161
  // purchase, and to complete it, they should follow instructions that are
128
162
  // given to them. You can also choose to remind the user to complete the
129
163
  // purchase if you detect that it is still pending.
130
164
  if (purchaseCall != null) {
131
165
  purchaseCall.reject("Purchase is pending");
166
+ } else {
167
+ Log.d(TAG, "purchaseCall is null for pending purchase");
132
168
  }
133
169
  } else {
170
+ Log.d(TAG, "Purchase state is OTHER: " + purchase.getPurchaseState());
134
171
  // Handle any other error codes.
135
172
  if (purchaseCall != null) {
136
173
  purchaseCall.reject("Purchase is not purchased");
174
+ } else {
175
+ Log.d(TAG, "purchaseCall is null for failed purchase");
137
176
  }
138
177
  }
139
178
  }
140
179
 
141
180
  private void acknowledgePurchase(String purchaseToken) {
181
+ Log.d(TAG, "acknowledgePurchase() called with token: " + purchaseToken);
142
182
  AcknowledgePurchaseParams acknowledgePurchaseParams =
143
183
  AcknowledgePurchaseParams.newBuilder()
144
184
  .setPurchaseToken(purchaseToken)
@@ -149,6 +189,14 @@ public class NativePurchasesPlugin extends Plugin {
149
189
  @Override
150
190
  public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
151
191
  // Handle the result of the acknowledge purchase
192
+ Log.d(TAG, "onAcknowledgePurchaseResponse() called");
193
+ Log.d(
194
+ TAG,
195
+ "Acknowledge result: " +
196
+ billingResult.getResponseCode() +
197
+ " - " +
198
+ billingResult.getDebugMessage()
199
+ );
152
200
  Log.i(
153
201
  NativePurchasesPlugin.TAG,
154
202
  "onAcknowledgePurchaseResponse" + billingResult
@@ -159,10 +207,12 @@ public class NativePurchasesPlugin extends Plugin {
159
207
  }
160
208
 
161
209
  private void initBillingClient(PluginCall purchaseCall) {
210
+ Log.d(TAG, "initBillingClient() called");
162
211
  semaphoreWait(10);
163
212
  closeBillingClient();
164
213
  semaphoreUp();
165
214
  CountDownLatch semaphoreReady = new CountDownLatch(1);
215
+ Log.d(TAG, "Creating new BillingClient");
166
216
  billingClient = BillingClient.newBuilder(getContext())
167
217
  .setListener(
168
218
  new PurchasesUpdatedListener() {
@@ -171,6 +221,18 @@ public class NativePurchasesPlugin extends Plugin {
171
221
  BillingResult billingResult,
172
222
  List<Purchase> purchases
173
223
  ) {
224
+ Log.d(TAG, "onPurchasesUpdated() called");
225
+ Log.d(
226
+ TAG,
227
+ "Billing result: " +
228
+ billingResult.getResponseCode() +
229
+ " - " +
230
+ billingResult.getDebugMessage()
231
+ );
232
+ Log.d(
233
+ TAG,
234
+ "Purchases count: " + (purchases != null ? purchases.size() : 0)
235
+ );
174
236
  Log.i(
175
237
  NativePurchasesPlugin.TAG,
176
238
  "onPurchasesUpdated" + billingResult
@@ -180,12 +242,17 @@ public class NativePurchasesPlugin extends Plugin {
180
242
  BillingClient.BillingResponseCode.OK &&
181
243
  purchases != null
182
244
  ) {
245
+ Log.d(
246
+ TAG,
247
+ "Purchase update successful, processing first purchase"
248
+ );
183
249
  // for (Purchase purchase : purchases) {
184
250
  // handlePurchase(purchase, purchaseCall);
185
251
  // }
186
252
  handlePurchase(purchases.get(0), purchaseCall);
187
253
  } else {
188
254
  // Handle any other error codes.
255
+ Log.d(TAG, "Purchase update failed or purchases is null");
189
256
  Log.i(
190
257
  NativePurchasesPlugin.TAG,
191
258
  "onPurchasesUpdated" + billingResult
@@ -201,59 +268,91 @@ public class NativePurchasesPlugin extends Plugin {
201
268
  )
202
269
  .enablePendingPurchases()
203
270
  .build();
271
+ Log.d(TAG, "Starting billing client connection");
204
272
  billingClient.startConnection(
205
273
  new BillingClientStateListener() {
206
274
  @Override
207
275
  public void onBillingSetupFinished(BillingResult billingResult) {
276
+ Log.d(TAG, "onBillingSetupFinished() called");
277
+ Log.d(
278
+ TAG,
279
+ "Setup result: " +
280
+ billingResult.getResponseCode() +
281
+ " - " +
282
+ billingResult.getDebugMessage()
283
+ );
208
284
  if (
209
285
  billingResult.getResponseCode() ==
210
286
  BillingClient.BillingResponseCode.OK
211
287
  ) {
288
+ Log.d(TAG, "Billing setup successful, client is ready");
212
289
  // The BillingClient is ready. You can query purchases here.
213
290
  semaphoreReady.countDown();
291
+ } else {
292
+ Log.d(TAG, "Billing setup failed");
214
293
  }
215
294
  }
216
295
 
217
296
  @Override
218
297
  public void onBillingServiceDisconnected() {
298
+ Log.d(TAG, "onBillingServiceDisconnected() called");
219
299
  // Try to restart the connection on the next request to
220
300
  // Google Play by calling the startConnection() method.
221
301
  }
222
302
  }
223
303
  );
224
304
  try {
305
+ Log.d(TAG, "Waiting for billing client setup to finish");
225
306
  semaphoreReady.await();
307
+ Log.d(TAG, "Billing client setup completed");
226
308
  } catch (InterruptedException e) {
309
+ Log.d(
310
+ TAG,
311
+ "InterruptedException while waiting for billing setup: " +
312
+ e.getMessage()
313
+ );
227
314
  e.printStackTrace();
228
315
  }
229
316
  }
230
317
 
231
318
  @PluginMethod
232
319
  public void getPluginVersion(final PluginCall call) {
320
+ Log.d(TAG, "getPluginVersion() called");
233
321
  try {
234
322
  final JSObject ret = new JSObject();
235
323
  ret.put("version", this.PLUGIN_VERSION);
324
+ Log.d(TAG, "Returning plugin version: " + this.PLUGIN_VERSION);
236
325
  call.resolve(ret);
237
326
  } catch (final Exception e) {
327
+ Log.d(TAG, "Error getting plugin version: " + e.getMessage());
238
328
  call.reject("Could not get plugin version", e);
239
329
  }
240
330
  }
241
331
 
242
332
  @PluginMethod
243
333
  public void purchaseProduct(PluginCall call) {
334
+ Log.d(TAG, "purchaseProduct() called");
244
335
  String productIdentifier = call.getString("productIdentifier");
245
336
  String planIdentifier = call.getString("planIdentifier");
246
337
  String productType = call.getString("productType", "inapp");
247
338
  Number quantity = call.getInt("quantity", 1);
339
+
340
+ Log.d(TAG, "Product identifier: " + productIdentifier);
341
+ Log.d(TAG, "Plan identifier: " + planIdentifier);
342
+ Log.d(TAG, "Product type: " + productType);
343
+ Log.d(TAG, "Quantity: " + quantity);
344
+
248
345
  // cannot use quantity, because it's done in native modal
249
346
  Log.d("CapacitorPurchases", "purchaseProduct: " + productIdentifier);
250
347
  if (productIdentifier == null || productIdentifier.isEmpty()) {
251
348
  // Handle error: productIdentifier is empty
349
+ Log.d(TAG, "Error: productIdentifier is empty");
252
350
  call.reject("productIdentifier is empty");
253
351
  return;
254
352
  }
255
353
  if (productType == null || productType.isEmpty()) {
256
354
  // Handle error: productType is empty
355
+ Log.d(TAG, "Error: productType is empty");
257
356
  call.reject("productType is empty");
258
357
  return;
259
358
  }
@@ -262,20 +361,29 @@ public class NativePurchasesPlugin extends Plugin {
262
361
  (planIdentifier == null || planIdentifier.isEmpty())
263
362
  ) {
264
363
  // Handle error: no planIdentifier with productType subs
364
+ Log.d(
365
+ TAG,
366
+ "Error: planIdentifier cannot be empty if productType is subs"
367
+ );
265
368
  call.reject("planIdentifier cannot be empty if productType is subs");
266
369
  return;
267
370
  }
268
371
  if (quantity.intValue() < 1) {
269
372
  // Handle error: quantity is less than 1
373
+ Log.d(TAG, "Error: quantity is less than 1");
270
374
  call.reject("quantity is less than 1");
271
375
  return;
272
376
  }
377
+
378
+ String productId = productType.equals("inapp")
379
+ ? productIdentifier
380
+ : planIdentifier;
381
+ Log.d(TAG, "Using product ID for query: " + productId);
382
+
273
383
  ImmutableList<QueryProductDetailsParams.Product> productList =
274
384
  ImmutableList.of(
275
385
  QueryProductDetailsParams.Product.newBuilder()
276
- .setProductId(
277
- productType.equals("inapp") ? productIdentifier : planIdentifier
278
- )
386
+ .setProductId(productId)
279
387
  .setProductType(
280
388
  productType.equals("inapp")
281
389
  ? BillingClient.ProductType.INAPP
@@ -286,8 +394,10 @@ public class NativePurchasesPlugin extends Plugin {
286
394
  QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
287
395
  .setProductList(productList)
288
396
  .build();
397
+ Log.d(TAG, "Initializing billing client for purchase");
289
398
  this.initBillingClient(call);
290
399
  try {
400
+ Log.d(TAG, "Querying product details for purchase");
291
401
  billingClient.queryProductDetailsAsync(
292
402
  params,
293
403
  new ProductDetailsResponseListener() {
@@ -295,7 +405,18 @@ public class NativePurchasesPlugin extends Plugin {
295
405
  BillingResult billingResult,
296
406
  List<ProductDetails> productDetailsList
297
407
  ) {
408
+ Log.d(TAG, "onProductDetailsResponse() called for purchase");
409
+ Log.d(
410
+ TAG,
411
+ "Query result: " +
412
+ billingResult.getResponseCode() +
413
+ " - " +
414
+ billingResult.getDebugMessage()
415
+ );
416
+ Log.d(TAG, "Product details count: " + productDetailsList.size());
417
+
298
418
  if (productDetailsList.size() == 0) {
419
+ Log.d(TAG, "No products found");
299
420
  closeBillingClient();
300
421
  call.reject("Product not found");
301
422
  return;
@@ -305,16 +426,28 @@ public class NativePurchasesPlugin extends Plugin {
305
426
  BillingFlowParams.ProductDetailsParams
306
427
  > productDetailsParamsList = new ArrayList<>();
307
428
  for (ProductDetails productDetailsItem : productDetailsList) {
429
+ Log.d(
430
+ TAG,
431
+ "Processing product: " + productDetailsItem.getProductId()
432
+ );
308
433
  BillingFlowParams.ProductDetailsParams.Builder productDetailsParams =
309
434
  BillingFlowParams.ProductDetailsParams.newBuilder()
310
435
  .setProductDetails(productDetailsItem);
311
436
  if (productType.equals("subs")) {
437
+ Log.d(TAG, "Processing subscription product");
312
438
  // list the SubscriptionOfferDetails and find the one who match the planIdentifier if not found get the first one
313
439
  ProductDetails.SubscriptionOfferDetails selectedOfferDetails =
314
440
  null;
441
+ Log.d(
442
+ TAG,
443
+ "Available offer details count: " +
444
+ productDetailsItem.getSubscriptionOfferDetails().size()
445
+ );
315
446
  for (ProductDetails.SubscriptionOfferDetails offerDetails : productDetailsItem.getSubscriptionOfferDetails()) {
447
+ Log.d(TAG, "Checking offer: " + offerDetails.getBasePlanId());
316
448
  if (offerDetails.getBasePlanId().equals(planIdentifier)) {
317
449
  selectedOfferDetails = offerDetails;
450
+ Log.d(TAG, "Found matching plan: " + planIdentifier);
318
451
  break;
319
452
  }
320
453
  }
@@ -322,10 +455,19 @@ public class NativePurchasesPlugin extends Plugin {
322
455
  selectedOfferDetails = productDetailsItem
323
456
  .getSubscriptionOfferDetails()
324
457
  .get(0);
458
+ Log.d(
459
+ TAG,
460
+ "Using first available offer: " +
461
+ selectedOfferDetails.getBasePlanId()
462
+ );
325
463
  }
326
464
  productDetailsParams.setOfferToken(
327
465
  selectedOfferDetails.getOfferToken()
328
466
  );
467
+ Log.d(
468
+ TAG,
469
+ "Set offer token: " + selectedOfferDetails.getOfferToken()
470
+ );
329
471
  }
330
472
  productDetailsParamsList.add(productDetailsParams.build());
331
473
  }
@@ -333,10 +475,18 @@ public class NativePurchasesPlugin extends Plugin {
333
475
  .setProductDetailsParamsList(productDetailsParamsList)
334
476
  .build();
335
477
  // Launch the billing flow
478
+ Log.d(TAG, "Launching billing flow");
336
479
  BillingResult billingResult2 = billingClient.launchBillingFlow(
337
480
  getActivity(),
338
481
  billingFlowParams
339
482
  );
483
+ Log.d(
484
+ TAG,
485
+ "Billing flow launch result: " +
486
+ billingResult2.getResponseCode() +
487
+ " - " +
488
+ billingResult2.getDebugMessage()
489
+ );
340
490
  Log.i(
341
491
  NativePurchasesPlugin.TAG,
342
492
  "onProductDetailsResponse2" + billingResult2
@@ -345,16 +495,19 @@ public class NativePurchasesPlugin extends Plugin {
345
495
  }
346
496
  );
347
497
  } catch (Exception e) {
498
+ Log.d(TAG, "Exception during purchase: " + e.getMessage());
348
499
  closeBillingClient();
349
500
  call.reject(e.getMessage());
350
501
  }
351
502
  }
352
503
 
353
504
  private void processUnfinishedPurchases() {
505
+ Log.d(TAG, "processUnfinishedPurchases() called");
354
506
  QueryPurchasesParams queryInAppPurchasesParams =
355
507
  QueryPurchasesParams.newBuilder()
356
508
  .setProductType(BillingClient.ProductType.INAPP)
357
509
  .build();
510
+ Log.d(TAG, "Querying unfinished in-app purchases");
358
511
  billingClient.queryPurchasesAsync(
359
512
  queryInAppPurchasesParams,
360
513
  this::handlePurchases
@@ -364,6 +517,7 @@ public class NativePurchasesPlugin extends Plugin {
364
517
  QueryPurchasesParams.newBuilder()
365
518
  .setProductType(BillingClient.ProductType.SUBS)
366
519
  .build();
520
+ Log.d(TAG, "Querying unfinished subscription purchases");
367
521
  billingClient.queryPurchasesAsync(
368
522
  querySubscriptionsParams,
369
523
  this::handlePurchases
@@ -374,21 +528,40 @@ public class NativePurchasesPlugin extends Plugin {
374
528
  BillingResult billingResult,
375
529
  List<Purchase> purchases
376
530
  ) {
531
+ Log.d(TAG, "handlePurchases() called");
532
+ Log.d(
533
+ TAG,
534
+ "Query purchases result: " +
535
+ billingResult.getResponseCode() +
536
+ " - " +
537
+ billingResult.getDebugMessage()
538
+ );
539
+ Log.d(
540
+ TAG,
541
+ "Purchases count: " + (purchases != null ? purchases.size() : 0)
542
+ );
543
+
377
544
  if (
378
545
  billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
379
546
  ) {
380
547
  for (Purchase purchase : purchases) {
548
+ Log.d(TAG, "Processing purchase: " + purchase.getOrderId());
549
+ Log.d(TAG, "Purchase state: " + purchase.getPurchaseState());
381
550
  if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
382
551
  if (purchase.isAcknowledged()) {
552
+ Log.d(TAG, "Purchase already acknowledged, consuming");
383
553
  ConsumeParams consumeParams = ConsumeParams.newBuilder()
384
554
  .setPurchaseToken(purchase.getPurchaseToken())
385
555
  .build();
386
556
  billingClient.consumeAsync(consumeParams, this::onConsumeResponse);
387
557
  } else {
558
+ Log.d(TAG, "Purchase not acknowledged, acknowledging");
388
559
  acknowledgePurchase(purchase.getPurchaseToken());
389
560
  }
390
561
  }
391
562
  }
563
+ } else {
564
+ Log.d(TAG, "Query purchases failed");
392
565
  }
393
566
  }
394
567
 
@@ -396,17 +569,29 @@ public class NativePurchasesPlugin extends Plugin {
396
569
  BillingResult billingResult,
397
570
  String purchaseToken
398
571
  ) {
572
+ Log.d(TAG, "onConsumeResponse() called");
573
+ Log.d(
574
+ TAG,
575
+ "Consume result: " +
576
+ billingResult.getResponseCode() +
577
+ " - " +
578
+ billingResult.getDebugMessage()
579
+ );
580
+ Log.d(TAG, "Purchase token: " + purchaseToken);
581
+
399
582
  if (
400
583
  billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
401
584
  ) {
402
585
  // Handle the success of the consume operation.
403
586
  // For example, you can update the UI to reflect that the item has been consumed.
587
+ Log.d(TAG, "Consume operation successful");
404
588
  Log.i(
405
589
  NativePurchasesPlugin.TAG,
406
590
  "onConsumeResponse OK " + billingResult + purchaseToken
407
591
  );
408
592
  } else {
409
593
  // Handle error responses.
594
+ Log.d(TAG, "Consume operation failed");
410
595
  Log.i(
411
596
  NativePurchasesPlugin.TAG,
412
597
  "onConsumeResponse OTHER " + billingResult + purchaseToken
@@ -416,10 +601,12 @@ public class NativePurchasesPlugin extends Plugin {
416
601
 
417
602
  @PluginMethod
418
603
  public void restorePurchases(PluginCall call) {
604
+ Log.d(TAG, "restorePurchases() called");
419
605
  Log.d(NativePurchasesPlugin.TAG, "restorePurchases");
420
606
  this.initBillingClient(null);
421
607
  this.processUnfinishedPurchases();
422
608
  call.resolve();
609
+ Log.d(TAG, "restorePurchases() completed");
423
610
  }
424
611
 
425
612
  private void queryProductDetails(
@@ -427,24 +614,41 @@ public class NativePurchasesPlugin extends Plugin {
427
614
  String productType,
428
615
  PluginCall call
429
616
  ) {
617
+ Log.d(TAG, "queryProductDetails() called");
618
+ Log.d(TAG, "Product identifiers count: " + productIdentifiers.size());
619
+ Log.d(TAG, "Product type: " + productType);
620
+ for (String id : productIdentifiers) {
621
+ Log.d(TAG, "Product ID: " + id);
622
+ }
623
+
430
624
  List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
431
625
  for (String productIdentifier : productIdentifiers) {
626
+ String productTypeForQuery = productType.equals("inapp")
627
+ ? BillingClient.ProductType.INAPP
628
+ : BillingClient.ProductType.SUBS;
629
+ Log.d(
630
+ TAG,
631
+ "Creating query product: ID='" +
632
+ productIdentifier +
633
+ "', Type='" +
634
+ productTypeForQuery +
635
+ "'"
636
+ );
432
637
  productList.add(
433
638
  QueryProductDetailsParams.Product.newBuilder()
434
639
  .setProductId(productIdentifier)
435
- .setProductType(
436
- productType.equals("inapp")
437
- ? BillingClient.ProductType.INAPP
438
- : BillingClient.ProductType.SUBS
439
- )
640
+ .setProductType(productTypeForQuery)
440
641
  .build()
441
642
  );
442
643
  }
644
+ Log.d(TAG, "Total products in query list: " + productList.size());
443
645
  QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
444
646
  .setProductList(productList)
445
647
  .build();
648
+ Log.d(TAG, "Initializing billing client for product query");
446
649
  this.initBillingClient(call);
447
650
  try {
651
+ Log.d(TAG, "Querying product details");
448
652
  billingClient.queryProductDetailsAsync(
449
653
  params,
450
654
  new ProductDetailsResponseListener() {
@@ -452,25 +656,54 @@ public class NativePurchasesPlugin extends Plugin {
452
656
  BillingResult billingResult,
453
657
  List<ProductDetails> productDetailsList
454
658
  ) {
659
+ Log.d(TAG, "onProductDetailsResponse() called for query");
660
+ Log.d(
661
+ TAG,
662
+ "Query result: " +
663
+ billingResult.getResponseCode() +
664
+ " - " +
665
+ billingResult.getDebugMessage()
666
+ );
667
+ Log.d(TAG, "Product details count: " + productDetailsList.size());
668
+
455
669
  if (productDetailsList.size() == 0) {
670
+ Log.d(TAG, "No products found in query");
671
+ Log.d(TAG, "This usually means:");
672
+ Log.d(TAG, "1. Product doesn't exist in Google Play Console");
673
+ Log.d(TAG, "2. Product is not published/active");
674
+ Log.d(
675
+ TAG,
676
+ "3. App is not properly configured for the product type"
677
+ );
678
+ Log.d(TAG, "4. Wrong product ID or type");
456
679
  closeBillingClient();
457
680
  call.reject("Product not found");
458
681
  return;
459
682
  }
460
683
  JSONArray products = new JSONArray();
461
684
  for (ProductDetails productDetails : productDetailsList) {
685
+ Log.d(
686
+ TAG,
687
+ "Processing product details: " + productDetails.getProductId()
688
+ );
462
689
  JSObject product = new JSObject();
463
690
  product.put("title", productDetails.getName());
464
691
  product.put("description", productDetails.getDescription());
692
+ Log.d(TAG, "Product title: " + productDetails.getName());
693
+ Log.d(
694
+ TAG,
695
+ "Product description: " + productDetails.getDescription()
696
+ );
697
+
465
698
  if (productType.equals("inapp")) {
699
+ Log.d(TAG, "Processing as in-app product");
466
700
  product.put("identifier", productDetails.getProductId());
467
- product.put(
468
- "price",
701
+ double price =
469
702
  productDetails
470
703
  .getOneTimePurchaseOfferDetails()
471
704
  .getPriceAmountMicros() /
472
- 1000000.0
473
- );
705
+ 1000000.0;
706
+ product.put("price", price);
474
707
  product.put(
475
708
  "priceString",
476
709
  productDetails
@@ -483,20 +716,35 @@ public class NativePurchasesPlugin extends Plugin {
483
716
  .getOneTimePurchaseOfferDetails()
484
717
  .getPriceCurrencyCode()
485
718
  );
719
+ Log.d(TAG, "Price: " + price);
720
+ Log.d(
721
+ TAG,
722
+ "Formatted price: " +
723
+ productDetails
724
+ .getOneTimePurchaseOfferDetails()
725
+ .getFormattedPrice()
726
+ );
727
+ Log.d(
728
+ TAG,
729
+ "Currency: " +
730
+ productDetails
731
+ .getOneTimePurchaseOfferDetails()
732
+ .getPriceCurrencyCode()
733
+ );
486
734
  } else {
735
+ Log.d(TAG, "Processing as subscription product");
487
736
  ProductDetails.SubscriptionOfferDetails selectedOfferDetails =
488
737
  productDetails.getSubscriptionOfferDetails().get(0);
489
738
  product.put("planIdentifier", productDetails.getProductId());
490
739
  product.put("identifier", selectedOfferDetails.getBasePlanId());
491
- product.put(
492
- "price",
740
+ double price =
493
741
  selectedOfferDetails
494
742
  .getPricingPhases()
495
743
  .getPricingPhaseList()
496
744
  .get(0)
497
745
  .getPriceAmountMicros() /
498
- 1000000.0
499
- );
746
+ 1000000.0;
747
+ product.put("price", price);
500
748
  product.put(
501
749
  "priceString",
502
750
  selectedOfferDetails
@@ -513,18 +761,44 @@ public class NativePurchasesPlugin extends Plugin {
513
761
  .get(0)
514
762
  .getPriceCurrencyCode()
515
763
  );
764
+ Log.d(TAG, "Plan identifier: " + productDetails.getProductId());
765
+ Log.d(
766
+ TAG,
767
+ "Base plan ID: " + selectedOfferDetails.getBasePlanId()
768
+ );
769
+ Log.d(TAG, "Price: " + price);
770
+ Log.d(
771
+ TAG,
772
+ "Formatted price: " +
773
+ selectedOfferDetails
774
+ .getPricingPhases()
775
+ .getPricingPhaseList()
776
+ .get(0)
777
+ .getFormattedPrice()
778
+ );
779
+ Log.d(
780
+ TAG,
781
+ "Currency: " +
782
+ selectedOfferDetails
783
+ .getPricingPhases()
784
+ .getPricingPhaseList()
785
+ .get(0)
786
+ .getPriceCurrencyCode()
787
+ );
516
788
  }
517
789
  product.put("isFamilyShareable", false);
518
790
  products.put(product);
519
791
  }
520
792
  JSObject ret = new JSObject();
521
793
  ret.put("products", products);
794
+ Log.d(TAG, "Returning " + products.length() + " products");
522
795
  closeBillingClient();
523
796
  call.resolve(ret);
524
797
  }
525
798
  }
526
799
  );
527
800
  } catch (Exception e) {
801
+ Log.d(TAG, "Exception during product query: " + e.getMessage());
528
802
  closeBillingClient();
529
803
  call.reject(e.getMessage());
530
804
  }
@@ -532,26 +806,51 @@ public class NativePurchasesPlugin extends Plugin {
532
806
 
533
807
  @PluginMethod
534
808
  public void getProducts(PluginCall call) {
809
+ Log.d(TAG, "getProducts() called");
535
810
  JSONArray productIdentifiersArray = call.getArray("productIdentifiers");
536
811
  String productType = call.getString("productType", "inapp");
812
+ Log.d(TAG, "Product type: " + productType);
813
+ Log.d(TAG, "Raw productIdentifiersArray: " + productIdentifiersArray);
814
+ Log.d(
815
+ TAG,
816
+ "productIdentifiersArray length: " +
817
+ (productIdentifiersArray != null
818
+ ? productIdentifiersArray.length()
819
+ : "null")
820
+ );
821
+
537
822
  if (
538
823
  productIdentifiersArray == null || productIdentifiersArray.length() == 0
539
824
  ) {
825
+ Log.d(TAG, "Error: productIdentifiers array missing or empty");
540
826
  call.reject("productIdentifiers array missing");
541
827
  return;
542
828
  }
829
+
543
830
  List<String> productIdentifiers = new ArrayList<>();
544
831
  for (int i = 0; i < productIdentifiersArray.length(); i++) {
545
- productIdentifiers.add(productIdentifiersArray.optString(i, ""));
832
+ String productId = productIdentifiersArray.optString(i, "");
833
+ Log.d(TAG, "Array index " + i + ": '" + productId + "'");
834
+ productIdentifiers.add(productId);
835
+ Log.d(TAG, "Added product identifier: " + productId);
546
836
  }
837
+ Log.d(
838
+ TAG,
839
+ "Final productIdentifiers list: " + productIdentifiers.toString()
840
+ );
547
841
  queryProductDetails(productIdentifiers, productType, call);
548
842
  }
549
843
 
550
844
  @PluginMethod
551
845
  public void getProduct(PluginCall call) {
846
+ Log.d(TAG, "getProduct() called");
552
847
  String productIdentifier = call.getString("productIdentifier");
553
848
  String productType = call.getString("productType", "inapp");
849
+ Log.d(TAG, "Product identifier: " + productIdentifier);
850
+ Log.d(TAG, "Product type: " + productType);
851
+
554
852
  if (productIdentifier.isEmpty()) {
853
+ Log.d(TAG, "Error: productIdentifier is empty");
555
854
  call.reject("productIdentifier is empty");
556
855
  return;
557
856
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/native-purchases",
3
- "version": "7.1.27",
3
+ "version": "7.1.30",
4
4
  "description": "In-app Subscriptions Made Easy",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",