Skip to content
Merged
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
1 change: 1 addition & 0 deletions dist/doc/developer-tools/inlinejs/payment-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ try {
email: 'testuser@paystack.com',
amount: 10000,
currency: "NGN",
channels: ['card', 'google_pay'],
container: 'payment-request-buttons',
loadPaystackCheckoutButton: 'pay-button',
styles: {
Expand Down
1 change: 1 addition & 0 deletions src/doc/developer-tools/inlinejs/payment-request/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ try {
email: 'testuser@paystack.com',
amount: 10000,
currency: "NGN",
channels: ['card', 'google_pay'],
container: 'payment-request-buttons',
loadPaystackCheckoutButton: 'pay-button',
styles: {
Expand Down
3 changes: 3 additions & 0 deletions src/doc/guides/checkout-webview/handling-redirect/index.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@override
Widget build(BuildContext context) {
// Deep-link/universal-link handoff for partner channels (e.g. Zap) isn't
// documented for Flutter yet. See the React Native, iOS, and Android tabs
// for the pattern this needs.
return Scaffold(
body: WebView(
initialUrl: 'https://checkout.paystack.com/7zu1ot06d0qn9h6',
Expand Down
42 changes: 36 additions & 6 deletions src/doc/guides/checkout-webview/handling-redirect/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
import React from 'react';
import { Linking } from 'react-native';
import { WebView } from 'react-native-webview';

// String matchers match by prefix; RegExp matchers use `.test(url)`.
// Prefix, not `includes` - otherwise `https://evil.com/?u=https://joinzap.com/app/`
// would match.
const DEEP_LINK_HOSTS = [
'https://joinzap.com/app/',
// add your own partner hosts here
];

const shouldHandleExternally = (url, hosts = DEEP_LINK_HOSTS) =>
!!url &&
hosts.some((matcher) =>
typeof matcher === 'string' ? url.indexOf(matcher) === 0 : matcher.test(url),
);

const openExternalUrl = async (url) => {
try {
if (!(await Linking.canOpenURL(url))) return;
await Linking.openURL(url);
} catch {
// Nothing actionable - leaving the WebView is best-effort.
}
};

export default function App() {

const authorization_url = 'https://checkout.paystack.com/luKuasMan';
const callback_url = 'https://yourcallback.com';
const cancel_url = "https://your-cancel-url.com";

onNavigationStateChange = state => {
const { url } = state;
onShouldStartLoadWithRequest = request => {

const url = request.url;

if (!url) return;
if (!url) return true;

if (shouldHandleExternally(url)) {
void openExternalUrl(url);
return false; // cancel the in-WebView load
}

if (url === callback_url) {
// get transaction reference from url and verify transaction, then redirect
Expand All @@ -25,13 +53,15 @@ export default function App() {
// Use a navigator to pop off the view
// Run the cancel payment function if you have one
}

return true;
};

return (
<WebView
<WebView
source={{ uri: authorization_url }}
style={{ marginTop: 40 }}
onNavigationStateChange={ this.onNavigationStateChange }
onShouldStartLoadWithRequest={ this.onShouldStartLoadWithRequest }
/>
);
}
23 changes: 22 additions & 1 deletion src/doc/guides/checkout-webview/handling-redirect/index.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ class MainActivity : AppCompatActivity() {
private val cancelUrl: String
get() = "https://your-cancel-url.com"

// Prefix, not substring - otherwise a page could smuggle the host into a
// query string and force an external open.
private val deepLinkPrefixes = listOf("https://joinzap.com/app/")

override fun onCreate(savedInstanceState: Bundle?) {
// ...
}
Expand All @@ -24,6 +28,23 @@ class MainActivity : AppCompatActivity() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url: Uri? = request?.url

if (url != null && deepLinkPrefixes.any { url.toString().startsWith(it) }) {
// Partner-app handoff: resolve to an installed app only; do not open a browser.
val intent = Intent(Intent.ACTION_VIEW, url).apply {
addCategory(Intent.CATEGORY_BROWSABLE)
// Requires API 30+. On older versions, query
// packageManager.queryIntentActivities and only start the intent
// if a non-browser activity handles it.
flags = Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER
}
return try {
startActivity(intent)
true // navigation consumed
} catch (e: ActivityNotFoundException) {
false // no app installed - let the WebView load the web page
}
}

if (url?.host == callbackUrl) {
return true
}
Expand All @@ -32,7 +53,7 @@ class MainActivity : AppCompatActivity() {
// Run the cancel payment function if you have one
return true
}

return super.shouldOverrideUrlLoading(view, request)
}
}
Expand Down
59 changes: 44 additions & 15 deletions src/doc/guides/checkout-webview/handling-redirect/index.swift
Original file line number Diff line number Diff line change
@@ -1,41 +1,70 @@
import WebKit

class CheckoutViewController: UIViewController, WKNavigationDelegate {

class CheckoutViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {

// Prefix, not substring - otherwise a page could smuggle the host into a
// query string (e.g. https://evil.com/?u=https://joinzap.com/app/) and
// force an external open.
private let deepLinkPrefixes = ["https://joinzap.com/app/"]

// ..........
//This is helper to get url params

//This is helper to get url params
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}

// This is a WKNavigationDelegate func we can use to handle redirection
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping ((WKNavigationActionPolicy) -> Void) {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

if let url = navigationAction.request.url {


// Partner-app handoff: cancel the load and ask the OS to open it.
// `universalLinksOnly: true` means open the app, or fail - never fall
// back to loading the page in Safari.
if deepLinkPrefixes.contains(where: { url.absoluteString.hasPrefix($0) }) {
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { opened in
if !opened {
UIApplication.shared.open(url) // app not installed; web fallback
}
}
decisionHandler(.cancel)
return
}

/*
We control here when the user wants to cancel a payment.
By default a cancel action redirects to http://cancelurl.com/.
Based on our workflow we can for example remove the webview or push
another view to the user.
By default a cancel action redirects to http://cancelurl.com/.
Based on our workflow we can for example remove the webview or push
another view to the user.
*/
if url.absoluteString == "http://cancelurl.com/"{
decisionHandler(.cancel)
}
else{
decisionHandler(.allow)
}
//After a successful transaction we can check if the current url is the callback url
//and do what makes sense for our workflow. We can get the transaction reference for example.

//After a successful transaction we can check if the current url is the callback url
//and do what makes sense for our workflow. We can get the transaction reference for example.

if url.absoluteString.hasPrefix(callbackUrl){
let reference = getQueryStringParameter(url: url.absoluteString, param: "reference")
print("reference \(reference)")
}
}
}
}

// A link with target="_blank" produces no navigation action in the main
// frame, so without this WKUIDelegate method the tap is silently dropped.
// Requires `webView.uiDelegate = self` to be set where the WebView is created.
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
}
return nil
}
}
15 changes: 8 additions & 7 deletions src/doc/guides/checkout-webview/initialize-webview/index.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import WebKit

class CheckoutViewController: UIViewController, WKNavigationDelegate {
let callbackUrl = "CALLBACK_URL_GOES_HERE"
let pstkUrl = "AUTHORIZATION_URL_GOES_HERE"
class CheckoutViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {

let callbackUrl = "CALLBACK_URL_GOES_HERE"
let pstkUrl = "AUTHORIZATION_URL_GOES_HERE"
let 3dsUrl = "https://standard.paystack.co/close"

@IBOutlet weak var webView: WKWebView!

override func viewDidLoad() {
super.viewDidLoad()

let urlRequest = URLRequest.init(url: URL.init(string: pstkUrl)!)
webView.load(urlRequest)
self.webView.navigationDelegate = self
}
self.webView.uiDelegate = self
}
}