@onramper/onramper-react-native 1.2.2-1 → 1.3.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +45 -3
  3. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  4. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios.swiftinterface +107 -4
  5. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/OnramperSDK +0 -0
  6. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/DWARF/OnramperSDK +0 -0
  7. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/OnramperSDK.yml +3560 -2684
  8. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  9. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +107 -4
  10. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  11. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +107 -4
  12. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/OnramperSDK +0 -0
  13. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/DWARF/OnramperSDK +0 -0
  14. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/OnramperSDK.yml +3560 -2684
  15. package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/OnramperSDK.yml +3523 -2651
  16. package/ios/HybridOnramperCheckoutButton.swift +1 -1
  17. package/ios/HybridOnramperNitro.swift +140 -78
  18. package/ios/OnramperSDKBridge.swift +19 -2
  19. package/ios/PreparedIntentRegistry.swift +124 -6
  20. package/ios/SDKLogRelay.swift +41 -0
  21. package/lib/commonjs/OnramperClient.js +66 -5
  22. package/lib/commonjs/OnramperClient.js.map +1 -1
  23. package/lib/commonjs/errors.js +19 -5
  24. package/lib/commonjs/errors.js.map +1 -1
  25. package/lib/module/OnramperClient.js +66 -5
  26. package/lib/module/OnramperClient.js.map +1 -1
  27. package/lib/module/errors.js +19 -5
  28. package/lib/module/errors.js.map +1 -1
  29. package/lib/typescript/commonjs/src/OnramperClient.d.ts +5 -1
  30. package/lib/typescript/commonjs/src/OnramperClient.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/src/errors.d.ts +6 -1
  32. package/lib/typescript/commonjs/src/errors.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/src/events.d.ts +10 -0
  34. package/lib/typescript/commonjs/src/events.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/src/index.d.ts +1 -1
  36. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/src/specs/OnramperNitro.nitro.d.ts +3 -0
  38. package/lib/typescript/commonjs/src/specs/OnramperNitro.nitro.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/src/types.d.ts +32 -3
  40. package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
  41. package/lib/typescript/module/src/OnramperClient.d.ts +5 -1
  42. package/lib/typescript/module/src/OnramperClient.d.ts.map +1 -1
  43. package/lib/typescript/module/src/errors.d.ts +6 -1
  44. package/lib/typescript/module/src/errors.d.ts.map +1 -1
  45. package/lib/typescript/module/src/events.d.ts +10 -0
  46. package/lib/typescript/module/src/events.d.ts.map +1 -1
  47. package/lib/typescript/module/src/index.d.ts +1 -1
  48. package/lib/typescript/module/src/index.d.ts.map +1 -1
  49. package/lib/typescript/module/src/specs/OnramperNitro.nitro.d.ts +3 -0
  50. package/lib/typescript/module/src/specs/OnramperNitro.nitro.d.ts.map +1 -1
  51. package/lib/typescript/module/src/types.d.ts +32 -3
  52. package/lib/typescript/module/src/types.d.ts.map +1 -1
  53. package/nitrogen/generated/ios/OnramperReactNative-Swift-Cxx-Bridge.cpp +3 -3
  54. package/nitrogen/generated/ios/OnramperReactNative-Swift-Cxx-Bridge.hpp +7 -7
  55. package/nitrogen/generated/ios/c++/HybridOnramperNitroSpecSwift.hpp +6 -0
  56. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +5 -5
  57. package/nitrogen/generated/ios/swift/HybridOnramperNitroSpec.swift +1 -0
  58. package/nitrogen/generated/ios/swift/HybridOnramperNitroSpec_cxx.swift +27 -11
  59. package/nitrogen/generated/ios/swift/OnramperNitroConfig.swift +16 -6
  60. package/nitrogen/generated/shared/c++/HybridOnramperNitroSpec.cpp +1 -0
  61. package/nitrogen/generated/shared/c++/HybridOnramperNitroSpec.hpp +1 -0
  62. package/nitrogen/generated/shared/c++/OnramperNitroConfig.hpp +10 -2
  63. package/package.json +3 -2
  64. package/src/OnramperClient.ts +67 -4
  65. package/src/errors.ts +29 -6
  66. package/src/events.ts +5 -0
  67. package/src/index.ts +3 -0
  68. package/src/specs/OnramperNitro.nitro.ts +3 -0
  69. package/src/types.ts +36 -3
@@ -84,7 +84,7 @@ open class HybridOnramperNitroSpec_cxx {
84
84
  }
85
85
  }
86
86
 
87
-
87
+
88
88
 
