4
4
5
5
@available ( iOS 15 . 0 , macOS 12 . 0 , * )
6
6
extension InAppPurchasePlugin : InAppPurchase2API {
7
+
7
8
// MARK: - Pigeon Functions
8
9
9
- // Wrapper method around StoreKit2's canMakePayments() method
10
- // https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments
10
+ /// Wrapper method around StoreKit2's canMakePayments() method
11
+ /// https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments
11
12
func canMakePayments( ) throws -> Bool {
12
13
return AppStore . canMakePayments
13
14
}
14
15
15
- // Wrapper method around StoreKit2's products() method
16
- // https://developer.apple.com/documentation/storekit/product/3851116-products
16
+ /// Wrapper method around StoreKit2's products() method
17
+ /// https://developer.apple.com/documentation/storekit/product/3851116-products
17
18
func products(
18
19
identifiers: [ String ] , completion: @escaping ( Result < [ SK2ProductMessage ] , Error > ) -> Void
19
20
) {
@@ -34,4 +35,144 @@ extension InAppPurchasePlugin: InAppPurchase2API {
34
35
}
35
36
}
36
37
}
38
+
39
+ /// Gets the appropriate product, then calls purchase on it.
40
+ /// https://developer.apple.com/documentation/storekit/product/3791971-purchase
41
+ func purchase(
42
+ id: String , options: SK2ProductPurchaseOptionsMessage ? ,
43
+ completion: @escaping ( Result < SK2ProductPurchaseResultMessage , Error > ) -> Void
44
+ ) {
45
+ Task { @MainActor in
46
+ do {
47
+ guard let product = try await Product . products ( for: [ id] ) . first else {
48
+ let error = PigeonError (
49
+ code: " storekit2_failed_to_fetch_product " ,
50
+ message: " Storekit has failed to fetch this product. " ,
51
+ details: " Product ID : \( id) " )
52
+ return completion ( . failure( error) )
53
+ }
54
+
55
+ let result = try await product. purchase ( options: [ ] )
56
+
57
+ switch result {
58
+ case . success( let verification) :
59
+ switch verification {
60
+ case . verified( let transaction) :
61
+ self . sendTransactionUpdate ( transaction: transaction)
62
+ completion ( . success( result. convertToPigeon ( ) ) )
63
+ case . unverified( _, let error) :
64
+ completion ( . failure( error) )
65
+ }
66
+ case . pending:
67
+ completion (
68
+ . failure(
69
+ PigeonError (
70
+ code: " storekit2_purchase_pending " ,
71
+ message:
72
+ " This transaction is still pending and but may complete in the future. If it completes, it will be delivered via `purchaseStream` " ,
73
+ details: " Product ID : \( id) " ) ) )
74
+ case . userCancelled:
75
+ completion (
76
+ . failure(
77
+ PigeonError (
78
+ code: " storekit2_purchase_cancelled " ,
79
+ message: " This transaction has been cancelled by the user. " ,
80
+ details: " Product ID : \( id) " ) ) )
81
+ @unknown default :
82
+ fatalError ( " An unknown StoreKit PurchaseResult has been encountered. " )
83
+ }
84
+ } catch {
85
+ completion ( . failure( error) )
86
+ }
87
+ }
88
+ }
89
+
90
+ /// Wrapper method around StoreKit2's transactions() method
91
+ /// https://developer.apple.com/documentation/storekit/product/3851116-products
92
+ func transactions(
93
+ completion: @escaping ( Result < [ SK2TransactionMessage ] , Error > ) -> Void
94
+ ) {
95
+ Task {
96
+ @MainActor in
97
+ do {
98
+ let transactionsMsgs = await rawTransactions ( ) . map {
99
+ $0. convertToPigeon ( )
100
+ }
101
+ completion ( . success( transactionsMsgs) )
102
+ }
103
+ }
104
+ }
105
+
106
+ /// Wrapper method around StoreKit2's finish() method https://developer.apple.com/documentation/storekit/transaction/3749694-finish
107
+ func finish( id: Int64 , completion: @escaping ( Result < Void , Error > ) -> Void ) {
108
+ Task {
109
+ let transaction = try await fetchTransaction ( by: UInt64 ( id) )
110
+ if let transaction = transaction {
111
+ await transaction. finish ( )
112
+ }
113
+ }
114
+ }
115
+
116
+ /// This Task listens to Transation.updates as shown here
117
+ /// https://developer.apple.com/documentation/storekit/transaction/3851206-updates
118
+ /// This function should be called as soon as the app starts to avoid missing any Transactions done outside of the app.
119
+ func startListeningToTransactions( ) throws {
120
+ self . setListenerTaskAsTask (
121
+ task: Task { [ weak self] in
122
+ for await verificationResult in Transaction . updates {
123
+ switch verificationResult {
124
+ case . verified( let transaction) :
125
+ self ? . sendTransactionUpdate ( transaction: transaction)
126
+ case . unverified:
127
+ break
128
+ }
129
+ }
130
+ } )
131
+ }
132
+
133
+ /// Stop subscribing to Transaction.updates
134
+ func stopListeningToTransactions( ) throws {
135
+ getListenerTaskAsTask. cancel ( )
136
+ }
137
+
138
+ /// Sends an transaction back to Dart. Access these transactions with `purchaseStream`
139
+ func sendTransactionUpdate( transaction: Transaction ) {
140
+ let transactionMessage = transaction. convertToPigeon ( )
141
+ transactionCallbackAPI? . onTransactionsUpdated ( newTransaction: transactionMessage) { result in
142
+ switch result {
143
+ case . success: break
144
+ case . failure( let error) :
145
+ print ( " Failed to send transaction updates: \( error) " )
146
+ }
147
+ }
148
+ }
149
+
150
+ /// Helper function that fetches and unwraps all verified transactions
151
+ private func rawTransactions( ) async -> [ Transaction ] {
152
+ var transactions : [ Transaction ] = [ ]
153
+ for await verificationResult in Transaction . all {
154
+ switch verificationResult {
155
+ case . verified( let transaction) :
156
+ transactions. append ( transaction)
157
+ case . unverified:
158
+ break
159
+ }
160
+ }
161
+ return transactions
162
+ }
163
+
164
+ /// Helper function to fetch specific transaction
165
+ private func fetchTransaction( by id: UInt64 ) async throws -> Transaction ? {
166
+ for await result in Transaction . all {
167
+ switch result {
168
+ case . verified( let transaction) :
169
+ if transaction. id == id {
170
+ return transaction
171
+ }
172
+ case . unverified:
173
+ continue
174
+ }
175
+ }
176
+ return nil
177
+ }
37
178
}
0 commit comments