---
title: SDKs (Web and Mobile) Integration Best Practices
slug: sdks/best-practices
excerpt: Learn the best practices to follow before beginning with the SDK integration.
hidden: false
sidebar_order: 2
metadata:
  title: SDK Integration Best Practices | Pine Labs
  description: >-
    SDK integration best practices for Pine Labs: improve security, performance,
    reliability and UX across web, Android and iOS payment integrations.
sidebar_label: SDKs Best Practices
---
<div className="pill-tabs">
<Tabs>
<Tab label="Android">

<div className="px-4">

## Android SDKs Best Practices

<CardGrid columns="3">
<Card icon="link" title="One-Time Token Per Attempt" description="Generate the checkout token or redirect URL on your backend per order attempt and pass it to the SDK only when the user is ready to pay. Never reuse old tokens across retries." />
<Card icon="workflow" title="Handle All Three Callbacks" description="Handle success, error, and cancel explicitly in your app flow so users are never stuck in an ambiguous state. Reference: ExpressSDKCallback.kt" />
<Card icon="server" title="Server-Authoritative Order State" description="Use the SDK callback as a client signal only. Confirm final payment status on your backend via order-status API, webhook, or reconciliation before marking an order paid." />
<Card icon="lock" title="Never Log Sensitive Data" description="Do not log full tokens, customer identifiers, card/UPI details, or OTPs. Keep logs masked and environment-aware (debug vs release builds)." />
<Card icon="settings" title="Separate Sandbox and Production" description="Use a strict config switch for sandbox and production environments. Never hardcode sandbox mode in release builds." />
<Card icon="zap" title="Lifecycle-Safe UI Integration" description="If a callback updates UI, guard against dead activity or fragment state. Route users to a robust order-status screen after payment." />
</CardGrid>

</div>

</Tab>

<Tab label="iOS">

<div className="px-4">

## iOS SDKs Best Practices

<CardGrid columns="3">
<Card icon="code" title="Use a Single SDK Initialization" description="Initialize the SDK only once during your app lifecycle. Re-initializing multiple times can cause unexpected behaviour or runtime errors." />
<Card icon="check" title="Match Swift & iOS Compatibility" description="The SDK is built in Swift 5. Ensure your app targets Swift 5 or later and keep your iOS deployment target up-to-date to avoid build errors." />
<Card icon="shield-check" title="Network & Security Configuration" description="Allowlist required domains and configure App Transport Security (ATS) properly. Common issue in simulators or corporate networks with SSL restrictions." />
<Card icon="lock" title="Handle API Keys Securely" description="Never hardcode API keys in the app bundle. Generate and supply them securely from your backend to prevent exposure and misuse." />
<Card icon="refresh-cw" title="Keep SDK Updated" description="Right-click the package name and click Update Package to get the latest security patches, new payment methods, and compatibility fixes." />
</CardGrid>

## Recommended Way to Start the SDK

<Callout type="info">
Start the SDK from a **fully presented, visible view controller** that is part of your app's normal navigation flow.

Trigger the SDK launch via a user-initiated action, such as tapping a **"Pay Now"** button.

Once launched, the SDK manages its entire internal navigation using its own navigation stack.
</Callout>

### Ideal Entry Points

<div className="not-prose card-grid-2">
 <div className="card-grid-item">
 <span className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-2"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="m8.5 12.5 2.3 2.3 4.7-4.8"/></svg></span>
    <h4>Ideal Entry Points</h4>
    <p>The SDK should be started from:</p>
  <ul class="entry-list">
                        <li class="entry-item">
                        
                            <span>Checkout screen</span>
                        </li>
                        <li class="entry-item">
                        
                            <span>Order summary screen</span>
                        </li>
                        <li class="entry-item">
                           
                            <span>Payment selection screen</span>
                        </li>
                    </ul>
                    <p>These screens are stable, full-screen, and suitable for presenting the SDK.</p>
  </div>
 <div className="card-grid-item">
<span className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-2"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3 21 20H3L12 3Z"/><path d="M12 9v5"/><circle cx="12" cy="17" r="0.8" fill="#003434"/></svg></span>
  <h4>What to Avoid</h4>
  <p>Do not start the SDK from:</p>
<ul class="entry-list">
                        <li class="entry-item">
                            
                            <span>Alerts (UIAlertController)</span>
                        </li>
                        <li class="entry-item">
                     
                            <span>Bottom sheets or half sheets (.pageSheet)</span>
                        </li>
                        <li class="entry-item">
                        
                            <span>Popups or transient modals</span>
                        </li>
                        <li class="entry-item">
                           
                            <span>Views that are already being presented or dismissed</span>
                        </li>
                    </ul>
</div>

</div>




--- 
<Callout type="warning" title="Launching from incorrect contexts can cause:">
- Presentation warnings
- Broken navigation transitions
- Improper SDK dismissal
- Unexpected runtime behavior
</Callout>

## Navigation Ownership

| Responsibility | Owner |
|---|---|
| Presenting the SDK | Merchant app |
| Creating its own navigation controller | SDK |
| Pushing all SDK screens | SDK |
| Handling retry and error flows | SDK |
| Dismissing itself when the flow ends | SDK |

