react-native-hyperswitch-dev-sdk 0.4.3 → 0.4.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.
Files changed (33) hide show
  1. package/HyperswitchSdkReactNative.podspec +4 -0
  2. package/android/build.gradle +0 -6
  3. package/ios/Modules/ReactNative/HyperModule.h +25 -0
  4. package/ios/Modules/ReactNative/HyperModule.mm +169 -0
  5. package/ios/Modules/ReactNative/HyperswitchModule.swift +223 -165
  6. package/ios/Modules/ReactNative/NativeHyperswitchModule.mm +17 -12
  7. package/ios/Modules/ReactNative/NativeHyperswitchModuleImpl.swift +18 -7
  8. package/ios/Modules/ReactNative/NativePaymentElementModule.mm +122 -122
  9. package/ios/Views/Fabric/NativePaymentElementView.mm +8 -0
  10. package/ios/Views/Native/NativePaymentWidget.swift +26 -22
  11. package/ios/hyperswitchSDK/Core/HyperCVCWidget/CVCWidget.swift +3 -2
  12. package/ios/hyperswitchSDK/Core/HyperPaymentCardTextField/PaymentHandler.swift +1 -1
  13. package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView+UIKit.swift +79 -13
  14. package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView.swift +2 -2
  15. package/ios/hyperswitchSDK/Core/HyperPaymentWidget/PaymentWidget.swift +5 -4
  16. package/ios/hyperswitchSDK/Core/HyperSession/PaymentSession+UIKit.swift +14 -0
  17. package/ios/hyperswitchSDK/Core/NativeModule/{HyperModule.swift → HyperModuleImpl.swift} +156 -113
  18. package/ios/hyperswitchSDK/Core/NativeModule/RNHeadlessManager.swift +58 -22
  19. package/ios/hyperswitchSDK/Core/NativeModule/RNViewManager.swift +84 -29
  20. package/ios/hyperswitchSDK/Core/NativeModule/hyper-Bridging-Header.h +2 -0
  21. package/ios/hyperswitchSDK/Public/RNBridgeFactory.h +28 -0
  22. package/ios/hyperswitchSDK/Public/RNBridgeFactory.mm +167 -0
  23. package/ios/hyperswitchSDK/Shared/PaymentSheet.swift +1 -1
  24. package/lib/commonjs/index.bundle.js +1 -1
  25. package/lib/commonjs/index.bundle.js.map +1 -1
  26. package/lib/module/index.bundle.js +1 -1
  27. package/lib/module/index.bundle.js.map +1 -1
  28. package/package.json +6 -2
  29. package/src/context/PaymentSession.ts +2 -0
  30. package/src/specs/NativeHyperswitchModule.ts +2 -2
  31. package/ios/Modules/ReactNative/HyperswitchSdkReactNative.h +0 -5
  32. package/ios/Modules/ReactNative/HyperswitchSdkReactNative.mm +0 -121
  33. package/ios/hyperswitchSDK/Core/NativeModule/HyperModule.m +0 -31
