@capgo/native-purchases 7.18.0-alpha.0 → 7.18.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.
package/README.md CHANGED
@@ -28,6 +28,17 @@ Perfect for apps monetizing through one-time purchases or recurring subscription
28
28
 
29
29
  The most complete doc is available here: https://capgo.app/docs/plugins/native-purchases/
30
30
 
31
+ ## Compatibility
32
+
33
+ | Plugin version | Capacitor compatibility | Maintained |
34
+ | -------------- | ----------------------- | ---------- |
35
+ | v8.\*.\* | v8.\*.\* | ✅ |
36
+ | v7.\*.\* | v7.\*.\* | On demand |
37
+ | v6.\*.\* | v6.\*.\* | ❌ |
38
+ | v5.\*.\* | v5.\*.\* | ❌ |
39
+
40
+ > **Note:** The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (e.g., plugin v8 for Capacitor 8). Only the latest major version is actively maintained.
41
+
31
42
  ## Install
32
43
 
33
44
  ```bash
@@ -169,6 +180,8 @@ class PurchaseManager {
169
180
  productIdentifiers: [this.monthlySubId, this.yearlySubId],
170
181
  productType: PURCHASE_TYPE.SUBS
171
182
  });
183
+ // Android note: subscriptions can include multiple entries per product (one per offer/base plan).
184
+ // Use `identifier` (base plan), `offerToken`, and optional `offerId` to pick a specific offer.
172
185
 
173
186
  console.log('Products loaded:', {
174
187
  premium: premiumProduct,
@@ -447,12 +460,24 @@ const buyInAppProduct = async () => {
447
460
 
448
461
  alert('Purchase successful! Transaction ID: ' + result.transactionId);
449
462
 
450
- // iOS will also return receipt data for validation
463
+ // Access the full receipt data for backend validation
451
464
  if (result.receipt) {
452
- // Send to your backend for validation
465
+ // iOS: Base64-encoded StoreKit receipt - send this to your backend
466
+ console.log('iOS Receipt (base64):', result.receipt);
453
467
  await validateReceipt(result.receipt);
454
468
  }
455
469
 
470
+ if (result.jwsRepresentation) {
471
+ // iOS: StoreKit 2 JWS representation - alternative to receipt
472
+ console.log('iOS JWS:', result.jwsRepresentation);
473
+ }
474
+
475
+ if (result.purchaseToken) {
476
+ // Android: Purchase token - send this to your backend
477
+ console.log('Android Purchase Token:', result.purchaseToken);
478
+ await validatePurchaseToken(result.purchaseToken, result.productIdentifier);
479
+ }
480
+
456
481
  } catch (error) {
457
482
  alert('Purchase failed: ' + error.message);
458
483
  }
@@ -493,12 +518,24 @@ const buySubscription = async () => {
493
518
 
494
519
  alert('Subscription successful! Transaction ID: ' + result.transactionId);
495
520
 
496
- // iOS will also return receipt data for validation
521
+ // Access the full receipt data for backend validation
497
522
  if (result.receipt) {
498
- // Send to your backend for validation
523
+ // iOS: Base64-encoded StoreKit receipt - send this to your backend
524
+ console.log('iOS Receipt (base64):', result.receipt);
499
525
  await validateReceipt(result.receipt);
500
526
  }
501
527
 
528
+ if (result.jwsRepresentation) {
529
+ // iOS: StoreKit 2 JWS representation - alternative to receipt
530
+ console.log('iOS JWS:', result.jwsRepresentation);
531
+ }
532
+
533
+ if (result.purchaseToken) {
534
+ // Android: Purchase token - send this to your backend
535
+ console.log('Android Purchase Token:', result.purchaseToken);
536
+ await validatePurchaseToken(result.purchaseToken, result.productIdentifier);
537
+ }
538
+
502
539
  } catch (error) {
503
540
  alert('Subscription failed: ' + error.message);
504
541
  }
@@ -1216,12 +1253,65 @@ await NativePurchases.getPluginVersion();
1216
1253
 
1217
1254
  ## Backend Validation
1218
1255
 
1219
- It's crucial to validate receipts on your server to ensure the integrity of purchases. Here's an example of how to implement backend validation using a Cloudflare Worker:
1256
+ ### Full Receipt Data Access
1257
+
1258
+ **This plugin provides complete access to verified receipt data for server-side validation.** You get all the information needed to validate purchases with Apple and Google servers.
1259
+
1260
+ **For iOS:**
1261
+ - ✅ `transaction.receipt` - Complete base64-encoded StoreKit receipt (for Apple's receipt verification API)
1262
+ - ✅ `transaction.jwsRepresentation` - StoreKit 2 JSON Web Signature (for App Store Server API v2)
1263
+
1264
+ **For Android:**
1265
+ - ✅ `transaction.purchaseToken` - Google Play purchase token (for Google Play Developer API)
1266
+ - ✅ `transaction.orderId` - Google Play order identifier
1267
+
1268
+ These fields contain the **full verified receipt payload** that you can send directly to your backend for validation with Apple's and Google's servers.
1269
+
1270
+ #### Migrating from cordova-plugin-purchase?
1271
+
1272
+ If you're coming from cordova-plugin-purchase, here's the mapping:
1273
+
1274
+ | cordova-plugin-purchase | @capgo/native-purchases | Platform | Notes |
1275
+ |-------------------------|-------------------------|----------|-------|
1276
+ | `transaction.transactionReceipt` | `transaction.receipt` (base64) | iOS | Legacy StoreKit receipt format (same value as Cordova) |
1277
+ | — | `transaction.jwsRepresentation` (JWS) | iOS | StoreKit 2 JWS format (iOS 15+, additional field with no Cordova equivalent; Apple's recommended modern format for new implementations) |
1278
+ | `transaction.purchaseToken` | `transaction.purchaseToken` | Android | Same field name |
1279
+
1280
+ **This plugin already exposes everything you need for backend verification!** The `receipt` and `purchaseToken` fields contain the complete verified receipt data, and `jwsRepresentation` provides an additional StoreKit 2 representation when available.
1281
+
1282
+ **Note:** On iOS, `jwsRepresentation` is only available for StoreKit 2 transactions (iOS 15+) and is Apple's recommended modern format. For maximum compatibility, use `receipt` which works on all iOS versions; when available, you can also send `jwsRepresentation` to backends that support App Store Server API v2.
1220
1283
 
1221
- Cloudflare Worker Setup
1284
+ ### Why Backend Validation?
1285
+
1286
+ It's crucial to validate receipts on your server to ensure the integrity of purchases. Client-side data can be manipulated, but server-side validation with Apple/Google servers ensures purchases are legitimate.
1287
+
1288
+ ### Receipt Data Available for Backend Verification
1289
+
1290
+ The `Transaction` object returned by `purchaseProduct()`, `getPurchases()`, and `restorePurchases()` includes all data needed for server-side validation:
1291
+
1292
+ **iOS Receipt Data:**
1293
+ - **`receipt`** - Base64-encoded StoreKit receipt (legacy format, works with Apple's receipt verification API)
1294
+ - **`jwsRepresentation`** - JSON Web Signature for StoreKit 2 (recommended for new implementations, works with App Store Server API)
1295
+ - **`transactionId`** - Unique transaction identifier
1296
+
1297
+ **Android Receipt Data:**
1298
+ - **`purchaseToken`** - Google Play purchase token (required for server-side validation)
1299
+ - **`orderId`** - Google Play order identifier
1300
+ - **`transactionId`** - Alias for purchaseToken
1301
+
1302
+ **All platforms include:**
1303
+ - `productIdentifier` - The product that was purchased
1304
+ - `purchaseDate` - When the purchase occurred
1305
+ - Additional metadata like `appAccountToken`, `quantity`, etc.
1306
+
1307
+ ### Complete Backend Validation Example
1308
+
1309
+ #### Cloudflare Worker Setup
1222
1310
  Create a new Cloudflare Worker and follow the instructions in folder (`validator`)[/validator/README.md]
1223
1311
 
1224
- Then in your app, modify the purchase function to validate the receipt on the server:
1312
+ #### Client-Side Implementation
1313
+
1314
+ Here's how to access the receipt data and send it to your backend for validation:
1225
1315
 
1226
1316
  ```typescript