<Callout type="warning">
Merchants should **not** push SDK screens or attempt to control SDK navigation.
</Callout>

## SDK Initialization Rules

<Steps>
<Step number={1} title="One launch per payment attempt">
Initialize and launch the SDK only once per payment attempt. Do not re-initialize mid-flow.
</Step>
<Step number={2} title="Valid token required before launch">
Ensure a valid order token is available from your backend before starting the SDK.
</Step>
<Step number={3} title="No parallel launches">
Avoid triggering multiple simultaneous SDK launches — guard the launch with a state flag.
</Step>
</Steps>
</div>

</Tab>

<Tab label="Flutter">
<div className="px-4">
## Flutter SDK Best Practices

<CardGrid columns="3">
<Card icon="check" title="Start from a Stable Screen" description="Launch from a fully visible screen in your normal navigation flow, such as checkout, order summary, or payment selection." />
<Card icon="server" title="Always Verify on Backend" description="Treat PaymentResult as a client signal only. Confirm final status from your backend using the Order Inquiry API before fulfillment." />
<Card icon="workflow" title="Single SDK Call Per Attempt" description="Call startPayment or startPaymentWithRedirect once per payment attempt. Wait for PaymentResult before retrying." />
<Card icon="info" title="Handle All Statuses" description="Handle success, failure, cancelled, and invalid_request with clear UI actions so users are never stuck." />
<Card icon="refresh-cw" title="Use Fresh Redirect URL" description="Tokens and redirect URLs are short-lived. Generate a fresh order and redirect_url for every new attempt." />
<Card icon="settings" title="Keep SDK Updated" description="Stay on the latest SDK version to receive security fixes, payment method updates, and bug fixes." />
</CardGrid>

## Launch Context Guidance

<Callout type="info" title="Recommended launch points">
Trigger SDK launch from stable full-screen routes via explicit user action (for example, tapping **Pay Now**).
</Callout>

| Recommended | Avoid |
|---|---|
| Checkout screen | Dialog boxes or `showDialog()` |
| Order summary screen | Bottom sheets or `showModalBottomSheet()` |
| Payment selection screen | Screens currently being pushed or popped |
| User-initiated action | Auto-launch from `initState()` or `build()` |

<Callout type="warning" title="Incorrect launch contexts can cause:">
Navigation conflicts, unstable transitions, and unexpected behavior.
</Callout>

## Payment Result Handling

| Status | What to show |
|---|---|
| `success` | Order confirmation screen. Also verify on backend. |
| `failure` | Error message with a **Retry** option. |
| `cancelled` | Message: "Payment was not completed" with **Try Again**. |
| `invalid_request` | Developer error; log the `message` field for debugging. |

## Runtime and Security Safeguards

<Steps>
<Step number={1} title="Verify payment server-side">
Protect against callback interruption, client-side tampering, and WebView-close edge cases by confirming final status from your backend.
</Step>
<Step number={2} title="Do not modify SDK WebView">
Do not inject JavaScript, intercept navigation, or overlay custom UI on the payment WebView.
</Step>
<Step number={3} title="Test UPI on real devices">
UPI intent flows need physical devices with payment apps installed. Final validation must be done on real Android and iPhone devices.
</Step>
</Steps>

## Update SDK Version

```yaml
dependencies:
	pine_payment_sdk: ^1.0.0
```

```bash
flutter pub get
```
</div>
</Tab>


<Tab label="React Native">

<div className="px-4">

## React Native SDKs Best Practices

<CardGrid columns="3">
<Card icon="link" title="One-Time Token Per Attempt" description="Generate a fresh checkout redirect URL on your backend per order and never hardcode tokens in your app code." />
<Card icon="server" title="Server-Authoritative Order State" description="Treat backend payment enquiry as the final source of truth before marking an order as success." />
<Card icon="workflow" title="Idempotent Order Updates" description="Implement idempotency in your order update API so repeated callbacks do not create duplicate fulfillment." />
<Card icon="check" title="Handle All Callback Branches" description="Handle success, error reason, and error status explicitly so users are never stuck in an ambiguous state." />
<Card icon="lock" title="Never Log Sensitive Data" description="Log callback events with correlation IDs (order, transaction, session), but redact sensitive values like tokens and card details." />
<Card icon="zap" title="Timeout & Retry Logic" description="Implement timeout and retry logic around your backend enquiry call to prevent stuck checkout states." />
<Card icon="refresh-cw" title="Keep Dependencies Updated" description="Maintain react-native-webview within the supported version range during upgrades to avoid compatibility issues." />
<Card icon="shield-check" title="Network & Security Validation" description="Validate network and security policies so checkout URLs and redirects are not blocked on enterprise or restricted networks." />
<Card icon="check" title="Comprehensive Testing" description="Test the full payment flow in staging and production-like environments: success, failure, cancellation, app-switch, and offline scenarios." />
</CardGrid>

</div>

</Tab>


</Tabs>
</div>