@@ -27,6 +27,58 @@ public class HyperswitchModule: NSObject {
27
27
  /// can resolve native widget views by reactTag.
28
28
  @objc public var viewRegistry_DEPRECATED: RCTViewRegistry?
29
29
 
30
+ // MARK: - JSON string helpers
31
+
32
+ /// Serialise a plain `[String: Any?]` to a compact JSON string.
33
+ /// Returns `"{}"` on failure so callers always receive a valid string.
34
+ private static func toJSONString(_ dict: [String: Any?]) -> String {
35
+ // JSONSerialization can't handle `Any?` keys that are nil, so strip them first.
36
+ let cleaned = dict.compactMapValues { $0 }
37
+ guard
38
+ let data = try? JSONSerialization.data(withJSONObject: cleaned, options: []),
39
+ let str = String(data: data, encoding: .utf8)
40
+ else { return "{}" }
41
+ return str
42
+ }
43
+
44
+ /// Encode an `Encodable` value to a JSON string.
45
+ private static func encodeToJSONString<T: Encodable>(_ value: T) -> String? {
46
+ guard
47
+ let data = try? JSONEncoder().encode(value),
48
+ let str = String(data: data, encoding: .utf8)
49
+ else { return nil }
50
+ return str
51
+ }
52
+
53
+ /// `{"code": ..., "message": ..., "status": ..., "error": ...}`
54
+ private static func standardResult(
55
+ status: String,
56
+ code: String? = nil,
57
+ message: String? = nil,
58
+ error: String? = nil
59
+ ) -> String {
60
+ return toJSONString([
61
+ "status": status,
62
+ "code": code as Any?,
63
+ "message": message as Any?,
64
+ "error": error as Any?,
65
+ ])
66
+ }
67
+
68
+ private static func paymentResultToJSONString(_ result: PaymentResult) -> String {
69
+ switch result {
70
+ case .completed:
71
+ return standardResult(status: "success")
72
+ case .failed(let error as NSError):
73
+ let msg = error.userInfo["message"] as? String ?? error.localizedDescription
74
+ return standardResult(status: "failed", code: error.domain, message: msg, error: msg)
75
+ case .canceled:
76
+ return standardResult(status: "cancelled", message: "Payment cancelled")
77
+ }
78
+ }
79
+
80
+ // MARK: - initialise
81
+
30
82
  @objc(initialiseWithPublishableKey:customBackendUrl:customLogUrl:customParams:resolve:reject:)
31
83
  public func initialise(
32
84
  publishableKey: String,
@@ -102,6 +154,8 @@ public class HyperswitchModule: NSObject {
102
154
  return shared.activeProfileId
103
155
  }
104
156
 
157
+ // MARK: - presentPaymentSheet
158
+
105
159
  @objc(presentPaymentSheet:resolve:reject:)
106
160
  public func presentPaymentSheet(
107
161
  configuration: [String: Any],
@@ -109,29 +163,32 @@ public class HyperswitchModule: NSObject {
109
163
  reject: @escaping RCTPromiseRejectBlock
110
164
  ) {
111
165
  guard let session = activePaymentSession else {
112
- reject("PRESENT_ERROR", "Payment session not initialized. Call initPaymentSession first.", NSError(domain: "HyperswitchModule", code: 0))
166
+ resolve(HyperswitchModule.standardResult(
167
+ status: "failed",
168
+ code: "PRESENT_ERROR",
169
+ message: "Payment session not initialized. Call initPaymentSession first.",
170
+ error: "Payment session not initialized."
171
+ ))
113
172
  return
114
173
  }
115
174
 
116
175
  DispatchQueue.main.async {
117
176
  guard let vc = RCTPresentedViewController() else {
118
- reject("error", "Could not find presented view controller", NSError())
177
+ resolve(HyperswitchModule.standardResult(
178
+ status: "failed",
179
+ code: "PRESENT_ERROR",
180
+ message: "Could not find presented view controller",
181
+ error: "Could not find presented view controller"
182
+ ))
119
183
  return
120
184
  }
121
185
  session.presentPaymentSheetWithParams(
122
186
  viewController: vc,
123
187
  params: configuration,
124
- completion: { result in
125
- switch result {
126
- case .completed(let data):
127
- resolve(["status": "completed", "message": data])
128
- case .failed(let error as NSError):
129
- resolve([
130
- "status": "failed", "code": error.domain, "message": "Payment failed: \(error.userInfo["message"] ?? "Failed")",
131
- ])
132
- case .canceled(let data):
133
- resolve(["status": "cancelled", "message": data])
134
- }
188
+ rawCompletion: { raw in
189
+ // `raw` is the unmodified JSON string sent by the JS bundle via
190
+ // exitPaymentsheet — identical to what Android resolves with.
191
+ resolve(raw)
135
192
  }
136
193
  )
137
194
  }
@@ -146,100 +203,119 @@ public class HyperswitchModule: NSObject {
146
203
  reject: @escaping RCTPromiseRejectBlock
147
204
  ) {
148
205
  guard let session = activePaymentSession else {
149
- resolve([
150
- "status": "error",
151
- "code": "NO_SESSION",
152
- "message": "Payment session not initialized. Call initPaymentSession first.",
153
- ])
206
+ resolve(HyperswitchModule.standardResult(
207
+ status: "failed",
208
+ code: "NO_SESSION",
209
+ message: "Payment session not initialized. Call initPaymentSession first.",
210
+ error: "Payment session not initialized."
211
+ ))
154
212
  return
155
213
  }
156
214
 
157
215
  session.getCustomerSavedPaymentMethods { [weak self] handler in
158
216
  self?.activePaymentSessionHandler = handler
159
- resolve(["status": "success", "message": "Payment methods initialized"])
217
+ resolve(HyperswitchModule.toJSONString([
218
+ "code": "success",
219
+ "message": "Saved payment methods is initialized",
220
+ ]))
160
221
  }
161
222
  }
162
223
 
224
+ /// Mirrors Android: resolves with the payment-method data JSON string directly (no wrapper).
163
225
  @objc(getCustomerDefaultSavedPaymentMethodDataWithResolve:reject:)
164
226
  public func getCustomerDefaultSavedPaymentMethodData(
165
227
  resolve: @escaping RCTPromiseResolveBlock,
166
228
  reject: @escaping RCTPromiseRejectBlock
167
229
  ) {
168
230
  guard let handler = activePaymentSessionHandler else {
169
- resolve([
170
- "status": "error",
171
- "message": "Payment session handler not initialized.",
172
- ])
231
+ resolve(HyperswitchModule.standardResult(
232
+ status: "failed",
233
+ code: "NO_HANDLER",
234
+ message: "Payment session handler not initialized.",
235
+ error: "Payment session handler not initialized."
236
+ ))
173
237
  return
174
238
  }
175
239
 
176
240
  let result = handler.getCustomerDefaultSavedPaymentMethodData()
177
241
  switch result {
178
242
  case .success(let paymentMethod):
179
- if let jsonData = try? JSONEncoder().encode(paymentMethod),
180
- let jsonDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
181
- {
182
- resolve([
183
- "status": "success",
184
- "message": "Default payment method retrieved",
185
- "data": jsonDict,
186
- ])
187
- } else {
188
- resolve([
189
- "status": "error",
190
- "code": "ENCODE_ERROR",
191
- "message": "Failed to encode payment method data",
192
- ])
193
- }
243
+ // Return the payment-method data JSON directly, matching Android's
244
+ // `ConversionUtils.convertMapToJson(data.toMap()).toString()`.
245
+ resolve(HyperswitchModule.encodeToJSONString(paymentMethod) ?? "{}")
194
246
  case .failure(let error):
195
- resolve([
196
- "status": "failed",
197
- "code": error.code,
198
- "message": error.message,
199
- ])
247
+ resolve(HyperswitchModule.standardResult(
248
+ status: "failed",
249
+ code: error.code,
250
+ message: error.message,
251
+ error: error.message
252
+ ))
200
253
  }
201
254
  }
202
255
 
256
+ /// Mirrors Android: resolves with the payment-method data JSON string directly (no wrapper).
203
257
  @objc(getCustomerLastUsedPaymentMethodDataWithResolve:reject:)
204
258
  public func getCustomerLastUsedPaymentMethodData(
205
259
  resolve: @escaping RCTPromiseResolveBlock,
206
260
  reject: @escaping RCTPromiseRejectBlock
207
261
  ) {
208
262
  guard let handler = activePaymentSessionHandler else {
209
- resolve([
210
- "status": "error",
211
- "message": "Payment session handler not initialized.",
212
- ])
263
+ resolve(HyperswitchModule.standardResult(
264
+ status: "failed",
265
+ code: "NO_HANDLER",
266
+ message: "Payment session handler not initialized.",
267
+ error: "Payment session handler not initialized."
268
+ ))
213
269
  return
214
270
  }
215
271
 
216
272
  let result = handler.getCustomerLastUsedPaymentMethodData()
217
273
  switch result {
218
274
  case .success(let paymentMethod):
219
- if let jsonData = try? JSONEncoder().encode(paymentMethod),
220
- let jsonDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
221
- {
222
- resolve([
223
- "status": "success",
224
- "message": "Last used payment method retrieved",
225
- "data": jsonDict,
226
- ])
227
- } else {
228
- resolve([
229
- "status": "error",
230
- "code": "ENCODE_ERROR",
231
- "message": "Failed to encode payment method",
232
- ])
233
- }
275
+ resolve(HyperswitchModule.encodeToJSONString(paymentMethod) ?? "{}")
234
276
  case .failure(let error):
235
- resolve([
236
- "status": "failed",
237
- "code": error.code,
238
- "message": error.message,
239
- ])
277
+ resolve(HyperswitchModule.standardResult(
278
+ status: "failed",
279
+ code: error.code,
280
+ message: error.message,
281
+ error: error.message
282
+ ))
240
283
  }
241
284
  }
242
285
 
286
+ /// Mirrors Android: resolves with a JSON array string of all saved payment methods.
287
+ @objc(getCustomerSavedPaymentMethodDataWithResolve:reject:)
288
+ public func getCustomerSavedPaymentMethodData(
289
+ resolve: @escaping RCTPromiseResolveBlock,
290
+ reject: @escaping RCTPromiseRejectBlock
291
+ ) {
292
+ guard let handler = activePaymentSessionHandler else {
293
+ resolve(HyperswitchModule.standardResult(
294
+ status: "failed",
295
+ code: "NO_HANDLER",
296
+ message: "Payment session handler not initialized.",
297
+ error: "Payment session handler not initialized."
298
+ ))
299
+ return
300
+ }
301
+
302
+ let result = handler.getCustomerSavedPaymentMethodData()
303
+ switch result {
304
+ case .success(let paymentMethods):
305
+ // Return a JSON array string, matching Android's `jsonArray.toString()`.
306
+ resolve(HyperswitchModule.encodeToJSONString(paymentMethods) ?? "[]")
307
+ case .failure(let error):
308
+ resolve(HyperswitchModule.standardResult(
309
+ status: "failed",
310
+ code: error.code,
311
+ message: error.message,
312
+ error: error.message
313
+ ))
314
+ }
315
+ }
316
+
317
+ // MARK: - Confirm methods
318
+
243
319
  @objc(confirmWithCustomerDefaultPaymentMethod:resolve:reject:)
244
320
  public func confirmWithCustomerDefaultPaymentMethod(
245
321
  cvcWidgetReactTag: String?,
@@ -247,56 +323,52 @@ public class HyperswitchModule: NSObject {
247
323
  reject: @escaping RCTPromiseRejectBlock
248
324
  ) {
249
325
  guard let handler = activePaymentSessionHandler else {
250
- resolve([
251
- "status": "error",
252
- "message": "Payment session handler not initialized.",
253
- ])
326
+ resolve(HyperswitchModule.standardResult(
327
+ status: "failed",
328
+ code: "NO_HANDLER",
329
+ message: "Payment session handler not initialized.",
330
+ error: "Payment session handler not initialized."
331
+ ))
254
332
  return
255
333
  }
256
334
 
257
335
  let reactTag = Int(cvcWidgetReactTag ?? "") ?? 0
258
336
 
259
337
  if reactTag > 0 {
260
- // CvcWidget reactTag provided — route through the native widget view
261
- // so we can use the inner RCTRootView tag (widgetReactTag).
262
338
  let result = handler.getCustomerDefaultSavedPaymentMethodData()
263
339
  switch result {
264
340
  case .success(let paymentMethod):
265
341
  if paymentMethod.requiresCvv && paymentMethod.paymentMethod == "card" {
266
342
  self.withNativePaymentWidgetView(
267
343
  NSNumber(value: reactTag),
268
- onFound: { view in
269
- view.confirmCVCPayment(
270
- paymentToken: paymentMethod.paymentToken,
271
- paymentMethodId: paymentMethod.paymentMethodId,
272
- resolve: resolve
273
- )
344
+ onFound: { _ in
345
+ // CVC widget confirm path — not yet fully wired; fall through.
274
346
  },
275
347
  onMissing: {
276
- resolve([
277
- "status": "failed",
278
- "code": "WIDGET_NOT_FOUND",
279
- "message": "CVC widget view not found for reactTag \(reactTag)",
280
- ])
348
+ resolve(HyperswitchModule.standardResult(
349
+ status: "failed",
350
+ code: "WIDGET_NOT_FOUND",
351
+ message: "CVC widget view not found for reactTag \(reactTag)",
352
+ error: "CVC widget view not found for reactTag \(reactTag)"
353
+ ))
281
354
  }
282
355
  )
283
356
  } else {
284
- // Not a card or requiresCvv is false — bypass CvcWidget, confirm directly with cvc = nil
285
357
  handler.confirmWithCustomerDefaultPaymentMethod { result in
286
- resolve(HyperswitchModule.paymentResultToDict(result))
358
+ resolve(HyperswitchModule.paymentResultToJSONString(result))
287
359
  }
288
360
  }
289
361
  case .failure(let error):
290
- resolve([
291
- "status": "failed",
292
- "code": error.code,
293
- "message": error.message,
294
- ])
362
+ resolve(HyperswitchModule.standardResult(
363
+ status: "failed",
364
+ code: error.code,
365
+ message: error.message,
366
+ error: error.message
367
+ ))
295
368
  }
296
369
  } else {
297
- // No CvcWidget — confirm through HeadlessTask callback (cvc will be nil)
298
370
  handler.confirmWithCustomerDefaultPaymentMethod { result in
299
- resolve(HyperswitchModule.paymentResultToDict(result))
371
+ resolve(HyperswitchModule.paymentResultToJSONString(result))
300
372
  }
301
373
  }
302
374
  }
@@ -308,66 +380,59 @@ public class HyperswitchModule: NSObject {
308
380
  reject: @escaping RCTPromiseRejectBlock
309
381
  ) {
310
382
  guard let handler = activePaymentSessionHandler else {
311
- resolve([
312
- "status": "error",
313
- "message": "Payment session handler not initialized.",
314
- ])
383
+ resolve(HyperswitchModule.standardResult(
384
+ status: "failed",
385
+ code: "NO_HANDLER",
386
+ message: "Payment session handler not initialized.",
387
+ error: "Payment session handler not initialized."
388
+ ))
315
389
  return
316
390
  }
317
391
 
318
392
  let reactTag = Int(cvcWidgetReactTag ?? "") ?? 0
319
393
 
320
394
  if reactTag > 0 {
321
- // CvcWidget reactTag provided — route through the native widget view
322
- // so we can use the inner RCTRootView tag (widgetReactTag).
323
395
  let result = handler.getCustomerLastUsedPaymentMethodData()
324
396
  switch result {
325
397
  case .success(let paymentMethod):
326
398
  if paymentMethod.requiresCvv && paymentMethod.paymentMethod == "card" {
327
399
  self.withNativePaymentWidgetView(
328
400
  NSNumber(value: reactTag),
329
- onFound: { view in
330
- guard let cvcWidget = view.cvcWidgetRef else {
331
- resolve([
332
- "status": "failed",
333
- "code": "WIDGET_NOT_READY",
334
- "message": "CVC widget is not ready",
335
- ])
336
- return
337
- }
338
- handler.confirmWithCustomerLastUsedPaymentMethod(cvcWidget) { result in
339
- resolve(HyperswitchModule.paymentResultToDict(result))
340
- }
401
+ onFound: { _ in
402
+ // CVC widget confirm path not yet fully wired; fall through.
341
403
  },
342
404
  onMissing: {
343
- resolve([
344
- "status": "failed",
345
- "code": "WIDGET_NOT_FOUND",
346
- "message": "CVC widget view not found for reactTag \(reactTag)",
347
- ])
405
+ resolve(HyperswitchModule.standardResult(
406
+ status: "failed",
407
+ code: "WIDGET_NOT_FOUND",
408
+ message: "CVC widget view not found for reactTag \(reactTag)",
409
+ error: "CVC widget view not found for reactTag \(reactTag)"
410
+ ))
348
411
  }
349
412
  )
350
413
  } else {
351
- // Not a card or requiresCvv is false — bypass CvcWidget, confirm directly with cvc = nil
352
- resolve([
353
- "status": "failed",
354
- "code": "CVC_WIDGET_REQUIRED",
355
- "message": "CVC widget is required to confirm the last used card payment method",
356
- ])
414
+ resolve(HyperswitchModule.standardResult(
415
+ status: "failed",
416
+ code: "CVC_WIDGET_REQUIRED",
417
+ message: "CVC widget is required to confirm the last used card payment method",
418
+ error: "CVC widget is required"
419
+ ))
357
420
  }
358
421
  case .failure(let error):
359
- resolve([
360
- "status": "failed",
361
- "code": error.code,
362
- "message": error.message,
363
- ])
422
+ resolve(HyperswitchModule.standardResult(
423
+ status: "failed",
424
+ code: error.code,
425
+ message: error.message,
426
+ error: error.message
427
+ ))
364
428
  }
365
429
  } else {
366
- resolve([
367
- "status": "failed",
368
- "code": "CVC_WIDGET_REQUIRED",
369
- "message": "CVC widget is required to confirm the last used payment method",
370
- ])
430
+ resolve(HyperswitchModule.standardResult(
431
+ status: "failed",
432
+ code: "CVC_WIDGET_REQUIRED",
433
+ message: "CVC widget is required to confirm the last used payment method",
434
+ error: "CVC widget is required"
435
+ ))
371
436
  }
372
437
  }
373
438
 
@@ -378,15 +443,17 @@ public class HyperswitchModule: NSObject {
378
443
  reject: @escaping RCTPromiseRejectBlock
379
444
  ) {
380
445
  guard let handler = activePaymentSessionHandler else {
381
- resolve([
382
- "status": "error",
383
- "message": "Payment session handler not initialized.",
384
- ])
446
+ resolve(HyperswitchModule.standardResult(
447
+ status: "failed",
448
+ code: "NO_HANDLER",
449
+ message: "Payment session handler not initialized.",
450
+ error: "Payment session handler not initialized."
451
+ ))
385
452
  return
386
453
  }
387
454
 
388
- handler.confirmWithCustomerPaymentToken(paymentToken: paymentToken) { result in
389
- resolve(HyperswitchModule.paymentResultToDict(result))
455
+ handler.confirmWithCustomerPaymentToken(paymentToken: paymentToken) { result in
456
+ resolve(HyperswitchModule.paymentResultToJSONString(result))
390
457
  }
391
458
  }
392
459
 
@@ -397,7 +464,11 @@ public class HyperswitchModule: NSObject {
397
464
  reject: @escaping RCTPromiseRejectBlock
398
465
  ) {
399
466
  guard let session = activePaymentSession else {
400
- reject("UPDATE_INTENT_ERROR", "Payment session not initialized. Call initPaymentSession first.", NSError(domain: "HyperswitchModule", code: 0))
467
+ reject(
468
+ "UPDATE_INTENT_ERROR",
469
+ "Payment session not initialized. Call initPaymentSession first.",
470
+ NSError(domain: "HyperswitchModule", code: 0)
471
+ )
401
472
  return
402
473
  }
403
474
 
@@ -408,9 +479,9 @@ public class HyperswitchModule: NSObject {
408
479
  completion: { result in
409
480
  switch result {
410
481
  case .success:
411
- resolve(["status": "success", "message": "Payment intent updated"])
482
+ resolve(HyperswitchModule.standardResult(status: "success", message: "Payment intent updated"))
412
483
  case .cancelled:
413
- resolve(["status": "cancelled", "message": "Payment intent update cancelled"])
484
+ resolve(HyperswitchModule.standardResult(status: "cancelled", message: "Payment intent update cancelled"))
414
485
  case .failure(let error as NSError):
415
486
  reject(error.domain, error.userInfo[NSLocalizedDescriptionKey] as? String ?? "Payment intent update failed", error)
416
487
  }
@@ -420,12 +491,9 @@ public class HyperswitchModule: NSObject {
420
491
 
421
492
  // MARK: - CvcWidget View Lookup
422
493
 
423
- /// Looks up the NativePaymentWidgetView for the supplied React tag by walking up the view
424
- /// hierarchy, mirroring the approach used in HyperModule. Calls `onFound` when the widget
425
- /// wrapper is located, otherwise `onMissing`.
426
494
  private func withNativePaymentWidgetView(
427
495
  _ reactTag: NSNumber,
428
- onFound: @escaping (NativePaymentWidgetView) -> Void,
496
+ onFound: @escaping (PaymentWidget) -> Void,
429
497
  onMissing: @escaping () -> Void
430
498
  ) {
431
499
  guard let viewRegistry = self.viewRegistry_DEPRECATED else {
@@ -435,7 +503,7 @@ public class HyperswitchModule: NSObject {
435
503
  let view = viewRegistry.view(forReactTag: reactTag)
436
504
  var current: UIView? = view
437
505
  while let v = current {
438
- if let nativeWidget = v as? NativePaymentWidgetView {
506
+ if let nativeWidget = v as? PaymentWidget {
439
507
  onFound(nativeWidget)
440
508
  return
441
509
  }
@@ -444,27 +512,17 @@ public class HyperswitchModule: NSObject {
444
512
  onMissing()
445
513
  }
446
514
 
447
- /// Convert a PaymentResult to a dictionary suitable for RCTPromiseResolveBlock.
448
- private static func paymentResultToDict(_ result: PaymentResult) -> [String: Any] {
515
+ // MARK: - Internal helpers (used by HyperswitchModule.mm)
516
+
517
+ internal static func paymentResultToDict(_ result: PaymentResult) -> [String: Any] {
449
518
  switch result {
450
- case .completed(let data):
451
- return [
452
- "status": "success",
453
- "message": "Payment confirmed successfully",
454
- "data": data,
455
- ]
519
+ case .completed:
520
+ return ["status": "success", "code": NSNull(), "message": NSNull(), "error": NSNull()]
456
521
  case .failed(let error as NSError):
457
- return [
458
- "status": "failed",
459
- "code": error.domain,
460
- "message": error.userInfo["message"] as? String ?? "Payment confirmation failed",
461
- ]
462
- case .canceled(let data):
463
- return [
464
- "status": "cancelled",
465
- "message": "Payment confirmation cancelled",
466
- "data": data,
467
- ]
522
+ let msg = error.userInfo["message"] as? String ?? error.localizedDescription
523
+ return ["status": "failed", "code": error.domain, "message": msg, "error": msg]
524
+ case .canceled:
525
+ return ["status": "cancelled", "code": NSNull(), "message": "Payment cancelled", "error": NSNull()]
468
526
  }
469
527
  }
470
528
  }
@@ -65,10 +65,11 @@ RCT_EXPORT_METHOD(presentPaymentSheet:(nonnull NSDictionary *)params
65
65
 
66
66
  // ---------------------------------------------------------------------------
67
67
  // getCustomerSavedPaymentMethods
68
- // JS: getCustomerSavedPaymentMethods(params?: sessionData)
68
+ // JS: getCustomerSavedPaymentMethods(params?: Object)
69
+ // iOS codegen: getCustomerSavedPaymentMethods(NSDictionary *params, Promise)
69
70
  // Android: getCustomerSavedPaymentMethods(@Nullable ReadableMap params, Promise)
70
71
  // ---------------------------------------------------------------------------
71
- RCT_EXPORT_METHOD(getCustomerSavedPaymentMethods:(nullable NSDictionary *)params
72
+ RCT_EXPORT_METHOD(getCustomerSavedPaymentMethods:(nonnull NSDictionary *)params
72
73
  resolve:(nonnull RCTPromiseResolveBlock)resolve
73
74
  reject:(nonnull RCTPromiseRejectBlock)reject)
74
75
  {
@@ -111,25 +112,29 @@ RCT_EXPORT_METHOD(getCustomerSavedPaymentMethodData:(nonnull RCTPromiseResolveBl
111
112
  }
112
113
 
113
114
  // ---------------------------------------------------------------------------
114
- // confirmWithCustomerLastUsedPaymentMethod (no params — matches Android)
115
- // Android: confirmWithCustomerLastUsedPaymentMethod(Promise?) TODO
115
+ // confirmWithCustomerLastUsedPaymentMethod
116
+ // JS / Android: confirmWithCustomerLastUsedPaymentMethod(reactTag: number): Promise<string>
116
117
  // ---------------------------------------------------------------------------
117
- RCT_EXPORT_METHOD(confirmWithCustomerLastUsedPaymentMethod:(nonnull RCTPromiseResolveBlock)resolve
118
+ RCT_EXPORT_METHOD(confirmWithCustomerLastUsedPaymentMethod:(double)reactTag
119
+ resolve:(nonnull RCTPromiseResolveBlock)resolve
118
120
  reject:(nonnull RCTPromiseRejectBlock)reject)
119
121
  {
120
- [[self moduleImpl] confirmWithCustomerLastUsedPaymentMethodWithResolve:resolve
121
- reject:reject];
122
+ [[self moduleImpl] confirmWithCustomerLastUsedPaymentMethod:reactTag
123
+ resolve:resolve
124
+ reject:reject];
122
125
  }
123
126
 
124
127
  // ---------------------------------------------------------------------------
125
- // confirmWithCustomerDefaultPaymentMethod (no params — matches Android)
126
- // Android: confirmWithCustomerDefaultPaymentMethod(Promise?) TODO
128
+ // confirmWithCustomerDefaultPaymentMethod
129
+ // JS / Android: confirmWithCustomerDefaultPaymentMethod(reactTag: number): Promise<string>
127
130
  // ---------------------------------------------------------------------------
128
- RCT_EXPORT_METHOD(confirmWithCustomerDefaultPaymentMethod:(nonnull RCTPromiseResolveBlock)resolve
131
+ RCT_EXPORT_METHOD(confirmWithCustomerDefaultPaymentMethod:(double)reactTag
132
+ resolve:(nonnull RCTPromiseResolveBlock)resolve
129
133
  reject:(nonnull RCTPromiseRejectBlock)reject)
130
134
  {
131
- [[self moduleImpl] confirmWithCustomerDefaultPaymentMethodWithResolve:resolve
132
- reject:reject];
135
+ [[self moduleImpl] confirmWithCustomerDefaultPaymentMethod:reactTag
136
+ resolve:resolve
137
+ reject:reject];
133
138
  }
134
139
 
135
140
  // ---------------------------------------------------------------------------