expo-iap 2.2.4-rc.2 → 2.2.4
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/iap.md +30 -5
- package/ios/ExpoIapModule.swift +20 -41
- package/ios/Types.swift +0 -1
- package/package.json +1 -1
package/iap.md
CHANGED
|
@@ -142,17 +142,42 @@ This section describes purchase properties in `expo-iap`.
|
|
|
142
142
|
| `id` | `string` | Purchased product ID |
|
|
143
143
|
| `transactionId` | `string?` | Transaction ID (optional) |
|
|
144
144
|
| `transactionDate` | `number` | Unix timestamp |
|
|
145
|
-
| `
|
|
145
|
+
| `transactionReceipt` | `string` | Receipt data |
|
|
146
146
|
|
|
147
147
|
### Android-Only Purchase Types
|
|
148
148
|
|
|
149
|
-
- **`ProductPurchase`**:
|
|
150
|
-
|
|
149
|
+
- **`ProductPurchase`**:
|
|
150
|
+
|
|
151
|
+
- Adds the following properties specific to in-app product purchases:
|
|
152
|
+
- **`ids`**: `string[]` - A list of product IDs associated with the purchase (for multi-item purchases).
|
|
153
|
+
- **`dataAndroid`**: `string` - The raw purchase data from Google Play (e.g., JSON payload).
|
|
154
|
+
- **`signatureAndroid`**: `string` - The cryptographic signature from Google Play to verify the purchase's authenticity.
|
|
155
|
+
- **`purchaseStateAndroid`**: `number` - The state of the purchase (e.g., 0 = purchased, 1 = canceled, 2 = pending).
|
|
156
|
+
|
|
157
|
+
- **`SubscriptionPurchase`**:
|
|
158
|
+
|
|
159
|
+
- Extends the base properties and includes:
|
|
160
|
+
- **`autoRenewingAndroid`**: `boolean` - Indicates whether the subscription automatically renews (true) or not (false).
|
|
161
|
+
|
|
162
|
+
- **`purchaseTokenAndroid`**:
|
|
163
|
+
- **Description**: A unique identifier provided by Google Play for each purchase, used to track, verify, and manage the transaction. For example, it is required to "consume" a consumable product (e.g., in-game coins) or query purchase details via the Google Play Developer API.
|
|
151
164
|
|
|
152
165
|
### iOS-Only Purchase Types
|
|
153
166
|
|
|
154
|
-
- **`ProductPurchase`**:
|
|
155
|
-
|
|
167
|
+
- **`ProductPurchase`**:
|
|
168
|
+
|
|
169
|
+
- Extends the base purchase properties with iOS-specific fields:
|
|
170
|
+
- **`quantityIos`**: `number` - The quantity of the product purchased (e.g., how many units of an item were bought).
|
|
171
|
+
- **`expirationDateIos`**: `number?` - The expiration date of the purchase as a Unix timestamp (in milliseconds), if applicable (optional, may be null for non-expiring products).
|
|
172
|
+
- **`subscriptionGroupIdIos`**: `string?` - The identifier of the subscription group this product belongs to, used for managing related subscriptions in the App Store (optional, may be null for non-subscription products).
|
|
173
|
+
|
|
174
|
+
- **`SubscriptionPurchase`**:
|
|
175
|
+
- Extends the base purchase properties with iOS-specific subscription handling:
|
|
176
|
+
- Includes all fields from `ProductPurchase` where applicable, plus additional subscription-specific logic.
|
|
177
|
+
- May include fields like:
|
|
178
|
+
- **`expirationDateIos`**: `number?` - The date and time when the subscription expires, represented as a Unix timestamp (in milliseconds), unless auto-renewed.
|
|
179
|
+
- **`autoRenewingIos`**: `boolean` - Indicates whether the subscription is set to automatically renew (true) or not (false).
|
|
180
|
+
- Handles subscription-specific features such as renewals, grace periods, and App Store receipt validation.
|
|
156
181
|
|
|
157
182
|
## Implementation Notes
|
|
158
183
|
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -17,105 +17,84 @@ struct IapEvent {
|
|
|
17
17
|
|
|
18
18
|
@available(iOS 15.0, *)
|
|
19
19
|
func serializeTransaction(_ transaction: Transaction) -> [String: Any?] {
|
|
20
|
-
// Determine if this is a subscription by productType or expirationDate
|
|
21
20
|
let isSubscription =
|
|
22
21
|
transaction.productType.rawValue.lowercased().contains("renewable")
|
|
23
22
|
|| transaction.expirationDate != nil
|
|
24
23
|
|
|
25
|
-
// Parse transaction reason from jsonRepresentation if available
|
|
26
24
|
var transactionReasonIos: String? = nil
|
|
27
25
|
var webOrderLineItemId: Int? = nil
|
|
28
26
|
var jsonData: [String: Any]? = nil
|
|
27
|
+
var jwsReceipt: String = ""
|
|
28
|
+
|
|
29
|
+
let jsonRep = transaction.jsonRepresentation
|
|
30
|
+
jwsReceipt = String(data: jsonRep, encoding: .utf8) ?? ""
|
|
29
31
|
|
|
30
|
-
// JSON representation handling (JWS 데이터)
|
|
31
|
-
var jwsReceipt: String
|
|
32
32
|
do {
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
if let webOrderId = jsonObj["webOrderLineItemID"] as? NSNumber {
|
|
41
|
-
webOrderLineItemId = webOrderId.intValue
|
|
33
|
+
if let jsonObj = try JSONSerialization.jsonObject(with: jsonRep) as? [String: Any] {
|
|
34
|
+
jsonData = jsonObj
|
|
35
|
+
transactionReasonIos = jsonObj["transactionReason"] as? String
|
|
36
|
+
if let webOrderId = jsonObj["webOrderLineItemID"] as? NSNumber {
|
|
37
|
+
webOrderLineItemId = webOrderId.intValue
|
|
38
|
+
}
|
|
42
39
|
}
|
|
43
40
|
} catch {
|
|
44
41
|
print("Error parsing JSON representation: \(error)")
|
|
45
|
-
jwsReceipt = ""
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
// Create base purchase object that matches Purchase type in TypeScript
|
|
49
44
|
var purchaseMap: [String: Any?] = [
|
|
50
|
-
// Core purchase fields
|
|
51
45
|
"id": transaction.productID,
|
|
52
46
|
"ids": [transaction.productID],
|
|
53
47
|
"transactionId": String(transaction.id),
|
|
54
48
|
"transactionDate": transaction.purchaseDate.timeIntervalSince1970 * 1000,
|
|
55
|
-
"transactionReceipt": jwsReceipt,
|
|
49
|
+
"transactionReceipt": jwsReceipt,
|
|
56
50
|
|
|
57
|
-
// iOS specific fields - basic info
|
|
58
51
|
"quantityIos": transaction.purchasedQuantity,
|
|
59
52
|
"originalTransactionDateIos": transaction.originalPurchaseDate.timeIntervalSince1970 * 1000,
|
|
60
53
|
"originalTransactionIdentifierIos": transaction.originalID,
|
|
61
54
|
"appAccountToken": transaction.appAccountToken?.uuidString,
|
|
62
55
|
|
|
63
|
-
// App and Product Identifiers
|
|
64
56
|
"appBundleIdIos": transaction.appBundleID,
|
|
65
57
|
"productTypeIos": transaction.productType.rawValue,
|
|
66
58
|
"subscriptionGroupIdIos": transaction.subscriptionGroupID,
|
|
67
59
|
|
|
68
|
-
// Transaction Identifiers
|
|
69
60
|
"webOrderLineItemIdIos": webOrderLineItemId,
|
|
70
61
|
|
|
71
|
-
// Purchase and Expiration Dates
|
|
72
62
|
"expirationDateIos": transaction.expirationDate.map { $0.timeIntervalSince1970 * 1000 },
|
|
73
63
|
|
|
74
|
-
// Purchase Details
|
|
75
64
|
"isUpgradedIos": transaction.isUpgraded,
|
|
76
65
|
"ownershipTypeIos": transaction.ownershipType.rawValue,
|
|
77
66
|
|
|
78
|
-
// Revocation Status
|
|
79
67
|
"revocationDateIos": transaction.revocationDate.map { $0.timeIntervalSince1970 * 1000 },
|
|
80
68
|
"revocationReasonIos": transaction.revocationReason?.rawValue,
|
|
69
|
+
"transactionReasonIos": transactionReasonIos,
|
|
81
70
|
]
|
|
82
71
|
|
|
83
|
-
// Environment (iOS 16.0+)
|
|
84
72
|
if #available(iOS 16.0, *) {
|
|
85
73
|
purchaseMap["environmentIos"] = transaction.environment.rawValue
|
|
86
74
|
}
|
|
87
75
|
|
|
88
|
-
// Storefront (iOS 17.0+)
|
|
89
76
|
if #available(iOS 17.0, *) {
|
|
90
77
|
purchaseMap["storefrontCountryCodeIos"] = transaction.storefront.countryCode
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Transaction Reason (iOS 17.0+)
|
|
94
|
-
if #available(iOS 17.0, *) {
|
|
95
78
|
purchaseMap["reasonIos"] = transaction.reason.rawValue
|
|
96
79
|
}
|
|
97
80
|
|
|
98
|
-
// reasonStringRepresentation은 Transaction에 없으므로 제거
|
|
99
|
-
purchaseMap["transactionReasonIos"] = transactionReasonIos
|
|
100
|
-
|
|
101
|
-
// Add offer information if available with proper availability check
|
|
102
81
|
if #available(iOS 17.2, *) {
|
|
103
82
|
if let offer = transaction.offer {
|
|
104
83
|
purchaseMap["offerIos"] = [
|
|
105
|
-
"id": offer.id
|
|
84
|
+
"id": offer.id,
|
|
106
85
|
"type": offer.type.rawValue,
|
|
107
86
|
"paymentMode": offer.paymentMode?.rawValue ?? "",
|
|
108
87
|
]
|
|
109
88
|
}
|
|
110
89
|
}
|
|
111
90
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
91
|
+
if #available(iOS 15.4, *), let jsonData = jsonData {
|
|
92
|
+
if let price = jsonData["price"] as? NSNumber {
|
|
93
|
+
purchaseMap["priceIos"] = price.doubleValue
|
|
94
|
+
}
|
|
95
|
+
if let currency = jsonData["currency"] as? String {
|
|
96
|
+
purchaseMap["currencyIos"] = currency
|
|
97
|
+
}
|
|
119
98
|
}
|
|
120
99
|
|
|
121
100
|
return purchaseMap
|
package/ios/Types.swift
CHANGED
|
@@ -14,7 +14,6 @@ public enum StoreError: Error {
|
|
|
14
14
|
|
|
15
15
|
enum IapErrors: String, CaseIterable {
|
|
16
16
|
case E_UNKNOWN = "E_UNKNOWN"
|
|
17
|
-
case E_NOT_INITIALIZED = "E_NOT_INITIALIZED"
|
|
18
17
|
case E_SERVICE_ERROR = "E_SERVICE_ERROR"
|
|
19
18
|
case E_USER_CANCELLED = "E_USER_CANCELLED"
|
|
20
19
|
case E_USER_ERROR = "E_USER_ERROR"
|