89
89
  /**
90
90
  * Get the memory size of the Swift class (plus size of any other allocations)
@@ -121,7 +121,7 @@ open class HybridOnramperNitroSpec_cxx {
121
121
  }
122
122
 
123
123
  // Properties
124
-
124
+
125
125
 
126
126
  // Methods
127
127
  @inline(__always)
@@ -142,7 +142,7 @@ open class HybridOnramperNitroSpec_cxx {
142
142
  return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
143
143
  }
144
144
  }
145
-
145
+
146
146
  @inline(__always)
147
147
  public final func initialize(sessionId: std.string, sessionToken: std.string) -> bridge.Result_std__shared_ptr_Promise_void___ {
148
148
  do {
@@ -161,7 +161,7 @@ open class HybridOnramperNitroSpec_cxx {
161
161
  return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
162
162
  }
163
163
  }
164
-
164
+
165
165
  @inline(__always)
166
166
  public final func reset() -> bridge.Result_std__shared_ptr_Promise_void___ {
167
167
  do {
@@ -180,7 +180,7 @@ open class HybridOnramperNitroSpec_cxx {
180
180
  return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
181
181
  }
182
182
  }
183
-
183
+
184
184
  @inline(__always)
185
185
  public final func signOut() -> bridge.Result_std__shared_ptr_Promise_void___ {
186
186
  do {
@@ -199,7 +199,7 @@ open class HybridOnramperNitroSpec_cxx {
199
199
  return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
200
200
  }
201
201
  }
202
-
202
+
203
203
  @inline(__always)
204
204
  public final func getCheckoutRequirements(requestJson: std.string, prefillJson: std.string, styleJson: std.string) -> bridge.Result_std__shared_ptr_Promise_PreparedIntentResult___ {
205
205
  do {
@@ -218,7 +218,7 @@ open class HybridOnramperNitroSpec_cxx {
218
218
  return bridge.create_Result_std__shared_ptr_Promise_PreparedIntentResult___(__exceptionPtr)
219
219
  }
220
220
  }
221
-
221
+
222
222
  @inline(__always)
223
223
  public final func cancelPreparedIntent(intentHandle: std.string) -> bridge.Result_std__shared_ptr_Promise_void___ {
224
224
  do {
@@ -237,7 +237,23 @@ open class HybridOnramperNitroSpec_cxx {
237
237
  return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
238
238
  }
239
239
  }
240
-
240
+
241
+ @inline(__always)
242
+ public final func setLogListener(onLog: bridge.Func_void_std__string) -> bridge.Result_void_ {
243
+ do {
244
+ try self.__implementation.setLogListener(onLog: { () -> (String) -> Void in
245
+ let __wrappedFunction = bridge.wrap_Func_void_std__string(onLog)
246
+ return { (__recordJson: String) -> Void in
247
+ __wrappedFunction.call(std.string(__recordJson))
248
+ }
249
+ }())
250
+ return bridge.create_Result_void_()
251
+ } catch (let __error) {
252
+ let __exceptionPtr = __error.toCpp()
253
+ return bridge.create_Result_void_(__exceptionPtr)
254
+ }
255
+ }
256
+
241
257
  @inline(__always)
242
258
  public final func setStateListener(onState: bridge.Func_void_std__string) -> bridge.Result_void_ {
243
259
  do {
@@ -253,7 +269,7 @@ open class HybridOnramperNitroSpec_cxx {
253
269
  return bridge.create_Result_void_(__exceptionPtr)
254
270
  }
255
271
  }
256
-
272
+
257
273
  @inline(__always)
258
274
  public final func setEventListener(onEvent: bridge.Func_void_std__string) -> bridge.Result_void_ {
259
275
  do {
@@ -269,7 +285,7 @@ open class HybridOnramperNitroSpec_cxx {
269
285
  return bridge.create_Result_void_(__exceptionPtr)
270
286
  }
271
287
  }
272
-
288
+
273
289
  @inline(__always)
274
290
  public final func setTransactionIdListener(onTransactionId: bridge.Func_void_std__optional_std__string_) -> bridge.Result_void_ {
275
291
  do {
@@ -291,7 +307,7 @@ open class HybridOnramperNitroSpec_cxx {
291
307
  return bridge.create_Result_void_(__exceptionPtr)
292
308
  }
293
309
  }
294
-
310
+
295
311
  @inline(__always)
296
312
  public final func setSessionExpirationHandler(handler: bridge.Func_std__shared_ptr_Promise_std__shared_ptr_Promise_NitroSessionCredentials____) -> bridge.Result_void_ {
297
313
  do {
@@ -18,32 +18,42 @@ public extension OnramperNitroConfig {
18
18
  /**
19
19
  * Create a new instance of `OnramperNitroConfig`.
20
20
  */
