@juspay-tech/react-native-hyperswitch 1.3.4 → 1.3.6
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/android/build.gradle +22 -1
- package/android/src/main/assets/hyperswitch.bundle +590 -590
- package/android/src/main/java/com/hyperswitchsdkreactnative/modules/NativeLog.kt +107 -0
- package/android/src/main/java/com/hyperswitchsdkreactnative/modules/ReactNativeHyperswitchModule.kt +318 -117
- package/android/src/main/java/io/hyperswitch/paymentsession/ExitHeadlessCallBackManager.kt +4 -2
- package/android/src/main/java/io/hyperswitch/paymentsession/PaymentSessionHandlerImpl.kt +20 -4
- package/android/src/main/java/io/hyperswitch/paymentsession/PaymentSessionReactLauncher.kt +104 -9
- package/android/src/main/java/io/hyperswitch/paymentsheet/PaymentResult.kt +7 -1
- package/android/src/main/java/io/hyperswitch/react/HyperModule.kt +13 -0
- package/android/src/main/java/io/hyperswitch/view/PaymentWidgetView.kt +27 -4
- package/android/src/rn79/assets/hyperswitch.bundle +590 -590
- package/android/src/rn79/java/io/hyperswitch/react/HyperFragment.kt +20 -0
- package/android/src/rn81/java/io/hyperswitch/react/HyperFragment.kt +20 -0
- package/ios/Modules/ReactNative/NativeHyperswitchModuleImpl.swift +3 -1
- package/ios/hyperswitchSDK/Core/ApplePay/ApplePayHandler.swift +49 -38
- package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView+UIKit.swift +1 -5
- package/ios/hyperswitchSDK/Core/NativeModule/HyperModuleImpl.swift +49 -34
- package/ios/hyperswitchSDK/Core/NativeModule/RNHeadlessManager.swift +2 -12
- package/ios/hyperswitchSDK/Core/Resources/hyperswitch-rn82plus.bundle +590 -590
- package/ios/hyperswitchSDK/Core/Resources/hyperswitch.bundle +590 -590
- package/ios/hyperswitchSDK/Shared/Environment.swift +49 -3
- package/ios/hyperswitchSDK/Shared/HyperswitchEnvironment.swift +1 -0
- package/ios/hyperswitchSDK/Shared/NativeLogger/LogBuilder.swift +15 -0
- package/ios/hyperswitchSDK/Shared/NativeLogger/LogManager.swift +10 -2
- package/ios/hyperswitchSDK/Shared/NativeLogger/LogPayload.swift +1 -0
- package/ios/hyperswitchSDK/Shared/NativeLogger/NativeLog.swift +101 -0
- package/ios/hyperswitchSDK/Shared/PaymentSession.swift +8 -1
- package/lib/commonjs/index.bundle.js +2 -2
- package/lib/commonjs/index.bundle.js.map +1 -1
- package/lib/module/index.bundle.js +2 -2
- package/lib/module/index.bundle.js.map +1 -1
- package/lib/typescript/src/context/HyperElements.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/NativeResponseMapper.d.ts +2 -1
- package/lib/typescript/src/native/NativeResponseMapper.d.ts.map +1 -1
- package/lib/typescript/src/session/SavedPaymentMethods.d.ts.map +1 -1
- package/lib/typescript/src/session/presentPaymentSheet.d.ts +0 -12
- package/lib/typescript/src/session/presentPaymentSheet.d.ts.map +1 -1
- package/lib/typescript/src/views/PaymentElement.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/context/HyperElements.tsx +7 -6
- package/src/index.ts +9 -1
- package/src/native/NativeResponseMapper.ts +20 -4
- package/src/session/SavedPaymentMethods.ts +10 -2
- package/src/session/presentPaymentSheet.ts +7 -1
- package/src/views/PaymentElement.tsx +17 -8
package/android/src/main/java/com/hyperswitchsdkreactnative/modules/ReactNativeHyperswitchModule.kt
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
package com.hyperswitchsdkreactnative.modules
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.pm.ActivityInfo
|
|
5
|
+
import android.content.res.Configuration
|
|
3
6
|
import com.facebook.react.bridge.Promise
|
|
4
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
8
|
import com.facebook.react.bridge.ReadableMap
|
|
@@ -18,6 +21,7 @@ import com.google.android.gms.common.GoogleApiAvailability
|
|
|
18
21
|
import com.google.android.gms.wallet.IsReadyToPayRequest
|
|
19
22
|
import com.google.android.gms.wallet.Wallet
|
|
20
23
|
import com.google.android.gms.wallet.WalletConstants
|
|
24
|
+
import io.hyperswitch.logs.HyperLogManager
|
|
21
25
|
import io.hyperswitch.paymentsession.GetPaymentSessionCallBackManager
|
|
22
26
|
import io.hyperswitch.paymentsession.GetWalletSessionCallBackManager
|
|
23
27
|
import io.hyperswitch.paymentsession.WalletSessionHandler
|
|
@@ -32,6 +36,7 @@ import io.hyperswitch.utils.StandardResult
|
|
|
32
36
|
import io.hyperswitch.view.PaymentWidgetView
|
|
33
37
|
import org.json.JSONArray
|
|
34
38
|
import org.json.JSONObject
|
|
39
|
+
import java.lang.ref.WeakReference
|
|
35
40
|
import java.util.UUID
|
|
36
41
|
import kotlin.String
|
|
37
42
|
import kotlin.collections.orEmpty
|
|
@@ -40,11 +45,22 @@ import kotlin.collections.orEmpty
|
|
|
40
45
|
class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
41
46
|
NativeHyperswitchModuleSpec(reactContext) {
|
|
42
47
|
|
|
48
|
+
init {
|
|
49
|
+
NativeLog.attach(reactContext)
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
private var paymentSessionReactLauncher: PaymentSessionReactLauncher? = null
|
|
44
|
-
private var launchOptions: LaunchOptions? = null
|
|
45
53
|
private var handler: PaymentSessionHandler? = null
|
|
46
54
|
private var walletHandler: WalletSessionHandler? = null
|
|
47
55
|
private var walletSdkAuthorization: String? = null
|
|
56
|
+
/*
|
|
57
|
+
* The Activity seen by the last SDK call and a copy of its configuration, kept so the log can
|
|
58
|
+
* say why and how it changed when a later call finds a different one.
|
|
59
|
+
*/
|
|
60
|
+
private var lastActivity: WeakReference<Activity>? = null
|
|
61
|
+
private var lastConfiguration: Configuration? = null
|
|
62
|
+
/* Set only once initialise has completed. */
|
|
63
|
+
private var initialised = false
|
|
48
64
|
|
|
49
65
|
private val uiManagerType = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
50
66
|
UIManagerType.FABRIC
|
|
@@ -56,6 +72,19 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
56
72
|
return NAME
|
|
57
73
|
}
|
|
58
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Accepts the same spellings as the iOS module ("PROD"/"PRODUCTION", "SANDBOX", "INTEG",
|
|
77
|
+
* case-insensitive). Anything else yields null (SDK picks the environment from the
|
|
78
|
+
* publishable key) instead of throwing from `valueOf`.
|
|
79
|
+
*/
|
|
80
|
+
private fun parseEnvironment(value: String?): HyperswitchEnvironment? =
|
|
81
|
+
when (value?.trim()?.uppercase()) {
|
|
82
|
+
"PROD", "PRODUCTION" -> HyperswitchEnvironment.PROD
|
|
83
|
+
"SANDBOX" -> HyperswitchEnvironment.SANDBOX
|
|
84
|
+
"INTEG" -> HyperswitchEnvironment.INTEG
|
|
85
|
+
else -> HyperswitchEnvironment.PROD
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
override fun initialise(
|
|
60
89
|
publishableKey: String?,
|
|
61
90
|
platformPublishableKey: String?,
|
|
@@ -64,6 +93,7 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
64
93
|
customEndpoints: ReadableMap?,
|
|
65
94
|
promise: Promise?
|
|
66
95
|
) {
|
|
96
|
+
initialised = false
|
|
67
97
|
val activity = reactApplicationContext.currentActivity
|
|
68
98
|
if (publishableKey.isNullOrBlank()) {
|
|
69
99
|
promise?.reject("INITIALIZATION_ERROR", "publishableKey is required")
|
|
@@ -74,38 +104,156 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
74
104
|
return
|
|
75
105
|
}
|
|
76
106
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
val
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
try {
|
|
108
|
+
val launcher = paymentSessionReactLauncher
|
|
109
|
+
?: PaymentSessionReactLauncher { reactApplicationContext.currentActivity }
|
|
110
|
+
.also { paymentSessionReactLauncher = it }
|
|
111
|
+
launcher.initializeReactNativeInstance()
|
|
112
|
+
val overrideEndpoints: OverrideEndpoints? = customEndpoints?.getMap("overrideEndpoints")?.let {
|
|
113
|
+
val overrideEndpointsMap = customEndpoints.getMap("overrideEndpoints")
|
|
114
|
+
OverrideEndpoints(
|
|
115
|
+
customBackendEndpoint = overrideEndpointsMap?.getString("customBackendEndpoint"),
|
|
116
|
+
customLoggingEndpoint = overrideEndpointsMap?.getString("customLoggingEndpoint"),
|
|
117
|
+
customAssetEndpoint = overrideEndpointsMap?.getString("customAssetEndpoint"),
|
|
118
|
+
customSDKConfigEndpoint = overrideEndpointsMap?.getString("customSDKConfigEndpoint"),
|
|
119
|
+
customConfirmEndpoint = overrideEndpointsMap?.getString("customConfirmEndpoint"),
|
|
120
|
+
customAirborneEndpoint = overrideEndpointsMap?.getString("customAirborneEndpoint"),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
val customConfig = CustomEndpointConfiguration(
|
|
124
|
+
overrideEndpoints = overrideEndpoints,
|
|
125
|
+
commonEndpoint = customEndpoints?.getString("commonEndpoint")
|
|
126
|
+
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
hyperswitchConfig = HyperswitchConfiguration(
|
|
130
|
+
publishableKey = publishableKey,
|
|
131
|
+
profileId = profileId,
|
|
132
|
+
environment = parseEnvironment(environment),
|
|
133
|
+
customConfig = customConfig
|
|
134
|
+
)
|
|
135
|
+
// Without this, logs added through HyperLogManager are kept in memory and never sent.
|
|
136
|
+
val loggingEndpoint =
|
|
137
|
+
getLoggingEndpoint(publishableKey, overrideEndpoints, customConfig.commonEndpoint)
|
|
138
|
+
NativeLog.enabled = loggingEndpoint != null
|
|
139
|
+
loggingEndpoint?.let {
|
|
140
|
+
HyperLogManager.initialise(publishableKey, it)
|
|
141
|
+
HyperLogManager.setSdkVersion(BuildConfig.VERSION_NAME)
|
|
142
|
+
HyperLogManager.setAppId(reactApplicationContext.packageName)
|
|
143
|
+
}
|
|
144
|
+
lastActivity = WeakReference(activity)
|
|
145
|
+
lastConfiguration = Configuration(activity.resources.configuration)
|
|
146
|
+
NativeLog.debug(
|
|
147
|
+
"sdk_initialised",
|
|
148
|
+
mapOf(
|
|
149
|
+
"environment" to NativeLog.environmentName(hyperswitchConfig?.environment?.name),
|
|
150
|
+
"activity" to activity.javaClass.simpleName
|
|
151
|
+
)
|
|
90
152
|
)
|
|
153
|
+
initialised = true
|
|
154
|
+
val handle = UUID.randomUUID().toString()
|
|
155
|
+
promise?.resolve(handle)
|
|
156
|
+
} catch (e: Exception) {
|
|
157
|
+
/* initializeReactNativeInstance throws on a broken RN setup; without this the
|
|
158
|
+
initialise promise stayed pending and loadHyper never settled. */
|
|
159
|
+
promise?.reject("INITIALIZATION_ERROR", "Failed to initialise the SDK: ${e.message}")
|
|
91
160
|
}
|
|
92
|
-
|
|
93
|
-
overrideEndpoints = overrideEndpoints,
|
|
94
|
-
commonEndpoint = customEndpoints?.getString("commonEndpoint")
|
|
161
|
+
}
|
|
95
162
|
|
|
96
|
-
|
|
163
|
+
/**
|
|
164
|
+
* The live Activity to present on or start a headless flow from. When there is none, or the SDK
|
|
165
|
+
* was never initialised, resolves [promise] with a failure and returns null, so a call never
|
|
166
|
+
* hangs waiting on a sheet that cannot be shown.
|
|
167
|
+
*/
|
|
168
|
+
private fun requireReady(trigger: String, promise: Promise?): Activity? {
|
|
169
|
+
if (!requireInitialised(trigger, promise)) return null
|
|
170
|
+
val activity = reactApplicationContext.currentActivity
|
|
171
|
+
if (
|
|
172
|
+
activity == null ||
|
|
173
|
+
activity.isFinishing ||
|
|
174
|
+
activity.isDestroyed ||
|
|
175
|
+
activity.isChangingConfigurations
|
|
176
|
+
) {
|
|
177
|
+
NativeLog.error("activity_unavailable", mapOf("trigger" to trigger))
|
|
178
|
+
promise?.resolve(failed("ACTIVITY_UNAVAILABLE", "No active Activity"))
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
noteActivityChange(trigger, activity)
|
|
182
|
+
return activity
|
|
183
|
+
}
|
|
97
184
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
185
|
+
private fun requireInitialised(trigger: String, promise: Promise?): Boolean {
|
|
186
|
+
if (initialised && paymentSessionReactLauncher != null) return true
|
|
187
|
+
NativeLog.error("sdk_not_initialised", mapOf("trigger" to trigger))
|
|
188
|
+
promise?.resolve(failed("NOT_INITIALISED", "SDK is not initialised"))
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private fun failed(code: String, message: String): String =
|
|
193
|
+
StandardResult.Failed(code = code, message = message, error = Throwable(message)).toJSONString()
|
|
194
|
+
|
|
195
|
+
private fun launchOptionsFor(activity: Activity) =
|
|
196
|
+
LaunchOptions(activity, BuildConfig.VERSION_NAME, hyperswitchConfig)
|
|
197
|
+
|
|
198
|
+
/** Logs when the host Activity differs from the one the previous SDK call saw. */
|
|
199
|
+
private fun noteActivityChange(trigger: String, current: Activity) {
|
|
200
|
+
val previous = lastActivity?.get()
|
|
201
|
+
if (previous === current) return
|
|
202
|
+
NativeLog.warning(
|
|
203
|
+
"activity_changed",
|
|
204
|
+
mapOf(
|
|
205
|
+
"trigger" to trigger,
|
|
206
|
+
"reason" to replacementReason(previous),
|
|
207
|
+
"configChanged" to configurationChanges(lastConfiguration, current.resources.configuration),
|
|
208
|
+
"activity" to current.javaClass.simpleName
|
|
209
|
+
)
|
|
103
210
|
)
|
|
104
|
-
|
|
105
|
-
|
|
211
|
+
lastActivity = WeakReference(current)
|
|
212
|
+
lastConfiguration = Configuration(current.resources.configuration)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Why the previously bound Activity is no longer the current one. */
|
|
216
|
+
private fun replacementReason(previous: Activity?): String = when {
|
|
217
|
+
previous == null -> "unknown, previous Activity already collected"
|
|
218
|
+
previous.isChangingConfigurations -> "configuration change or recreate()"
|
|
219
|
+
previous.isFinishing -> "previous Activity finished"
|
|
220
|
+
previous.isDestroyed -> "previous Activity destroyed by the system"
|
|
221
|
+
else -> "another Activity is in front"
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Names of the configuration fields that differ, e.g. "fontScale,locale"; "none" for recreate(). */
|
|
225
|
+
private fun configurationChanges(previous: Configuration?, current: Configuration): String {
|
|
226
|
+
previous ?: return "unknown"
|
|
227
|
+
val diff = previous.diff(current)
|
|
228
|
+
return CONFIG_NAMES.filter { (bit, _) -> diff and bit != 0 }.joinToString(",") { it.second }
|
|
229
|
+
.ifEmpty { if (diff == 0) "none" else "0x" + Integer.toHexString(diff) }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Mirrors the JS logger (GlobalHooks.getLoggingUrl): endpoints come from overrideEndpoints when
|
|
234
|
+
* given, otherwise from commonEndpoint. A custom backend without a logging endpoint, or an empty
|
|
235
|
+
* logging endpoint, turns logging off (null). With neither, the default host is used, picked
|
|
236
|
+
* like GlobalVars.checkEnv: sandbox for pk_snd_ keys, production otherwise.
|
|
237
|
+
*/
|
|
238
|
+
private fun getLoggingEndpoint(
|
|
239
|
+
publishableKey: String,
|
|
240
|
+
overrideEndpoints: OverrideEndpoints?,
|
|
241
|
+
commonEndpoint: String?
|
|
242
|
+
): String? {
|
|
243
|
+
val backend: String?
|
|
244
|
+
val logs: String?
|
|
245
|
+
if (overrideEndpoints != null) {
|
|
246
|
+
backend = overrideEndpoints.customBackendEndpoint
|
|
247
|
+
logs = overrideEndpoints.customLoggingEndpoint
|
|
248
|
+
} else {
|
|
249
|
+
backend = commonEndpoint?.let { "$it$BACKEND_PATH" }
|
|
250
|
+
logs = commonEndpoint?.let { "$it$LOGS_PATH" }
|
|
251
|
+
}
|
|
252
|
+
return when {
|
|
253
|
+
logs != null -> logs.ifEmpty { null }
|
|
254
|
+
backend != null -> null
|
|
255
|
+
else -> (if (publishableKey.startsWith("pk_snd_")) SANDBOX_HOST else PROD_HOST) + LOGS_PATH
|
|
106
256
|
}
|
|
107
|
-
val handle = UUID.randomUUID().toString()
|
|
108
|
-
promise?.resolve(handle)
|
|
109
257
|
}
|
|
110
258
|
|
|
111
259
|
override fun presentPaymentSheet(
|
|
@@ -113,25 +261,37 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
113
261
|
promise: Promise?
|
|
114
262
|
) {
|
|
115
263
|
try {
|
|
264
|
+
NativeLog.setSdkAuthorization(params?.getMap("paymentSessionConfig")?.getString("sdkAuthorization"))
|
|
265
|
+
val activity = requireReady("presentPaymentSheet", promise) ?: return
|
|
266
|
+
NativeLog.debug("present_payment_sheet_called", mapOf("activity" to activity.javaClass.simpleName))
|
|
116
267
|
val props = mutableMapOf<String, Any?>().apply {
|
|
117
268
|
putAll(params?.toHashMap().orEmpty())
|
|
118
269
|
put("type", "payment")
|
|
119
270
|
}
|
|
120
|
-
val bundle =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
271
|
+
val bundle = launchOptionsFor(activity).getBundleWithHyperParams(props)
|
|
272
|
+
if (PaymentSheetCallbackManager.getCallback() != null) {
|
|
273
|
+
if (paymentSessionReactLauncher?.isSheetVisible(activity) == true) {
|
|
274
|
+
promise?.resolve(
|
|
275
|
+
JSONObject().apply {
|
|
276
|
+
put("status", "cancelled")
|
|
277
|
+
put("code", "sheet_already_presented")
|
|
278
|
+
put("message", "A payment sheet is already presented.")
|
|
279
|
+
}.toString()
|
|
280
|
+
)
|
|
281
|
+
return
|
|
125
282
|
}
|
|
126
|
-
|
|
283
|
+
/* The previous call's sheet is gone without reporting back; settle it before replacing. */
|
|
284
|
+
PaymentSheetCallbackManager.executeCallback(
|
|
285
|
+
failed("SHEET_REPLACED", "Replaced by a newer presentPaymentSheet call")
|
|
286
|
+
)
|
|
127
287
|
}
|
|
128
|
-
|
|
129
|
-
val
|
|
130
|
-
|
|
131
|
-
put("message", "failed to open")
|
|
132
|
-
put("reason", e.message.toString())
|
|
288
|
+
val isFragment = paymentSessionReactLauncher?.presentSheet(bundle, activity)
|
|
289
|
+
val resultCallback: (String) -> Unit = { it ->
|
|
290
|
+
promise?.resolve(it)
|
|
133
291
|
}
|
|
134
|
-
|
|
292
|
+
PaymentSheetCallbackManager.setCallback(resultCallback, isFragment == true)
|
|
293
|
+
} catch (e: Exception) {
|
|
294
|
+
promise?.resolve(failed("PRESENT_FAILED", "failed to open: ${e.message}"))
|
|
135
295
|
}
|
|
136
296
|
}
|
|
137
297
|
|
|
@@ -139,17 +299,17 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
139
299
|
params: ReadableMap?,
|
|
140
300
|
promise: Promise
|
|
141
301
|
) {
|
|
302
|
+
NativeLog.setSdkAuthorization(params?.getMap("paymentSessionConfig")?.getString("sdkAuthorization"))
|
|
303
|
+
val activity = requireReady("getCustomerSavedPaymentMethods", promise) ?: return
|
|
142
304
|
val props = mutableMapOf<String, Any?>().apply {
|
|
143
305
|
putAll(params?.toHashMap().orEmpty())
|
|
144
306
|
put("type", "payment")
|
|
145
307
|
}
|
|
146
308
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
bundle?.let {
|
|
152
|
-
val savedPaymentMethodCallback: (PaymentSessionHandler) -> Unit = { it ->
|
|
309
|
+
// sdkParams carries appId, which the headless confirm needs to send a return_url.
|
|
310
|
+
val bundle = launchOptionsFor(activity).getBundleWithHyperParams(props)
|
|
311
|
+
run {
|
|
312
|
+
val savedPaymentMethodCallback: (PaymentSessionHandler) -> Unit = {
|
|
153
313
|
handler = it
|
|
154
314
|
promise.resolve(
|
|
155
315
|
JSONObject().apply {
|
|
@@ -162,75 +322,89 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
162
322
|
params?.getMap("paymentSessionConfig")?.getString("sdkAuthorization"),
|
|
163
323
|
savedPaymentMethodCallback
|
|
164
324
|
)
|
|
165
|
-
paymentSessionReactLauncher?.recreateReactContext(
|
|
325
|
+
if (paymentSessionReactLauncher?.recreateReactContext(bundle) != true) {
|
|
326
|
+
promise.resolve(failed("ACTIVITY_UNAVAILABLE", "No active Activity"))
|
|
327
|
+
}
|
|
166
328
|
}
|
|
167
329
|
|
|
168
330
|
}
|
|
169
331
|
|
|
170
332
|
override fun getCustomerLastUsedPaymentMethodData(promise: Promise) {
|
|
171
|
-
handler
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
onFailure = { error ->
|
|
177
|
-
val pmError = error as? PMError
|
|
178
|
-
promise.resolve(
|
|
179
|
-
StandardResult.Failed(
|
|
180
|
-
code = pmError?.code ?: "UNKNOWN",
|
|
181
|
-
message = pmError?.message ?: error.message ?: "Unknown error",
|
|
182
|
-
error = Throwable(pmError?.message ?: error.message ?: "Unknown error")
|
|
183
|
-
).toJSONString()
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
)
|
|
333
|
+
val handler = handler
|
|
334
|
+
if (handler == null) {
|
|
335
|
+
/* A null handler previously fell through ?.let without settling the promise. */
|
|
336
|
+
promise.resolve(failed("NO_HANDLER", "Saved payment methods are not initialized"))
|
|
337
|
+
return
|
|
187
338
|
}
|
|
339
|
+
handler.getCustomerLastUsedPaymentMethodData().fold(
|
|
340
|
+
onSuccess = { data ->
|
|
341
|
+
promise.resolve(ConversionUtils.convertMapToJson(data.toMap()).toString())
|
|
342
|
+
},
|
|
343
|
+
onFailure = { error ->
|
|
344
|
+
val pmError = error as? PMError
|
|
345
|
+
promise.resolve(
|
|
346
|
+
StandardResult.Failed(
|
|
347
|
+
code = pmError?.code ?: "UNKNOWN",
|
|
348
|
+
message = pmError?.message ?: error.message ?: "Unknown error",
|
|
349
|
+
error = Throwable(pmError?.message ?: error.message ?: "Unknown error")
|
|
350
|
+
).toJSONString()
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
)
|
|
188
354
|
}
|
|
189
355
|
|
|
190
356
|
override fun getCustomerDefaultSavedPaymentMethodData(promise: Promise) {
|
|
191
|
-
handler
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
onFailure = { error ->
|
|
197
|
-
val pmError = error as? PMError
|
|
198
|
-
promise.resolve(
|
|
199
|
-
StandardResult.Failed(
|
|
200
|
-
code = pmError?.code ?: "UNKNOWN",
|
|
201
|
-
message = pmError?.message ?: error.message ?: "Unknown error",
|
|
202
|
-
error = Throwable(pmError?.message ?: error.message ?: "Unknown error")
|
|
203
|
-
).toJSONString()
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
)
|
|
357
|
+
val handler = handler
|
|
358
|
+
if (handler == null) {
|
|
359
|
+
/* A null handler previously fell through ?.let without settling the promise. */
|
|
360
|
+
promise.resolve(failed("NO_HANDLER", "Saved payment methods are not initialized"))
|
|
361
|
+
return
|
|
207
362
|
}
|
|
363
|
+
handler.getCustomerDefaultSavedPaymentMethodData().fold(
|
|
364
|
+
onSuccess = { data ->
|
|
365
|
+
promise.resolve(ConversionUtils.convertMapToJson(data.toMap()).toString())
|
|
366
|
+
},
|
|
367
|
+
onFailure = { error ->
|
|
368
|
+
val pmError = error as? PMError
|
|
369
|
+
promise.resolve(
|
|
370
|
+
StandardResult.Failed(
|
|
371
|
+
code = pmError?.code ?: "UNKNOWN",
|
|
372
|
+
message = pmError?.message ?: error.message ?: "Unknown error",
|
|
373
|
+
error = Throwable(pmError?.message ?: error.message ?: "Unknown error")
|
|
374
|
+
).toJSONString()
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
)
|
|
208
378
|
}
|
|
209
379
|
|
|
210
380
|
override fun getCustomerSavedPaymentMethodData(promise: Promise) {
|
|
211
|
-
handler
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
val pmError = error as? PMError
|
|
224
|
-
promise.resolve(
|
|
225
|
-
StandardResult.Failed(
|
|
226
|
-
code = pmError?.code ?: "UNKNOWN",
|
|
227
|
-
message = pmError?.message ?: error.message ?: "Unknown error",
|
|
228
|
-
error = Throwable(pmError?.message ?: error.message ?: "Unknown error")
|
|
229
|
-
).toJSONString()
|
|
381
|
+
val handler = handler
|
|
382
|
+
if (handler == null) {
|
|
383
|
+
/* A null handler previously fell through ?.let without settling the promise. */
|
|
384
|
+
promise.resolve(failed("NO_HANDLER", "Saved payment methods are not initialized"))
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
handler.getCustomerSavedPaymentMethodData().fold(
|
|
388
|
+
onSuccess = { data ->
|
|
389
|
+
val jsonArray = JSONArray()
|
|
390
|
+
data.forEach { item ->
|
|
391
|
+
jsonArray.put(
|
|
392
|
+
ConversionUtils.convertMapToJson(item.toMap())
|
|
230
393
|
)
|
|
231
394
|
}
|
|
232
|
-
|
|
233
|
-
|
|
395
|
+
promise.resolve(jsonArray.toString())
|
|
396
|
+
},
|
|
397
|
+
onFailure = { error ->
|
|
398
|
+
val pmError = error as? PMError
|
|
399
|
+
promise.resolve(
|
|
400
|
+
StandardResult.Failed(
|
|
401
|
+
code = pmError?.code ?: "UNKNOWN",
|
|
402
|
+
message = pmError?.message ?: error.message ?: "Unknown error",
|
|
403
|
+
error = Throwable(pmError?.message ?: error.message ?: "Unknown error")
|
|
404
|
+
).toJSONString()
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
)
|
|
234
408
|
}
|
|
235
409
|
|
|
236
410
|
override fun confirmWithCustomerLastUsedPaymentMethod(reactTag: Double, promise: Promise?) {
|
|
@@ -241,6 +415,7 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
241
415
|
)
|
|
242
416
|
return
|
|
243
417
|
}
|
|
418
|
+
paymentSessionReactLauncher?.resumeHostForCurrentActivity()
|
|
244
419
|
|
|
245
420
|
val reactTag = reactTag.toInt()
|
|
246
421
|
|
|
@@ -282,6 +457,7 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
282
457
|
)
|
|
283
458
|
return
|
|
284
459
|
}
|
|
460
|
+
paymentSessionReactLauncher?.resumeHostForCurrentActivity()
|
|
285
461
|
|
|
286
462
|
val reactTag = reactTag.toInt()
|
|
287
463
|
|
|
@@ -340,6 +516,7 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
340
516
|
)
|
|
341
517
|
return
|
|
342
518
|
} else {
|
|
519
|
+
paymentSessionReactLauncher?.resumeHostForCurrentActivity()
|
|
343
520
|
handler?.confirmWithCustomerPaymentToken(token, null) { result ->
|
|
344
521
|
promise?.resolve(result.toJSONString())
|
|
345
522
|
}
|
|
@@ -382,6 +559,7 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
382
559
|
override fun getWalletSession(params: ReadableMap?, promise: Promise) {
|
|
383
560
|
val sdkAuthorization =
|
|
384
561
|
params?.getMap("paymentSessionConfig")?.getString("sdkAuthorization")
|
|
562
|
+
NativeLog.setSdkAuthorization(sdkAuthorization)
|
|
385
563
|
|
|
386
564
|
if (sdkAuthorization.isNullOrEmpty()) {
|
|
387
565
|
promise.resolve(
|
|
@@ -394,8 +572,12 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
394
572
|
return
|
|
395
573
|
}
|
|
396
574
|
|
|
397
|
-
|
|
398
|
-
|
|
575
|
+
if (!requireInitialised("getWalletSession", promise)) return
|
|
576
|
+
|
|
577
|
+
/*
|
|
578
|
+
* Reuse only while the intent is unchanged. A new sdkAuthorization means
|
|
579
|
+
* updateIntent replaced the intent, so the handler is rebuilt against it.
|
|
580
|
+
*/
|
|
399
581
|
if (walletHandler != null && walletSdkAuthorization == sdkAuthorization) {
|
|
400
582
|
promise.resolve(
|
|
401
583
|
JSONObject().apply {
|
|
@@ -406,6 +588,7 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
406
588
|
return
|
|
407
589
|
}
|
|
408
590
|
|
|
591
|
+
val activity = requireReady("getWalletSession", promise) ?: return
|
|
409
592
|
walletHandler = null
|
|
410
593
|
walletSdkAuthorization = sdkAuthorization
|
|
411
594
|
|
|
@@ -414,19 +597,8 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
414
597
|
put("type", "walletWidget")
|
|
415
598
|
}
|
|
416
599
|
|
|
417
|
-
|
|
418
|
-
val bundle =
|
|
419
|
-
|
|
420
|
-
if (bundle == null) {
|
|
421
|
-
promise.resolve(
|
|
422
|
-
StandardResult.Failed(
|
|
423
|
-
code = "NOT_INITIALISED",
|
|
424
|
-
message = "SDK is not initialised",
|
|
425
|
-
error = Throwable("SDK is not initialised")
|
|
426
|
-
).toJSONString()
|
|
427
|
-
)
|
|
428
|
-
return
|
|
429
|
-
}
|
|
600
|
+
// sdkParams carries appId, which the headless confirm needs to send a return_url.
|
|
601
|
+
val bundle = launchOptionsFor(activity).getBundleWithHyperParams(props)
|
|
430
602
|
|
|
431
603
|
var resolved = false
|
|
432
604
|
val walletSessionCallback: (WalletSessionHandler) -> Unit = {
|
|
@@ -443,7 +615,10 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
443
615
|
}
|
|
444
616
|
|
|
445
617
|
GetWalletSessionCallBackManager.setCallback(sdkAuthorization, walletSessionCallback)
|
|
446
|
-
paymentSessionReactLauncher?.recreateReactContext(bundle)
|
|
618
|
+
if (paymentSessionReactLauncher?.recreateReactContext(bundle) != true) {
|
|
619
|
+
resolved = true
|
|
620
|
+
promise.resolve(failed("ACTIVITY_UNAVAILABLE", "No active Activity"))
|
|
621
|
+
}
|
|
447
622
|
}
|
|
448
623
|
|
|
449
624
|
override fun isWalletEligible(wallet: String, promise: Promise) {
|
|
@@ -467,8 +642,12 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
467
642
|
)
|
|
468
643
|
return
|
|
469
644
|
}
|
|
470
|
-
|
|
471
|
-
|
|
645
|
+
requireReady("launchWallet", promise) ?: return
|
|
646
|
+
val launcher = paymentSessionReactLauncher ?: return
|
|
647
|
+
launcher.runWithHostResumed {
|
|
648
|
+
current.launchWallet(wallet) { result ->
|
|
649
|
+
promise.resolve(result.toJSONString())
|
|
650
|
+
}
|
|
472
651
|
}
|
|
473
652
|
}
|
|
474
653
|
|
|
@@ -530,5 +709,27 @@ class ReactNativeHyperswitchModule(reactContext: ReactApplicationContext) :
|
|
|
530
709
|
private const val IS_READY_TO_PAY_REQUEST =
|
|
531
710
|
"""{"apiVersion":2,"apiVersionMinor":0,"allowedPaymentMethods":[{"type":"CARD","parameters":{"allowedAuthMethods":["PAN_ONLY","CRYPTOGRAM_3DS"],"allowedCardNetworks":["AMEX","DISCOVER","JCB","MASTERCARD","VISA"]}}]}"""
|
|
532
711
|
private var hyperswitchConfig: HyperswitchConfiguration? = null
|
|
712
|
+
private const val PROD_HOST = "https://live.hyperswitch.io"
|
|
713
|
+
private const val SANDBOX_HOST = "https://app.hyperswitch.io"
|
|
714
|
+
private const val BACKEND_PATH = "/api"
|
|
715
|
+
private val CONFIG_NAMES = listOf(
|
|
716
|
+
ActivityInfo.CONFIG_FONT_SCALE to "fontScale",
|
|
717
|
+
ActivityInfo.CONFIG_LOCALE to "locale",
|
|
718
|
+
ActivityInfo.CONFIG_LAYOUT_DIRECTION to "layoutDirection",
|
|
719
|
+
ActivityInfo.CONFIG_UI_MODE to "uiMode",
|
|
720
|
+
ActivityInfo.CONFIG_DENSITY to "density",
|
|
721
|
+
ActivityInfo.CONFIG_ORIENTATION to "orientation",
|
|
722
|
+
ActivityInfo.CONFIG_SCREEN_SIZE to "screenSize",
|
|
723
|
+
ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE to "smallestScreenSize",
|
|
724
|
+
ActivityInfo.CONFIG_SCREEN_LAYOUT to "screenLayout",
|
|
725
|
+
ActivityInfo.CONFIG_KEYBOARD to "keyboard",
|
|
726
|
+
ActivityInfo.CONFIG_KEYBOARD_HIDDEN to "keyboardHidden",
|
|
727
|
+
ActivityInfo.CONFIG_NAVIGATION to "navigation",
|
|
728
|
+
ActivityInfo.CONFIG_TOUCHSCREEN to "touchscreen",
|
|
729
|
+
ActivityInfo.CONFIG_MCC to "mcc",
|
|
730
|
+
ActivityInfo.CONFIG_MNC to "mnc",
|
|
731
|
+
ActivityInfo.CONFIG_COLOR_MODE to "colorMode",
|
|
732
|
+
)
|
|
733
|
+
private const val LOGS_PATH = "/api/logs/sdk"
|
|
533
734
|
}
|
|
534
735
|
}
|
|
@@ -36,8 +36,10 @@ object ExitHeadlessCallBackManager {
|
|
|
36
36
|
return when (val status = message.getString("status")) {
|
|
37
37
|
"cancelled" -> PaymentResult.Canceled(status)
|
|
38
38
|
"failed", "requires_payment_method" -> {
|
|
39
|
-
|
|
40
|
-
throwable
|
|
39
|
+
// optString: the core doesn't always send both keys, and getString would throw.
|
|
40
|
+
val throwable = Throwable(message.optString("message", ""))
|
|
41
|
+
message.optString("code", "").takeIf { it.isNotEmpty() }
|
|
42
|
+
?.let { throwable.initCause(Throwable(it)) }
|
|
41
43
|
PaymentResult.Failed(throwable)
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -40,17 +40,33 @@ internal class PaymentSessionHandlerImpl(
|
|
|
40
40
|
override fun confirmWithCustomerDefaultPaymentMethod(
|
|
41
41
|
cvc: String?, resultHandler: (PaymentResult) -> Unit
|
|
42
42
|
) {
|
|
43
|
-
defaultMethodData.getString("payment_token")
|
|
44
|
-
|
|
43
|
+
val paymentToken = defaultMethodData.getString("payment_token")
|
|
44
|
+
if (paymentToken == null) {
|
|
45
|
+
resultHandler(paymentTokenMissingFailure("default"))
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
confirmWithCustomerPaymentToken(paymentToken, cvc, resultHandler)
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
override fun confirmWithCustomerLastUsedPaymentMethod(
|
|
48
52
|
cvc: String?, resultHandler: (PaymentResult) -> Unit
|
|
49
53
|
) {
|
|
50
|
-
lastUsedMethodData.getString("payment_token")
|
|
51
|
-
|
|
54
|
+
val paymentToken = lastUsedMethodData.getString("payment_token")
|
|
55
|
+
if (paymentToken == null) {
|
|
56
|
+
resultHandler(paymentTokenMissingFailure("last used"))
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
confirmWithCustomerPaymentToken(paymentToken, cvc, resultHandler)
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
/* A missing token previously fell through ?.let without invoking resultHandler,
|
|
63
|
+
leaving the merchant's confirm promise pending forever. */
|
|
64
|
+
private fun paymentTokenMissingFailure(which: String) = PaymentResult.Failed(
|
|
65
|
+
Throwable("payment_token missing on the $which saved payment method").apply {
|
|
66
|
+
initCause(Throwable("PAYMENT_TOKEN_MISSING"))
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
54
70
|
override fun updateSdkAuthorization(sdkAuthorization: String){
|
|
55
71
|
this.sdkAuthorization = sdkAuthorization
|
|
56
72
|
}
|