Skip to main content

Handle offsite payments in your app

Some payment providers finalize transactions by redirecting buyers to secure HTTPS web pages, while others use deep linking to send buyers to external banking apps. To enhance the user experience for the latter scenario, you can configure your storefront to support Universal Links in iOS and App Links in Android. This allows users to be redirected back to your app after their payment is completed.

By default, Checkout Sheet Kit includes the lifecycle methods to manage deep-linking interactions. The following code ensures that external links, such as HTTPS and Deep Links, are handled gracefully by the OS.

// Default implementation in Checkout Sheet Kit
public func checkoutDidClickLink(url: URL) {
// Check if the URL can be opened by iOS
if UIApplication.shared.canOpenURL(url) {
// Open the URL
UIApplication.shared.open(url)
}
}
// Default implementation in Checkout Sheet Kit
override fun onCheckoutLinkClicked(uri: Uri) {
// Handle different URI schemes
when (uri.scheme) {
"tel" -> context.launchPhoneApp(uri.schemeSpecificPart)
"mailto" -> context.launchEmailApp(uri.schemeSpecificPart)
"https", "http" -> context.launchBrowser(uri)
// Non-matching URIs will be treated as deep links
else -> context.tryLaunchDeepLink(uri)
}
}

  • You have a custom domain connected to your storefront.
  • Your custom domain uses SSL (HTTPS).
  • You have a custom Shopify app with access to the Storefront API.

Anchor to Step 1: Configure your storefrontStep 1: Configure your storefront

You can set up your storefront to support Universal Links for iOS and App Links for Android, and re-route storefront URLs to your mobile app.

  1. Go to your storefront's admin panel.
  2. Click the name of your app.
  3. In your app's Configuration tab, click Edit on the Storefront API integration section.
  4. Set up Universal Links for iOS or App Links for Android:
  • Universal Links for iOS: In the iOS Buy SDK section, enter your Apple App ID, enable Use iOS Universal Links, and upload your Apple app certificate.
  • App Links for Android: In the Android Buy SDK section, provide your Android application ID and SHA256 fingerprint certificate values from the Play Store.

Configuring Universal Links or App Links for your store generates two .well-known JSON files on your public storefront domain:

  • apple-app-site-association for iOS

  • assetlinks.json for Android

    Both operating systems periodically retrieve these files to ensure URLs open directly in your app, rather than in a web browser.

{
"applinks": {
"apps": [],
"details": [
{
"appID": "{APPLE_TEAM_ID}.{BUNDLE_IDENTIFIER}",
"paths": [
"NOT /admin/*",
"NOT /*/amazon_payments/callback",
"NOT /*/checkouts/*/express/callback",
"/*"
]
}
]
}
}
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "{PACKAGE_NAME}",
"sha256_cert_fingerprints": [
"{SHA256_FINGERPRINT}"
]
}
}
]
Caution