21
- init(apiKey: String, clientId: String, environment: String, theme: String, logLevel: String) {
22
- self.init(std.string(apiKey), std.string(clientId), std.string(environment), std.string(theme), std.string(logLevel))
21
+ init(apiKey: String, clientId: String, environment: String, theme: String, logLevel: String, isEventLoggingEnabled: Bool, isEventConsoleLoggingEnabled: Bool) {
22
+ self.init(std.string(apiKey), std.string(clientId), std.string(environment), std.string(theme), std.string(logLevel), isEventLoggingEnabled, isEventConsoleLoggingEnabled)
23
23
  }
24
24
 
25
25
  @inline(__always)
26
26
  var apiKey: String {
27
27
  return String(self.__apiKey)
28
28
  }
29
-
29
+
30
30
  @inline(__always)
31
31
  var clientId: String {
32
32
  return String(self.__clientId)
33
33
  }
34
-
34
+
35
35
  @inline(__always)
36
36
  var environment: String {
37
37
  return String(self.__environment)
38
38
  }
39
-
39
+
40
40
  @inline(__always)
41
41
  var theme: String {
42
42
  return String(self.__theme)
43
43
  }
44
-
44
+
45
45
  @inline(__always)
46
46
  var logLevel: String {
47
47
  return String(self.__logLevel)
48
48
  }
49
+
50
+ @inline(__always)
51
+ var isEventLoggingEnabled: Bool {
52
+ return self.__isEventLoggingEnabled
53
+ }
54
+
55
+ @inline(__always)
56
+ var isEventConsoleLoggingEnabled: Bool {
57
+ return self.__isEventConsoleLoggingEnabled
58
+ }
49
59
  }
@@ -20,6 +20,7 @@ namespace margelo::nitro::onramper {
20
20
  prototype.registerHybridMethod("signOut", &HybridOnramperNitroSpec::signOut);
21
21
  prototype.registerHybridMethod("getCheckoutRequirements", &HybridOnramperNitroSpec::getCheckoutRequirements);
22
22
  prototype.registerHybridMethod("cancelPreparedIntent", &HybridOnramperNitroSpec::cancelPreparedIntent);
23
+ prototype.registerHybridMethod("setLogListener", &HybridOnramperNitroSpec::setLogListener);
23
24
  prototype.registerHybridMethod("setStateListener", &HybridOnramperNitroSpec::setStateListener);
24
25
  prototype.registerHybridMethod("setEventListener", &HybridOnramperNitroSpec::setEventListener);
25
26
  prototype.registerHybridMethod("setTransactionIdListener", &HybridOnramperNitroSpec::setTransactionIdListener);
@@ -65,6 +65,7 @@ namespace margelo::nitro::onramper {
65
65
  virtual std::shared_ptr<Promise<void>> signOut() = 0;
66
66
  virtual std::shared_ptr<Promise<PreparedIntentResult>> getCheckoutRequirements(const std::string& requestJson, const std::string& prefillJson, const std::string& styleJson) = 0;
67
67
  virtual std::shared_ptr<Promise<void>> cancelPreparedIntent(const std::string& intentHandle) = 0;
68
+ virtual void setLogListener(const std::function<void(const std::string& /* recordJson */)>& onLog) = 0;
68
69
  virtual void setStateListener(const std::function<void(const std::string& /* stateJson */)>& onState) = 0;
69
70
  virtual void setEventListener(const std::function<void(const std::string& /* eventJson */)>& onEvent) = 0;
70
71
  virtual void setTransactionIdListener(const std::function<void(const std::optional<std::string>& /* transactionId */)>& onTransactionId) = 0;
@@ -44,10 +44,12 @@ namespace margelo::nitro::onramper {
44
44
  std::string environment SWIFT_PRIVATE;
45
45
  std::string theme SWIFT_PRIVATE;
46
46
  std::string logLevel SWIFT_PRIVATE;
47
+ bool isEventLoggingEnabled SWIFT_PRIVATE;
48
+ bool isEventConsoleLoggingEnabled SWIFT_PRIVATE;
47
49
 
48
50
  public:
49
51
  OnramperNitroConfig() = default;
50
- explicit OnramperNitroConfig(std::string apiKey, std::string clientId, std::string environment, std::string theme, std::string logLevel): apiKey(apiKey), clientId(clientId), environment(environment), theme(theme), logLevel(logLevel) {}
52
+ explicit OnramperNitroConfig(std::string apiKey, std::string clientId, std::string environment, std::string theme, std::string logLevel, bool isEventLoggingEnabled, bool isEventConsoleLoggingEnabled): apiKey(apiKey), clientId(clientId), environment(environment), theme(theme), logLevel(logLevel), isEventLoggingEnabled(isEventLoggingEnabled), isEventConsoleLoggingEnabled(isEventConsoleLoggingEnabled) {}
51
53
 
52
54
  public:
53
55
  friend bool operator==(const OnramperNitroConfig& lhs, const OnramperNitroConfig& rhs) = default;
@@ -67,7 +69,9 @@ namespace margelo::nitro {
67
69
  JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "clientId"))),
68
70
  JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "environment"))),