1227
1317
  import { Capacitor } from '@capacitor/core';
@@ -1284,18 +1374,40 @@ class Store {
1284
1374
 
1285
1375
  private async validatePurchaseOnServer(transaction: Transaction) {
1286
1376
  const serverUrl = 'https://your-server-url.com/validate-purchase';
1377
+ const platform = Capacitor.getPlatform();
1378
+
1287
1379
  try {
1380
+ // Prepare receipt data based on platform
1381
+ const receiptData = platform === 'ios'
1382
+ ? {
1383
+ // iOS: Send the full receipt (base64 encoded) or JWS representation
1384
+ receipt: transaction.receipt, // StoreKit receipt (base64)
1385
+ jwsRepresentation: transaction.jwsRepresentation, // StoreKit 2 JWS (optional, recommended for new apps)
1386
+ transactionId: transaction.transactionId,
1387
+ platform: 'ios'
1388
+ }
1389
+ : {
1390
+ // Android: Send the purchase token and order ID
1391
+ purchaseToken: transaction.purchaseToken, // Required for Google Play validation
1392
+ orderId: transaction.orderId, // Google Play order ID
1393
+ transactionId: transaction.transactionId,
1394
+ platform: 'android'
1395
+ };
1396
+
1288
1397
  const response = await axios.post(serverUrl, {
1289
- transactionId: transaction.transactionId,
1290
- platform: Capacitor.getPlatform(),
1291
- // Include any other relevant information
1398
+ ...receiptData,
1399
+ productId: transaction.productIdentifier,
1400
+ purchaseDate: transaction.purchaseDate,
1401
+ // Include user ID or other app-specific data
1402
+ userId: 'your-user-id'
1292
1403
  });
1293
1404
 
1294
1405
  console.log('Server validation response:', response.data);
1295
- // The server will handle the actual validation with the Cloudflare Worker
1406
+ return response.data;
1296
1407
  } catch (error) {
1297
1408
  console.error('Error in server-side validation:', error);
1298
1409
  // Implement retry logic or notify the user if necessary
1410
+ throw error;
1299
1411
  }
1300
1412
  }
1301
1413
  }
