Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions app/src/main/kotlin/io/hyperswitch/react/HyperModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import io.hyperswitch.BuildConfig
import io.hyperswitch.payments.GooglePayCallbackManager
import io.hyperswitch.payments.PazeCallbackManager
import io.hyperswitch.payments.view.WidgetLauncher
import io.hyperswitch.paymentsession.LaunchOptions
import io.hyperswitch.paymentsession.PaymentSheetCallbackManager
Expand Down Expand Up @@ -123,6 +124,40 @@ class HyperModule internal constructor(private val rct: ReactApplicationContext)
}
}

// Method to launch Paze payment
@ReactMethod
fun launchPaze(pazeRequest: String, callBack: Callback) {
currentActivity?.let {
PazeCallbackManager.setCallback(
it,
pazeRequest,
fun(data: Map<String, Any?>) {
callBack.invoke(
Arguments.fromBundle(
LaunchOptions(
it, BuildConfig.VERSION_NAME
).toBundle(data)
)
)
},
)
} ?: run {
PazeCallbackManager.setCallback(
reactApplicationContext,
pazeRequest,
fun(data: Map<String, Any?>) {
callBack.invoke(
Arguments.fromBundle(
LaunchOptions(
reactApplicationContext, BuildConfig.VERSION_NAME
).toBundle(data)
)
)
},
)
}
}

// Method to exit the payment sheet
@ReactMethod
fun exitPaymentsheet(rootTag: Int, paymentResult: String, reset: Boolean) {
Expand Down
5 changes: 5 additions & 0 deletions hyperswitch-sdk-android-common/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
android:exported="false"
android:theme="@style/HyperTransparentTheme" />

<activity
android:name="io.hyperswitch.payments.PazeActivity"
android:exported="false"
android:theme="@style/HyperTransparentTheme" />

<meta-data
android:name="com.google.android.gms.wallet.api.enabled"
android:value="true" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package io.hyperswitch.payments

import android.annotation.SuppressLint
import android.app.Activity
import android.net.http.SslError
import android.os.Bundle
import android.util.Log
import android.webkit.JavascriptInterface
import android.webkit.SslErrorHandler
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import org.json.JSONObject

class PazeActivity : Activity() {

private lateinit var webView: WebView

@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val pazeRequest = intent.getStringExtra("pazeRequest") ?: "{}"
val pazeJson = JSONObject(pazeRequest)

val publishableKey = pazeJson.optString("publishable_key", "")
val clientId = pazeJson.optString("client_id", "")
val clientName = pazeJson.optString("client_name", "")
val clientProfileId = pazeJson.optString("client_profile_id", "")
val emailAddress = pazeJson.optString("email_address", "")
val transactionAmount = pazeJson.optString("transaction_amount", "")
val transactionCurrencyCode = pazeJson.optString("transaction_currency_code", "")
val sessionId = pazeJson.optString("session_id", "")

val pazeScriptUrl = if (publishableKey.startsWith("pk_snd"))
"https://sandbox.digitalwallet.earlywarning.com/web/resources/js/digitalwallet-sdk.js"
else
"https://checkout.paze.com/web/resources/js/digitalwallet-sdk.js"

webView = WebView(this)
setContentView(webView)

webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.javaScriptCanOpenWindowsAutomatically = true
webView.settings.setSupportMultipleWindows(false)

webView.addJavascriptInterface(PazeJSInterface(this), "PazeNative")

webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean = false

override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
// Inject the Paze SDK script and run the flow
val js = buildPazeFlowScript(
pazeScriptUrl,
clientId,
clientName,
clientProfileId,
emailAddress,
transactionAmount,
transactionCurrencyCode,
sessionId
)
view?.evaluateJavascript(js, null)
}

@SuppressLint("WebViewClientOnReceivedSslError")
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
handler?.cancel()
}
}

webView.webChromeClient = WebChromeClient()

// Load a blank page; once loaded, onPageFinished will inject the Paze SDK
webView.loadData(
"<html><head><meta name='viewport' content='width=device-width, initial-scale=1.0'></head><body></body></html>",
"text/html",
"utf-8"
)
}

private fun buildPazeFlowScript(
pazeScriptUrl: String,
clientId: String,
clientName: String,
clientProfileId: String,
emailAddress: String,
transactionAmount: String,
transactionCurrencyCode: String,
sessionId: String
): String {
return """
(function() {
var script = document.createElement('script');
script.src = '$pazeScriptUrl';
script.onload = function() {
(async function() {
try {
await DIGITAL_WALLET_SDK.initialize({
client: {
id: '$clientId',
name: '$clientName',
profileId: '$clientProfileId'
}
});

var canCheckout = await DIGITAL_WALLET_SDK.canCheckout({
emailAddress: '$emailAddress'
});

var transactionValue = {
transactionAmount: '$transactionAmount',
transactionCurrencyCode: '$transactionCurrencyCode'
};

await DIGITAL_WALLET_SDK.checkout({
acceptedPaymentCardNetworks: ['VISA', 'MASTERCARD'],
emailAddress: canCheckout.consumerPresent ? '$emailAddress' : '',
sessionId: '$sessionId',
actionCode: 'START_FLOW',
transactionValue: transactionValue,
shippingPreference: 'ALL'
});

var completeResponse = await DIGITAL_WALLET_SDK.complete({
transactionOptions: {
billingPreference: 'ALL',
merchantCategoryCode: 'US',
payloadTypeIndicator: 'PAYMENT'
},
transactionId: '',
sessionId: '$sessionId',
transactionType: 'PURCHASE',
transactionValue: transactionValue
});

var responseStr = '';
if (completeResponse && completeResponse.completeResponse) {
responseStr = completeResponse.completeResponse;
} else if (typeof completeResponse === 'string') {
responseStr = completeResponse;
} else {
responseStr = JSON.stringify(completeResponse);
}

PazeNative.onSuccess(responseStr);
} catch(e) {
var errMsg = e.message || JSON.stringify(e) || 'Unknown error';
PazeNative.onError(errMsg);
}
})();
};
script.onerror = function() {
PazeNative.onError('Failed to load Paze SDK script');
};
document.head.appendChild(script);
})();
""".trimIndent()
}