.well-known/* files aren't served from "*.myshopify.com" domains. You must connect a custom domain under https://admin.shopify.com/settings/domains to serve these files.

The /* catch-all route in the apple-app-site-association configuration indicates that iOS should open any URL from your domain in your app, except for those paths specifically excluded by "NOT" rules. Similarly, the delegate_permission/common.handle_all_urls relation in the assetlinks.json does the equivalent for Android.

  • Email Links: Links in emails, such as abandoned carts, will open in your app when clicked.
  • Shared Links: General web links from your domain will open in your app unless they're specifically excluded.
  • Webview Links: Links opened in a webview won't trigger the app unless it's set up to support them.
  • Excluded Paths: (iOS only) URLs defined with a NOT clause in the apple-app-site-association file will open outside the app as specified.

The following section describes the steps to enable Universal Links in iOS and set up App Linking in Android.

To enable Universal Links in your iOS app, you'll need to configure an entitlements file which specifies the associated domains that your app is allowed to handle:

  1. Launch Xcode and open your project or workspace file.
  2. Click on your project in the Project Navigator.
  3. Select your app target from the list of targets.
  4. Click on the Capabilities tab at the top of the editor.
  5. In the Capabilities section, find Associated Domains and toggle the switch to enable it. A new section should appear to configure your associated domains.
  6. Add each domain you want to associate with your app in the format applinks:yourdomain.com. The following example shows an example XML entitlements file:

Example.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
<string>applinks:subdomain.example.com</string>
</array>
</dict>
</plist>

Anchor to Set up App Linking in AndroidSet up App Linking in Android

To set up App Linking for Android, you need to:

  1. Configure your AndroidManifest.xml to specify intents to allow the app to handle App Links by specifying intent filters for the supported URL schemes and hosts you wish to capture.
  2. Update MainActivity.kt to handle these intents.

AndroidManifest.xml

<activity android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="https" android:host="example.com" />
</intent-filter>
</activity>

The same steps from the iOS and Android sections apply to React Native. Ensure you have configured both the App.entitlements file for Swift and AndroidManifest.xml file for Android.


The following section describes how to update your application entrypoint to route requests to the appropriate screen in your app.

For example, you might choose to open "/checkout" URLs in the Checkout Sheet Kit, route "/cart" requests to a cart screen or sheet or route "/product" requests to a product detail page.

If there are specific URLs in your app which you do not plan to support, it is important to re-route these unhandled routes back to a mobile browser. This will ensure customers are always able to complete their journey without encountering dead ends.

// App.swift

import SwiftUI
import ShopifyCheckoutSheetKit

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// Handle Universal Links
handleUniversalLink(url: url)
}
}
}

private func handleUniversalLink(url: URL) {
let storefrontUrl = StorefrontURL(from: url)

switch true {
// Handle checkout URLs
case storefrontUrl.isCheckout() && !storefrontUrl.isThankYouPage():
presentCheckout(url)
// Handle cart URLs
case storefrontUrl.isCart():
navigateToCart()
// Fallback: Re-route everything else to Safari
default:
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
}
}

struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}

public struct StorefrontURL {
public let url: URL

init(from url: URL) {
self.url = url
}

public func isThankYouPage() -> Bool {
return url.path.range(of: "/thank[-_]you", options: .regularExpression) != nil
}

public func isCheckout() -> Bool {
return url.path.contains("/checkout")
}

public func isCart() -> Bool {
return url.path.contains("/cart")
}
}
// AppDelegate.swift

import UIKit
import ShopifyCheckoutSheetKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Only handle universal links from known activity types.
// You might also guard against your storefront domain here for additional security.
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
handleUniversalLink(url: url)
return true
}

return false
}

private func handleUniversalLink(url: URL) {
let storefrontUrl = StorefrontURL(from: url)

switch true {
// Handle checkout URLs
case storefrontUrl.isCheckout() && !storefrontUrl.isThankYouPage():
presentCheckout(url)
// Handle cart URLs
case storefrontUrl.isCart():
navigateToCart()
/// Fallback: Re-route everything else to Safari
default:
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
}
}

public struct StorefrontURL {
public let url: URL

init(from url: URL) {
self.url = url
}

public func isThankYouPage() -> Bool {
return url.path.range(of: "/thank[-_]you", options: .regularExpression) != nil
}

public func isCheckout() -> Bool {
return url.path.contains("/checkout")
}

public func isCart() -> Bool {
return url.path.contains("/cart")
}
}
// MainActivity.kt

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
App()
}

// Handle the intent that started the activity
handleIntent(intent)
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// Handle new intents
handleIntent(intent)
}

private fun handleIntent(intent: Intent) {
// Ensure the intent action is VIEW
if (intent.action == Intent.ACTION_VIEW) {
intent.data?.let { uri ->
if (uri.path == "/cart") {
// Navigate to the cart screen
navigateToCart()
}
}
}
}

private fun navigateToCart() {
// ... implementation for navigating to the cart screen
}
}
// src/App.tsx

import { Linking } from 'react-native';
import { useShopifyCheckoutSheet } from '@shopify/checkout-sheet-kit';

// Hook for managing URLs that were used to open the app
const useInitialURL = (): {url: string | null} => {
const [url, setUrl] = useState<string | null>(null);

useEffect(() => {
const getUrlAsync = async () => {
// Get the deep link used to open the app
const initialUrl = await Linking.getInitialURL();

if (initialUrl !== url) {
setUrl(initialUrl);
}
};

getUrlAsync();
}, [url]);

return {
url,
};
};

function Routes() {
const shopify = useShopifyCheckoutSheet();
// Optional: `useNavigation` comes from `react-native-navigation`
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const {url: initialUrl} = useInitialURL();

useEffect(() => {
async function handleUniversalLink(url: string) {
const storefrontUrl = new StorefrontURL(url);

switch (true) {
// Handle checkout URLs
case storefrontUrl.isCheckout() && !storefrontUrl.isThankYouPage():
shopify.present(url);
return;
// Handle cart URLs
case storefrontUrl.isCart():
navigation.navigate('Cart');
return;
}

const canOpenUrl = await Linking.canOpenURL(url);

if (canOpenUrl) {
// Open everything else in the mobile browser
await Linking.openURL(url);
}
}

if (initialUrl) {
handleUniversalLink(initialUrl);
}

// Subscribe to universal links
const subscription = Linking.addEventListener('url', ({url}) => {
handleUniversalLink(url);
});

return () => {
// Remove event listeners on unmount to prevent memory leaks
subscription.remove();
};
}, [initialUrl, shopify, navigation]);

return (
// ... route components ...
);
}

class StorefrontURL {
readonly url: string;

constructor(url: string) {
this.url = url;
}

isThankYouPage(): boolean {
return /thank[-_]you/i.test(this.url);
}

isCheckout(): boolean {
return this.url.includes('/checkout');
}

isCart() {
return this.url.includes('/cart');
}
}

Anchor to Step 4: Test your changesStep 4: Test your changes

This section describes steps you can take to ensure Universal Links and App Links are configured correctly in your app.

Anchor to Validate well-known filesValidate well-known files

Verify the configuration of Universal Links and App Links by using CURL to check the well-known routes on your store:

curl https://example.com/.well-known/apple-app-site-association | jq .
curl https://example.com/.well-known/assetlinks.json | jq .

Anchor to Simulate linking with terminalSimulate linking with terminal

With Universal Links set up, you can test the implementation in the simulator by triggering URLs manually with:

xcrun simctl openurl booted "https://www.example.com/cart"
adb shell am start -a android.intent.action.VIEW -d "https://example.com/cart"

Open Settings on a real device and search Universal Links:

Settings > DeveloperUniversal Links > DiagnosticsLink configured
Settings - Developer
Settings - Developer - Universal Links
Enter Storefront URL

Use the App Links Assistant and App Link Testing features in Android Studio to test your app links:

Android App Links Assistant

Anchor to Security considerationsSecurity considerations

  • Data privacy: Universal Links can pass sensitive information through URLs. Ensure any personal or sensitive data is encrypted or tokenized to protect user privacy.
  • URL handling: Rigorously validate and sanitize any data received from Universal Links to prevent URL injection attacks. Only allow expected URL formats and handle unexpected URLs gracefully.
  • App update and expiry: Keep your app updated to handle links correctly, and ensure that any links requiring special app capabilities are managed. If a link cannot be handled by the currently installed app, consider providing users with a fallback option or gracefully notifying them.
  • Testing and monitoring: Regularly test the functionality of Universal Links to ensure they are correctly pointing to your app and not opening other apps or browsers with potentially harmful content. Monitor your app for any attempts at misuse or abuse of links.
  • Certificate management: Maintain secure and appropriate certificate management practices for your app to prevent unauthorized access or configuration changes related to Universal Links.

Was this page helpful?