@@ -1317,7 +1429,7 @@ try {
1317
1429
  }
1318
1430
  ```
1319
1431
 
1320
- Now, let's look at how the server-side (Node.js) code might handle the validation:
1432
+ Now, let's look at how the server-side (Node.js) code handles the validation:
1321
1433
 
1322
1434
  ```typescript
1323
1435
  import express from 'express';
@@ -1329,49 +1441,179 @@ app.use(express.json());
1329
1441
  const CLOUDFLARE_WORKER_URL = 'https://your-cloudflare-worker-url.workers.dev';
1330
1442
 
1331
1443
  app.post('/validate-purchase', async (req, res) => {
1332
- const { transactionId, platform } = req.body;
1444
+ const { platform, receipt, jwsRepresentation, purchaseToken, productId, userId } = req.body;
1333
1445
 
1334
1446
  try {
1335
- const endpoint = platform === 'ios' ? '/apple' : '/google';
1336
- const validationResponse = await axios.post(`${CLOUDFLARE_WORKER_URL}${endpoint}`, {
1337
- receipt: transactionId
1338
- });
1447
+ let validationResponse;
1448
+
1449
+ if (platform === 'ios') {
1450
+ // iOS: Validate using receipt or JWS representation
1451
+ if (!receipt && !jwsRepresentation) {
1452
+ return res.status(400).json({
1453
+ success: false,
1454
+ error: 'Missing receipt data: either receipt or jwsRepresentation is required for iOS'
1455
+ });
1456
+ }
1457
+
1458
+ // Option 1: Use legacy receipt validation (recommended for compatibility)
1459
+ if (receipt) {
1460
+ validationResponse = await axios.post(`${CLOUDFLARE_WORKER_URL}/apple`, {
1461
+ receipt: receipt, // Base64-encoded receipt from transaction.receipt
1462
+ password: 'your-app-shared-secret' // App-Specific Shared Secret from App Store Connect (required for auto-renewable subscriptions)
1463
+ });
1464
+ }
1465
+ // Option 2: Use StoreKit 2 App Store Server API (recommended for new implementations)
1466
+ else if (jwsRepresentation) {
1467
+ // Validate JWS token with App Store Server API
1468
+ // Note: JWS verification requires decoding and validating the signature
1469
+ // Implementation depends on your backend setup - see Apple's documentation:
1470
+ // https://developer.apple.com/documentation/appstoreserverapi/jwstransaction
1471
+ validationResponse = await axios.post(`${CLOUDFLARE_WORKER_URL}/apple-jws`, {
1472
+ jws: jwsRepresentation
1473
+ });
1474
+ }
1475
+ } else if (platform === 'android') {
1476
+ // Android: Validate using purchase token with Google Play Developer API
1477
+ if (!purchaseToken) {
1478
+ return res.status(400).json({
1479
+ success: false,
1480
+ error: 'Missing purchaseToken for Android validation'
1481
+ });
1482
+ }
1483
+
1484
+ validationResponse = await axios.post(`${CLOUDFLARE_WORKER_URL}/google`, {
1485
+ purchaseToken: purchaseToken, // From transaction.purchaseToken
1486
+ productId: productId,
1487
+ packageName: 'com.yourapp.package'
1488
+ });
1489
+ } else {
1490
+ return res.status(400).json({
1491
+ success: false,
1492
+ error: 'Invalid platform'
1493
+ });
1494
+ }
1339
1495
 
1340
1496
  const validationResult = validationResponse.data;
1341
1497
 
1342
1498
  // Process the validation result
1343
1499
  if (validationResult.isValid) {
1344
1500
  // Update user status in the database
1345
- // await updateUserStatus(userId, 'paid');
1501
+ await updateUserPurchase(userId, {
1502
+ productId,
1503
+ platform,
1504
+ transactionId: req.body.transactionId,
1505
+ validated: true,
1506
+ validatedAt: new Date(),
1507
+ receiptData: validationResult
1508
+ });
1346
1509
 
1347
- // Log the successful validation
1348
- console.log(`Purchase validated for transaction ${transactionId}`);
1510
+ console.log(`Purchase validated for user ${userId}, product ${productId}`);
1349
1511
 
1350
- // You might want to store the validation result for future reference
1351
- // await storeValidationResult(userId, transactionId, validationResult);
1512
+ res.json({
1513
+ success: true,
1514
+ validated: true,
1515
+ message: 'Purchase successfully validated'
1516
+ });
1352
1517
  } else {
1353
1518
  // Handle invalid purchase
1354
- console.warn(`Invalid purchase detected for transaction ${transactionId}`);
1355
- // You might want to flag this for further investigation
1356
- // await flagSuspiciousPurchase(userId, transactionId);
1519
+ console.warn(`Invalid purchase detected for user ${userId}`);
1520
+
1521
+ // Flag for investigation but don't block the user immediately
1522
+ await flagSuspiciousPurchase(userId, req.body);
1523
+
1524
+ res.json({
1525
+ success: true, // Don't block the user
1526
+ validated: false,
1527
+ message: 'Purchase validation pending review'
1528
+ });
1357
1529
  }
1358
1530
 
1359
- // Always respond with a success to the app
1360
- // This ensures the app doesn't block the user's access
1361
- res.json({ success: true });
1362
1531
  } catch (error) {
1363
1532
  console.error('Error validating purchase:', error);
1533
+
1534
+ // Log the error for investigation
1535
+ await logValidationError(userId, req.body, error);
1536
+
1364
1537
  // Still respond with success to the app
1365
- res.json({ success: true });
1366
- // You might want to log this error or retry the validation later
1367
- // await logValidationError(userId, transactionId, error);
1538
+ // This ensures the app doesn't block the user's access
1539
+ res.json({
1540
+ success: true,
1541
+ validated: 'pending',
1542
+ message: 'Validation will be retried'
1543
+ });
1368
1544
  }
1369
1545
  });