/**
* JavaScript interface to receive callbacks from the Paze SDK running in the WebView.
*/
class PazeJSInterface(private val activity: PazeActivity) {
@JavascriptInterface
fun onSuccess(completeResponse: String) {
PazeCallbackManager.executeCallback(mutableMapOf<String, Any?>().apply {
put("paymentMethodData", completeResponse)
})
activity.finish()
}

@JavascriptInterface
fun onError(errorMessage: String) {
PazeCallbackManager.executeCallback(mutableMapOf<String, Any?>().apply {
put("error", errorMessage)
})
activity.finish()
}

@JavascriptInterface
fun onCancel() {
PazeCallbackManager.executeCallback(mutableMapOf<String, Any?>().apply {
put("error", "Cancel")
})
activity.finish()
}
}

@Deprecated("Deprecated in Java")
override fun onBackPressed() {
PazeCallbackManager.executeCallback(mutableMapOf<String, Any?>().apply {
put("error", "Cancel")
})
super.onBackPressed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.hyperswitch.payments

import android.content.Context
import android.content.Intent

object PazeCallbackManager {
private var callback: Callback? = null

fun setCallback(appContext: Context, request: String, newCallback: Callback) {
callback = newCallback
val myIntent = Intent(
appContext,
PazeActivity::class.java
)
myIntent.putExtra("pazeRequest", request)
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
appContext.startActivity(myIntent)
}

fun getCallback(): Callback? {
return callback
}

fun executeCallback(data: Map<String, Any?>) {
callback?.invoke(data) ?: println("No callback set")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.webkit.JavascriptInterface
import android.webkit.WebView
import io.hyperswitch.payments.GooglePayCallbackManager
import io.hyperswitch.payments.PazeCallbackManager
import io.hyperswitch.paymentsession.PaymentSheetCallbackManager
import io.hyperswitch.webview.utils.Arguments
import io.hyperswitch.webview.utils.Callback
Expand Down Expand Up @@ -88,6 +89,14 @@ open class WebViewFragment : Fragment() {
)
}

fun launchPaze(data: JSONObject) {
PazeCallbackManager.setCallback(
context,
data.toString(),
::sendPazeResultToWebView,
)
}

// private fun sendResultToWebView(result: Map<String, Any?>) {
// try {
// val javascriptFunction =
Expand Down Expand Up @@ -170,6 +179,10 @@ open class WebViewFragment : Fragment() {
launchGPay(jsonObject.getJSONObject("launchGPay"))
}

if (jsonObject.has("launchPaze")) {
launchPaze(jsonObject.getJSONObject("launchPaze"))
}

if (jsonObject.has("launchScanCard")) {
launchScanCard(jsonObject.getJSONObject("launchScanCard"))
}
Expand Down Expand Up @@ -474,6 +487,19 @@ open class WebViewFragment : Fragment() {
}
}

private fun sendPazeResultToWebView(result: Map<String, Any?>) {
try {
val javascriptFunction =
"""window.postMessage(JSON.stringify({"pazeData": ${JSONObject(result)}}), '*');""".trimIndent()

val args = Arguments.createArray()
args.pushString(javascriptFunction)
hSWebViewManagerImpl.receiveCommand(hSWebViewWrapper, "injectJavaScript", args)
} catch (e: Exception) {
Log.e("sendPazeResultToWebView", "Error sending Paze result to WebView", e)
}
}

/**
* Inner class to define a JavaScript interface for the WebView.
*
Expand Down Expand Up @@ -509,6 +535,15 @@ open class WebViewFragment : Fragment() {
)
}

@JavascriptInterface
fun launchPaze(data: String) {
PazeCallbackManager.setCallback(
context,
data,
::sendPazeResultToWebView,
)
}

private fun sendResultToWebView(result: Map<String, Any?>) {
try {
val javascriptFunction =
Expand All @@ -521,6 +556,18 @@ open class WebViewFragment : Fragment() {
}
}

private fun sendPazeResultToWebView(result: Map<String, Any?>) {
try {
val javascriptFunction =
"""window.postMessage(JSON.stringify({"pazeData": ${JSONObject(result)}}), '*');""".trimIndent()
context.runOnUiThread {
webView.evaluateJavascript(javascriptFunction, null)
}
} catch (e: Exception) {
Log.e("sendPazeResultToWebView", "Error sending Paze result to WebView", e)
}
}

@JavascriptInterface
fun sdkInitialised(data: String) {
/* activity.runOnUiThread {
Expand Down
Loading