@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 +293 -48
- package/android/build.gradle +2 -2
- package/android/src/main/java/ee/forgr/nativepurchases/NativePurchasesPlugin.java +148 -77
- package/dist/docs.json +37 -4
- package/dist/esm/definitions.d.ts +76 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/NativePurchasesPlugin/NativePurchasesPlugin.swift +204 -304
- package/ios/Sources/NativePurchasesPlugin/Product+CapacitorPurchasesPlugin.swift +0 -1
- package/ios/Sources/NativePurchasesPlugin/TransactionHelpers.swift +85 -129
- package/package.json +1 -1
|
@@ -1,87 +1,61 @@
|
|
|
1
|
-
//
|
|
2
|
-
// TransactionHelpers.swift
|
|
3
|
-
// CapgoNativePurchases
|
|
4
|
-
//
|
|
5
|
-
// Created by Martin DONADIEU
|
|
6
|
-
//
|
|
7
|
-
|
|
8
1
|
import Foundation
|
|
9
2
|
import StoreKit
|
|
10
3
|
|
|
11
|
-
@available(iOS 15.0, *)
|
|
12
4
|
internal class TransactionHelpers {
|
|
13
5
|
|
|
14
|
-
static func buildTransactionResponse(
|
|
6
|
+
static func buildTransactionResponse(
|
|
7
|
+
from transaction: Transaction,
|
|
8
|
+
jwsRepresentation: String? = nil,
|
|
9
|
+
alwaysIncludeWillCancel: Bool = false
|
|
10
|
+
) async -> [String: Any] {
|
|
15
11
|
var response: [String: Any] = ["transactionId": String(transaction.id)]
|
|
16
12
|
|
|
17
|
-
// Always include willCancel key with NSNull() default if requested (for transaction listener)
|
|
18
13
|
if alwaysIncludeWillCancel {
|
|
19
14
|
response["willCancel"] = NSNull()
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
addReceiptAndJws(to: &response, jws: jwsRepresentation)
|
|
18
|
+
addTransactionDetails(to: &response, transaction: transaction)
|
|
19
|
+
|
|
20
|
+
if transaction.productType == .autoRenewable {
|
|
21
|
+
addSubscriptionInfo(to: &response, transaction: transaction)
|
|
22
|
+
await addRenewalInfo(to: &response, transaction: transaction)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return response
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static func addReceiptAndJws(to response: inout [String: Any], jws: String?) {
|
|
23
29
|
if let receiptBase64 = getReceiptData() {
|
|
24
30
|
response["receipt"] = receiptBase64
|
|
25
31
|
}
|
|
26
|
-
|
|
27
|
-
// Add StoreKit 2 JWS representation (always available when passed from VerificationResult)
|
|
28
|
-
if let jws = jwsRepresentation {
|
|
32
|
+
if let jws = jws {
|
|
29
33
|
response["jwsRepresentation"] = jws
|
|
30
34
|
}
|
|
35
|
+
}
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
private static func addTransactionDetails(to response: inout [String: Any], transaction: Transaction) {
|
|
33
38
|
response["productIdentifier"] = transaction.productID
|
|
34
39
|
response["purchaseDate"] = ISO8601DateFormatter().string(from: transaction.purchaseDate)
|
|
35
40
|
response["productType"] = transaction.productType == .autoRenewable ? "subs" : "inapp"
|
|
36
41
|
response["isUpgraded"] = transaction.isUpgraded
|
|
42
|
+
response["ownershipType"] = transaction.ownershipType.descriptionString
|
|
37
43
|
|
|
38
44
|
if let revocationDate = transaction.revocationDate {
|
|
39
45
|
response["revocationDate"] = ISO8601DateFormatter().string(from: revocationDate)
|
|
40
46
|
}
|
|
41
|
-
|
|
42
47
|
if let revocationReason = transaction.revocationReason {
|
|
43
48
|
response["revocationReason"] = revocationReason.descriptionString
|
|
44
49
|
}
|
|
45
|
-
|
|
46
50
|
if #available(iOS 17.0, *) {
|
|
47
51
|
response["transactionReason"] = transaction.reason.descriptionString
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
// Add ownership type (purchased or familyShared)
|
|
51
|
-
switch transaction.ownershipType {
|
|
52
|
-
case .purchased:
|
|
53
|
-
response["ownershipType"] = "purchased"
|
|
54
|
-
case .familyShared:
|
|
55
|
-
response["ownershipType"] = "familyShared"
|
|
56
|
-
default:
|
|
57
|
-
response["ownershipType"] = "purchased"
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Add environment (Sandbox, Production, or Xcode) - iOS 16.0+
|
|
61
53
|
if #available(iOS 16.0, *) {
|
|
62
|
-
|
|
63
|
-
case .sandbox:
|
|
64
|
-
response["environment"] = "Sandbox"
|
|
65
|
-
case .production:
|
|
66
|
-
response["environment"] = "Production"
|
|
67
|
-
case .xcode:
|
|
68
|
-
response["environment"] = "Xcode"
|
|
69
|
-
default:
|
|
70
|
-
response["environment"] = "Production"
|
|
71
|
-
}
|
|
54
|
+
response["environment"] = transaction.environment.descriptionString
|
|
72
55
|
}
|
|
73
|
-
|
|
74
56
|
if let token = transaction.appAccountToken {
|
|
75
57
|
response["appAccountToken"] = token.uuidString
|
|
76
58
|
}
|
|
77
|
-
|
|
78
|
-
// Add subscription-specific information
|
|
79
|
-
if transaction.productType == .autoRenewable {
|
|
80
|
-
addSubscriptionInfo(to: &response, transaction: transaction)
|
|
81
|
-
await addRenewalInfo(to: &response, transaction: transaction)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return response
|
|
85
59
|
}
|
|
86
60
|
|
|
87
61
|
static func getReceiptData() -> String? {
|
|
@@ -94,7 +68,9 @@ internal class TransactionHelpers {
|
|
|
94
68
|
}
|
|
95
69
|
|
|
96
70
|
static func addSubscriptionInfo(to response: inout [String: Any], transaction: Transaction) {
|
|
97
|
-
response["originalPurchaseDate"] = ISO8601DateFormatter().string(
|
|
71
|
+
response["originalPurchaseDate"] = ISO8601DateFormatter().string(
|
|
72
|
+
from: transaction.originalPurchaseDate
|
|
73
|
+
)
|
|
98
74
|
if let expirationDate = transaction.expirationDate {
|
|
99
75
|
response["expirationDate"] = ISO8601DateFormatter().string(from: expirationDate)
|
|
100
76
|
response["isActive"] = expirationDate > Date()
|
|
@@ -102,8 +78,7 @@ internal class TransactionHelpers {
|
|
|
102
78
|
}
|
|
103
79
|
|
|
104
80
|
static func addRenewalInfo(to response: inout [String: Any], transaction: Transaction) async {
|
|
105
|
-
let subscriptionStatus = await transaction.subscriptionStatus
|
|
106
|
-
guard let subscriptionStatus = subscriptionStatus else {
|
|
81
|
+
guard let subscriptionStatus = await transaction.subscriptionStatus else {
|
|
107
82
|
response["willCancel"] = NSNull()
|
|
108
83
|
return
|
|
109
84
|
}
|
|
@@ -111,11 +86,9 @@ internal class TransactionHelpers {
|
|
|
111
86
|
response["subscriptionState"] = subscriptionStatus.state.descriptionString
|
|
112
87
|
|
|
113
88
|
if subscriptionStatus.state == .subscribed {
|
|
114
|
-
let
|
|
115
|
-
switch renewalInfo {
|
|
116
|
-
case .verified(let value):
|
|
89
|
+
if case .verified(let value) = subscriptionStatus.renewalInfo {
|
|
117
90
|
response["willCancel"] = !value.willAutoRenew
|
|
118
|
-
|
|
91
|
+
} else {
|
|
119
92
|
response["willCancel"] = NSNull()
|
|
120
93
|
}
|
|
121
94
|
} else {
|
|
@@ -123,78 +96,49 @@ internal class TransactionHelpers {
|
|
|
123
96
|
}
|
|
124
97
|
}
|
|
125
98
|
|
|
126
|
-
static func shouldFilterTransaction(_ transaction: Transaction, filter: String?) -> Bool {
|
|
127
|
-
guard let filter = filter else { return false }
|
|
128
|
-
let transactionAccountToken = transaction.appAccountToken?.uuidString
|
|
129
|
-
return transactionAccountToken != filter
|
|
130
|
-
}
|
|
131
|
-
|
|
132
99
|
static func collectAllPurchases(appAccountTokenFilter: String?) async -> [[String: Any]] {
|
|
133
100
|
var allPurchases: [[String: Any]] = []
|
|
101
|
+
var seenIds = Set<String>()
|
|
134
102
|
|
|
135
|
-
// Get all current entitlements (active subscriptions)
|
|
136
|
-
await collectCurrentEntitlements(appAccountTokenFilter: appAccountTokenFilter, into: &allPurchases)
|
|
137
|
-
|
|
138
|
-
// Also get all transactions (including non-consumables and expired subscriptions)
|
|
139
|
-
await collectAllTransactions(appAccountTokenFilter: appAccountTokenFilter, into: &allPurchases)
|
|
140
|
-
|
|
141
|
-
return allPurchases
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
static func collectCurrentEntitlements(appAccountTokenFilter: String?, into allPurchases: inout [[String: Any]]) async {
|
|
145
103
|
for await result in Transaction.currentEntitlements {
|
|
146
|
-
guard case .verified(let transaction) = result else {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
continue
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if shouldFilterTransaction(transaction, filter: appAccountTokenFilter) {
|
|
154
|
-
continue
|
|
155
|
-
}
|
|
104
|
+
guard case .verified(let transaction) = result else { continue }
|
|
105
|
+
if let filter = appAccountTokenFilter,
|
|
106
|
+
transaction.appAccountToken?.uuidString != filter { continue }
|
|
156
107
|
|
|
157
|
-
let
|
|
158
|
-
|
|
108
|
+
let idStr = String(transaction.id)
|
|
109
|
+
seenIds.insert(idStr)
|
|
110
|
+
let data = await buildTransactionResponse(
|
|
111
|
+
from: transaction,
|
|
112
|
+
jwsRepresentation: result.jwsRepresentation
|
|
113
|
+
)
|
|
114
|
+
allPurchases.append(data)
|
|
159
115
|
}
|
|
160
|
-
}
|
|
161
116
|
|
|
162
|
-
static func collectAllTransactions(appAccountTokenFilter: String?, into allPurchases: inout [[String: Any]]) async {
|
|
163
117
|
for await result in Transaction.all {
|
|
164
|
-
guard case .verified(let transaction) = result else {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
continue
|
|
169
|
-
}
|
|
118
|
+
guard case .verified(let transaction) = result else { continue }
|
|
119
|
+
if let filter = appAccountTokenFilter,
|
|
120
|
+
transaction.appAccountToken?.uuidString != filter { continue }
|
|
170
121
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
122
|
+
let idStr = String(transaction.id)
|
|
123
|
+
if seenIds.contains(idStr) { continue }
|
|
174
124
|
|
|
175
|
-
let
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if !alreadyExists {
|
|
181
|
-
let purchaseData = await buildTransactionResponse(from: transaction, jwsRepresentation: result.jwsRepresentation)
|
|
182
|
-
allPurchases.append(purchaseData)
|
|
183
|
-
}
|
|
125
|
+
let data = await buildTransactionResponse(
|
|
126
|
+
from: transaction,
|
|
127
|
+
jwsRepresentation: result.jwsRepresentation
|
|
128
|
+
)
|
|
129
|
+
allPurchases.append(data)
|
|
184
130
|
}
|
|
131
|
+
|
|
132
|
+
return allPurchases
|
|
185
133
|
}
|
|
186
134
|
}
|
|
187
135
|
|
|
188
|
-
@available(iOS 15.0, *)
|
|
189
136
|
private extension Transaction.RevocationReason {
|
|
190
137
|
var descriptionString: String {
|
|
191
138
|
switch self {
|
|
192
|
-
case .developerIssue:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return "other"
|
|
196
|
-
default:
|
|
197
|
-
return "unknown"
|
|
139
|
+
case .developerIssue: return "developerIssue"
|
|
140
|
+
case .other: return "other"
|
|
141
|
+
default: return "unknown"
|
|
198
142
|
}
|
|
199
143
|
}
|
|
200
144
|
}
|
|
@@ -203,32 +147,44 @@ private extension Transaction.RevocationReason {
|
|
|
203
147
|
private extension Transaction.Reason {
|
|
204
148
|
var descriptionString: String {
|
|
205
149
|
switch self {
|
|
206
|
-
case .purchase:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
150
|
+
case .purchase: return "purchase"
|
|
151
|
+
case .renewal: return "renewal"
|
|
152
|
+
default: return "unknown"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private extension Transaction.OwnershipType {
|
|
158
|
+
var descriptionString: String {
|
|
159
|
+
switch self {
|
|
160
|
+
case .purchased: return "purchased"
|
|
161
|
+
case .familyShared: return "familyShared"
|
|
162
|
+
default: return "purchased"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@available(iOS 16.0, *)
|
|
168
|
+
private extension AppStore.Environment {
|
|
169
|
+
var descriptionString: String {
|
|
170
|
+
switch self {
|
|
171
|
+
case .sandbox: return "Sandbox"
|
|
172
|
+
case .production: return "Production"
|
|
173
|
+
case .xcode: return "Xcode"
|
|
174
|
+
default: return "Production"
|
|
212
175
|
}
|
|
213
176
|
}
|
|
214
177
|
}
|
|
215
178
|
|
|
216
|
-
@available(iOS 15.0, *)
|
|
217
179
|
private extension Product.SubscriptionInfo.RenewalState {
|
|
218
180
|
var descriptionString: String {
|
|
219
181
|
switch self {
|
|
220
|
-
case .subscribed:
|
|
221
|
-
|
|
222
|
-
case .
|
|
223
|
-
|
|
224
|
-
case .
|
|
225
|
-
|
|
226
|
-
case .inGracePeriod:
|
|
227
|
-
return "inGracePeriod"
|
|
228
|
-
case .inBillingRetryPeriod:
|
|
229
|
-
return "inBillingRetryPeriod"
|
|
230
|
-
default:
|
|
231
|
-
return "unknown"
|
|
182
|
+
case .subscribed: return "subscribed"
|
|
183
|
+
case .expired: return "expired"
|
|
184
|
+
case .revoked: return "revoked"
|
|
185
|
+
case .inGracePeriod: return "inGracePeriod"
|
|
186
|
+
case .inBillingRetryPeriod: return "inBillingRetryPeriod"
|
|
187
|
+
default: return "unknown"
|
|
232
188
|
}
|
|
233
189
|
}
|
|
234
190
|
}
|