react-native-hyperswitch-dev-sdk 0.4.1 → 0.4.3

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.
@@ -154,8 +154,8 @@ dependencies {
154
154
  implementation "com.google.android.gms:play-services-wallet:19.4.0"
155
155
  implementation 'androidx.activity:activity-ktx:1.10.1'
156
156
  implementation 'org.greenrobot:eventbus:3.1.0'
157
- implementation "io.hyperswitch:hyperswitch-sdk-android-airborne:1.0.0"
158
- implementation "io.hyperswitch:hyperswitch-sdk-android-common:1.0.3"
157
+ // implementation "io.hyperswitch:hyperswitch-sdk-android-airborne:1.0.0"
158
+ implementation "io.hyperswitch:hyperswitch-sdk-android-common:1.0.1"
159
159
  implementation "io.hyperswitch:hyperswitch-sdk-android-logger:1.0.0"
160
160
  implementation 'io.hyperswitch:hyperswitch-sdk-android-webview-utils:1.0.6'
161
161
  }
@@ -253,7 +253,7 @@ class HyperswitchAutolinking {
253
253
  }
254
254
  }
255
255
  } else {
256
- if(nameCleansed != "juspay-tech_react-native-hyperswitch") {
256
+ if(nameCleansed != "react-native-hyperswitch-dev-sdk") {
257
257
  if (dependencyConfiguration != null) {
258
258
  add("${dependencyConfiguration}", project.project(":${nameCleansed}"))
259
259
  } else {
@@ -0,0 +1,114 @@
1
+ package io.hyperswitch.model
2
+
3
+ import android.os.Bundle
4
+
5
+ data class OverrideEndpoints(
6
+ val customBackendEndpoint: String? = null,
7
+ val customLoggingEndpoint: String? = null,
8
+ val customAssetEndpoint: String? = null,
9
+ val customSDKConfigEndpoint: String? = null,
10
+ val customConfirmEndpoint: String? = null,
11
+ val customAirborneEndpoint: String? = null,
12
+ )
13
+
14
+ data class CustomEndpointConfiguration(
15
+ val overrideEndpoints: OverrideEndpoints? = null,
16
+ val commonEndpoint: String? = null,
17
+ )
18
+
19
+ enum class HyperswitchEnvironment {
20
+ PROD,
21
+ SANDBOX,
22
+ INTEG
23
+ }
24
+
25
+ sealed interface HyperswitchBaseConfiguration {
26
+ val publishableKey: String?
27
+ val profileId: String?
28
+ val platformPublishableKey: String?
29
+ val customConfig: CustomEndpointConfiguration?
30
+ val environment: HyperswitchEnvironment?
31
+
32
+ /**
33
+ * Serialises this configuration into the `hyperswitchConfig` Bundle structure expected by
34
+ * the React Native layer.
35
+ */
36
+ fun toBundle(): Bundle = Bundle().apply {
37
+ putString("publishableKey", publishableKey ?: "")
38
+ platformPublishableKey?.takeIf { it.isNotEmpty() }
39
+ ?.let { putString("platformPublishableKey", it) }
40
+ profileId?.takeIf { it.isNotEmpty() }?.let { putString("profileId", it) }
41
+ environment?.let { putString("environment", it.name) }
42
+
43
+ val cfg = customConfig
44
+ if (cfg != null) {
45
+ val oe = cfg.overrideEndpoints
46
+ val hasAnyEndpoint = listOfNotNull(
47
+ cfg.commonEndpoint,
48
+ oe?.customBackendEndpoint,
49
+ oe?.customLoggingEndpoint,
50
+ oe?.customAssetEndpoint,
51
+ oe?.customSDKConfigEndpoint,
52
+ oe?.customConfirmEndpoint,
53
+ oe?.customAirborneEndpoint,
54
+ ).any { it.isNotEmpty() }
55
+
56
+ if (hasAnyEndpoint) {
57
+ putBundle("customEndpoints", Bundle().apply {
58
+ cfg.commonEndpoint?.takeIf { it.isNotEmpty() }
59
+ ?.let { putString("commonEndpoint", it) }
60
+ if (oe != null) {
61
+ putBundle("overrideEndpoints", Bundle().apply {
62
+ oe.customBackendEndpoint?.takeIf { it.isNotEmpty() }
63
+ ?.let { putString("customBackendEndpoint", it) }
64
+ oe.customLoggingEndpoint?.takeIf { it.isNotEmpty() }
65
+ ?.let { putString("customLoggingEndpoint", it) }
66
+ oe.customAssetEndpoint?.takeIf { it.isNotEmpty() }
67
+ ?.let { putString("customAssetEndpoint", it) }
68
+ oe.customSDKConfigEndpoint?.takeIf { it.isNotEmpty() }
69
+ ?.let { putString("customSDKConfigEndpoint", it) }
70
+ oe.customConfirmEndpoint?.takeIf { it.isNotEmpty() }
71
+ ?.let { putString("customConfirmEndpoint", it) }
72
+ oe.customAirborneEndpoint?.takeIf { it.isNotEmpty() }
73
+ ?.let { putString("customAirborneEndpoint", it) }
74
+ })
75
+ }
76
+ })
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ data class HyperswitchConfiguration(
83
+ override val publishableKey: String? = null,
84
+ override val profileId: String? = null,
85
+ override val customConfig: CustomEndpointConfiguration? = null,
86
+ override val environment: HyperswitchEnvironment? = HyperswitchEnvironment.PROD,
87
+ override val platformPublishableKey: String? = null
88
+ ) : HyperswitchBaseConfiguration
89
+
90
+ data class HyperswitchPlatformConfiguration(
91
+ override val publishableKey: String? = null,
92
+ override val profileId: String? = null,
93
+ override val customConfig: CustomEndpointConfiguration? = null,
94
+ override val platformPublishableKey: String? = null,
95
+ override val environment: HyperswitchEnvironment? = HyperswitchEnvironment.PROD
96
+ ) : HyperswitchBaseConfiguration
97
+
98
+ data class PaymentSessionConfiguration(
99
+ val sdkAuthorization: String,
100
+ ) {
101
+ /** Serialises this configuration into the `paymentSessionConfig` Bundle. */
102
+ fun toBundle(): Bundle = Bundle().apply {
103
+ putString("sdkAuthorization", sdkAuthorization)
104
+ }
105
+ }
106
+
107
+ data class Appearance(
108
+ val primaryColor: String? = null,
109
+ )
110
+
111
+ data class PaymentSheetConfiguration(
112
+ val appearance: Appearance? = null,
113
+ )
114
+
@@ -0,0 +1,270 @@
1
+ package io.hyperswitch.paymentsession
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.graphics.Rect
6
+ import android.os.Build
7
+ import android.os.Bundle
8
+ import android.view.View
9
+ import android.view.WindowInsets
10
+ import android.webkit.WebSettings
11
+ import androidx.annotation.RequiresApi
12
+ import io.hyperswitch.model.HyperswitchBaseConfiguration
13
+ import io.hyperswitch.model.PaymentSessionConfiguration
14
+ import org.json.JSONObject
15
+
16
+ /**
17
+ * Builds the launch bundles passed into React Native / WebView layers.
18
+ *
19
+ * All configuration is accepted as plain [Bundle]s so this class has no dependency on
20
+ * [hyperswitch-sdk-android-api]. Callers are responsible for converting their typed model
21
+ * objects before passing them here (e.g. `hsConfig.toBundle()`, `config.bundle`).
22
+ */
23
+ class LaunchOptions(
24
+ private val context: Context? = null,
25
+ private val sdkVersion: String,
26
+ private val hsConfig: HyperswitchBaseConfiguration? = null,
27
+ ) {
28
+
29
+ // ── SDK params ────────────────────────────────────────────────────────────
30
+
31
+ private fun getSdkParams(): Bundle =
32
+ Bundle().apply {
33
+ putString("appId", context?.packageName)
34
+ putString("country", context?.resources?.configuration?.locales?.get(0)?.country)
35
+ putString("user-agent", getUserAgent(context))
36
+ putDouble("launchTime", getCurrentTime())
37
+ putString("sdkVersion", sdkVersion)
38
+ putString("device_model", Build.MODEL)
39
+ putString("os_type", "android")
40
+ putString("os_version", Build.VERSION.RELEASE)
41
+ putString("deviceBrand", Build.BRAND)
42
+ putString("sessionId", "")
43
+ putBoolean("confirm", false)
44
+ getBottomInset(context)?.let { insets ->
45
+ putFloat("topInset", insets.top)
46
+ putFloat("leftInset", insets.left)
47
+ putFloat("rightInset", insets.right)
48
+ putFloat("bottomInset", insets.bottom)
49
+ }
50
+ }
51
+
52
+ private fun getSdkParamsMap(map: Map<*, *>): Map<*, *> =
53
+ (map["sdkParams"] as? Map<*, *> ?: mutableMapOf<String, Any?>()).apply {
54
+ plus(Pair("appId", context?.packageName))
55
+ plus(Pair("country", context?.resources?.configuration?.locales?.get(0)?.country))
56
+ plus(Pair("user-agent", getUserAgent(context)))
57
+ plus(Pair("launchTime", getCurrentTime()))
58
+ plus(Pair("sdkVersion", sdkVersion))
59
+ plus(Pair("device_model", Build.MODEL))
60
+ plus(Pair("os_type", "android"))
61
+ plus(Pair("os_version", Build.VERSION.RELEASE))
62
+ plus(Pair("deviceBrand", Build.BRAND))
63
+ plus(Pair("sessionId", ""))
64
+ plus(Pair("confirm", false))
65
+ getBottomInset(context)?.let { insets ->
66
+ plus(Pair("topInset", insets.top))
67
+ plus(Pair("leftInset", insets.left))
68
+ plus(Pair("rightInset", insets.right))
69
+ plus(Pair("bottomInset", insets.bottom))
70
+ }
71
+ }
72
+
73
+ // ── Payment-session bundle (headless task / payment sheet) ────────────────
74
+
75
+ /**
76
+ * Convenience overload — uses the [Context] supplied at construction time.
77
+ *
78
+ * @param sessionConfig Serialised `PaymentSessionConfiguration` bundle.
79
+ * @param configuration Serialised `PaymentSheet.Configuration` bundle.
80
+ */
81
+ fun getBundle(
82
+ sessionConfig: PaymentSessionConfiguration? = null,
83
+ configuration: Bundle? = null,
84
+ subscribedEvents: List<String> = emptyList(),
85
+ ): Bundle = getBundle(
86
+ type = "payment",
87
+ sessionConfig = sessionConfig,
88
+ configuration = configuration,
89
+ subscribedEvents= subscribedEvents
90
+ )
91
+
92
+ // ── Widget bundle ─────────────────────────────────────────────────────────
93
+
94
+ /**
95
+ * Builds the launch bundle for native widgets (e.g. `PaymentWidgetView`).
96
+ *
97
+ * Custom backend / logging URLs are extracted from the nested
98
+ * `customEndpoints.overrideEndpoints` structure inside [hsConfig].
99
+ *
100
+ * @param configuration Serialised configuration bundle (already a Bundle).
101
+ * @param sessionConfig Serialised `PaymentSessionConfiguration` bundle.
102
+ */
103
+ fun getBundle(
104
+ type: String? = "payment",
105
+ sessionConfig: PaymentSessionConfiguration? = null,
106
+ configuration: Bundle? = null,
107
+ subscribedEvents: List<String> = emptyList(),
108
+ ): Bundle = Bundle().apply {
109
+ putBundle("props", Bundle().apply {
110
+ putString("type", type)
111
+ hsConfig?.let { putBundle("hyperswitchConfig", it.toBundle()) }
112
+ sessionConfig?.let { putBundle("paymentSessionConfig", it.toBundle()) }
113
+ val configCopy = configuration?.let { Bundle(it) }
114
+ if (subscribedEvents.isNotEmpty()) {
115
+ configCopy?.putStringArrayList("subscribedEvents", ArrayList(subscribedEvents))
116
+ }
117
+ putBundle("configuration", configCopy)
118
+ putBundle("sdkParams", getSdkParams())
119
+ })
120
+ }
121
+
122
+ // ── Map-based overload (used by React Native bridge path) ─────────────────
123
+
124
+ fun getBundleWithHyperParams(
125
+ readableMap: Map<*, *>,
126
+ subscribedEvents: List<String> = emptyList(),
127
+ ): Bundle = Bundle().apply {
128
+ putBundle("props", toBundle(readableMap).apply {
129
+ putBundle("sdkParams", getSdkParams())
130
+ putStringArrayList("subscribedEvents", ArrayList(subscribedEvents))
131
+ })
132
+ }
133
+
134
+ // ── JSON helpers (used by WebView / lite SDK) ─────────────────────────────
135
+
136
+ fun getJson(
137
+ sessionConfig: PaymentSessionConfiguration? = null,
138
+ configuration: Bundle? = null,
139
+ ): JSONObject = toJson(getBundle(configuration = configuration, sessionConfig = sessionConfig))
140
+
141
+ fun getJson(configurationMap: Map<*, *>): JSONObject =
142
+ toJson(getMapWithHyperParams(configurationMap))
143
+
144
+ private fun getMapWithHyperParams(map: Map<*, *>): Map<*, *> = mapOf(
145
+ "props" to map.apply {
146
+ plus(Pair("sdkParams", getSdkParamsMap(map)))
147
+ }
148
+ )
149
+ // ── Bundle / Map / JSON conversion utilities ──────────────────────────────
150
+
151
+ fun fromBundle(bundle: Bundle): Map<*, *> {
152
+ val map = mutableMapOf<String, Any?>()
153
+ for (key in bundle.keySet()) {
154
+ val value = bundle[key]
155
+ when {
156
+ value == null -> {}
157
+ value.javaClass.isArray -> map[key] = value
158
+ value is String -> map[key] = value
159
+ value is Number -> map[key] = value as? Int ?: value.toDouble()
160
+ value is Boolean -> map[key] = value
161
+ value is Bundle -> map[key] = fromBundle(value)
162
+ value is List<*> -> map[key] = fromList(value)
163
+ else -> throw IllegalArgumentException("Could not convert ${value.javaClass}")
164
+ }
165
+ }
166
+ return map
167
+ }
168
+
169
+ private fun fromList(list: List<*>): List<Any?> = list.map { item ->
170
+ when (item) {
171
+ is Bundle -> fromBundle(item)
172
+ is List<*> -> fromList(item)
173
+ else -> item
174
+ }
175
+ }
176
+
177
+ fun toBundle(readableMap: Map<*, *>): Bundle {
178
+ val bundle = Bundle()
179
+ for ((key, value) in readableMap) {
180
+ val k = key.toString()
181
+ when (value) {
182
+ null -> {}
183
+ is Boolean -> bundle.putBoolean(k, value)
184
+ is Number -> bundle.putDouble(k, value.toDouble())
185
+ is String -> bundle.putString(k, value)
186
+ is Map<*, *> -> bundle.putBundle(k, toBundle(value))
187
+ is Array<*> -> bundle.putSerializable(k, value)
188
+ is List<*> -> bundle.putSerializable(k, toSerializableArrayList(value))
189
+ else -> throw IllegalArgumentException("Could not convert object with key: $k.")
190
+ }
191
+ }
192
+ return bundle
193
+ }
194
+
195
+ private fun toSerializableArrayList(list: List<*>): ArrayList<Any?> =
196
+ ArrayList(list.map { item ->
197
+ when (item) {
198
+ is Map<*, *> -> toBundle(item)
199
+ is List<*> -> toSerializableArrayList(item)
200
+ else -> item
201
+ }
202
+ })
203
+
204
+ private fun toJson(bundle: Bundle): JSONObject {
205
+ val json = JSONObject()
206
+ for (key in bundle.keySet()) {
207
+ when (val value = bundle[key]) {
208
+ null -> {}
209
+ is Bundle -> json.put(key, toJson(value))
210
+ is Boolean, is Int, is Float, is Long, is Double, is String -> json.put(key, value)
211
+ is Array<*> -> json.put(key, JSONObject.wrap(value))
212
+ is List<*> -> json.put(key, value)
213
+ else -> throw IllegalArgumentException("Unsupported type for key: $key, $value")
214
+ }
215
+ }
216
+ return json
217
+ }
218
+
219
+ private fun toJson(map: Map<*, *>): JSONObject = JSONObject(map)
220
+
221
+ // ── Device / inset helpers ────────────────────────────────────────────────
222
+
223
+ private fun getUserAgent(context: Context?): String? =
224
+ try {
225
+ if (context == null) System.getProperty("http.agent")
226
+ else WebSettings.getDefaultUserAgent(context)
227
+ } catch (_: RuntimeException) {
228
+ System.getProperty("http.agent")
229
+ }
230
+
231
+ @RequiresApi(Build.VERSION_CODES.R)
232
+ private fun getRootWindowInsetsCompatR(rootView: View): EdgeInsets? {
233
+ val insets = rootView.rootWindowInsets?.getInsets(
234
+ WindowInsets.Type.statusBars() or
235
+ WindowInsets.Type.displayCutout() or
236
+ WindowInsets.Type.navigationBars() or
237
+ WindowInsets.Type.captionBar()
238
+ ) ?: return null
239
+ return EdgeInsets(
240
+ top = insets.top.toFloat(),
241
+ right = insets.right.toFloat(),
242
+ bottom = insets.bottom.toFloat(),
243
+ left = insets.left.toFloat(),
244
+ )
245
+ }
246
+
247
+ private fun getRootWindowInsetsCompatBase(rootView: View): EdgeInsets? {
248
+ val rect = Rect()
249
+ rootView.getWindowVisibleDisplayFrame(rect)
250
+ return EdgeInsets(
251
+ top = rect.top.toFloat(),
252
+ right = (rootView.width - rect.right).toFloat(),
253
+ bottom = (rootView.height - rect.bottom).toFloat(),
254
+ left = rect.left.toFloat(),
255
+ )
256
+ }
257
+
258
+ private fun getBottomInset(context: Context?): EdgeInsets? {
259
+ val activity = context as? Activity ?: return null
260
+ val rootView = activity.window.decorView
261
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
262
+ getRootWindowInsetsCompatR(rootView)
263
+ else
264
+ getRootWindowInsetsCompatBase(rootView)
265
+ }
266
+
267
+ private fun getCurrentTime(): Double = System.currentTimeMillis().toDouble()
268
+ }
269
+
270
+ data class EdgeInsets(val top: Float, val right: Float, val bottom: Float, val left: Float)
@@ -42,10 +42,10 @@ internal class NativePaymentWidget: RCTViewManager {
42
42
  }
43
43
  // New-arch (Fabric) path: the Fabric NativePaymentElementView registered
44
44
  // the inner NativePaymentWidgetView in the shared registry by the same tag.
45
- if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: reactTag) as? NativePaymentWidgetView {
46
- view.confirmPayment(rnCallback)
47
- return
48
- }
45
+ // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: reactTag) as? NativePaymentWidgetView {
46
+ // view.confirmPayment(rnCallback)
47
+ // return
48
+ // }
49
49
  rnCallback([["status": "failed", "message": "Widget view not found for tag \(reactTag)"]])
50
50
  }
51
51
  }
@@ -56,10 +56,10 @@ internal class NativePaymentWidget: RCTViewManager {
56
56
  view.updateIntentInit(rnCallback)
57
57
  return
58
58
  }
59
- if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: rootTag) as? NativePaymentWidgetView {
60
- view.updateIntentInit(rnCallback)
61
- return
62
- }
59
+ // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: rootTag) as? NativePaymentWidgetView {
60
+ // view.updateIntentInit(rnCallback)
61
+ // return
62
+ // }
63
63
  rnCallback([["status": "failed", "message": "Widget view not found for tag \(rootTag)"]])
64
64
  }
65
65
  }
@@ -74,10 +74,10 @@ internal class NativePaymentWidget: RCTViewManager {
74
74
  view.updateIntentComplete(sdkAuthorization: sdkAuthorization, resolve: rnCallback)
75
75
  return
76
76
  }
77
- if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: rootTag) as? NativePaymentWidgetView {
78
- view.updateIntentComplete(sdkAuthorization: sdkAuthorization, resolve: rnCallback)
79
- return
80
- }
77
+ // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: rootTag) as? NativePaymentWidgetView {
78
+ // view.updateIntentComplete(sdkAuthorization: sdkAuthorization, resolve: rnCallback)
79
+ // return
80
+ // }
81
81
  rnCallback([["status": "failed", "message": "Widget view not found for tag \(rootTag)"]])
82
82
  }
83
83
  }
@@ -93,10 +93,10 @@ internal class NativePaymentWidget: RCTViewManager {
93
93
  view.confirmCVCPayment(paymentToken: paymentToken, paymentMethodId: paymentMethodId, resolve: rnCallback)
94
94
  return
95
95
  }
96
- if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: reactTag) as? NativePaymentWidgetView {
97
- view.confirmCVCPayment(paymentToken: paymentToken, paymentMethodId: paymentMethodId, resolve: rnCallback)
98
- return
99
- }
96
+ // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: reactTag) as? NativePaymentWidgetView {
97
+ // view.confirmCVCPayment(paymentToken: paymentToken, paymentMethodId: paymentMethodId, resolve: rnCallback)
98
+ // return
99
+ // }
100
100
  rnCallback([["status": "failed", "message": "Widget view not found for tag \(reactTag)"]])
101
101
  }
102
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-hyperswitch-dev-sdk",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Hyperswitch SDK for React Native",
5
5
  "main": "lib/commonjs/index.bundle.js",
6
6
  "module": "lib/module/index.bundle.js",