1370
1546
 
1547
+ // Helper function to update user purchase status
1548
+ async function updateUserPurchase(userId: string, purchaseData: any) {
1549
+ // Implement your database logic here
1550
+ console.log('Updating purchase for user:', userId);
1551
+ }
1552
+
1553
+ // Helper function to flag suspicious purchases
1554
+ async function flagSuspiciousPurchase(userId: string, purchaseData: any) {
1555
+ // Implement your logic to flag and review suspicious purchases
1556
+ console.log('Flagging suspicious purchase:', userId);
1557
+ }
1558
+
1559
+ // Helper function to log validation errors
1560
+ async function logValidationError(userId: string, purchaseData: any, error: any) {
1561
+ // Implement your error logging logic
1562
+ console.log('Logging validation error:', userId, error);
1563
+ }
1564
+
1371
1565
  // Start the server
1372
1566
  app.listen(3000, () => console.log('Server running on port 3000'));
1373
1567
  ```
1374
1568
 
1569
+ ### Alternative: Direct Store API Validation
1570
+
1571
+ Instead of using a Cloudflare Worker, you can validate directly with Apple and Google:
1572
+
1573
+ **iOS - Apple Receipt Verification API:**
1574
+ ```typescript
1575
+ // Production: https://buy.itunes.apple.com/verifyReceipt
1576
+ // Sandbox: https://sandbox.itunes.apple.com/verifyReceipt
1577
+
1578
+ async function validateAppleReceipt(receiptData: string) {
1579
+ const response = await axios.post('https://buy.itunes.apple.com/verifyReceipt', {
1580
+ 'receipt-data': receiptData,
1581
+ 'password': 'your-shared-secret', // App-Specific Shared Secret from App Store Connect (required for auto-renewable subscriptions)
1582
+ 'exclude-old-transactions': true
1583
+ });
1584
+
1585
+ return response.data;
1586
+ }
1587
+ ```
1588
+
1589
+ **Android - Google Play Developer API:**
1590
+ ```typescript
1591
+ // Requires Google Play Developer API credentials
1592
+ // See: https://developers.google.com/android-publisher/getting_started
1593
+
1594
+ import { google } from 'googleapis';
1595
+
1596
+ async function validateGooglePurchase(packageName: string, productId: string, purchaseToken: string) {
1597
+ const androidPublisher = google.androidpublisher('v3');
1598
+
1599
+ const auth = new google.auth.GoogleAuth({
1600
+ keyFile: 'path/to/service-account-key.json',
1601
+ scopes: ['https://www.googleapis.com/auth/androidpublisher'],
1602
+ });
1603
+
1604
+ const authClient = await auth.getClient();
1605
+
1606
+ const response = await androidPublisher.purchases.products.get({
1607
+ auth: authClient,
1608
+ packageName: packageName,
1609
+ productId: productId,
1610
+ token: purchaseToken
1611
+ });
1612
+
1613
+ return response.data;
1614
+ }
1615
+ ```
1616
+
1375
1617
  Key points about this approach:
1376
1618
 
1377
1619
  1. The app immediately grants access after a successful purchase, ensuring a smooth user experience.
@@ -1776,8 +2018,8 @@ which is useful for determining if users are entitled to features from earlier b
1776
2018
  | Prop | Type | Description | Default | Since |
1777
2019
  | -------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------ |
1778
2020
  | **`transactionId`** | <code>string</code> | Unique identifier for the transaction. | | 1.0.0 |
1779
- | **`receipt`** | <code>string</code> | Receipt data for validation (base64 encoded StoreKit receipt). Send this to your backend for server-side validation with Apple's receipt verification API. The receipt remains available even after refund - server validation is required to detect refunded transactions. | | 1.0.0 |
1780
- | **`jwsRepresentation`** | <code>string</code> | StoreKit 2 JSON Web Signature (JWS) payload describing the verified transaction. Send this to your backend when using Apple's App Store Server API v2 instead of raw receipts. Only available when the transaction originated from StoreKit 2 APIs (e.g. <a href="#transaction">Transaction</a>.updates). | | 7.13.2 |
2021
+ | **`receipt`** | <code>string</code> | Receipt data for validation (base64 encoded StoreKit receipt). **This is the full verified receipt payload from Apple StoreKit.** Send this to your backend for server-side validation with Apple's receipt verification API. The receipt remains available even after refund - server validation is required to detect refunded transactions. **For backend validation:** - Use Apple's receipt verification API: https://buy.itunes.apple.com/verifyReceipt (production) - Or sandbox: https://sandbox.itunes.apple.com/verifyReceipt - This contains all transaction data needed for validation **Note:** Apple recommends migrating to App Store Server API v2 with `jwsRepresentation` for new implementations. The legacy receipt verification API continues to work but may be deprecated in the future. | | 1.0.0 |
2022
+ | **`jwsRepresentation`** | <code>string</code> | StoreKit 2 JSON Web Signature (JWS) payload describing the verified transaction. **This is the full verified receipt in JWS format (StoreKit 2).** Send this to your backend when using Apple's App Store Server API v2 instead of raw receipts. Only available when the transaction originated from StoreKit 2 APIs (e.g. <a href="#transaction">Transaction</a>.updates). **For backend validation:** - Use Apple's App Store Server API v2 to decode and verify the JWS - This is the modern alternative to the legacy receipt format - Contains signed transaction information from Apple | | 7.13.2 |
1781
2023
  | **`appAccountToken`** | <code>string \| null</code> | An optional obfuscated identifier that uniquely associates the transaction with a user account in your app. PURPOSE: - Fraud detection: Helps platforms detect irregular activity (e.g., many devices purchasing on the same account) - User linking: Links purchases to in-game characters, avatars, or in-app profiles PLATFORM DIFFERENCES: - iOS: Must be a valid UUID format (e.g., "550e8400-e29b-41d4-a716-446655440000") Apple's StoreKit 2 requires UUID format for the appAccountToken parameter - Android: Can be any obfuscated string (max 64 chars), maps to Google Play's ObfuscatedAccountId Google recommends using encryption or one-way hash SECURITY REQUIREMENTS (especially for Android): - DO NOT store Personally Identifiable Information (PII) like emails in cleartext - Use encryption or a one-way hash to generate an obfuscated identifier - Maximum length: 64 characters (both platforms) - Storing PII in cleartext will result in purchases being blocked by Google Play IMPLEMENTATION EXAMPLE: ```typescript // For iOS: Generate a deterministic UUID from user ID import { v5 as uuidv5 } from 'uuid'; const NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; // Your app's namespace UUID const appAccountToken = uuidv5(userId, NAMESPACE); // For Android: Can also use UUID or any hashed value // The same UUID approach works for both platforms ``` | | |