69
71
  JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "theme"))),
70
- JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "logLevel")))
72
+ JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "logLevel"))),
73
+ JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isEventLoggingEnabled"))),
74
+ JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isEventConsoleLoggingEnabled")))
71
75
  );
72
76
  }
73
77
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::onramper::OnramperNitroConfig& arg) {
@@ -77,6 +81,8 @@ namespace margelo::nitro {
77
81
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "environment"), JSIConverter<std::string>::toJSI(runtime, arg.environment));
78
82
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "theme"), JSIConverter<std::string>::toJSI(runtime, arg.theme));
79
83
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "logLevel"), JSIConverter<std::string>::toJSI(runtime, arg.logLevel));
84
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "isEventLoggingEnabled"), JSIConverter<bool>::toJSI(runtime, arg.isEventLoggingEnabled));
85
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "isEventConsoleLoggingEnabled"), JSIConverter<bool>::toJSI(runtime, arg.isEventConsoleLoggingEnabled));
80
86
  return obj;
81
87
  }
82
88
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -92,6 +98,8 @@ namespace margelo::nitro {
92
98
  if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "environment")))) return false;
93
99
  if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "theme")))) return false;
94
100
  if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "logLevel")))) return false;
101
+ if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isEventLoggingEnabled")))) return false;
102
+ if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isEventConsoleLoggingEnabled")))) return false;
95
103
  return true;
96
104
  }
97
105
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onramper/onramper-react-native",
3
- "version": "1.2.2-1",
3
+ "version": "1.3.0",
4
4
  "description": "React Native wrapper for the Onramper iOS SDK",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -41,6 +41,7 @@
41
41
  "lint": "biome check src",
42
42
  "format": "biome format --write src",
43
43
  "test": "jest",
44
+ "test:native": "bash scripts/test-native.sh",
44
45
  "typecheck": "tsc --noEmit",
45
46
  "nitrogen": "nitrogen && node scripts/sync-view-config.js",
46
47
  "prepare": "bob build",
@@ -64,7 +65,7 @@
64
65
  "license": "Apache-2.0",
65
66
  "homepage": "https://github.com/onramper/onramper-react-native#readme",
66
67
  "onramperSDK": {
67
- "checksum": "754184c10a736e1db3292b592ca634ddbaa61dd01540b60610df4aa73a61de9a"
68
+ "checksum": "7611f869eab69109b317bb2b81e23c99f9d6f108bb6425ed2aace5cc4d5ad766"
68
69
  },
