Payment flow in native apps
Enable SmartPix payments in your native Android or iOS app using a WebView.
You can embed dLocal's payment experience directly into your native Android or iOS app using a WebView.
This integration provides a seamless in-app checkout experience for your users, avoiding redirection to an external browser until the final step, when they must authenticate with their bank or payment provider.
This option is only available for the redirect flow.
How it works
At the core of this solution is that your app takes over the final step of the payment flow. As our payment page is about to redirect to the bank, your app will catch that redirect and open the bank's page in the device's system browser.
Prerequisites
To use this embedded solution, make sure you have:
- A native mobile app (Android or iOS).
- An existing integration using dLocal's redirect flow.
- A deep link or universal link to handle the redirection from the bank's app back to your app after the payment process.
Integration overview
The integration involves the following steps:
- Creating a payment request
- Loading the redirect URL in a WebView
- Intercepting external redirects to open them natively
Integration steps
Step 1: Create a payment
First, you'll need to create a payment request using the redirect flow. Refer to the Authorization checkout managed by dLocal page for request details.
Example response
{
"payment": {
"status": "PENDING",
"redirect_url": "https://pay.dlocal.com/gmf-apm/smart-pix/R-4-8b4f332f-b2fe-4f35-9cc1-63..."
}
}
Step 2: Get the redirect URL
After a successful payment request, the API will return a redirect_url
that links to dLocal's hosted payment page.
This is the URL of our payment page that you will load in your WebView.
Example URL
https://pay.dlocal.com/gmf-apm/smart-pix/R-4-8b4f332f-b2fe-4f35-9cc1-63...
Step 3: Add the embedded parameter
This is a critical and required step. To ensure our solution works correctly within your WebView, you must append embedded=true
to the redirect_url
returned in the payment response.
Example URL
https://pay.dlocal.com/gmf-apm/smart-pix/R-4-8b4f332f-b2fe-4f35-9cc1-63...?embedded=true
Step 4: Open the payment page
Once the URL is modified (with the embedded=true
parameter added), load it in a WebView.
Example
Android (Java)
String redirectUrl = "https://pay.dlocal.com/gmf-apm/...?embedded=true";
webView.loadUrl(redirectUrl);
iOS (Swift)
let redirectUrl = URL(string: "https://pay.dlocal.com/gmf-apm/...embedded=true")!
let request = URLRequest(url: redirectUrl)
webView.load(request)
Step 5: Intercept the redirect
The final and most important step is to program your native app to detect when the WebView attempts to redirect to an external domain, such as a bank's website. You must prevent the WebView from doing this redirection and instead handle the redirect yourself using your device's native capabilities.
Code implementation for URL interception
Android (Java)
The core of this solution lies in intercepting WebView
navigations to handle them natively. Below is the recommended Java code for your WebViewClient
to do just that.
// Add this WebViewClient to your WebView
webView.setWebViewClient(new WebViewClient() {
// Save the initial host when the first page finishes loading
@Override
public void onPageFinished(WebView view, String url) {
if (initialHost == null) {
Uri u = Uri.parse(url);
if ("http".equalsIgnoreCase(u.getScheme()) || "https".equalsIgnoreCase(u.getScheme())) {
initialHost = u.getHost(); // Example: sandbox.dlocal.com
}
}
}
// For Android < 7 (API < 24)
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return handleNavigationOutside(Uri.parse(url));
}
// For Android >= 7 (API >= 24)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return handleNavigationOutside(request.getUrl());
}
/**
* Main logic for handling external redirections
*/
private boolean handleNavigationOutside(Uri uri) {
final String scheme = uri.getScheme() != null ? uri.getScheme().toLowerCase() : "";
// 1) Allow internal navigation (same host, http/https)
if (("http".equals(scheme) || "https".equals(scheme)) && sameInitialHost(uri)) {
return false; // Keep navigation inside WebView
}
// 2) Handle intent:// links (apps or fallbacks)
if ("intent".equals(scheme)) {
try {
Intent intent = Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setComponent(null); // Let Android decide the app
startActivity(intent);
return true;
} catch (Exception ignored) {
// Try browser_fallback_url or Play Store if available
try {
Intent parsed = Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME);
String fallback = parsed.getStringExtra("browser_fallback_url");
if (fallback != null) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(fallback)));
return true;
}
String pkg = parsed.getPackage();
if (pkg != null) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + pkg)));
return true;
}
} catch (Exception ignored2) {}
}
return true;
}
// 3) Handle non-http(s) schemes (tel:, mailto:, custom app links, etc.)
if (!"http".equals(scheme) && !"https".equals(scheme)) {
openExternal(uri);
return true;
}
// 4) Handle different http(s) host => open outside
openExternal(uri);
return true;
}
/**
* Check if the URL host matches the initial one
*/
private boolean sameInitialHost(Uri uri) {
if (initialHost == null) return false;
String host = uri.getHost();
return host != null && host.equalsIgnoreCase(initialHost);
}
/**
* Open the given URL outside the WebView
*/
private void openExternal(Uri uri) {
try {
Intent i = new Intent(Intent.ACTION_VIEW, uri);
i.addCategory(Intent.CATEGORY_BROWSABLE);
i.setComponent(null); // Do not force any package
startActivity(i);
} catch (ActivityNotFoundException ignored) {
// No app can handle this URL (optional: add a Toast/log here)
}
}
});
iOS (Swift)
The core of this solution lies in intercepting WebView
navigations to handle them natively. Below is the recommended Swift code for your WKNavigationDelegate
to do just that.
import WebKit
// ViewController: intercepts WebView navigations and decides
// whether to let the WebView load them or hand them off to iOS.
class ViewController: NSObject, WKNavigationDelegate {
// Called on every navigation attempt (links, redirects, etc.)
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
// Allow the initial load and any navigation inside the same host
if webView.url == nil || url.host == webView.url?.host {
decisionHandler(.allow)
return
}
// For any external redirect (different host or deep link),
// cancel the WebView navigation and open it outside.
decisionHandler(.cancel)
if let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" {
// Try to open as Universal Link (into the app if associated).
// If no app handles it, fallback to Safari.
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { opened in
if !opened {
// Not a Universal Link / app not installed -> open in Safari
UIApplication.shared.open(url)
}
}
} else {
// For custom deep links (e.g., bankapp://...)
// try to open the corresponding app directly.
UIApplication.shared.open(url)
}
}
}
Updated about 18 hours ago