1782
2024
  | **`productIdentifier`** | <code>string</code> | <a href="#product">Product</a> identifier associated with the transaction. | | 1.0.0 |
1783
2025
  | **`purchaseDate`** | <code>string</code> | Purchase date of the transaction in ISO 8601 format. | | 1.0.0 |
@@ -1791,7 +2033,7 @@ which is useful for determining if users are entitled to features from earlier b
1791
2033
  | **`subscriptionState`** | <code>'unknown' \| 'subscribed' \| 'expired' \| 'revoked' \| 'inGracePeriod' \| 'inBillingRetryPeriod'</code> | Current subscription state reported by StoreKit. Possible values: - `"subscribed"`: Auto-renewing and in good standing - `"expired"`: Lapsed with no access - `"revoked"`: Access removed due to refund or issue - `"inGracePeriod"`: Payment issue but still in grace access window - `"inBillingRetryPeriod"`: StoreKit retrying failed billing - `"unknown"`: StoreKit did not report a state | | 7.13.2 |
1792
2034
  | **`purchaseState`** | <code>string</code> | Purchase state of the transaction (numeric string value). **Android Values:** - `"1"`: Purchase completed and valid (PURCHASED state) - `"0"`: Payment pending (PENDING state, e.g., cash payment processing) - Other numeric values: Various other states Always check `purchaseState === "1"` on Android to verify a valid purchase. Refunded purchases typically disappear from getPurchases() rather than showing a different state. | | 1.0.0 |
