Mobile SDKs

Accept payments natively on iOS, Android, and React Native.

View as MarkdownInstall skills

VINR mobile SDKs give you a prebuilt payment sheet and PCI-aware tokenization for native apps, so card details never touch your servers. The flow mirrors the web SDKs: your backend creates a payment intent, your app presents the sheet, and you confirm the result. This page covers installation, the prebuilt sheet, custom flows, and platform wallets.

When to use a mobile SDKAsk

  • You want a native, in-app payment experience instead of redirecting to a browser.
  • You need Apple Pay and Google Pay with the system wallet UI.
  • You want the smallest PCI scope on mobile — card data is collected and tokenized inside the SDK on a VINR-controlled surface, keeping you at SAQ-A.

The payment sheet always requires a client secret created server-side. Never embed your VINR_SECRET_KEY in a mobile app — ship only the publishable key (pk_...) and fetch the client secret from your own backend.

InstallationAsk

# Swift Package Manager — add the package URL in Xcode, or:
# https://github.com/vinr/vinr-ios

# CocoaPods
pod 'VinrPayments', '~> 3.0'
# build.gradle (app module)
dependencies {
  implementation 'com.vinr:payments:3.0.0'
}
npm install @vinr/react-native
cd ios && pod install

Create the intent on your backendAsk

Every mobile payment starts with a payment intent created from a trusted server using your secret key. Return the clientSecret (and id) to the app.

import { Vinr } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

// POST /api/payment-sheet
const intent = await vinr.payments.create({
  amount: 4200,            // EUR 42.00 in minor units
  currency: 'EUR',
  customer: 'cust_abc123',
  captureMethod: 'automatic',
});

return { clientSecret: intent.clientSecret, paymentId: intent.id };

Prebuilt payment sheetAsk

The payment sheet renders cards, saved payment methods, Apple Pay / Google Pay, and any 3DS challenge with no UI work on your side.

import VinrPayments

Vinr.publishableKey = "pk_live_123"

// 1. Fetch clientSecret from your backend, then configure the sheet.
var config = PaymentSheet.Configuration()
config.merchantDisplayName = "Acme Store"
config.applePay = .init(merchantId: "merchant.com.acme", merchantCountryCode: "DE")

let sheet = PaymentSheet(clientSecret: clientSecret, configuration: config)
sheet.present(from: self) { result in
  switch result {
  case .completed: print("paid")
  case .canceled:  print("canceled")
  case .failed(let error): print(error.localizedDescription)
  }
}
import com.vinr.payments.PaymentSheet

val paymentSheet = PaymentSheet(this) { result ->
  when (result) {
    is PaymentSheetResult.Completed -> Log.d("vinr", "paid")
    is PaymentSheetResult.Canceled  -> Log.d("vinr", "canceled")
    is PaymentSheetResult.Failed    -> Log.e("vinr", result.error.message ?: "error")
  }
}

// After fetching clientSecret from your backend:
paymentSheet.present(
  clientSecret = clientSecret,
  configuration = PaymentSheet.Configuration(
    merchantDisplayName = "Acme Store",
    googlePay = PaymentSheet.GooglePayConfiguration(countryCode = "DE", currencyCode = "EUR"),
  ),
)
import { initPaymentSheet, presentPaymentSheet } from '@vinr/react-native';

const { clientSecret } = await fetch('/api/payment-sheet', { method: 'POST' })
  .then((r) => r.json());

const init = await initPaymentSheet({
  clientSecret,
  merchantDisplayName: 'Acme Store',
  applePay: { merchantCountryCode: 'DE' },
  googlePay: { merchantCountryCode: 'DE', currencyCode: 'EUR' },
});
if (init.error) throw init.error;

const { error } = await presentPaymentSheet();
if (error && error.code !== 'Canceled') {
  console.error(error.message);
}

A successful sheet result means the customer authorized the payment — it is not your source of truth. Always confirm the final state from your backend by checking the payment status or listening for the payment.completed webhook before fulfilling an order.

Custom flowsAsk

If the prebuilt sheet doesn't fit your design, collect the card with the SDK's secure card field, create a payment method token client-side, then confirm against the same clientSecret. The token (pm_...) is the only card-derived value that leaves the SDK, so you stay at SAQ-A.

// React Native — manual confirm with a tokenized card field
import { createPaymentMethod, confirmPayment } from '@vinr/react-native';

const pm = await createPaymentMethod({ type: 'card' }); // reads the secure card field
const { paymentId, status } = await confirmPayment(clientSecret, {
  paymentMethodId: pm.paymentMethodId,
});

if (status === 'requires_action') {
  // The SDK auto-presents the 3DS challenge; re-check status after it resolves.
}

Apple Pay and Google PayAsk

Wallet payments are part of the same sheet — enable them in configuration and the SDK shows the native wallet button when the device supports it.

Prop

Type

For Apple Pay you must register a Merchant ID and Apple Pay certificate in your developer account and the VINR Dashboard. For Google Pay, the SDK runs in test mode against your sandbox key and switches to production automatically with a live pk_live_ key.

Testing on deviceAsk

Use sandbox publishable keys and the standard VINR test cards. Wallet sheets in sandbox use test instruments, so no real card is charged.

Card numberOutcome
4242 4242 4242 4242Success
4000 0000 0000 0002Declined
4000 0000 0000 3220Triggers a 3DS challenge

Point the app at sandbox

Set the SDK publishable key to your pk_test_ key and your backend to VINR_SECRET_KEY for sandbox (sk_test_). The base URL switches to https://sandbox.api.vinr.com automatically.

Run the sheet with a test card

Enter 4242 4242 4242 4242, any future expiry, and any CVC. The sheet returns completed.

Verify the backend state

Confirm the payment moved to completed server-side, then test the decline and 3DS cards to exercise your error and challenge paths.

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page