@tapayadot/accept-react-native 1.0.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.
@@ -0,0 +1,412 @@
1
+ import AcceptSDK
2
+ import CoreLocation
3
+ import ExpoModulesCore
4
+
5
+ // MARK: - Records
6
+
7
+ final class CardPaymentIntentRecord: Record {
8
+ @Field var paymentIntentId: String = ""
9
+ @Field var amount: Int = 0
10
+ @Field var requestedCurrency: String = ""
11
+ @Field var settlementCurrency: String? = nil
12
+ }
13
+
14
+ final class SepaPaymentIntentRecord: Record {
15
+ @Field var paymentIntentId: String = ""
16
+ @Field var amount: Int = 0
17
+ @Field var requestedCurrency: String = ""
18
+ @Field var settlementCurrency: String? = nil
19
+ }
20
+
21
+ final class CertisPaymentIntentRecord: Record {
22
+ @Field var paymentIntentId: String = ""
23
+ @Field var amount: Int = 0
24
+ @Field var requestedCurrency: String = ""
25
+ @Field var settlementCurrency: String? = nil
26
+ }
27
+
28
+ final class CardPaymentResultRecord: Record {
29
+ @Field var status: String = ""
30
+ @Field var last4: String? = nil
31
+ @Field var companyName: String = ""
32
+ @Field var companyAddress1: String = ""
33
+ @Field var companyAddress2: String? = nil
34
+ @Field var companyCity: String = ""
35
+ @Field var companyCountry: Int = 0
36
+ @Field var terminalId: String = ""
37
+ @Field var authorizationCode: String? = nil
38
+ @Field var brand: String? = nil
39
+ @Field var reason: String? = nil
40
+ }
41
+
42
+ final class BankTransferPaymentResultRecord: Record {
43
+ @Field var status: String = ""
44
+ @Field var postponed: Bool = false
45
+ @Field var receivedAt: String? = nil
46
+ @Field var receivedFromIban: String? = nil
47
+ }
48
+
49
+ final class KybPrefillDataRecord: Record {
50
+ @Field var businessType: String? = nil
51
+ @Field var countryCode: String? = nil
52
+ @Field var registrationNumber: String? = nil
53
+ @Field var vatNumber: String? = nil
54
+ @Field var legalName: String? = nil
55
+ @Field var addressLine1: String? = nil
56
+ @Field var addressLine2: String? = nil
57
+ @Field var city: String? = nil
58
+ @Field var postalCode: String? = nil
59
+ @Field var merchantCategoryCode: Int? = nil
60
+ @Field var businessEmail: String? = nil
61
+ @Field var bankAccountIban: String? = nil
62
+ @Field var businessUrl: String? = nil
63
+ @Field var productDescription: String? = nil
64
+ @Field var supportPhone: String? = nil
65
+
66
+ func toKybPrefillData() -> KybPrefillData {
67
+ var data = KybPrefillData()
68
+ data.countryCode = countryCode
69
+ data.registrationNumber = registrationNumber
70
+ data.vatNumber = vatNumber
71
+ data.legalName = legalName
72
+ data.addressLine1 = addressLine1
73
+ data.addressLine2 = addressLine2
74
+ data.city = city
75
+ data.postalCode = postalCode
76
+ data.merchantCategoryCode = merchantCategoryCode
77
+ data.businessEmail = businessEmail
78
+ data.bankAccountIban = bankAccountIban
79
+ data.businessUrl = businessUrl.flatMap { URL(string: $0) }
80
+ data.productDescription = productDescription
81
+ data.supportPhone = supportPhone
82
+ data.businessType = businessType.flatMap { value -> AcceptBusinessType? in
83
+ switch value {
84
+ case "individual": .individual
85
+ case "company": .company
86
+ case "nonProfit": .nonProfit
87
+ case "governmentEntity": .governmentEntity
88
+ default: nil
89
+ }
90
+ }
91
+ return data
92
+ }
93
+ }
94
+
95
+ // MARK: - Module
96
+
97
+ public class AcceptSdkReactNativeModule: Module {
98
+ public func definition() -> ModuleDefinition {
99
+ Name("AcceptSdkReactNative")
100
+
101
+ AsyncFunction("initialize") { (demo: Bool) async throws in
102
+ do {
103
+ try await Accept.initialize(environment: demo ? .sandbox : .production)
104
+ } catch let error as AcceptSDKError {
105
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
106
+ }
107
+ return true
108
+ }
109
+
110
+ AsyncFunction("authenticate") { (token: String) async throws in
111
+ do {
112
+ try await Accept.authenticate(merchantToken: token)
113
+ } catch let error as AcceptSDKError {
114
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
115
+ }
116
+ return true
117
+ }
118
+
119
+ AsyncFunction("getStatus") { () async -> String in
120
+ let status = await Accept.status
121
+ switch status {
122
+ case .idle: return "IDLE"
123
+ case .initialized: return "INITIALIZED"
124
+ case .authenticated: return "LOGGED_IN"
125
+ @unknown default: return "IDLE"
126
+ }
127
+ }
128
+
129
+ AsyncFunction("isPaymentPending") { () async -> Bool in
130
+ await Accept.isPaymentInProgress()
131
+ }
132
+
133
+ AsyncFunction("logOut") { () async throws in
134
+ do {
135
+ try await Accept.logout()
136
+ } catch let error as AcceptSDKError {
137
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
138
+ }
139
+ return true
140
+ }
141
+
142
+ AsyncFunction("presentKyb") { (prefilling: KybPrefillDataRecord?) async throws in
143
+ let prefillData = prefilling?.toKybPrefillData()
144
+ try await Accept.presentKyb(prefilling: prefillData)
145
+ }
146
+
147
+ AsyncFunction("requestLocationPermission") { () async throws -> Bool in
148
+ do {
149
+ try await Accept.requestLocationPermission()
150
+ } catch let error as AcceptSDKError {
151
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
152
+ }
153
+ return true
154
+ }
155
+
156
+ AsyncFunction("pairWithCompanionApp") { () async throws in
157
+ do {
158
+ try await Accept.pairWithCompanionApp()
159
+ } catch let error as AcceptSDKError {
160
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
161
+ }
162
+ }
163
+
164
+ AsyncFunction("isCompanionAppInstalled") { () async -> Bool in
165
+ await MainActor.run { Accept.isCompanionAppInstalled() }
166
+ }
167
+
168
+ AsyncFunction("presentCompanionAppSheet") { () async throws in
169
+ do {
170
+ try await Accept.presentCompanionAppSheet()
171
+ } catch let error as AcceptSDKError {
172
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
173
+ }
174
+ }
175
+
176
+ AsyncFunction("getPaymentStatus") { (paymentIntentId: String) async throws -> String in
177
+ do {
178
+ let status = try await Accept.getPaymentStatus(for: paymentIntentId)
179
+ return try InfoResultSerializer.serialize(status)
180
+ } catch let error as AcceptSDKError {
181
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
182
+ }
183
+ }
184
+
185
+ AsyncFunction("startCardPayment") { (intentRecord: CardPaymentIntentRecord) async throws -> CardPaymentResultRecord in
186
+ do {
187
+ let outcome = try await Accept.pay(intentRecord.toPaymentIntent())
188
+ return outcome.toCardResult()
189
+ } catch let error as AcceptSDKError {
190
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
191
+ }
192
+ }
193
+
194
+ AsyncFunction("startSepaPayment") { (intentRecord: SepaPaymentIntentRecord) async throws -> BankTransferPaymentResultRecord in
195
+ do {
196
+ let outcome = try await Accept.pay(intentRecord.toPaymentIntent())
197
+ return outcome.toBankTransferResult()
198
+ } catch let error as AcceptSDKError {
199
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
200
+ }
201
+ }
202
+
203
+ AsyncFunction("startCertisPayment") { (intentRecord: CertisPaymentIntentRecord) async throws -> BankTransferPaymentResultRecord in
204
+ do {
205
+ let outcome = try await Accept.pay(intentRecord.toPaymentIntent())
206
+ return outcome.toBankTransferResult()
207
+ } catch let error as AcceptSDKError {
208
+ throw Exception(name: "AcceptSDKError", description: error.localizedDescription)
209
+ }
210
+ }
211
+ }
212
+ }
213
+
214
+ // MARK: - Info result serialization
215
+
216
+ private struct CardInfoResult: Encodable {
217
+ let status: String
218
+ let readerStatus: String
219
+ let last4: String
220
+ let companyName: String
221
+ let companyAddress1: String
222
+ let companyAddress2: String
223
+ let companyCity: String
224
+ let companyCountry: Int
225
+ let terminalId: String
226
+ let authorizationCode: String
227
+ let brand: String
228
+ let reason: String
229
+ let type: String
230
+ }
231
+
232
+ private struct BankTransferInfoResult: Encodable {
233
+ let status: String
234
+ let postponed: Bool
235
+ let receivedAt: String
236
+ let receivedFromIban: String
237
+ let type: String
238
+ }
239
+
240
+ private enum InfoResultSerializer {
241
+ static func serialize(_ status: any PaymentStatus) throws -> String {
242
+ let encoder = JSONEncoder()
243
+ let data: Data
244
+
245
+ switch status.paymentMethod {
246
+ case .card:
247
+ guard let cardStatus = status as? CardPaymentStatus else {
248
+ throw Exception(name: "SerializationError", description: "Unexpected card status type")
249
+ }
250
+ data = try encoder.encode(CardInfoResult(
251
+ status: cardStatus.status.rnString,
252
+ readerStatus: "",
253
+ last4: cardStatus.details?.last4 ?? "",
254
+ companyName: cardStatus.merchantDetails.legalName,
255
+ companyAddress1: cardStatus.merchantDetails.addressLine1,
256
+ companyAddress2: cardStatus.merchantDetails.addressLine2 ?? "",
257
+ companyCity: cardStatus.merchantDetails.city,
258
+ companyCountry: cardStatus.merchantDetails.country.rawValue,
259
+ terminalId: cardStatus.terminalId,
260
+ authorizationCode: cardStatus.details?.authorizationCode ?? "",
261
+ brand: cardStatus.details?.brand.rawValue.uppercased() ?? "",
262
+ reason: cardStatus.details?.issuerDeclineReason?.rawValue ?? "",
263
+ type: "card"
264
+ ))
265
+
266
+ case .sepaIct:
267
+ guard let bankStatus = status as? BankTransferPaymentStatus else {
268
+ throw Exception(name: "SerializationError", description: "Unexpected bank transfer status type")
269
+ }
270
+ data = try encoder.encode(BankTransferInfoResult(
271
+ status: bankStatus.status.rnString,
272
+ postponed: bankStatus.details?.postponed ?? false,
273
+ receivedAt: bankStatus.processedAt.map { ISO8601DateFormatter().string(from: $0) } ?? "",
274
+ receivedFromIban: bankStatus.details?.bankAccountNumber ?? "",
275
+ type: "sepa"
276
+ ))
277
+
278
+ case .certisCz:
279
+ guard let bankStatus = status as? BankTransferPaymentStatus else {
280
+ throw Exception(name: "SerializationError", description: "Unexpected bank transfer status type")
281
+ }
282
+ data = try encoder.encode(BankTransferInfoResult(
283
+ status: bankStatus.status.rnString,
284
+ postponed: bankStatus.details?.postponed ?? false,
285
+ receivedAt: bankStatus.processedAt.map { ISO8601DateFormatter().string(from: $0) } ?? "",
286
+ receivedFromIban: bankStatus.details?.bankAccountNumber ?? "",
287
+ type: "certis"
288
+ ))
289
+
290
+ default:
291
+ throw Exception(name: "UnsupportedPaymentMethod", description: "Payment method not supported by info()")
292
+ }
293
+
294
+ return String(data: data, encoding: .utf8) ?? "{}"
295
+ }
296
+ }
297
+
298
+ private extension AcceptTransactionStatus {
299
+ var rnString: String {
300
+ switch self {
301
+ case .pending: "PENDING"
302
+ case .success: "APPROVED"
303
+ case .cancelled: "ABORTED"
304
+ case .refunded: "REFUNDED"
305
+ case .failed: "DECLINED"
306
+ case .actionNeeded: "PENDING"
307
+ @unknown default: "PENDING"
308
+ }
309
+ }
310
+
311
+ var cardPaymentStatus: String {
312
+ switch self {
313
+ case .pending: "pending"
314
+ case .success: "approved"
315
+ case .cancelled: "aborted"
316
+ case .refunded: "refunded"
317
+ case .failed: "declined"
318
+ case .actionNeeded: "pending"
319
+ @unknown default: "pending"
320
+ }
321
+ }
322
+
323
+ var bankTransferStatus: String {
324
+ switch self {
325
+ case .pending: "pending"
326
+ case .success: "verified"
327
+ case .cancelled: "aborted"
328
+ case .refunded: "refunded"
329
+ case .failed: "declined"
330
+ case .actionNeeded: "pending"
331
+ @unknown default: "pending"
332
+ }
333
+ }
334
+ }
335
+
336
+ // MARK: - Payment intent conversion
337
+
338
+ private extension CardPaymentIntentRecord {
339
+ func toPaymentIntent() throws -> AcceptCardPaymentIntent {
340
+ let requested = try AcceptCurrency.from(requestedCurrency)
341
+ let settlement = try settlementCurrency.map { try AcceptCurrency.from($0) } ?? requested
342
+ return AcceptCardPaymentIntent(id: paymentIntentId, amount: amount, requestedCurrency: requested, settlementCurrency: settlement)
343
+ }
344
+ }
345
+
346
+ private extension SepaPaymentIntentRecord {
347
+ func toPaymentIntent() throws -> AcceptSepaPaymentIntent {
348
+ let requested = try AcceptCurrency.from(requestedCurrency)
349
+ let settlement = try settlementCurrency.map { try AcceptCurrency.from($0) } ?? requested
350
+ return AcceptSepaPaymentIntent(id: paymentIntentId, amount: amount, requestedCurrency: requested, settlementCurrency: settlement)
351
+ }
352
+ }
353
+
354
+ private extension CertisPaymentIntentRecord {
355
+ func toPaymentIntent() throws -> AcceptCertisPaymentIntent {
356
+ let requested = try AcceptCurrency.from(requestedCurrency)
357
+ let settlement = try settlementCurrency.map { try AcceptCurrency.from($0) } ?? requested
358
+ return AcceptCertisPaymentIntent(id: paymentIntentId, amount: amount, requestedCurrency: requested, settlementCurrency: settlement)
359
+ }
360
+ }
361
+
362
+ private extension AcceptCurrency {
363
+ static func from(_ code: String) throws -> AcceptCurrency {
364
+ switch code.uppercased() {
365
+ case "CZK": return .czk
366
+ case "EUR": return .eur
367
+ case "GBP": return .gbp
368
+ default: throw Exception(name: "InvalidCurrency", description: "Unsupported currency: \(code)")
369
+ }
370
+ }
371
+ }
372
+
373
+ // MARK: - Payment outcome conversion
374
+
375
+ private extension AcceptPaymentOutcome where Result == CardPaymentStatus {
376
+ func toCardResult() -> CardPaymentResultRecord {
377
+ let record = CardPaymentResultRecord()
378
+ switch self {
379
+ case .canceled:
380
+ record.status = "aborted"
381
+ case .completed(let status):
382
+ record.status = status.status.cardPaymentStatus
383
+ record.last4 = status.details?.last4
384
+ record.companyName = status.merchantDetails.legalName
385
+ record.companyAddress1 = status.merchantDetails.addressLine1
386
+ record.companyAddress2 = status.merchantDetails.addressLine2
387
+ record.companyCity = status.merchantDetails.city
388
+ record.companyCountry = status.merchantDetails.country.rawValue
389
+ record.terminalId = status.terminalId
390
+ record.authorizationCode = status.details?.authorizationCode
391
+ record.brand = status.details?.brand.rawValue.uppercased()
392
+ record.reason = status.details?.issuerDeclineReason?.rawValue
393
+ }
394
+ return record
395
+ }
396
+ }
397
+
398
+ private extension AcceptPaymentOutcome where Result == BankTransferPaymentStatus {
399
+ func toBankTransferResult() -> BankTransferPaymentResultRecord {
400
+ let record = BankTransferPaymentResultRecord()
401
+ switch self {
402
+ case .canceled:
403
+ record.status = "aborted"
404
+ case .completed(let status):
405
+ record.status = status.status.bankTransferStatus
406
+ record.postponed = status.details?.postponed ?? false
407
+ record.receivedAt = status.processedAt.map { ISO8601DateFormatter().string(from: $0) }
408
+ record.receivedFromIban = status.details?.bankAccountNumber
409
+ }
410
+ return record
411
+ }
412
+ }
package/ios/Podfile ADDED
@@ -0,0 +1,63 @@
1
+ require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
2
+ require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
3
+
4
+ require 'json'
5
+ podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
6
+
7
+ def ccache_enabled?(podfile_properties)
8
+ # Environment variable takes precedence
9
+ return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE']
10
+
11
+ # Fall back to Podfile properties
12
+ podfile_properties['apple.ccacheEnabled'] == 'true'
13
+ end
14
+
15
+ ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false'
16
+ ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
17
+ ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
18
+ ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
19
+ platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
20
+
21
+ prepare_react_native_project!
22
+
23
+ target 'acceptsdkreactnative' do
24
+ use_expo_modules!
25
+
26
+ if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
27
+ config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
28
+ else
29
+ config_command = [
30
+ 'node',
31
+ '--no-warnings',
32
+ '--eval',
33
+ 'require(\'expo/bin/autolinking\')',
34
+ 'expo-modules-autolinking',
35
+ 'react-native-config',
36
+ '--json',
37
+ '--platform',
38
+ 'ios'
39
+ ]
40
+ end
41
+
42
+ config = use_native_modules!(config_command)
43
+
44
+ use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
45
+ use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
46
+
47
+ use_react_native!(
48
+ :path => config[:reactNativePath],
49
+ :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
50
+ # An absolute path to your application root.
51
+ :app_path => "#{Pod::Config.instance.installation_root}/..",
52
+ :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
53
+ )
54
+
55
+ post_install do |installer|
56
+ react_native_post_install(
57
+ installer,
58
+ config[:reactNativePath],
59
+ :mac_catalyst_enabled => false,
60
+ :ccache_enabled => ccache_enabled?(podfile_properties),
61
+ )
62
+ end
63
+ end
@@ -0,0 +1,4 @@
1
+ {
2
+ "expo.jsEngine": "hermes",
3
+ "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true"
4
+ }