1793
2035
  | **`orderId`** | <code>string</code> | Order ID associated with the transaction. Use this for server-side verification on Android. This is the Google Play order ID. | | 1.0.0 |
1794
- | **`purchaseToken`** | <code>string</code> | Purchase token associated with the transaction. Send this to your backend for server-side validation with Google Play Developer API. This is the Android equivalent of iOS's receipt field. | | 1.0.0 |
2036
+ | **`purchaseToken`** | <code>string</code> | Purchase token associated with the transaction. **This is the full verified purchase token from Google Play.** Send this to your backend for server-side validation with Google Play Developer API. This is the Android equivalent of iOS's receipt field. **For backend validation:** - Use Google Play Developer API v3 to verify the purchase - API endpoint: androidpublisher.purchases.products.get() or purchases.subscriptions.get() - This token contains all data needed for validation with Google servers - Can also be used for subscription status checks and cancellation detection | | 1.0.0 |
1795
2037
  | **`isAcknowledged`** | <code>boolean</code> | Whether the purchase has been acknowledged. Purchases must be acknowledged within 3 days or they will be refunded. By default, this plugin automatically acknowledges purchases unless you set `autoAcknowledgePurchases: false` in purchaseProduct(). | | 1.0.0 |
1796
2038
  | **`quantity`** | <code>number</code> | Quantity purchased. | <code>1</code> | 1.0.0 |
1797
2039
  | **`productType`** | <code>string</code> | <a href="#product">Product</a> type. - `"inapp"`: One-time in-app purchase - `"subs"`: Subscription | | 1.0.0 |
