@yuno-payments/yuno-sdk-react-native 1.0.16 → 1.0.17-rc.10
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 +795 -262
- package/YunoSdk.podspec +48 -0
- package/android/src/main/java/com/yunosdkreactnative/YunoSdkModule.kt +341 -0
- package/ios/YunoPaymentMethodsViewManager.m +10 -0
- package/ios/YunoPaymentMethodsViewManager.swift +324 -0
- package/ios/YunoSdk.m +42 -5
- package/ios/YunoSdk.swift +303 -70
- package/lib/commonjs/YunoPaymentMethods.js +9 -9
- package/lib/commonjs/YunoPaymentMethods.js.map +1 -1
- package/lib/commonjs/YunoSdk.js +184 -25
- package/lib/commonjs/YunoSdk.js.map +1 -1
- package/lib/commonjs/core/types/HeadlessTypes.js +20 -0
- package/lib/commonjs/core/types/HeadlessTypes.js.map +1 -0
- package/lib/module/YunoPaymentMethods.js +9 -9
- package/lib/module/YunoPaymentMethods.js.map +1 -1
- package/lib/module/YunoSdk.js +184 -25
- package/lib/module/YunoSdk.js.map +1 -1
- package/lib/module/core/types/HeadlessTypes.js +16 -0
- package/lib/module/core/types/HeadlessTypes.js.map +1 -0
- package/package.json +1 -1
- package/src/YunoPaymentMethods.tsx +17 -15
- package/src/YunoSdk.ts +216 -39
- package/src/core/types/HeadlessTypes.ts +110 -0
- package/src/core/types/OneTimeTokenInfo.ts +0 -1
- package/src/core/types/index.ts +17 -0
package/ios/YunoSdk.swift
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import React
|
|
2
3
|
import YunoSDK
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Implementation of PaymentMethodSelected protocol for Lite SDK.
|
|
7
|
+
*/
|
|
8
|
+
private class PaymentMethodSelection: NSObject, PaymentMethodSelected {
|
|
9
|
+
let paymentMethodType: String
|
|
10
|
+
let vaultedToken: String?
|
|
11
|
+
|
|
12
|
+
init(paymentMethodType: String, vaultedToken: String?) {
|
|
13
|
+
self.paymentMethodType = paymentMethodType
|
|
14
|
+
self.vaultedToken = vaultedToken
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
/**
|
|
5
19
|
* Yuno SDK React Native Module for iOS.
|
|
6
20
|
*
|
|
@@ -12,8 +26,12 @@ class YunoSdk: RCTEventEmitter {
|
|
|
12
26
|
private var isInitialized = false
|
|
13
27
|
private var currentCountryCode: String?
|
|
14
28
|
private var currentLanguage: String?
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
var customerSession: String = ""
|
|
30
|
+
var checkoutSession: String = ""
|
|
31
|
+
|
|
32
|
+
// Store last payment status to prevent stale status from previous flows
|
|
33
|
+
private var lastPaymentStatus: String?
|
|
34
|
+
private var isPaymentFlowCleared: Bool = false
|
|
17
35
|
|
|
18
36
|
override init() {
|
|
19
37
|
super.init()
|
|
@@ -27,10 +45,23 @@ class YunoSdk: RCTEventEmitter {
|
|
|
27
45
|
return [
|
|
28
46
|
"YunoPaymentStatus",
|
|
29
47
|
"YunoEnrollmentStatus",
|
|
30
|
-
"YunoOneTimeToken"
|
|
48
|
+
"YunoOneTimeToken",
|
|
49
|
+
"YunoOneTimeTokenInfo",
|
|
50
|
+
"onPaymentMethodSelected",
|
|
51
|
+
"onPaymentMethodError"
|
|
31
52
|
]
|
|
32
53
|
}
|
|
33
54
|
|
|
55
|
+
@objc
|
|
56
|
+
override func addListener(_ eventName: String) {
|
|
57
|
+
super.addListener(eventName)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@objc
|
|
61
|
+
override func removeListeners(_ count: Double) {
|
|
62
|
+
super.removeListeners(count)
|
|
63
|
+
}
|
|
64
|
+
|
|
34
65
|
/**
|
|
35
66
|
* Initialize the Yuno SDK with configuration.
|
|
36
67
|
*/
|
|
@@ -55,28 +86,36 @@ class YunoSdk: RCTEventEmitter {
|
|
|
55
86
|
self.currentLanguage = lang
|
|
56
87
|
}
|
|
57
88
|
|
|
58
|
-
// Parse card flow
|
|
59
|
-
var cardFlow:
|
|
60
|
-
if let flow = yunoConfig["
|
|
61
|
-
cardFlow = self.
|
|
89
|
+
// Parse card flow (React Native sends "cardType", not "cardFlow")
|
|
90
|
+
var cardFlow: CardFormType = .oneStep
|
|
91
|
+
if let flow = yunoConfig["cardType"] as? String {
|
|
92
|
+
cardFlow = self.mapToCardFormType(flow)
|
|
62
93
|
}
|
|
63
94
|
|
|
64
95
|
// Parse other config options
|
|
65
|
-
let saveCardEnabled = yunoConfig["
|
|
66
|
-
|
|
96
|
+
let saveCardEnabled = yunoConfig["savedCardEnable"] as? Bool ?? false
|
|
97
|
+
// keepLoader is inverted from showPaymentStatus (if showPaymentStatus is false, keepLoader should be true)
|
|
98
|
+
let showPaymentStatus = yunoConfig["showPaymentStatus"] as? Bool ?? true
|
|
99
|
+
let keepLoader = !showPaymentStatus
|
|
67
100
|
|
|
68
101
|
// Initialize Yuno SDK
|
|
69
102
|
Yuno.initialize(
|
|
70
103
|
apiKey: apiKey,
|
|
71
104
|
config: YunoConfig(
|
|
72
|
-
|
|
105
|
+
cardFormType: cardFlow,
|
|
73
106
|
saveCardEnabled: saveCardEnabled,
|
|
74
107
|
keepLoader: keepLoader
|
|
75
|
-
)
|
|
108
|
+
),
|
|
109
|
+
callback: {
|
|
110
|
+
print("✅ Yuno.initialize callback invoked")
|
|
111
|
+
resolver(nil)
|
|
112
|
+
}
|
|
76
113
|
)
|
|
77
114
|
|
|
115
|
+
// Mark as initialized immediately after calling initialize (not inside callback)
|
|
116
|
+
// Similar to Android's approach where it marks immediately after Yuno.initialize()
|
|
78
117
|
self.isInitialized = true
|
|
79
|
-
|
|
118
|
+
print("✅ YunoSdk module marked as initialized (countryCode: \(countryCode), language: \(self.currentLanguage ?? "nil"))")
|
|
80
119
|
} catch {
|
|
81
120
|
rejecter("INITIALIZATION_ERROR", "Failed to initialize Yuno SDK: \(error.localizedDescription)", error)
|
|
82
121
|
}
|
|
@@ -155,20 +194,20 @@ class YunoSdk: RCTEventEmitter {
|
|
|
155
194
|
throw NSError(domain: "YunoSdk", code: -1, userInfo: [NSLocalizedDescriptionKey: "methodSelected is required"])
|
|
156
195
|
}
|
|
157
196
|
|
|
158
|
-
guard let vaultedToken = methodSelected["vaultedToken"] as? String, !vaultedToken.isEmpty else {
|
|
159
|
-
throw NSError(domain: "YunoSdk", code: -1, userInfo: [NSLocalizedDescriptionKey: "vaultedToken is required"])
|
|
160
|
-
}
|
|
161
|
-
|
|
162
197
|
guard let paymentMethodType = methodSelected["paymentMethodType"] as? String, !paymentMethodType.isEmpty else {
|
|
163
198
|
throw NSError(domain: "YunoSdk", code: -1, userInfo: [NSLocalizedDescriptionKey: "paymentMethodType is required"])
|
|
164
199
|
}
|
|
165
200
|
|
|
201
|
+
// vaultedToken is OPTIONAL (only required when using saved cards)
|
|
202
|
+
let vaultedToken = methodSelected["vaultedToken"] as? String
|
|
203
|
+
|
|
166
204
|
let showPaymentStatus = arguments["showPaymentStatus"] as? Bool ?? true
|
|
167
205
|
|
|
168
206
|
self.checkoutSession = checkoutSession
|
|
169
207
|
self.currentCountryCode = countryCode
|
|
170
208
|
|
|
171
|
-
|
|
209
|
+
// Create a PaymentMethodSelected implementation
|
|
210
|
+
let paymentSelected = PaymentMethodSelection(
|
|
172
211
|
paymentMethodType: paymentMethodType,
|
|
173
212
|
vaultedToken: vaultedToken
|
|
174
213
|
)
|
|
@@ -201,12 +240,9 @@ class YunoSdk: RCTEventEmitter {
|
|
|
201
240
|
}
|
|
202
241
|
|
|
203
242
|
DispatchQueue.main.async {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
} catch {
|
|
208
|
-
rejecter("PAYMENT_ERROR", "Failed to start payment: \(error.localizedDescription)", error)
|
|
209
|
-
}
|
|
243
|
+
// Start payment using the delegate already registered via getPaymentMethodViewAsync
|
|
244
|
+
Yuno.startPayment(showPaymentStatus: showPaymentStatus)
|
|
245
|
+
resolver(nil)
|
|
210
246
|
}
|
|
211
247
|
}
|
|
212
248
|
|
|
@@ -215,7 +251,9 @@ class YunoSdk: RCTEventEmitter {
|
|
|
215
251
|
*/
|
|
216
252
|
@objc
|
|
217
253
|
func continuePayment(
|
|
218
|
-
_
|
|
254
|
+
_ checkoutSession: String,
|
|
255
|
+
countryCode: String,
|
|
256
|
+
showPaymentStatus: Bool,
|
|
219
257
|
resolver: @escaping RCTPromiseResolveBlock,
|
|
220
258
|
rejecter: @escaping RCTPromiseRejectBlock
|
|
221
259
|
) {
|
|
@@ -224,13 +262,18 @@ class YunoSdk: RCTEventEmitter {
|
|
|
224
262
|
return
|
|
225
263
|
}
|
|
226
264
|
|
|
227
|
-
DispatchQueue.main.async {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
265
|
+
DispatchQueue.main.async { [weak self] in
|
|
266
|
+
guard let self = self else { return }
|
|
267
|
+
|
|
268
|
+
// Update checkout session and country code for the delegate
|
|
269
|
+
self.checkoutSession = checkoutSession
|
|
270
|
+
self.currentCountryCode = countryCode
|
|
271
|
+
|
|
272
|
+
// Continue payment using the delegate already registered
|
|
273
|
+
// The SDK will read checkoutSession and countryCode from the delegate properties
|
|
274
|
+
Yuno.continuePayment()
|
|
275
|
+
|
|
276
|
+
resolver(nil)
|
|
234
277
|
}
|
|
235
278
|
}
|
|
236
279
|
|
|
@@ -261,14 +304,13 @@ class YunoSdk: RCTEventEmitter {
|
|
|
261
304
|
throw NSError(domain: "YunoSdk", code: -1, userInfo: [NSLocalizedDescriptionKey: "methodSelected is required"])
|
|
262
305
|
}
|
|
263
306
|
|
|
264
|
-
guard let vaultedToken = methodSelected["vaultedToken"] as? String, !vaultedToken.isEmpty else {
|
|
265
|
-
throw NSError(domain: "YunoSdk", code: -1, userInfo: [NSLocalizedDescriptionKey: "vaultedToken is required"])
|
|
266
|
-
}
|
|
267
|
-
|
|
268
307
|
guard let paymentMethodType = methodSelected["paymentMethodType"] as? String, !paymentMethodType.isEmpty else {
|
|
269
308
|
throw NSError(domain: "YunoSdk", code: -1, userInfo: [NSLocalizedDescriptionKey: "paymentMethodType is required"])
|
|
270
309
|
}
|
|
271
310
|
|
|
311
|
+
// vaultedToken is OPTIONAL (only required when using saved cards)
|
|
312
|
+
let vaultedToken = methodSelected["vaultedToken"] as? String
|
|
313
|
+
|
|
272
314
|
let showPaymentStatus = arguments["showPaymentStatus"] as? Bool ?? true
|
|
273
315
|
|
|
274
316
|
var countryCode = arguments["countryCode"] as? String
|
|
@@ -283,7 +325,7 @@ class YunoSdk: RCTEventEmitter {
|
|
|
283
325
|
self.checkoutSession = checkoutSession
|
|
284
326
|
self.currentCountryCode = safeCountryCode
|
|
285
327
|
|
|
286
|
-
let paymentSelected =
|
|
328
|
+
let paymentSelected = PaymentMethodSelection(
|
|
287
329
|
paymentMethodType: paymentMethodType,
|
|
288
330
|
vaultedToken: vaultedToken
|
|
289
331
|
)
|
|
@@ -343,30 +385,84 @@ class YunoSdk: RCTEventEmitter {
|
|
|
343
385
|
}
|
|
344
386
|
}
|
|
345
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Gets the last One Time Token (OTT) that was generated.
|
|
390
|
+
*/
|
|
391
|
+
@objc
|
|
392
|
+
func getLastOneTimeToken(
|
|
393
|
+
_ resolver: @escaping RCTPromiseResolveBlock,
|
|
394
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
395
|
+
) {
|
|
396
|
+
// iOS doesn't need this implementation since tokens are emitted immediately
|
|
397
|
+
// Return nil as there's no stored token
|
|
398
|
+
resolver(nil)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Gets the last OneTimeTokenInfo object stored by the SDK.
|
|
403
|
+
*/
|
|
404
|
+
@objc
|
|
405
|
+
func getLastOneTimeTokenInfo(
|
|
406
|
+
_ resolver: @escaping RCTPromiseResolveBlock,
|
|
407
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
408
|
+
) {
|
|
409
|
+
// iOS doesn't need this implementation since tokens are emitted immediately
|
|
410
|
+
// Return nil as there's no stored token info
|
|
411
|
+
resolver(nil)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Clears the last OTT tokens stored by the SDK.
|
|
416
|
+
*/
|
|
417
|
+
@objc
|
|
418
|
+
func clearLastOneTimeToken(
|
|
419
|
+
_ resolver: @escaping RCTPromiseResolveBlock,
|
|
420
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
421
|
+
) {
|
|
422
|
+
// iOS doesn't need this implementation since tokens aren't stored
|
|
423
|
+
// Just resolve successfully
|
|
424
|
+
resolver(nil)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Clears the last payment status to prevent stale status from previous flows.
|
|
429
|
+
*/
|
|
430
|
+
@objc
|
|
431
|
+
func clearLastPaymentStatus(
|
|
432
|
+
_ resolver: @escaping RCTPromiseResolveBlock,
|
|
433
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
434
|
+
) {
|
|
435
|
+
// Don't set to nil - keep the last status to compare against stale events
|
|
436
|
+
// Just mark that we're starting a fresh flow
|
|
437
|
+
isPaymentFlowCleared = true
|
|
438
|
+
print("💫 Payment status cleared - fresh flow starting (last status was: \(lastPaymentStatus ?? "nil"))")
|
|
439
|
+
resolver(true)
|
|
440
|
+
}
|
|
441
|
+
|
|
346
442
|
// MARK: - Helper Methods
|
|
347
443
|
|
|
348
|
-
private func
|
|
349
|
-
switch
|
|
350
|
-
case "STEP_BY_STEP":
|
|
444
|
+
private func mapToCardFormType(_ cardFormType: String) -> CardFormType {
|
|
445
|
+
switch cardFormType.uppercased() {
|
|
446
|
+
case "STEP_BY_STEP", "TWO_STEPS":
|
|
351
447
|
return .multiStep
|
|
352
448
|
default:
|
|
353
449
|
return .oneStep
|
|
354
450
|
}
|
|
355
451
|
}
|
|
356
452
|
|
|
357
|
-
private func
|
|
358
|
-
switch
|
|
359
|
-
case .
|
|
453
|
+
private func mapResultToString(_ result: Yuno.Result) -> String {
|
|
454
|
+
switch result {
|
|
455
|
+
case .reject:
|
|
360
456
|
return "REJECTED"
|
|
361
457
|
case .succeeded:
|
|
362
458
|
return "SUCCEEDED"
|
|
363
|
-
case .
|
|
459
|
+
case .fail:
|
|
364
460
|
return "FAILED"
|
|
365
461
|
case .processing:
|
|
366
462
|
return "PROCESSING"
|
|
367
463
|
case .internalError:
|
|
368
464
|
return "INTERNAL_ERROR"
|
|
369
|
-
case .
|
|
465
|
+
case .userCancelled:
|
|
370
466
|
return "CANCELLED_BY_USER"
|
|
371
467
|
@unknown default:
|
|
372
468
|
return "INTERNAL_ERROR"
|
|
@@ -375,21 +471,55 @@ class YunoSdk: RCTEventEmitter {
|
|
|
375
471
|
|
|
376
472
|
// MARK: - Event Emitters
|
|
377
473
|
|
|
378
|
-
|
|
379
|
-
|
|
474
|
+
internal func sendPaymentStatusEvent(result: Yuno.Result, token: String? = nil) {
|
|
475
|
+
let statusString = mapResultToString(result)
|
|
476
|
+
sendPaymentStatusEvent(status: statusString, token: token)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
internal func sendPaymentStatusEvent(status: String, token: String? = nil) {
|
|
480
|
+
// If we just cleared the status and this is the same status as before,
|
|
481
|
+
// it's a stale event from the native SDK - ignore it
|
|
482
|
+
if isPaymentFlowCleared && status == lastPaymentStatus {
|
|
483
|
+
print("🚫 Ignoring stale payment status: \(status) (from previous flow)")
|
|
484
|
+
isPaymentFlowCleared = false
|
|
485
|
+
lastPaymentStatus = nil // Clear it now that we've ignored the stale event
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Reset the cleared flag after processing first real new event
|
|
490
|
+
if isPaymentFlowCleared {
|
|
491
|
+
print("✅ First new status after clear: \(status) (previous was: \(lastPaymentStatus ?? "nil"))")
|
|
492
|
+
isPaymentFlowCleared = false
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Store the current status for future comparison
|
|
496
|
+
lastPaymentStatus = status
|
|
497
|
+
|
|
498
|
+
print("✅ Emitting payment status: \(status)")
|
|
499
|
+
var params: [String: Any] = ["status": status]
|
|
380
500
|
if let token = token {
|
|
381
501
|
params["token"] = token
|
|
382
502
|
}
|
|
383
503
|
sendEvent(withName: "YunoPaymentStatus", body: params)
|
|
384
504
|
}
|
|
385
505
|
|
|
386
|
-
private func sendEnrollmentStatusEvent(
|
|
387
|
-
let params: [String: Any] = ["status":
|
|
506
|
+
private func sendEnrollmentStatusEvent(result: Yuno.Result) {
|
|
507
|
+
let params: [String: Any] = ["status": mapResultToString(result)]
|
|
388
508
|
sendEvent(withName: "YunoEnrollmentStatus", body: params)
|
|
389
509
|
}
|
|
390
510
|
|
|
391
|
-
|
|
511
|
+
internal func sendOneTimeTokenEvent(token: String) {
|
|
392
512
|
sendEvent(withName: "YunoOneTimeToken", body: token)
|
|
513
|
+
|
|
514
|
+
// Also emit the OneTimeTokenInfo event with the token
|
|
515
|
+
let tokenInfo: [String: Any] = [
|
|
516
|
+
"token": token
|
|
517
|
+
]
|
|
518
|
+
sendEvent(withName: "YunoOneTimeTokenInfo", body: tokenInfo)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private func sendOneTimeTokenInfoEvent(tokenInfo: [String: Any]) {
|
|
522
|
+
sendEvent(withName: "YunoOneTimeTokenInfo", body: tokenInfo)
|
|
393
523
|
}
|
|
394
524
|
}
|
|
395
525
|
|
|
@@ -399,44 +529,147 @@ extension YunoSdk: YunoPaymentDelegate {
|
|
|
399
529
|
sendOneTimeTokenEvent(token: token)
|
|
400
530
|
}
|
|
401
531
|
|
|
532
|
+
func yunoCreatePayment(with token: String, information: [String : Any]) {
|
|
533
|
+
// Send both token and token info
|
|
534
|
+
sendOneTimeTokenEvent(token: token)
|
|
535
|
+
sendOneTimeTokenInfoEvent(tokenInfo: information)
|
|
536
|
+
}
|
|
537
|
+
|
|
402
538
|
func yunoPaymentResult(_ result: Yuno.Result) {
|
|
403
|
-
sendPaymentStatusEvent(
|
|
539
|
+
sendPaymentStatusEvent(result: result)
|
|
404
540
|
}
|
|
405
541
|
|
|
406
542
|
var countryCode: String {
|
|
407
543
|
return currentCountryCode ?? ""
|
|
408
544
|
}
|
|
409
545
|
|
|
410
|
-
var
|
|
411
|
-
return
|
|
546
|
+
var language: String? {
|
|
547
|
+
return currentLanguage
|
|
412
548
|
}
|
|
413
549
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
case .succeeded:
|
|
417
|
-
return .succeeded
|
|
418
|
-
case .failed:
|
|
419
|
-
return .failed
|
|
420
|
-
case .rejected:
|
|
421
|
-
return .rejected
|
|
422
|
-
case .processing:
|
|
423
|
-
return .processing
|
|
424
|
-
case .cancelledByUser:
|
|
425
|
-
return .cancelledByUser
|
|
426
|
-
@unknown default:
|
|
427
|
-
return .internalError
|
|
428
|
-
}
|
|
550
|
+
var viewController: UIViewController? {
|
|
551
|
+
return RCTPresentedViewController()
|
|
429
552
|
}
|
|
430
553
|
}
|
|
431
554
|
|
|
432
555
|
// MARK: - YunoEnrollmentDelegate Extension
|
|
433
556
|
extension YunoSdk: YunoEnrollmentDelegate {
|
|
434
557
|
func yunoEnrollmentResult(_ result: Yuno.Result) {
|
|
435
|
-
sendEnrollmentStatusEvent(
|
|
558
|
+
sendEnrollmentStatusEvent(result: result)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// MARK: - Headless Payment Flow
|
|
563
|
+
extension YunoSdk {
|
|
564
|
+
/**
|
|
565
|
+
* Generate a one-time token (OTT) from collected payment data using the headless flow.
|
|
566
|
+
* This method mirrors the native SDK's generateToken() API.
|
|
567
|
+
*
|
|
568
|
+
* @param tokenCollectedData Dictionary containing checkout_session and payment_method data
|
|
569
|
+
* @param checkoutSession The checkout session ID
|
|
570
|
+
* @param countryCode The country code for the payment
|
|
571
|
+
* @param resolver Promise resolver
|
|
572
|
+
* @param rejecter Promise rejecter
|
|
573
|
+
*/
|
|
574
|
+
@objc
|
|
575
|
+
func generateToken(
|
|
576
|
+
_ tokenCollectedData: NSDictionary,
|
|
577
|
+
checkoutSession: String,
|
|
578
|
+
countryCode: String,
|
|
579
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
580
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
581
|
+
) {
|
|
582
|
+
Task { @MainActor [weak self] in
|
|
583
|
+
guard let self = self else { return }
|
|
584
|
+
|
|
585
|
+
do {
|
|
586
|
+
// Convert NSDictionary to JSON Data
|
|
587
|
+
guard JSONSerialization.isValidJSONObject(tokenCollectedData) else {
|
|
588
|
+
rejecter("INVALID_DATA", "Invalid token collected data", nil)
|
|
589
|
+
return
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
let jsonData = try JSONSerialization.data(withJSONObject: tokenCollectedData, options: [])
|
|
593
|
+
|
|
594
|
+
// Debug: Print the JSON string
|
|
595
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
596
|
+
print("🐛 iOS generateToken JSON: \(jsonString)")
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Decode to TokenCollectedData using the native SDK's Codable model
|
|
600
|
+
// Note: Don't use .convertFromSnakeCase because the SDK's models have custom decoders
|
|
601
|
+
// that already handle snake_case field names (checkout_session, payment_method, etc)
|
|
602
|
+
let decoder = JSONDecoder()
|
|
603
|
+
let collectedData = try decoder.decode(TokenCollectedData.self, from: jsonData)
|
|
604
|
+
|
|
605
|
+
// Create API client using the native SDK's factory method
|
|
606
|
+
let apiClient = Yuno.apiClientPayment(
|
|
607
|
+
countryCode: countryCode,
|
|
608
|
+
checkoutSession: checkoutSession
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
// Generate token using async/await (native SDK uses async throws)
|
|
612
|
+
let response = try await apiClient.generateToken(data: collectedData)
|
|
613
|
+
|
|
614
|
+
// The native SDK returns [String: Any] with token in "token" key
|
|
615
|
+
if let token = response["token"] as? String {
|
|
616
|
+
let responseDict: [String: Any] = ["token": token]
|
|
617
|
+
resolver(responseDict)
|
|
618
|
+
} else if let error = response["error"] as? String {
|
|
619
|
+
rejecter("TOKEN_GENERATION_ERROR", error, nil)
|
|
620
|
+
} else {
|
|
621
|
+
rejecter("TOKEN_GENERATION_ERROR", "No token in response", nil)
|
|
622
|
+
}
|
|
623
|
+
} catch {
|
|
624
|
+
rejecter("TOKEN_GENERATION_ERROR", "Failed to generate token: \(error.localizedDescription)", error)
|
|
625
|
+
}
|
|
626
|
+
}
|
|
436
627
|
}
|
|
437
628
|
|
|
438
|
-
|
|
439
|
-
|
|
629
|
+
/**
|
|
630
|
+
* Get the 3D Secure challenge URL for a checkout session.
|
|
631
|
+
* This method mirrors the native SDK's getThreeDSecureChallenge() API.
|
|
632
|
+
*
|
|
633
|
+
* @param checkoutSession The checkout session ID
|
|
634
|
+
* @param countryCode The country code for the payment
|
|
635
|
+
* @param resolver Promise resolver
|
|
636
|
+
* @param rejecter Promise rejecter
|
|
637
|
+
*/
|
|
638
|
+
@objc
|
|
639
|
+
func getThreeDSecureChallenge(
|
|
640
|
+
_ checkoutSession: String,
|
|
641
|
+
countryCode: String,
|
|
642
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
643
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
644
|
+
) {
|
|
645
|
+
Task { @MainActor [weak self] in
|
|
646
|
+
guard let self = self else { return }
|
|
647
|
+
|
|
648
|
+
do {
|
|
649
|
+
// Create API client using the native SDK's factory method
|
|
650
|
+
let apiClient = Yuno.apiClientPayment(
|
|
651
|
+
countryCode: countryCode,
|
|
652
|
+
checkoutSession: checkoutSession
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
// Get 3DS challenge using async/await (native SDK uses async throws)
|
|
656
|
+
let response = try await apiClient.getThreeDSecureChallenge(checkoutSession: checkoutSession)
|
|
657
|
+
|
|
658
|
+
// The native SDK returns ThreeDSecureChallengeResponse with url property
|
|
659
|
+
let responseDict: [String: Any] = [
|
|
660
|
+
"type": "URL",
|
|
661
|
+
"data": response.url
|
|
662
|
+
]
|
|
663
|
+
|
|
664
|
+
resolver(responseDict)
|
|
665
|
+
} catch {
|
|
666
|
+
rejecter("THREE_DS_ERROR", "Failed to get 3DS challenge: \(error.localizedDescription)", error)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
440
669
|
}
|
|
441
670
|
}
|
|
442
671
|
|
|
672
|
+
// Note: TokenCollectedData, CollectedData, CardData, and other related models
|
|
673
|
+
// are provided by the YunoSDK framework (imported at the top).
|
|
674
|
+
// We don't need to redefine them here.
|
|
675
|
+
|
|
@@ -42,11 +42,11 @@ const NativeYunoPaymentMethodsView = (0, _reactNative.requireNativeComponent)('Y
|
|
|
42
42
|
* YunoPaymentMethods Component
|
|
43
43
|
*
|
|
44
44
|
* A React Native component that displays available payment methods using the native Yuno SDK.
|
|
45
|
-
* This component embeds the native
|
|
45
|
+
* This component embeds the native payment methods list view from the Yuno SDK.
|
|
46
46
|
*
|
|
47
47
|
* **Platform Support:**
|
|
48
48
|
* - ✅ Android (using Jetpack Compose)
|
|
49
|
-
* -
|
|
49
|
+
* - ✅ iOS (using SwiftUI with UIHostingController)
|
|
50
50
|
*
|
|
51
51
|
* **Important:**
|
|
52
52
|
* - The Yuno SDK must be initialized before using this component
|
|
@@ -87,6 +87,10 @@ const NativeYunoPaymentMethodsView = (0, _reactNative.requireNativeComponent)('Y
|
|
|
87
87
|
* - User interactions and selections
|
|
88
88
|
* - Error states and loading indicators
|
|
89
89
|
*
|
|
90
|
+
* **Technical Note:** On iOS, uses `UIHostingController` to wrap the SwiftUI view from
|
|
91
|
+
* Yuno SDK's `getPaymentMethodViewAsync`, providing seamless integration with React Native.
|
|
92
|
+
* Android uses Jetpack Compose with native `PaymentMethodListViewComponent`.
|
|
93
|
+
*
|
|
90
94
|
* @public
|
|
91
95
|
*/
|
|
92
96
|
const YunoPaymentMethods = ({
|
|
@@ -94,7 +98,8 @@ const YunoPaymentMethods = ({
|
|
|
94
98
|
countryCode,
|
|
95
99
|
onPaymentMethodSelected,
|
|
96
100
|
onPaymentMethodError,
|
|
97
|
-
style
|
|
101
|
+
style,
|
|
102
|
+
testID
|
|
98
103
|
}) => {
|
|
99
104
|
// Set up event listeners
|
|
100
105
|
(0, _react.useEffect)(() => {
|
|
@@ -129,13 +134,8 @@ const YunoPaymentMethods = ({
|
|
|
129
134
|
});
|
|
130
135
|
};
|
|
131
136
|
}, [onPaymentMethodSelected, onPaymentMethodError]);
|
|
132
|
-
|
|
133
|
-
// Platform check
|
|
134
|
-
if (_reactNative.Platform.OS !== 'android') {
|
|
135
|
-
console.warn('YunoPaymentMethods: This component is currently only supported on Android. iOS support coming soon.');
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
137
|
return /*#__PURE__*/_react.default.createElement(NativeYunoPaymentMethodsView, {
|
|
138
|
+
testID: testID,
|
|
139
139
|
checkoutSession: checkoutSession,
|
|
140
140
|
countryCode: countryCode,
|
|
141
141
|
style: style
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","LINKING_ERROR","Platform","select","ios","YunoSdkNative","NativeModules","YunoSdk","eventEmitter","NativeEventEmitter","NativeYunoPaymentMethodsView","requireNativeComponent","YunoPaymentMethods","checkoutSession","countryCode","onPaymentMethodSelected","onPaymentMethodError","style","useEffect","console","warn","subscriptions","selectionListener","addListener","event","push","errorListener","forEach","sub","remove","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","LINKING_ERROR","Platform","select","ios","YunoSdkNative","NativeModules","YunoSdk","eventEmitter","NativeEventEmitter","NativeYunoPaymentMethodsView","requireNativeComponent","YunoPaymentMethods","checkoutSession","countryCode","onPaymentMethodSelected","onPaymentMethodError","style","testID","useEffect","console","warn","subscriptions","selectionListener","addListener","event","push","errorListener","forEach","sub","remove","createElement","exports"],"sourceRoot":"../../src","sources":["YunoPaymentMethods.tsx"],"mappings":"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","ignoreList":[]}
|