Checkout Native Bridge Communication for apps
A native bridge, sometimes known as a JavaScript bridge, is a mechanism that facilitates communication between a WebView and native app code.
Mobile applications can integrate Billwerk+ Checkout when using WebViews. In order to receive events from Billwerk+ Checkout through WebView, you must register native bridge communication in your app's WebView.
The implemented WebView in your mobile app must load the checkout session URL created from charge session or other session types. Billwerk+ Checkout will fire the first event Init
, which your WebView will receive after having the correct JavaScript interface/channel registered. See examples below.
Native Bridge Channels
List of exposed JavaScript interfaces for creating bridge between Billwerk+ Checkout and mobile app WebViews:
Channel name | WebView | Platform | Functions |
---|---|---|---|
ReepayCheckout | WKWebView | Native iOS | resolveMessage(reply) => void |
AndroidWebViewListener | WebView | Native Android | N/A |
ReactNativeWebView | react-native-webview | React Native (iOS & Android) | resolveMessage(reply) => void |
CheckoutChannel | webview_flutter_wkwebview webview_flutter_android | Flutter (iOS & Android) | resolveMessage(reply) => void |
Events
Similar to Embedded Checkout and Modal Checkout, your WebView will receive a number of events which your mobile app WebView must subscribe to.
WebView Event Types
Checkout events
Event | Description |
---|---|
Init | Fires once Checkout is loaded in WebView. |
Open | Fires once Checkout content has appeared. |
Close | Optional Does not fire from Billwerk+ Checkout. WebView should implement this and fire event when user closes WebView. |
Accept | Fires once the transaction is successfully completed. |
Cancel | Fires once user clicks Cancel inside Checkout page, or Checkout only has a single payment method and cancels externally e.g. cancel from MobilePay app. |
Error | Fires whenever an error has occurred in attempting to finalize the transaction. |
User events
Event | Description |
---|---|
card_input_change | Fires once any of the card input fields have been modified |
WebView Event Response Signature
Each event responds with a signature of type:
{
event: string;
sessionState?: string;
data?: any;
}
State of the checkout session can be of following types:
"PAYMENT_METHOD_ALREADY_ADDED",
"INVOICE_ALREADY_PAID",
"INVOICE_PROCESSING,
"SUCCESS",
"SESSION_EXPIRED",
Data object uses the same signature as Embedded and Modal Checkout signatures. Only events Open
, Accept
, Cancel
and Error
will contain data objects.
{
id: string; // The current session id
invoice: string; // Invoice/charge handle
customer: string; // Customer handle
subscription: string; // Subscription handle
payment_method: string; // Payment method if a new one is created
error: string; // The error code
}
WebView Event Reply
Init
event is the first event fired from Billwerk+ Checkout:
{
event: "Init"
}
Once received by WebView, the app must reply the event message with { isWebView: true }
. It will set Billwerk+ Checkout to WebView mode, and thus continue to fire the other WebView events.
{
isWebView: true
}
WebView Event Reply Signature
{
isWebView: boolean; // Must be set to 'true' to receive WebView events
isWebViewChanged?: boolean; // Optional - notify Checkout that WebView has been modified by user
userAgent?: string; // Optional - define a custom user agent
}
Examples
Examples of how to add JavaScript channel interfaces for your WebView to receive and reply messages from/to Billwerk+ Checkout.
Hybrid apps
Hybrid apps usually imports libraries for WebView implementation. Below are some examples from our demo apps of how to subscribe the events sent from Billwerk+ Checkout.
React Native
React Native WebView has a JavaScript channel to perform communication between the app and webpage. It is pre-defined as window.ReactNativeWebView
. See detailed example in our React Native demo app.
render(): ReactNode {
return (
<WebView
...
javaScriptEnabled={true}
onMessage={this._handleWebViewMessageEvent}
...
/>
);
}
private _handleWebViewMessageEvent = (event: WebViewMessageEvent) => {
const rawData = event.nativeEvent.data;
try {
const message = JSON.parse(rawData);
const event = message.event;
switch (event) {
case "Init":
const customUserAgent = this._getCustomUserAgent();
const reply = JSON.stringify({
isWebView: true,
userAgent: customUserAgent,
});
const injectedJavaScript = `
if (window.ReactNativeWebView.resolveMessage) {
window.ReactNativeWebView.resolveMessage(${reply});
}
`;
this.webview.injectJavaScript(injectedJavaScript);
break;
default
// handle other cases
break;
}
} catch (error) {
// handle error
}
}
Flutter
Flutter WebView uses the pre-defined channel from Billwerk+ Checkout CheckoutChannel
. See detailed example in our Flutter Demo app.
final WebViewController controller = WebViewController()
...
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel('CheckoutChannel', onMessageReceived: (JavaScriptMessage message) {
_onMessageReceived(message);
});
void _onMessageReceived(JavaScriptMessage message) {
// handle events
}
// Reply event
final reply = {'isWebView': true};
final jsCode = '''
if (window.CheckoutChannel && typeof window.CheckoutChannel.resolveMessage === 'function') {
window.CheckoutChannel.resolveMessage(${jsonEncode(reply)});
}
''';
_controller.runJavaScript(jsCode);
Native apps
Native apps uses Android WebView and iOS WKWebView.
Android WebView
Android WebView uses the pre-defined channel from Billwerk+ Checkout AndroidWebViewListener
. See detailed example in our Android demo app.
class MyWebView(private val context: Context) {
fun showWebViewBottomSheet(sessionUrl: String) {
val bottomSheetDialog = BottomSheetDialog(context)
val bottomSheetView = View.inflate(context, R.layout.bottom_sheet_dialog, null)
val webView = bottomSheetView.findViewById<WebView>(R.id.webview)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(MyWebViewListener(context, bottomSheetDialog), "AndroidWebViewListener")
webView.loadUrl(sessionUrl)
...
}
}
class MyWebViewListener(private val context: Context, private val dialog: BottomSheetDialog) {
@JavascriptInterface
fun postMessage(jsonMessage: String) {
val mapType = object : TypeToken<Map<String, Any>>() {}.type
val messageMap: Map<String, Any> = Gson().fromJson(jsonMessage, mapType)
for ((key, value) in messageMap) {
if(key == "event"){
handleEvents(value.toString())
}
}
}
private fun handleEvents(event: String) {
when (event) {
"Init" -> Log.d("AndroidWebViewListener", "Checkout initiated")
"Open" -> Log.d("AndroidWebViewListener", "Checkout opened")
"Close" -> Log.d("AndroidWebViewListener", "Checkout closed")
"Accept" -> Log.d("AndroidWebViewListener", "Checkout payment succeeded")
"Cancel" -> Log.d("AndroidWebViewListener", "Checkout cancelled")
"Error" -> Log.d("AndroidWebViewListener", "Error occurred")
else -> Log.d("AndroidWebViewListener", "Unhandled event: $event")
}
}
}
iOS WKWebView
iOS WKWebView uses pre-defined channel from Billwerk+ Checkout ReepayCheckout
. See detailed example in our iOS demo app.
struct MyWebView: UIViewRepresentable {
...
func makeUIView(context: Context) -> WKWebView {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let configuration = WKWebViewConfiguration()
configuration.defaultWebpagePreferences = prefs
let webView = WKWebView(frame: .zero, configuration: configuration)
context.coordinator.webView = webView
let contentController = webView.configuration.userContentController
if #available(iOS 17.0, *) {
contentController.addScriptMessageHandler(context.coordinator, contentWorld: .page, name: "ReepayCheckout")
} else {
contentController.add(context.coordinator, name: "ReepayCheckout")
}
webView.navigationDelegate = context.coordinator
return webView
}
...
}
Reply the event message in versions below iOS 17:
func replyWithResolveMessage(message: String) {
let responseScript = "window.webkit.messageHandlers.ReepayCheckout.resolveMessage(\(message))"
webView?.evaluateJavaScript(responseScript, completionHandler: { _, error in
if let error = error {
fatalError("Error injecting JavaScript: \(error.localizedDescription)")
}
})
}
Reply the event message in version iOS 17 and above:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
if let response = message.body as? [String: Any] {
let eventName = response["event"] as? String
if (eventName == "Init") {
let reply = [
"isWebView": true,
]
replyHandler(reply, nil)
} else {
// Handle other events
}
} else {
fatalError("Error: Unsupported message type")
}
}
Further development
Enhance users payment experience by adding a seamless app switch flow between your app and external payment apps. Read more regarding redirect to external payment apps such as Vipps MobilePay and return to your app here.
Updated about 1 month ago