69
70
  "peerDependencies": {
70
71
  "react": ">=19.0.0",
@@ -8,6 +8,7 @@ import type {
8
8
  CheckoutButtonStyle,
9
9
  CheckoutRequest,
10
10
  OnramperConfiguration,
11
+ OnramperLogRecord,
11
12
  OnramperState,
12
13
  OnramperUserPrefill,
13
14
  QuoteResponse,
@@ -42,10 +43,24 @@ export class OnramperClient {
42
43
  // Resolved when the constructor-issued configure() completes. Public methods
43
44
  // that need the native client await this first.
44
45
  private readonly configured: Promise<void>;
46
+ private destroyed = false;
45
47
 
46
48
  constructor(config: OnramperConfiguration) {
49
+ if (config.logHandler) {
50
+ const handler = config.logHandler;
51
+ this.native.setLogListener((json) => {
52
+ if (this.destroyed) return;
53
+ try {
54
+ handler(JSON.parse(json) as OnramperLogRecord);
55
+ } catch {
56
+ // Diagnostics must never interrupt checkout; do not log callback data.
57
+ console.warn('Onramper logHandler failed.');
58
+ }
59
+ });
60
+ }
47
61
  // Single native state callback → update the mirror + fan out to listeners.
48
62
  this.native.setStateListener((json) => {
63
+ if (this.destroyed) return;
49
64
  const s = JSON.parse(json) as OnramperState;
50
65
  this.state = s;
51
66
  if (s.kind === 'failed') this.lastError = OnramperError.from(s.error);
@@ -55,6 +70,7 @@ export class OnramperClient {
55
70
  // Single native checkout-event callback → keep the transaction ID mirror
56
71
  // synchronized at finalization, then fan out to checkout listeners.
57
72
  this.native.setEventListener((json) => {
73
+ if (this.destroyed) return;
58
74
  const e = JSON.parse(json) as CheckoutEvent;
59
75
  if (e.type === 'checkoutFinalized') {
60
76
  this.updateCurrentTransactionId(e.response.onramperTransactionId);
@@ -63,6 +79,7 @@ export class OnramperClient {
63
79
  });
64
80
 
65
81
  this.native.setTransactionIdListener((transactionId) => {
82
+ if (this.destroyed) return;
66
83
  // The SDK transiently publishes nil while re-preparing after the payment
67
84
  // sheet is dismissed. The finalized transaction ID remains durable across
68
85
  // that attempt change; only explicit reset()/signOut() clear the JS mirror.
@@ -73,7 +90,9 @@ export class OnramperClient {
73
90
  // Nitro stores the async callback natively and awaits it directly — no
74
91
  // event round-trip needed.
75
92
  this.native.setSessionExpirationHandler(async () => {
93
+ this.assertActive();
76
94
  const creds = await config.onSessionExpired();
95
+ this.assertActive();
77
96
  return { sessionId: creds.sessionId, sessionToken: creds.sessionToken };
78
97
  });
79
98
 
@@ -84,10 +103,12 @@ export class OnramperClient {
84
103
  environment: config.environment,
85
104
  theme: config.theme ?? 'system',
86
105
  logLevel: config.logLevel ?? 'off',
106
+ isEventLoggingEnabled: config.isEventLoggingEnabled ?? false,
107
+ isEventConsoleLoggingEnabled: config.isEventConsoleLoggingEnabled ?? false,
87
108
  })
88
109
  .catch((e: unknown) => {
89
110
  const err = OnramperError.from(e);
90
- this.lastError = err;
111
+ if (!this.destroyed) this.lastError = err;
91
112
  throw err;
92
113
  });
93
114
  // Swallow the unhandled-rejection warning when no one awaits .configured
@@ -95,11 +116,23 @@ export class OnramperClient {
95
116
  this.configured.catch(() => undefined);
96
117
  }
97
118
 
119
+ private assertActive(): void {
120
+ if (this.destroyed) {
121
+ throw new OnramperError({
122
+ code: 'clientDestroyed',
123
+ message: 'This OnramperClient has been destroyed. Create a new client before making SDK calls.',
124
+ userMessage: 'This checkout session has ended. Please reopen checkout.',
125
+ });
126
+ }
127
+ }
128
+
98
129
  private updateCurrentTransactionId(next: string | null): void {
130
+ if (this.destroyed) return;
99
131
  if (next === this.currentTransactionId) return;
100
132
 
101
133
  this.currentTransactionId = next;
102
134
  for (const fn of [...this.transactionIdListeners]) {
135
+ if (this.destroyed) break;
103
136
  try {
104
137
  fn(next);
105
138
  } catch (error: unknown) {
@@ -110,19 +143,27 @@ export class OnramperClient {
110
143
 
111
144
  async initialize(creds: SessionCredentials): Promise<void> {
112
145
  try {
146
+ this.assertActive();
113
147
  await this.configured;
148
+ this.assertActive();
114
149
  await this.native.initialize(creds.sessionId, creds.sessionToken);
150
+ this.assertActive();
115
151
  } catch (e: unknown) {
152
+ this.assertActive();
116
153
  throw OnramperError.from(e);
117
154
  }
118
155
  }
119
156
 
120
157
  async reset(): Promise<void> {
121
158
  try {
159
+ this.assertActive();
122
160
  await this.configured;
161
+ this.assertActive();
123
162
  await this.native.reset();
163
+ this.assertActive();
124
164
  this.updateCurrentTransactionId(null);
125
165
  } catch (e: unknown) {
166
+ this.assertActive();
126
167
  throw OnramperError.from(e);
127
168
  }
128
169
  }
@@ -135,10 +176,14 @@ export class OnramperClient {
135
176
  */
136
177
  async signOut(): Promise<void> {
137
178
  try {
179
+ this.assertActive();
138
180
  await this.configured;
181
+ this.assertActive();
139
182
  await this.native.signOut();
183
+ this.assertActive();
140
184
  this.updateCurrentTransactionId(null);
141
185
  } catch (e: unknown) {
186
+ this.assertActive();
142
187
  throw OnramperError.from(e);
143
188
  }
144
189
  }
@@ -147,6 +192,8 @@ export class OnramperClient {
147
192
  * Fetches checkout requirements + quote and returns a native checkout button
148
193
  * to render plus the quote. The button handles login / finalize / webview
149
194
  * internally; outcomes surface via the event stream (`addEventListener`).
195
+ * A newer preparation, reset, or sign-out invalidates older unmounted buttons
196
+ * and rejects obsolete in-flight preparations with `intentInvalidated`.
150
197
  *
151
198
  * Pass `prefill` to pre-populate the OnramperID sign-in and additional-info
152
199
  * screens with values you already know about the user. It is best-effort and
@@ -164,27 +211,39 @@ export class OnramperClient {
164
211
  prefill: OnramperUserPrefill = {},
165
212
  ): Promise<GetCheckoutRequirementsResult> {
166
213
  try {
214
+ this.assertActive();
167
215
  await this.configured;
216
+ this.assertActive();
168
217
  const result = await this.native.getCheckoutRequirements(
169
218
  JSON.stringify(request),
170
219
  JSON.stringify(prefill),
171
220
  JSON.stringify(buttonStyle),
172
221
  );
222
+ this.assertActive();
173
223
  const quote = JSON.parse(result.quoteJson) as QuoteResponse;
174
224
  const button = createElement(CheckoutButton, { intentHandle: result.intentHandle });
175
225
  return { button, quote };
176
226
  } catch (e: unknown) {
227
+ this.assertActive();
177
228
  throw OnramperError.from(e);
178
229
  }
179
230
  }
180
231
 
181
232
  /** Discards a prepared intent (e.g. if the user navigates away before checkout). */
182
- cancelPreparedIntent(intentHandle: string): Promise<void> {
183
- return this.native.cancelPreparedIntent(intentHandle);
233
+ async cancelPreparedIntent(intentHandle: string): Promise<void> {
234
+ try {
235
+ this.assertActive();
236
+ await this.native.cancelPreparedIntent(intentHandle);
237
+ this.assertActive();
238
+ } catch (e: unknown) {
239
+ this.assertActive();
240
+ throw OnramperError.from(e);
241
+ }
184
242
  }
185
243
 
186
244
  /** Subscribe to state changes. Returns an unsubscribe function. */
187
245
  addStateListener(fn: (state: OnramperState) => void): () => void {
246
+ this.assertActive();
188
247
  this.stateListeners.add(fn);
189
248
  return () => {
190
249
  this.stateListeners.delete(fn);
@@ -193,6 +252,7 @@ export class OnramperClient {
193
252
 
194
253
  /** Subscribe to the durable Onramper transaction ID. Successful reset/signOut publish null. */
195
254
  addTransactionIdListener(fn: (transactionId: string | null) => void): () => void {
255
+ this.assertActive();
196
256
  this.transactionIdListeners.add(fn);
197
257
  return () => {
198
258
  this.transactionIdListeners.delete(fn);
@@ -201,6 +261,7 @@ export class OnramperClient {
201
261
 
202
262
  /** Subscribe to a specific checkout event. Returns an unsubscribe function. */
203
263
  addEventListener<K extends EventName>(name: K, fn: (e: EventPayload<K>) => void): () => void {
264
+ this.assertActive();
204
265
  const wrapper = (e: CheckoutEvent) => {
205
266
  if (e.type === name) fn(e as EventPayload<K>);
206
267
  };
@@ -210,8 +271,10 @@ export class OnramperClient {
210
271
  };
211
272
  }
212
273
 
213
- /** Tear down: clears JS listeners and releases the native callbacks + SDK client. */
274
+ /** Idempotent teardown. Further operations reject with `clientDestroyed`; create a new client to restart. */
214
275
  destroy(): void {
276
+ if (this.destroyed) return;
277
+ this.destroyed = true;
215
278
  this.stateListeners.clear();
216
279
  this.eventListeners.clear();
217
280
  this.transactionIdListeners.clear();
package/src/errors.ts CHANGED
@@ -8,6 +8,9 @@ export type OnramperErrorCode =
8
8
  | 'networkError'
9
9
  | 'decodingError'
10
10
  | 'timeout'
11
+ | 'paymentFailed'
12
+ | 'providerFailed'
13
+ | 'applePayNotConfigured'
11
14
  | 'requirementNotSatisfied'
12
15
  | 'amountOutOfRange'
13
16
  | 'oidcFlowCancelled'
@@ -26,42 +29,62 @@ export type OnramperErrorCode =
26
29
  | 'deviceBlocked'
27
30
  | 'securityStorageFailed'
28
31
  | 'securityTrustFailed'
29
- // JS-only codes
32
+ // Wrapper lifecycle and platform codes
30
33
  | 'platformUnsupported'
31
34
  | 'intentInvalidated'
32
35
  | 'intentAlreadyConsumed'
33
36
  | 'clientAlreadyConfigured'
37
+ | 'clientDestroyed'
34
38
  | 'sessionExpirationHandlerFailed';
35
39
 
36
40
  export interface OnramperErrorPayload {
37
41
  code: OnramperErrorCode;
38
42
  message: string;
43
+ /** Customer-facing SDK copy; optional for backwards-compatible payload construction. */
44
+ userMessage?: string;
45
+ supportCode?: string;
39
46
  info?: Record<string, unknown>;
40
47
  }
41
48
 
42
49
  export class OnramperError extends Error implements OnramperErrorPayload {
43
50
  readonly code: OnramperErrorCode;
51
+ readonly userMessage: string;
52
+ readonly supportCode: string;
44
53
  readonly info?: Record<string, unknown>;
45
54
 
46
55
  constructor(payload: OnramperErrorPayload) {
47
56
  super(payload.message);
48
57
  this.name = 'OnramperError';
49
58
  this.code = payload.code;
59
+ this.userMessage = payload.userMessage ?? 'Something went wrong. Please try again.';
60
+ this.supportCode = payload.supportCode ?? payload.code;
50
61
  this.info = payload.info;
51
62
  }
52
63
 
53
64
  static from(value: unknown): OnramperError {
54
65
  if (value instanceof OnramperError) return value;
66
+ // Nitro transports Swift exceptions as message-only JS Errors. Our native
67
+ // bridge encodes SDK errors explicitly so Promise rejections retain the
68
+ // same fields as failed events, without consulting unrelated client state.
69
+ const message = value instanceof Error ? value.message : typeof value === 'string' ? value : undefined;
70
+ if (message?.startsWith('ONRAMPER_ERROR:')) {
71
+ try {
72
+ const payload = JSON.parse(message.slice('ONRAMPER_ERROR:'.length));
73
+ if (payload && typeof payload.code === 'string' && typeof payload.message === 'string') {
74
+ return OnramperError.from(payload);
75
+ }
76
+ } catch {
77
+ // A malformed/non-SDK error falls back to the ordinary error path.
78
+ }
79
+ }
55
80
  if (value && typeof value === 'object') {
56
- // Structured errors (from the `.failed` state) arrive as a plain
57
- // `{ code, message, info? }` object. Thrown promise rejections surface
58
- // as a JS Error with just a message; we default the code in that case
59
- // (the structured error is also available on `client.state` / lastError).
60
- const v = value as { code?: string; message?: string; info?: Record<string, unknown> };
81
+ const v = value as Partial<OnramperErrorPayload>;
61
82
  return new OnramperError({
62
83
  code: (v.code ?? 'unrecoverable') as OnramperErrorCode,
63
84
  message: v.message ?? 'Unknown error',
64
85
  info: v.info,
86
+ userMessage: typeof v.userMessage === 'string' ? v.userMessage : undefined,
87
+ supportCode: typeof v.supportCode === 'string' ? v.supportCode : undefined,
65
88
  });
66
89
  }
67
90
  return new OnramperError({ code: 'unrecoverable', message: String(value) });
package/src/events.ts CHANGED
@@ -16,6 +16,11 @@ export type CheckoutEvent =
16
16
  | { type: 'providerReady' }
17
17
  | { type: 'paymentAuthorized' }
18
18
  | { type: 'paymentProcessing' }
19
+ /** Accepted but not settled; use currentTransactionId to track the payment. */
20
+ | { type: 'paymentPending' }
21
+ | { type: 'challengeStarted' }
22
+ | { type: 'challengeCompleted' }
23
+ | { type: 'customerOnboardingRequired' }
19
24
  | { type: 'paymentCancelled' }
20
25
  | { type: 'providerError'; reason: string };
21
26
 
package/src/index.ts CHANGED
@@ -16,6 +16,9 @@ export type {
16
16
  OnramperConfiguration,
17
17
  OnramperEnvironment,
18
18
  OnramperLogLevel,
19
+ OnramperLogRecord,
20
+ ProviderJSONValue,
21
+ PaymentDisclosure,
19
22
  OnramperState,
20
23
  OnramperTheme,
21
24
  OnramperUserPrefill,
@@ -8,6 +8,8 @@ export interface OnramperNitroConfig {
8
8
  environment: string; // 'development' | 'production'
9
9
  theme: string; // 'system' | 'light' | 'dark'
10
10
  logLevel: string; // 'off' | 'error' | 'info' | 'debug'
11
+ isEventLoggingEnabled: boolean;
12
+ isEventConsoleLoggingEnabled: boolean;
11
13
  }
12
14
 
13
15
  // Mirrors OnramperSDK.SessionCredentials. Named with a Nitro prefix to avoid a
@@ -50,6 +52,7 @@ export interface OnramperNitro extends HybridObject<{ ios: 'swift' }> {
50
52
  cancelPreparedIntent(intentHandle: string): Promise<void>;
51
53
 
52
54
  // Single native callback per stream; the JS wrapper fans out to multiple listeners.
55
+ setLogListener(onLog: (recordJson: string) => void): void;
53
56
  setStateListener(onState: (stateJson: string) => void): void;
54
57
  setEventListener(onEvent: (eventJson: string) => void): void;
55
58
  // Mirrors OnramperSDK.$currentTransactionId. `undefined` represents native nil.
package/src/types.ts CHANGED
@@ -5,6 +5,16 @@ import type { OnramperErrorPayload } from './errors';
5
5
 
6
6
  export type OnramperEnvironment = 'development' | 'production';
7
7
  export type OnramperLogLevel = 'off' | 'error' | 'info' | 'debug';
8
+ /** A sanitized native SDK diagnostic. Timestamp is Unix milliseconds. */
9
+ export interface OnramperLogRecord {
10
+ timestamp: number;
11
+ level: OnramperLogLevel;
12
+ category: string;
13
+ event: string;
14
+ context: Record<string, string>;
15
+ message: string;
16
+ }
17
+
8
18
  export type OnramperTheme = 'system' | 'light' | 'dark';
9
19
 
10
20
  export type TransactionType = 'buy' | 'sell';
@@ -20,12 +30,16 @@ export interface OnramperConfiguration {
20
30
  environment: OnramperEnvironment;
21
31
  theme?: OnramperTheme;
22
32
  logLevel?: OnramperLogLevel;
33
+ /** Receives native records in JS; system Console output is preserved. Off unless logLevel enables it. */
34
+ logHandler?: (record: OnramperLogRecord) => void;
35
+ /** Opt-in provider-event telemetry to the BFF. Defaults to false. */
36
+ isEventLoggingEnabled?: boolean;
37
+ /** Emit provider events at info even when logLevel is lower. Defaults to false. */
38
+ isEventConsoleLoggingEnabled?: boolean;
23
39
  /**
24
40
  * Called by the SDK when the session token expires and needs to be re-issued.
25
41
  *
26
- * May be synchronous or async — under the hood the bridge uses an event
27
- * round-trip (`onSessionExpired` → `provideSessionCredentials`) so Promises
28
- * are awaited normally. Throw or reject to signal that no fresh credentials
42
+ * May be synchronous or async; the Nitro bridge awaits the callback directly. Throw or reject to signal that no fresh credentials
29
43
  * are available; the SDK will surface `userTokenRefreshFailed`.
30
44
  */
31
45
  onSessionExpired: () => SessionCredentials | Promise<SessionCredentials>;
@@ -67,6 +81,23 @@ export interface QuoteResponse {
67
81
  networkFee: number;
68
82
  transactionFee: number;
69
83
  recommendations?: string[];
84
+ providerContext?: Record<string, ProviderJSONValue>;
85
+ }
86
+
87
+ /** JSON values used in provider-specific quote metadata (including booleans and limits). */
88
+ export type ProviderJSONValue =
89
+ | string
90
+ | number
91
+ | boolean
92
+ | null
93
+ | ProviderJSONValue[]
94
+ | { [key: string]: ProviderJSONValue };
95
+
96
+ export interface PaymentDisclosure {
97
+ code: string;
98
+ version: string;
99
+ contentMarkdown: string;
100
+ contentHtml: string;
70
101
  }
71
102
 
72
103
  // Mirrors Swift `OnramperUserPrefill`. Values you already know about the user,
@@ -111,6 +142,7 @@ export interface HeadlessCheckoutData {
111
142
  checkoutPaymentType: CheckoutPaymentType;
112
143
  url: string;
113
144
  renderType: RenderType;
145
+ providerBootstrap?: { framesBaseUrl?: string };
114
146
  }
115
147
 
116
148
  export interface CheckoutFinalizeResponse {
@@ -181,6 +213,7 @@ export type OnramperState =
181
213
  | { kind: 'readyToCheckout' }
182
214
  | { kind: 'finalizing' }
183
215
  | { kind: 'rendering'; url: string; renderType: RenderType; paymentType: CheckoutPaymentType }
216
+ | { kind: 'paymentPending' }
184
217
  | { kind: 'completed' }
185
218
  | { kind: 'failed'; error: OnramperErrorPayload };
186
219