expo-iap 2.0.0 → 2.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -19
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +10 -9
- package/build/ExpoIap.types.d.ts +31 -40
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +0 -11
- package/build/ExpoIap.types.js.map +1 -1
- package/build/index.d.ts +3 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +33 -46
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +12 -4
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +8 -4
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +16 -8
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +9 -5
- package/build/modules/ios.js.map +1 -1
- package/build/types/ExpoIapAndroid.types.d.ts +31 -31
- package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
- package/build/types/ExpoIapAndroid.types.js +6 -0
- package/build/types/ExpoIapAndroid.types.js.map +1 -1
- package/build/types/ExpoIapIos.types.d.ts +31 -34
- package/build/types/ExpoIapIos.types.d.ts.map +1 -1
- package/build/types/ExpoIapIos.types.js.map +1 -1
- package/build/useIap.d.ts +23 -0
- package/build/useIap.d.ts.map +1 -0
- package/build/useIap.js +100 -0
- package/build/useIap.js.map +1 -0
- package/bun.lockb +0 -0
- package/iap.md +161 -0
- package/ios/ExpoIapModule.swift +124 -31
- package/package.json +5 -5
- package/plugin/build/withIAP.d.ts +0 -3
- package/plugin/build/withIAP.js +31 -49
- package/plugin/src/withIAP.ts +39 -86
- package/src/ExpoIap.types.ts +48 -48
- package/src/index.ts +50 -96
- package/src/modules/android.ts +16 -12
- package/src/modules/ios.ts +21 -18
- package/src/types/ExpoIapAndroid.types.ts +37 -33
- package/src/types/ExpoIapIos.types.ts +36 -40
- package/src/useIap.ts +185 -0
package/iap.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Expo IAP Documentation
|
|
2
|
+
|
|
3
|
+
## Installation in Managed Expo Projects
|
|
4
|
+
|
|
5
|
+
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If the link shows no documentation, this library isn't yet supported in managed workflows—it's likely awaiting inclusion in a future Expo SDK release.
|
|
6
|
+
|
|
7
|
+
## Installation in Bare React Native Projects
|
|
8
|
+
|
|
9
|
+
For bare React Native projects, ensure the [`expo` package is installed and configured](https://docs.expo.dev/bare/installing-expo-modules/) before proceeding.
|
|
10
|
+
|
|
11
|
+
### Add the Package to Your npm Dependencies
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install expo-iap
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Configure for iOS
|
|
18
|
+
|
|
19
|
+
Run `npx pod-install` after installing the npm package. Since `expo-iap` uses `StoreKit2`, set the `deploymentTarget` to `15.0` or higher in your project configuration.
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
"ios": {
|
|
23
|
+
"deploymentTarget": "15.0"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Configure for Android
|
|
28
|
+
|
|
29
|
+
No additional configuration is required beyond installing the package, as `expo-iap` leverages Google Play Billing internally.
|
|
30
|
+
|
|
31
|
+
## IAP Types
|
|
32
|
+
|
|
33
|
+
`expo-iap` supports the following In-App Purchase types, aligned with platform-specific APIs (Google Play Billing for Android, StoreKit2 for iOS).
|
|
34
|
+
|
|
35
|
+
### Consumable
|
|
36
|
+
|
|
37
|
+
- **Description**: Items that are used up after purchase and can be bought again (e.g., in-game currency, consumable boosts).
|
|
38
|
+
- **Behavior**: Requires consumption acknowledgment after purchase to allow repurchasing.
|
|
39
|
+
- **Platforms**: Supported on both Android and iOS.
|
|
40
|
+
|
|
41
|
+
### Non-Consumable
|
|
42
|
+
|
|
43
|
+
- **Description**: One-time purchases that users own permanently (e.g., ad removal, premium features).
|
|
44
|
+
- **Behavior**: Supports restoration of previous purchases; cannot be repurchased.
|
|
45
|
+
- **Platforms**: Supported on both Android and iOS.
|
|
46
|
+
|
|
47
|
+
### Subscription
|
|
48
|
+
|
|
49
|
+
- **Description**: Recurring purchases for ongoing access to content or services (e.g., monthly premium membership).
|
|
50
|
+
- **Behavior**: Includes auto-renewing options and restore functionality.
|
|
51
|
+
- **Platforms**: Supported on both Android and iOS, with platform-specific subscription details.
|
|
52
|
+
|
|
53
|
+
## Product Type
|
|
54
|
+
|
|
55
|
+
This section outlines the properties of products supported by `expo-iap`, including common fields shared across platforms and platform-specific extensions.
|
|
56
|
+
|
|
57
|
+
### Common Product Types
|
|
58
|
+
|
|
59
|
+
These properties are shared between Android and iOS, defined in `BaseProduct`.
|
|
60
|
+
|
|
61
|
+
| Property | Type | Description |
|
|
62
|
+
| -------------- | ------------- | --------------------------------- |
|
|
63
|
+
| `id` | `string` | Unique identifier for the product |
|
|
64
|
+
| `title` | `string` | Title of the product |
|
|
65
|
+
| `description` | `string` | Description of the product |
|
|
66
|
+
| `type` | `ProductType` | Product type (inapp or subs) |
|
|
67
|
+
| `displayName` | `string?` | Name for UI display (optional) |
|
|
68
|
+
| `displayPrice` | `string?` | Display price (optional) |
|
|
69
|
+
| `price` | `number?` | Actual price (optional) |
|
|
70
|
+
| `currency` | `string?` | Currency code (optional) |
|
|
71
|
+
|
|
72
|
+
### Android-Only Product Types
|
|
73
|
+
|
|
74
|
+
- **`ProductAndroid`**
|
|
75
|
+
|
|
76
|
+
- `name: string`: Product name (used instead of `displayName` on Android).
|
|
77
|
+
- `oneTimePurchaseOfferDetails?: OneTimePurchaseOfferDetails`: Details for one-time purchases (e.g., price, currency).
|
|
78
|
+
- `subscriptionOfferDetails?: SubscriptionOfferDetail[]`: Subscription offer details.
|
|
79
|
+
|
|
80
|
+
- **`SubscriptionProductAndroid`**
|
|
81
|
+
- `subscriptionOfferDetails: SubscriptionOfferAndroid[]`: Subscription-specific offers, including base plan ID, offer token, and pricing phases.
|
|
82
|
+
|
|
83
|
+
### iOS-Only Product Types
|
|
84
|
+
|
|
85
|
+
- **`ProductIos`**
|
|
86
|
+
|
|
87
|
+
- `displayPrice: string`: Price formatted for display.
|
|
88
|
+
- `isFamilyShareable: boolean`: Indicates if the product supports family sharing.
|
|
89
|
+
- `jsonRepresentation: string`: JSON representation from StoreKit2.
|
|
90
|
+
- `subscription: SubscriptionInfo`: Subscription details (e.g., introductory offers, group ID).
|
|
91
|
+
|
|
92
|
+
- **`SubscriptionProductIos`**
|
|
93
|
+
- `discounts?: Discount[]`: Discount details (e.g., identifier, price).
|
|
94
|
+
- `introductoryPrice?: string`: Introductory pricing details (with additional iOS-specific fields like `introductoryPricePaymentModeIos`).
|
|
95
|
+
|
|
96
|
+
## Purchase Type
|
|
97
|
+
|
|
98
|
+
This section describes the properties of purchases supported by `expo-iap`, covering common fields and platform-specific extensions.
|
|
99
|
+
|
|
100
|
+
### Common Purchase Types
|
|
101
|
+
|
|
102
|
+
These properties are shared between Android and iOS, defined in `ProductPurchase`.
|
|
103
|
+
|
|
104
|
+
| Property | Type | Description |
|
|
105
|
+
| -------------------- | --------- | ---------------------------------------- |
|
|
106
|
+
| `id` | `string` | ID of the purchased product |
|
|
107
|
+
| `transactionId` | `string?` | Unique transaction identifier (optional) |
|
|
108
|
+
| `transactionDate` | `number` | Purchase timestamp (Unix) |
|
|
109
|
+
| `transactionReceipt` | `string` | Transaction receipt data |
|
|
110
|
+
| `purchaseToken` | `string?` | Purchase token (optional) |
|
|
111
|
+
|
|
112
|
+
### Android-Only Purchase Types
|
|
113
|
+
|
|
114
|
+
- **`ProductPurchase` (Android Extensions)**
|
|
115
|
+
|
|
116
|
+
- `ids?: string[]`: List of purchased product IDs.
|
|
117
|
+
- `dataAndroid?: string`: Payment data.
|
|
118
|
+
- `signatureAndroid?: string`: Signature data.
|
|
119
|
+
- `autoRenewingAndroid?: boolean`: Auto-renewal status.
|
|
120
|
+
- `purchaseStateAndroid?: PurchaseStateAndroid`: Purchase state (e.g., PURCHASED, PENDING).
|
|
121
|
+
|
|
122
|
+
- **`SubscriptionPurchase` (Android Extensions)**
|
|
123
|
+
- `autoRenewingAndroid?: boolean`: Subscription auto-renewal status.
|
|
124
|
+
|
|
125
|
+
### iOS-Only Purchase Types
|
|
126
|
+
|
|
127
|
+
- **`ProductPurchase` (iOS Extensions)**
|
|
128
|
+
|
|
129
|
+
- `quantityIos?: number`: Purchase quantity.
|
|
130
|
+
- `originalTransactionDateIos?: number`: Original transaction date.
|
|
131
|
+
- `originalTransactionIdentifierIos?: string`: Original transaction ID.
|
|
132
|
+
- `verificationResultIos?: string`: Verification result.
|
|
133
|
+
- `appAccountToken?: string`: App account token.
|
|
134
|
+
- `expirationDateIos?: number`: Expiration date for subscriptions.
|
|
135
|
+
- `webOrderLineItemIdIos?: number`: Web order line item ID.
|
|
136
|
+
- `environmentIos?: string`: App Store environment.
|
|
137
|
+
- `storefrontCountryCodeIos?: string`: App Store storefront country code.
|
|
138
|
+
- `appBundleIdIos?: string`: App bundle ID.
|
|
139
|
+
- `productTypeIos?: string`: Product type (e.g., "autoRenewable").
|
|
140
|
+
- `subscriptionGroupIdIos?: string`: Subscription group ID.
|
|
141
|
+
- `isUpgradedIos?: boolean`: Whether the subscription was upgraded.
|
|
142
|
+
- `ownershipTypeIos?: string`: Ownership type (e.g., individual, family sharing).
|
|
143
|
+
- `reasonIos?: string`: Transaction reason.
|
|
144
|
+
- `transactionReasonIos?: string`: Detailed transaction reason.
|
|
145
|
+
- `revocationDateIos?: number`: Date of revocation if refunded.
|
|
146
|
+
- `revocationReasonIos?: string`: Reason for revocation.
|
|
147
|
+
|
|
148
|
+
- **`SubscriptionPurchase` (iOS Extensions)**
|
|
149
|
+
- All the iOS-specific fields from `ProductPurchase`
|
|
150
|
+
- Automatic subscription-specific handling based on product type
|
|
151
|
+
|
|
152
|
+
## Implementation Notes
|
|
153
|
+
|
|
154
|
+
### Platform-Uniform Purchase Handling
|
|
155
|
+
|
|
156
|
+
`expo-iap` now processes transactions directly to `Purchase` or `SubscriptionPurchase` types on both platforms:
|
|
157
|
+
|
|
158
|
+
- **iOS**: StoreKit 2 transactions are directly mapped to `Purchase`/`SubscriptionPurchase` objects with iOS-specific fields using camelCase naming convention (e.g., `expirationDateIos`).
|
|
159
|
+
- **Android**: Google Play Billing purchases are similarly mapped to the same types with Android-specific fields.
|
|
160
|
+
|
|
161
|
+
This approach eliminates intermediate conversion layers, making the code more maintainable while still providing access to platform-specific details when needed.
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -15,6 +15,109 @@ struct IapEvent {
|
|
|
15
15
|
static let TransactionIapUpdated = "iap-transaction-updated"
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
@available(iOS 15.0, *)
|
|
19
|
+
func serializeTransaction(_ transaction: Transaction) -> [String: Any?] {
|
|
20
|
+
// Determine if this is a subscription by productType or expirationDate
|
|
21
|
+
let isSubscription = transaction.productType.rawValue.lowercased().contains("renewable") || transaction.expirationDate != nil
|
|
22
|
+
|
|
23
|
+
// Parse transaction reason from jsonRepresentation if available
|
|
24
|
+
var transactionReasonIos: String? = nil
|
|
25
|
+
var webOrderLineItemId: Int? = nil
|
|
26
|
+
var jsonData: [String: Any]? = nil
|
|
27
|
+
|
|
28
|
+
// JSON representation handling
|
|
29
|
+
do {
|
|
30
|
+
let jsonRep = transaction.jsonRepresentation
|
|
31
|
+
let jsonObj = try JSONSerialization.jsonObject(with: jsonRep) as! [String: Any]
|
|
32
|
+
jsonData = jsonObj
|
|
33
|
+
if let reason = jsonObj["transactionReason"] as? String {
|
|
34
|
+
transactionReasonIos = reason
|
|
35
|
+
}
|
|
36
|
+
if let webOrderId = jsonObj["webOrderLineItemID"] as? NSNumber {
|
|
37
|
+
webOrderLineItemId = webOrderId.intValue
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
print("Error parsing JSON representation: \(error)")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create base purchase object that matches Purchase type in TypeScript
|
|
44
|
+
var purchaseMap: [String: Any?] = [
|
|
45
|
+
// Core purchase fields
|
|
46
|
+
"id": transaction.productID,
|
|
47
|
+
"ids": [transaction.productID],
|
|
48
|
+
"transactionId": String(transaction.id),
|
|
49
|
+
"transactionDate": transaction.purchaseDate.timeIntervalSince1970 * 1000,
|
|
50
|
+
"transactionReceipt": "", // Not available in StoreKit 2
|
|
51
|
+
"purchaseToken": "", // Not applicable on iOS
|
|
52
|
+
|
|
53
|
+
// iOS specific fields - basic info
|
|
54
|
+
"quantityIos": transaction.purchasedQuantity,
|
|
55
|
+
"originalTransactionDateIos": transaction.originalPurchaseDate.timeIntervalSince1970 * 1000,
|
|
56
|
+
"originalTransactionIdentifierIos": transaction.originalID,
|
|
57
|
+
"appAccountToken": transaction.appAccountToken?.uuidString,
|
|
58
|
+
|
|
59
|
+
// App and Product Identifiers
|
|
60
|
+
"appBundleIdIos": transaction.appBundleID,
|
|
61
|
+
"productTypeIos": transaction.productType.rawValue,
|
|
62
|
+
"subscriptionGroupIdIos": transaction.subscriptionGroupID,
|
|
63
|
+
|
|
64
|
+
// Transaction Identifiers
|
|
65
|
+
"webOrderLineItemIdIos": webOrderLineItemId,
|
|
66
|
+
|
|
67
|
+
// Purchase and Expiration Dates
|
|
68
|
+
"expirationDateIos": transaction.expirationDate.map { $0.timeIntervalSince1970 * 1000 },
|
|
69
|
+
|
|
70
|
+
// Purchase Details
|
|
71
|
+
"isUpgradedIos": transaction.isUpgraded,
|
|
72
|
+
"ownershipTypeIos": transaction.ownershipType.rawValue,
|
|
73
|
+
|
|
74
|
+
// Revocation Status
|
|
75
|
+
"revocationDateIos": transaction.revocationDate.map { $0.timeIntervalSince1970 * 1000 },
|
|
76
|
+
"revocationReasonIos": transaction.revocationReason?.rawValue
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
// Environment (iOS 16.0+)
|
|
80
|
+
if #available(iOS 16.0, *) {
|
|
81
|
+
purchaseMap["environmentIos"] = transaction.environment.rawValue
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Storefront (iOS 17.0+)
|
|
85
|
+
if #available(iOS 17.0, *) {
|
|
86
|
+
purchaseMap["storefrontCountryCodeIos"] = transaction.storefront.countryCode
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Transaction Reason (iOS 17.0+)
|
|
90
|
+
if #available(iOS 17.0, *) {
|
|
91
|
+
purchaseMap["reasonIos"] = transaction.reason.rawValue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// reasonStringRepresentation과 transactionReasonIos는 명시적 타입 처리
|
|
95
|
+
purchaseMap["reasonStringRepresentationIos"] = transaction.reasonStringRepresentation
|
|
96
|
+
purchaseMap["transactionReasonIos"] = transactionReasonIos
|
|
97
|
+
|
|
98
|
+
// Add offer information if available with proper availability check
|
|
99
|
+
if #available(iOS 17.2, *) {
|
|
100
|
+
if let offer = transaction.offer {
|
|
101
|
+
purchaseMap["offerIos"] = [
|
|
102
|
+
"id": offer.id as Any,
|
|
103
|
+
"type": offer.type.rawValue,
|
|
104
|
+
"paymentMode": offer.paymentMode?.rawValue ?? ""
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add additional pricing info if available
|
|
110
|
+
if #available(iOS 15.4, *), let priceInfo = jsonData?["price"] as? NSNumber {
|
|
111
|
+
purchaseMap["priceIos"] = priceInfo.doubleValue
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if #available(iOS 15.4, *), let currencyInfo = jsonData?["currency"] as? String {
|
|
115
|
+
purchaseMap["currencyIos"] = currencyInfo
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return purchaseMap
|
|
119
|
+
}
|
|
120
|
+
|
|
18
121
|
@available(iOS 15.0, *)
|
|
19
122
|
func serializeProduct(_ p: Product) -> [String: Any?] {
|
|
20
123
|
return [
|
|
@@ -23,24 +126,26 @@ func serializeProduct(_ p: Product) -> [String: Any?] {
|
|
|
23
126
|
"displayName": p.displayName,
|
|
24
127
|
"displayPrice": p.displayPrice,
|
|
25
128
|
"id": p.id,
|
|
129
|
+
"title": p.displayName,
|
|
26
130
|
"isFamilyShareable": p.isFamilyShareable,
|
|
27
131
|
"jsonRepresentation": serializeDebug(String(data: p.jsonRepresentation, encoding: .utf8) ?? ""),
|
|
28
132
|
"price": p.price,
|
|
29
133
|
"subscription": p.subscription,
|
|
30
134
|
"type": p.type,
|
|
31
|
-
"currency": p.priceFormatStyle.currencyCode
|
|
135
|
+
"currency": p.priceFormatStyle.currencyCode,
|
|
136
|
+
"platform": "ios" // Add platform identifier
|
|
32
137
|
]
|
|
33
138
|
}
|
|
34
139
|
|
|
35
140
|
@available(iOS 15.0, *)
|
|
36
|
-
func
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
141
|
+
@Sendable func serialize(_ rs: Transaction.RefundRequestStatus?) -> String? {
|
|
142
|
+
guard let rs = rs else { return nil }
|
|
143
|
+
switch rs {
|
|
144
|
+
case .success: return "success"
|
|
145
|
+
case .userCancelled: return "userCancelled"
|
|
146
|
+
default:
|
|
147
|
+
return nil
|
|
148
|
+
}
|
|
44
149
|
}
|
|
45
150
|
|
|
46
151
|
@available(iOS 15.0, *)
|
|
@@ -70,22 +175,6 @@ func serializeRenewalInfo(_ renewalInfo: VerificationResult<Product.Subscription
|
|
|
70
175
|
}
|
|
71
176
|
}
|
|
72
177
|
|
|
73
|
-
@available(iOS 15.0, *)
|
|
74
|
-
func serialize(_ transaction: Transaction, _ result: VerificationResult<Transaction>) -> [String: Any?] {
|
|
75
|
-
return serializeTransaction(transaction)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
@available(iOS 15.0, *)
|
|
79
|
-
@Sendable func serialize(_ rs: Transaction.RefundRequestStatus?) -> String? {
|
|
80
|
-
guard let rs = rs else { return nil }
|
|
81
|
-
switch rs {
|
|
82
|
-
case .success: return "success"
|
|
83
|
-
case .userCancelled: return "userCancelled"
|
|
84
|
-
default:
|
|
85
|
-
return nil
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
178
|
@available(iOS 15.0, *)
|
|
90
179
|
public class ExpoIapModule: Module {
|
|
91
180
|
private var transactions: [String: Transaction] = [:]
|
|
@@ -103,12 +192,12 @@ public class ExpoIapModule: Module {
|
|
|
103
192
|
Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError, IapEvent.TransactionIapUpdated)
|
|
104
193
|
|
|
105
194
|
OnStartObserving {
|
|
106
|
-
hasListeners = true
|
|
195
|
+
self.hasListeners = true
|
|
107
196
|
self.addTransactionObserver()
|
|
108
197
|
}
|
|
109
198
|
|
|
110
199
|
OnStopObserving {
|
|
111
|
-
hasListeners = false
|
|
200
|
+
self.hasListeners = false
|
|
112
201
|
self.removeTransactionObserver()
|
|
113
202
|
}
|
|
114
203
|
|
|
@@ -241,11 +330,18 @@ public class ExpoIapModule: Module {
|
|
|
241
330
|
throw NSError(domain: "ExpoIapModule", code: 2, userInfo: [NSLocalizedDescriptionKey: "Could not find window scene"])
|
|
242
331
|
}
|
|
243
332
|
let result: Product.PurchaseResult
|
|
244
|
-
if
|
|
333
|
+
#if swift(>=5.9)
|
|
334
|
+
if #available(iOS 17.0, tvOS 17.0, *) {
|
|
245
335
|
result = try await product.purchase(confirmIn: windowScene, options: options)
|
|
246
336
|
} else {
|
|
337
|
+
#if !os(visionOS)
|
|
247
338
|
result = try await product.purchase(options: options)
|
|
339
|
+
#endif
|
|
248
340
|
}
|
|
341
|
+
#elseif !os(visionOS)
|
|
342
|
+
result = try await product.purchase(options: options)
|
|
343
|
+
#endif
|
|
344
|
+
|
|
249
345
|
switch result {
|
|
250
346
|
case .success(let verification):
|
|
251
347
|
let transaction = try self.checkVerified(verification)
|
|
@@ -303,7 +399,6 @@ public class ExpoIapModule: Module {
|
|
|
303
399
|
if let product = await productStore.getProduct(productID: sku) {
|
|
304
400
|
if let result = await product.currentEntitlement {
|
|
305
401
|
do {
|
|
306
|
-
// Check whether the transaction is verified. If it isn’t, catch `failedVerification` error.
|
|
307
402
|
let transaction = try self.checkVerified(result)
|
|
308
403
|
return serializeTransaction(transaction)
|
|
309
404
|
} catch StoreError.failedVerification {
|
|
@@ -327,7 +422,6 @@ public class ExpoIapModule: Module {
|
|
|
327
422
|
if let product = await productStore.getProduct(productID: sku) {
|
|
328
423
|
if let result = await product.latestTransaction {
|
|
329
424
|
do {
|
|
330
|
-
// Check whether the transaction is verified. If it isn’t, catch `failedVerification` error.
|
|
331
425
|
let transaction = try self.checkVerified(result)
|
|
332
426
|
return serializeTransaction(transaction)
|
|
333
427
|
} catch StoreError.failedVerification {
|
|
@@ -391,7 +485,6 @@ public class ExpoIapModule: Module {
|
|
|
391
485
|
Task {
|
|
392
486
|
for await result in Transaction.unfinished {
|
|
393
487
|
do {
|
|
394
|
-
// Check whether the transaction is verified. If it isn’t, catch `failedVerification` error.
|
|
395
488
|
let transaction = try self.checkVerified(result)
|
|
396
489
|
await transaction.finish()
|
|
397
490
|
self.transactions.removeValue(forKey: String(transaction.id))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-iap",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.2.0-rc.1",
|
|
4
4
|
"description": "In App Purchase module in Expo",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"homepage": "https://github.com/hyochan#readme",
|
|
39
39
|
"dependencies": {},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@types/react": "
|
|
42
|
-
"expo-module-scripts": "^3.5.2",
|
|
43
|
-
"expo-modules-core": "^1.12.19",
|
|
41
|
+
"@types/react": "~18.2.79",
|
|
44
42
|
"eslint": "8.57.0",
|
|
45
43
|
"eslint-config-expo": "^7.1.2",
|
|
46
44
|
"eslint-config-prettier": "^9.1.0",
|
|
47
|
-
"eslint-plugin-prettier": "^5.1.3"
|
|
45
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
46
|
+
"expo-module-scripts": "^3.5.2",
|
|
47
|
+
"expo-modules-core": "~1.12.26"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"expo": "*",
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
-
type PaymentProvider = 'Amazon AppStore' | 'both' | 'Play Store';
|
|
3
|
-
export declare const modifyAppBuildGradle: (buildGradle: string, paymentProvider: PaymentProvider) => string;
|
|
4
2
|
export declare const modifyProjectBuildGradle: (buildGradle: string) => string;
|
|
5
3
|
interface Props {
|
|
6
|
-
paymentProvider?: PaymentProvider;
|
|
7
4
|
}
|
|
8
5
|
declare const _default: ConfigPlugin<Props | undefined>;
|
|
9
6
|
export default _default;
|
package/plugin/build/withIAP.js
CHANGED
|
@@ -1,50 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.modifyProjectBuildGradle =
|
|
3
|
+
exports.modifyProjectBuildGradle = void 0;
|
|
4
4
|
const config_plugins_1 = require("expo/config-plugins");
|
|
5
5
|
const config_plugins_2 = require("expo/config-plugins");
|
|
6
6
|
const pkg = require('../../package.json');
|
|
7
|
-
const hasPaymentProviderProperValue = (paymentProvider) => {
|
|
8
|
-
return ['Amazon AppStore', 'Play Store', 'both'].includes(paymentProvider);
|
|
9
|
-
};
|
|
10
|
-
const linesToAdd = {
|
|
11
|
-
['Amazon AppStore']: `missingDimensionStrategy "store", "amazon"`,
|
|
12
|
-
['Play Store']: `missingDimensionStrategy "store", "play"`,
|
|
13
|
-
['both']: `flavorDimensions "appstore"
|
|
14
|
-
|
|
15
|
-
productFlavors {
|
|
16
|
-
googlePlay {
|
|
17
|
-
dimension "appstore"
|
|
18
|
-
missingDimensionStrategy "store", "play"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
amazon {
|
|
22
|
-
dimension "appstore"
|
|
23
|
-
missingDimensionStrategy "store", "amazon"
|
|
24
|
-
}
|
|
25
|
-
}`,
|
|
26
|
-
};
|
|
27
7
|
const addToBuildGradle = (newLine, anchor, offset, buildGradle) => {
|
|
28
8
|
const lines = buildGradle.split('\n');
|
|
29
9
|
const lineIndex = lines.findIndex((line) => line.match(anchor));
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
const modifyAppBuildGradle = (buildGradle, paymentProvider) => {
|
|
35
|
-
if (paymentProvider === 'both') {
|
|
36
|
-
if (buildGradle.includes(`flavorDimensions "appstore"`)) {
|
|
37
|
-
return buildGradle;
|
|
38
|
-
}
|
|
39
|
-
return addToBuildGradle(linesToAdd[paymentProvider], 'defaultConfig', -1, buildGradle);
|
|
10
|
+
if (lineIndex === -1) {
|
|
11
|
+
console.warn('Anchor "ext" not found in build.gradle, appending to end');
|
|
12
|
+
lines.push(newLine);
|
|
40
13
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return buildGradle;
|
|
14
|
+
else {
|
|
15
|
+
lines.splice(lineIndex + offset, 0, newLine);
|
|
44
16
|
}
|
|
45
|
-
return
|
|
17
|
+
return lines.join('\n');
|
|
46
18
|
};
|
|
47
|
-
exports.modifyAppBuildGradle = modifyAppBuildGradle;
|
|
48
19
|
const modifyProjectBuildGradle = (buildGradle) => {
|
|
49
20
|
const supportLibVersion = `supportLibVersion = "28.0.0"`;
|
|
50
21
|
if (buildGradle.includes(supportLibVersion)) {
|
|
@@ -53,30 +24,41 @@ const modifyProjectBuildGradle = (buildGradle) => {
|
|
|
53
24
|
return addToBuildGradle(supportLibVersion, 'ext', 1, buildGradle);
|
|
54
25
|
};
|
|
55
26
|
exports.modifyProjectBuildGradle = modifyProjectBuildGradle;
|
|
56
|
-
const withIAPAndroid = (config
|
|
57
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
58
|
-
config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
|
|
59
|
-
config.modResults.contents = (0, exports.modifyAppBuildGradle)(config.modResults.contents, paymentProvider);
|
|
60
|
-
return config;
|
|
61
|
-
});
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
27
|
+
const withIAPAndroid = (config) => {
|
|
63
28
|
config = (0, config_plugins_1.withProjectBuildGradle)(config, (config) => {
|
|
64
29
|
config.modResults.contents = (0, exports.modifyProjectBuildGradle)(config.modResults.contents);
|
|
65
30
|
return config;
|
|
66
31
|
});
|
|
32
|
+
// Adding BILLING permission to AndroidManifest.xml
|
|
33
|
+
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
34
|
+
console.log('Modifying AndroidManifest.xml...');
|
|
35
|
+
const manifest = config.modResults;
|
|
36
|
+
if (!manifest.manifest['uses-permission']) {
|
|
37
|
+
manifest.manifest['uses-permission'] = [];
|
|
38
|
+
}
|
|
39
|
+
const permissions = manifest.manifest['uses-permission'];
|
|
40
|
+
const billingPermission = {
|
|
41
|
+
$: { 'android:name': 'com.android.vending.BILLING' },
|
|
42
|
+
};
|
|
43
|
+
if (!permissions.some((perm) => perm.$['android:name'] === 'com.android.vending.BILLING')) {
|
|
44
|
+
permissions.push(billingPermission);
|
|
45
|
+
console.log('Added com.android.vending.BILLING to permissions');
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.log('com.android.vending.BILLING already exists in manifest');
|
|
49
|
+
}
|
|
50
|
+
return config;
|
|
51
|
+
});
|
|
67
52
|
return config;
|
|
68
53
|
};
|
|
69
54
|
const withIAP = (config, props) => {
|
|
70
|
-
const paymentProvider = props?.paymentProvider ?? 'Play Store';
|
|
71
|
-
if (!hasPaymentProviderProperValue(paymentProvider)) {
|
|
72
|
-
config_plugins_1.WarningAggregator.addWarningAndroid('expo-iap', `The payment provider '${paymentProvider}' is not supported. Please update your app.json file with one of the following supported values: 'Play Store', 'Amazon AppStore', or 'both'.`);
|
|
73
|
-
return config;
|
|
74
|
-
}
|
|
75
55
|
try {
|
|
76
|
-
|
|
56
|
+
console.log('Applying expo-iap plugin...');
|
|
57
|
+
config = withIAPAndroid(config);
|
|
77
58
|
}
|
|
78
59
|
catch (error) {
|
|
79
60
|
config_plugins_1.WarningAggregator.addWarningAndroid('expo-iap', `There was a problem configuring expo-iap in your native Android project: ${error}`);
|
|
61
|
+
console.error('Error in expo-iap plugin:', error);
|
|
80
62
|
}
|
|
81
63
|
return config;
|
|
82
64
|
};
|