@@ -1805,20 +2047,23 @@ which is useful for determining if users are entitled to features from earlier b
1805
2047
 
1806
2048
  #### Product
1807
2049
 
1808
- | Prop | Type | Description |
1809
- | --------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------ |
1810
- | **`identifier`** | <code>string</code> | <a href="#product">Product</a> Id. |
1811
- | **`description`** | <code>string</code> | Description of the product. |
1812
- | **`title`** | <code>string</code> | Title of the product. |
1813
- | **`price`** | <code>number</code> | Price of the product in the local currency. |
1814
- | **`priceString`** | <code>string</code> | Formatted price of the item, including its currency sign, such as €3.99. |
1815
- | **`currencyCode`** | <code>string</code> | Currency code for price and original price. |
1816
- | **`currencySymbol`** | <code>string</code> | Currency symbol for price and original price. |
1817
- | **`isFamilyShareable`** | <code>boolean</code> | Boolean indicating if the product is sharable with family |
1818
- | **`subscriptionGroupIdentifier`** | <code>string</code> | Group identifier for the product. |
1819
- | **`subscriptionPeriod`** | <code><a href="#subscriptionperiod">SubscriptionPeriod</a></code> | The <a href="#product">Product</a> subscription group identifier. |
1820
- | **`introductoryPrice`** | <code><a href="#skproductdiscount">SKProductDiscount</a> \| null</code> | The <a href="#product">Product</a> introductory Price. |
1821
- | **`discounts`** | <code>SKProductDiscount[]</code> | The <a href="#product">Product</a> discounts list. |
2050
+ | Prop | Type | Description |
2051
+ | --------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2052
+ | **`identifier`** | <code>string</code> | <a href="#product">Product</a> Id. Android subscriptions note: - `identifier` is the base plan ID (`offerDetails.getBasePlanId()`). - `planIdentifier` is the subscription product ID (`productDetails.getProductId()`). If you group/filter Android subscription results by `identifier`, you are grouping by base plan. |
2053
+ | **`description`** | <code>string</code> | Description of the product. |
2054
+ | **`title`** | <code>string</code> | Title of the product. |
2055
+ | **`price`** | <code>number</code> | Price of the product in the local currency. |
2056
+ | **`priceString`** | <code>string</code> | Formatted price of the item, including its currency sign, such as €3.99. |
2057
+ | **`currencyCode`** | <code>string</code> | Currency code for price and original price. |
2058
+ | **`currencySymbol`** | <code>string</code> | Currency symbol for price and original price. |
2059
+ | **`isFamilyShareable`** | <code>boolean</code> | Boolean indicating if the product is sharable with family |
2060
+ | **`subscriptionGroupIdentifier`** | <code>string</code> | Group identifier for the product. |
2061
+ | **`planIdentifier`** | <code>string</code> | Android subscriptions only: Google Play product identifier tied to the offer/base plan set. |
2062
+ | **`offerToken`** | <code>string</code> | Android subscriptions only: offer token required when purchasing specific offers. |
2063
+ | **`offerId`** | <code>string \| null</code> | Android subscriptions only: offer identifier (null/undefined for base offers). |
2064
+ | **`subscriptionPeriod`** | <code><a href="#subscriptionperiod">SubscriptionPeriod</a></code> | The <a href="#product">Product</a> subscription group identifier. |
2065
+ | **`introductoryPrice`** | <code><a href="#skproductdiscount">SKProductDiscount</a> \| null</code> | The <a href="#product">Product</a> introductory Price. |
2066
+ | **`discounts`** | <code>SKProductDiscount[]</code> | The <a href="#product">Product</a> discounts list. |
1822
2067
 
1823
2068
 
1824
2069
  #### SubscriptionPeriod
@@ -30,7 +30,7 @@ android {
30
30
  buildTypes {
31
31
  release {
32
32
  minifyEnabled false
33
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
34
34
  }
35
35
  }
36
36
  lintOptions {
@@ -50,7 +50,7 @@ repositories {
50
50
 
51
51
  dependencies {
52
52
  implementation "com.google.guava:guava:33.5.0-android"
53
- def billing_version = "8.2.0"
53
+ def billing_version = "8.3.0"
54
54
  implementation "com.android.billingclient:billing:$billing_version"
55
55
  implementation fileTree(dir: 'libs', include: ['*.jar'])
56
56
  implementation project(':capacitor-android')