> ## Documentation Index
> Fetch the complete documentation index at: https://docs.payments.sardine.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile Integration

You can quickly integrate Sardine Crypto On-ramp fiat via a mobile interface. For Mobile URL Webview implemenations, the checkout widget is generated via URL.

<Info>
  \*\*\*Goal <br />

  By the end, you should be able to embed Sardine On-ramp into your mobile app through a WebView
</Info>

### 0. Integrate the Risk SDK

Our Risk SDK is a **mandatory** requirement to help us fight fraud via device intelligence and behavior biometrics. Sardine’s proprietary technology is adept at this very task and is instrumental in identifying risky devices, behavior, and tools that are used during these sessions (example: VPNs, emulators, remote desktop protocols etc.)

Benefits of adding Risk SDK

* Higher limts for good users

* Better fraud identification

* Less friction for good users

Select the Mobile SDK as per your need:

|                                                                                                  |
| :----------------------------------------------------------------------------------------------: |
|            [Android](https://docs.sardine.ai/docs/risk-sdk/e719e8d815e00-android-sdk)            |
|               [iOS](https://docs.sardine.ai/docs/risk-sdk/e9b0663c649da-swift-sdk)               |
| [ReactNative](https://docs.sardine.ai/docs/risk-sdk/bb3b2fe867a26-react-native-i-os-android-sdk) |
|              [Flutter](https://docs.sardine.ai/docs/risk-sdk/7c1376a9c9263-flutter)              |

If you are unable to view the documents in the links above, please reach out to your Sardine representative to get access.

### 1. Obtain authorization token

<Note>
  Before we start, you'll need the following credentials

  ```yaml json_schema theme={null}
  $ref: "../../../models/Authorization-Parameters.yaml"
  ```
</Note>

You will need to obtain a `clientToken`, which is a unique identifier for each session and user.

Make a POST request to `/client-tokens` using Basic Auth by passing base64 encoding of `<clientId>:<clientSecret>`

```json http theme={null}
{
  "method": "POST",
  "url": "https://api.sandbox.sardine.ai/v1/auth/client-tokens",
  "headers" : {
    "Authorization" : "Basic MTY3NDRkZGMtYThhMy00OGIyLWE4ZTktNjA2YWU4OTk1NTM5OmYyMGJhNGRiLTczYzItNDk0Mi04NDAyLWRhNzc4OTllNzY2Mg==",
  }
}
```

<br />

If the request is successful, you should receive a response that contains the `clientToken` and `expiresAt` field

```
client_token = response["clientToken"]
expires_at = response["expiresAt"]
orderId = response["orderId"]
```

### 2. Determine your widget configuration and create URL.

Sardine's widget can be configured based on any parameters that are passed through. Please use [the configuration reference guide](/guides/integration/payments/OnOffRamps/GettingStarted/configuration).

By default, if no parameters are passed, users will be redirected to our standard checkout widget.

`client_token` is the only required parameter. Additional parameters that are passed will autofill the widget for the user and allow them to skip some screens.

### 3. Implement the web checkout widget

Once the URL has been generated, it can be embedded into your web app as a hyperlink. Below, we have included samples of different ways of integrating the checkout

To assist with development, we have created some code samples which illustrate how your code can be structured to create the checkout url and handle the finished trade.

<Note>
  `allowsInlineMediaPlayback` should be enabled either via code or through storyboard
</Note>

<Tabs>
  <Tab title="React Native">
    1. Start by importing Webview

    2. Craft the URL based on preferred parameters

    3. Create event handlers that will catch the `processed`,`expired` and `declined` events

    4. Call the WebView component with parameters set as such. These have been set for the optimal user expereince.

    Full code should look as such

    ```js theme={null}
    import { WebView } from ‘react-native-webview’;

            const uri = "https://crypto.sandbox.sardine.ai/?address=0xjadadhadskaj2123&fiat_amount=1000&&asset_type=usdc&network=ethereum&client_token=123-asd-456"; // URL prepared from above step
            const javaScriptFunction = `
                document.addEventListener('processed', function(data) {
                    const d = data.detail;
                    const v = d ? JSON.stringify(d) : "";
                    window.ReactNativeWebView && window.ReactNativeWebView.postMessage(v);
                });

                document.addEventListener('expired', function(data) {
                    const d = data.detail;
                    const v = d ? JSON.stringify(d) : "";
                    window.ReactNativeWebView && window.ReactNativeWebView.postMessage(v);
                });

                document.addEventListener('declined', function(data) {
                    const d = data.detail;
                    const v = d ? JSON.stringify(d) : "";
                    window.ReactNativeWebView && window.ReactNativeWebView.postMessage(v);
                });
            `
            if (uri) {
              let cryptoURL = uri;
                if(Platform.OS == "android") {
                  cryptoURL = `${uri}&android_package_name=com.your-app-pakage-name`
                } else if(Platform.OS == "ios") {
                  cryptoURL = `${uri}&plaid_redirect_url=https://your-domain-here.com`
                }
                return (
                    <View style={baseStyles.flexGrow}>
                        <WebView
                            ref={(webView) => this.webView = webView}
                            source={{ uri: cryptoURL }}
                            mediaPlaybackRequiresUserAction={false}
                            allowInlineMediaPlayback
                            allowsBackForwardNavigationGestures
                            javaScriptEnabled={true}
                            injectedJavaScript={javaScriptFunction}
                            originWhitelist={[‘*’]}
                            onMessage={event => {
                                const orderData = JSON.parse(event.nativeEvent.data);
                                if(orderData) {
                                    setTimeout(() => {
                                      // handle code for order success
                                        this.handleOrderSuccess(orderData)
                                    }, 2000);
                                }
                            }}
                        />
                    </View>
                );
            }

    ```
  </Tab>

  <Tab title="iOS">
    ```swift theme={null}

    import UIKit
    import WebKit

    class CryptoWebController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {

        // Private variables
        private var webView : WKWebView?
        private var cryptoAddress = ""
        private var fiatAmount = ""
        private var assetType = ""
        private var network = ""
        private var supportedTokens = ""
        private var clientToken = ""

        // Error response
        private let errorResponse = CryptoResponse(status: false, data: nil)

        // Public variables
        public var completion : ((CryptoResponse)->())?

        // Constants
        private enum CONSTANTS : String {
            case STATUS = "orderStatus"
            case SUCCESS = "order-success"
            case FAILURE = "order-fail"
            case TITLE = "Crypto ACH"
            case DISMISS = "Dismiss"
            case ALERT_TITLE = "Confirmation"
            case ALERT_MESSAGE = "Are you sure you want to dismiss?"
            case ACTION_YES = "Yes"
            case ACTION_NO = "No"
        }

        convenience init(withAddress address: String, fiatAmount: String, assetType: String, network: String, supportedTokens: String, clientToken: String) {
            self.init()
            self.cryptoAddress = address
            self.fiatAmount = fiatAmount
            self.assetType = assetType
            self.network = network
            self.supportedTokens = supportedTokens
            self.clientToken = clientToken
        }

        override func viewDidLoad() {
            super.viewDidLoad()

            self.title = CONSTANTS.TITLE.rawValue

            self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CONSTANTS.DISMISS.rawValue, style: .plain, target: self, action: #selector(dismissAction))

        }

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)

            guard let baseURL = URL(string:"https://crypto.sandbox.sardine.ai") else {
                return
            }

            let scheme = baseURL.scheme
            let host = baseURL.host
            let path = "/alpha/6ccfb278-5f94-44fe-bf33-4ea81325713b"
            let queryItems = [
                URLQueryItem(name: "address", value: cryptoAddress),
                URLQueryItem(name: "fiat_amount", value: fiatAmount),
                URLQueryItem(name: "asset_type", value: assetType),
                URLQueryItem(name: "network", value: network),
                URLQueryItem(name: "supported_tokens", value: supportedTokens),
                URLQueryItem(name: "client_token", value: clientToken),
                URLQueryItem(name: "plaid_redirect_url", value: "https://your-domain-here.com"),
            ]

            var urlComponents = URLComponents()
            urlComponents.scheme = scheme
            urlComponents.host = host
            urlComponents.path = path
            urlComponents.queryItems = queryItems

            guard let cryptoURL = urlComponents.url else {
                return
            }

            let cryptoRequest = URLRequest(url: cryptoURL)

            let preferences = WKPreferences()
            let contentController = WKUserContentController()
            preferences.javaScriptEnabled = true
            let source: String = """
                window.onload = function() {
                    window.document.addEventListener("\(CONSTANTS.SUCCESS.rawValue)", function(data) {
                        const d = data.detail;
                        const v = d ? JSON.stringify(d) : "";
                        const val = '"' + `${v}` + '"';
                        window.webkit.messageHandlers.\(CONSTANTS.STATUS.rawValue).postMessage(`${val}`);
                    });

                    window.document.addEventListener("\(CONSTANTS.FAILURE.rawValue)", function(data) {
                        window.webkit.messageHandlers.\(CONSTANTS.STATUS.rawValue).postMessage("\(CONSTANTS.FAILURE.rawValue)");
                    });
                }
                """
            let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
            contentController.addUserScript(script)
            contentController.add(self, name: CONSTANTS.STATUS.rawValue)

            let configuration = WKWebViewConfiguration()
            configuration.preferences = preferences
            configuration.userContentController = contentController

            self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)
            self.webView!.allowsBackForwardNavigationGestures = true
            self.webView!.uiDelegate = self
            self.webView!.navigationDelegate = self
            self.view = webView

            self.webView!.load(cryptoRequest)
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            completion?(errorResponse)
        }

        @objc private func dismissAction() {
            let alert = UIAlertController(title: CONSTANTS.ALERT_TITLE.rawValue, message: CONSTANTS.ALERT_MESSAGE.rawValue, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: CONSTANTS.ACTION_YES.rawValue, style: .cancel, handler: { _ in
                self.navigationController?.dismiss(animated: true, completion: nil)
                self.completion?(self.errorResponse)
            }))
            alert.addAction(UIAlertAction(title: CONSTANTS.ACTION_NO.rawValue, style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }

        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            switch message.name {
            case CONSTANTS.STATUS.rawValue:
                let isFailure = "\(message.body)" == CONSTANTS.FAILURE.rawValue
                if !isFailure {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                        self.navigationController?.dismiss(animated: true, completion: nil)

                        let stringValue = String("\(message.body)".dropFirst().dropLast())
                        var successResponse = CryptoResponse(status: true, data: nil)

                        guard let strData = stringValue.data(using: .utf8) else {
                            self.completion?(successResponse)
                            return
                        }

                        guard let d = try? JSONDecoder().decode(CryptoDetails.self, from: strData) else {
                            self.completion?(successResponse)
                            return
                        }
                        successResponse.data = d
                        self.completion?(successResponse)
                    }
                }

            default:
                break
            }
        }
    }

    class CryptoManager {

        public class func initiateCheckout(withAddress address: String, fiatAmount: String, assetType: String, network: String, supportedTokens: String, clientToken: String, completion : @escaping ((CryptoResponse)->())) {
            DispatchQueue.main.async {
                let cryptoVC = CryptoWebController(withAddress: address, fiatAmount: String, assetType: String, network: String, supportedTokens: String, clientToken: String)
                cryptoVC.completion = completion
                let navigationVC = UINavigationController(rootViewController: cryptoVC)
                navigationVC.modalPresentationStyle = .fullScreen
                self.present(navigationVC, animated: true, completion: nil)
            }
        }
    }
    ```
  </Tab>
</Tabs>

### 4. Confirmation of Order Status

Sardine can fire off events that can be caught by event handlers that can be leveraged to update the user on the order status outside of the Sardine widget

There are two ways to get Order status, either via polling an orders endpoint or providing a redirect URL that we will send the events to

<Tabs>
  <Tab title="Poll Orders Endpoint">
    Developers can poll to the [/orders](https://docs.sardine.ai/docs/integrate-payments/900670db39399-get-order-s) endpoint to check its status with the `order_id`

    ```json http theme={null}
    {
      "method": "GET",
      "url": "https://api.sandbox.sardine.ai/v1/orders/17983781730123",
      "query" : {
        "clientToken" : "7ce511d0-c973-4744-b819-d933a248ae51"
      },
      "headers" : {
        "Authorization" : "Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0",
      }
    }
    ```
  </Tab>

  <Tab title="Redirect URL (On-ramp)">
    Pass a `redirect_url` parameter in the URL when intiating the widget. Once the transaction is complete, Sardine will make a call to the 'redirect\_url'

    `Expired` - The user didn't complete the transaction within the time

    ```json theme={null}
    {
      "status" : "Expired",
      "data" : {
        "referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
      }
    }
    ```

    `Processed` - The payment is complete

    ```json theme={null}

    {
      "status" : "Processed",
      "data" : {
        "price": 100,
        "orderId": "123e4567-e89b-12d3-a456-426614174001",
        "transactionFee": 2,
        "networkfee": "0.35"
        "currencyCode": "usd",
        "paymentMethod": "ACH",
        "createdAt": 12312321312,
        "address": "0x10b195F7Be9B120efd05C58f16650A13f533eA33",
        "referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
      }

    }

    ```

    `Declined` - The transaction was declined due to issues with their payment method or risk profile.

    ```json theme={null}
    {
      "status" : "Declined",
      "data" : {
        "referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
      }
    }
    ```

    There may be a delay between when an order was successful and the crypto is delivered to the user's wallet. This delay is due to the time required for transactions to settle on the chain.
  </Tab>
</Tabs>

### 5. Testing and Verification

Once the defined URL has been set and triggered, it should open a window that goes through the user flows for Sardine's on ramp.

A developer can use the sample information provided in the [Testing Payment Flows](/guides/integration/payments/Onboarding/testingcredentials) page to test the integration.

### 6. Going Live in Production

Once you have completed end-to-end testing in sandbox, you can go live by swapping to a set of production keys and updating your URLs. Please see the linked guide for a step-by-step of going live in production.

[Going to Production](/guides/integration/payments/OnOffRamps/GettingStarted/goingtoproduction)

<Check>
  You should now be able use an integrated Sardine Checkout Widget!
</Check>
