NAV
php json

Introduction

Welcome to the OderoPay API! You can use our API to access Odero API endpoints, which can get information on various payments, cards, recurrings, deposits, refunds in our database.

We have language bindings in Shell, Php, Python, and Nodejs! You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.

Installation

You can access our SDK's directly on our Github Page.

PHP

Odero PHP Sdk

composer require oderopay/odero-php

Minimum Requirements;

Authentication

To authorize, use this code:

//Authentication is already handled by SDK
$config = new \Oderopay\OderoConfig('My Store Name', '{merchant-id}', '{merchant-token}', \Oderopay\OderoConfig::ENV_STG);

//Just initialize the Odero Client with given config.
$oderopay = new \Oderopay\OderoClient($config);

To be authenticated, you need to send X-MERCHANT-SIGNATURE value in header.

The header X-MERCHANT-SIGNATURE is generated by each merchant when a new request is initialized to API Endpoints.

Items used in header generation:

Used Algorithm: sha256

How is the header generated?

In-App Payment

Introduction

The Odero In-App Payment SDK lets you take card and wallet payments inside your own mobile app, without sending the customer to a browser and without your app ever touching raw card data. You drop in a ready-made, fully themeable payment sheet; the customer enters their card (or pays with Apple Pay / Google Pay); the SDK runs 3-D Secure and settles the charge with Odero; and you get the result back.

It ships as three native SDKs that expose the same flow and the same payment sheet:

  • iOS: OderoPaySDKIOS, distributed as a CocoaPods xcframework.
  • Android: oderopaysdkandroid, distributed as a Maven (AAR) library.
  • React Native: react-native-oderopay, a pure-RN package that wraps the same flow.

Capabilities:

  • Card payments with built-in validation (Luhn, brand detection, expiry, CVC).
  • Stored / saved cards (one-tap, token-based).
  • Apple Pay on iOS and Google Pay on Android.
  • 3-D Secure handled automatically in an in-sheet web view.
  • A drop-in payment sheet you can theme down to colours, fonts, labels and icons.
  • The customer never types card data into your code, which keeps your PCI scope minimal.

The card data is captured by Odero's UI and the actual charge is created server-to-server, so your application only ever handles an opaque sessionId and a result status.

The two pieces you build:

  1. A small piece of backend code that creates a payment session with your secret merchant credentials and returns a sessionId to the app. See Merchant setup & sessions.

  2. The app integration: hand that sessionId to the SDK and show the payment sheet. See iOS, Android or React Native.

Read How it works next for the end-to-end flow, then jump to your platform.

How it works

1. Create session your backend → Odero 2. Show sheet app passes sessionId 3. Customer pays card / wallet · 3-D Secure 4. Receive result success / failure

A payment has two sides: a one-time-per-checkout session created by your backend, and the app that shows the sheet for that session. The split exists for security: your secret merchant credentials live only on your server, and the app only ever sees an opaque sessionId.

1. Your backend creates a session

For each checkout, your server calls Odero with your merchant credentials, the amount, currency and the basket, and gets back a sessionId. The session also records which payment methods are allowed (card, Apple Pay, Google Pay). This is a two-step signed call. See Merchant setup & sessions.

2. The app shows the payment sheet

Your app receives the sessionId from your backend and hands it to the SDK (showPaymentSheet). The SDK fetches the session, reads the amount, currency and the allowed methods, and renders the sheet: the card form, saved cards, and the Apple Pay / Google Pay button when the session allows it.

3. The customer pays

The SDK runs the whole payment for you:

  1. The customer picks a method and confirms.
  2. The SDK creates the payment with Odero.
  3. If the bank requires 3-D Secure, the SDK opens the bank's challenge in an in-sheet web view and closes it automatically when done. No code from you.
  4. The SDK polls the payment status until it reaches a terminal state.

4. You receive the result

The SDK shows a success or failure page in the sheet, and reports the outcome to your code as a status: processingsuccess or failure. On success you get a reference number; on failure you get a message. How you receive it differs per platform (a notification on iOS, a callback on Android, a hook/event on React Native). See your platform page.

The same four steps apply to every platform; only the API names change. Next: Requirements, Merchant setup & sessions, and Payment types.

Requirements

Minimum platform and toolchain versions for each SDK. All three SDKs are released under the same version number; replace 1.0.0 in the snippets throughout these docs with the version Odero gave you.

iOS

RequirementValue
Deployment targetiOS 15.0+
Swift5.0
DistributionCocoaPods (binary xcframework)
LinksPassKit (Apple Pay), C++ standard library (handled by the pod)
Apple Pay testingA real device (the simulator cannot authorize)

Android

RequirementValue
minSdk23
compileSdk / targetSdk35
JDK17
Android Gradle Plugin8.7.3+
Kotlin2.1.20 (don't force a newer stdlib)
UIJetpack Compose (pulled in transitively; you don't need Compose yourself)
Host ActivityMust be a ComponentActivity (e.g. AppCompatActivity), required for Google Pay
Google Paycom.google.android.gms:play-services-wallet:20.0.0

React Native

RequirementValue
React NativeAny modern RN (built and tested on 0.81)
ReactAny (tested on 19.x)
Peer dependencyreact-native-webview >= 13.0.0 (required, for 3-D Secure)
Node>= 20
iOS (native side)iOS 12.0+ pod, PassKit; build on a real device for Apple Pay
Android (native side)minSdk 24, JDK 17, play-services-wallet

The card-only flow on React Native is pure JavaScript plus react-native-webview. Apple Pay / Google Pay add an optional native wallet module. See React Native.

Merchant setup & sessions

Before you write any app code, a few things have to be registered in the Token (Odero) ecosystem and kept on your backend. This section covers what to obtain, and how your server turns it into a sessionId for the app.

What you register with Odero

ItemWhat it isWhere it lives
Merchant ID Your Odero merchant account id (a UUID). Identifies you and signs every session. Also used as the Google Pay gatewayMerchantId. Your backend (not secret, but kept server-side)
Merchant API token The secret token that authorizes session creation. Your backend only, never in the app
Apple Pay merchant identifier An Apple-issued id such as merchant.com.yourapp, plus a Payment Processing certificate. Must be uploaded to the Token platform so Odero can decrypt the Apple Pay token. Apple Developer portal + uploaded to Odero; the id also goes in your app entitlement
Google Pay merchant id From the Google Pay & Wallet Console (production only). The tokenization gateway is fixed to oderopay; the gatewayMerchantId sent to Google is your Odero merchant id. Google console; nothing extra to register with Odero beyond your merchant account

The two "merchant id" values

  • Odero merchant ID (a UUID). Used to sign sessions, and as the Google Pay gatewayMerchantId.
  • Apple Pay merchant identifier (merchant.com.yourapp). A separate, Apple-issued id you pass to the SDK as applePayMerchantId and register in your app's Apple Pay entitlement.

Environments

There are two environments, selected per platform (see each SDK page):

EnvironmentAPI host
DEVhttps://api-dev.pay.odero.ro/
PRODhttps://pay.odero.ro/

For Google Pay, keep the wallet environment in sync with the API environment: DEV uses Google Pay TEST, PROD uses PRODUCTION.

Creating a payment session (on your backend)

For each checkout, your server makes a two-step signed call: first it gets a one-time signature for the payload, then it creates the session with that signature. The response is the sessionId you pass to the app.

Backend: create a session (Node example, runs on your server)

const BASE = 'https://api-dev.pay.odero.ro/';          // PROD: https://pay.odero.ro/
const merchantId       = process.env.ODERO_MERCHANT_ID;     // server-side secret
const merchantApiToken = process.env.ODERO_MERCHANT_TOKEN;  // server-side secret

const payload = {
  merchantId,
  amount: 50.0,                 // major units (e.g. 50.00 RON)
  currency: 'RON',
  extOrderId: 'order-333abc',   // your own order reference
  extOrderUrl: '', returnUrl: '', successUrl: '', failUrl: '',
  saveCard: false,
  recurring: false,
  submerchants: [{
    extId: merchantId,
    name: 'My Store',
    amount: 50.0,
    products: [
      { extId: '0', name: 'T-Shirt', price: 50.0, quantity: 1, total: 50.0 }
    ]
  }],
  // Optional: pre-fill the buyer. Include only if email+phone+country+city+address
  // are all set, otherwise leave null and the sheet will ask for them.
  customer: null
};

// Step 1: sign the payload
const sig = await fetch(BASE + 'util/merchant-signature-header-json', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
  body: JSON.stringify({ merchantId, merchantApiToken, payload })
}).then(r => r.json());        // => { merchant_signature }

// Step 2: create the session
const session = await fetch(BASE + 'api/payments/session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-MERCHANT-SIGNATURE': sig.merchant_signature },
  body: JSON.stringify(payload)
}).then(r => r.json());         // => { sessionId, message }

return session.sessionId;        // hand this to the app

Which methods the sheet offers is decided by the session, not the app. When the SDK loads the session it reads an availablePaymentMethods list (card, apple_pay, google_pay) configured on your Odero merchant, and shows the wallet buttons only for the methods it contains. See Payment types.

Once your backend returns a sessionId, move on to your platform: iOS, Android or React Native.

Payment types

The same payment sheet handles every method. What it shows for a given checkout is driven by the session (the availablePaymentMethods configured on your Odero merchant) and by what you pass to showPaymentSheet.

MethodiOSAndroidReact NativeShown when
Card (new card) YesYesYes Always
Saved / stored card YesYesYes You pass savedCards
Apple Pay YesiOS only Session allows apple_pay (+ wallet set up)
Google Pay YesAndroid only Session allows google_pay (+ wallet set up)

Card

The default method, always available. The customer enters card number, expiry, CVC and cardholder name with live validation (Luhn check, brand detection, MM/YY mask). Submitted internally as card.

Saved card

Offer previously tokenized cards for one-tap payment. You pass a list of saved cards to showPaymentSheet; the customer picks one and re-enters only the CVC. Submitted as stored_card using the card's cardToken. Each saved card carries:

  • cardToken: the stored-card token from Odero.
  • lastFourDigits, expirationMonth, expirationYear: for display.
  • cvv: may be blank; the sheet asks for it on the Enter-CVC step.

Apple Pay (iOS)

Shown when the session allows apple_pay and the device can pay. The SDK builds a PKPaymentRequest with your applePayMerchantId (Visa + Mastercard, 3-D Secure capability) and forwards the encrypted token to Odero (gateway oderopay). Requires the Apple Pay entitlement and that your merchant certificate is uploaded to Odero. See Merchant setup.

Google Pay (Android)

Shown when the session allows google_pay and Google Pay is ready on the device. Uses Google Play Services Wallet with tokenization gateway oderopay and your Odero merchant id as the gatewayMerchantId (Visa + Mastercard, PAN_ONLY + CRYPTOGRAM_3DS). The wallet environment follows the API environment (DEV → test, PROD → production).

3-D Secure

Any card or wallet payment can trigger a 3-D Secure challenge. The SDK handles it for you: it opens the bank's page in an in-sheet web view and closes it automatically when the challenge completes. No integrator code is needed.

Billing / customer information

If the session does not already carry the buyer's details, the sheet collects billing/contact fields (email, phone, country, city, address) before paying. If you included a customer when you created the session, these fields are skipped.

iOS

The iOS SDK is OderoPaySDKIOS, a binary xcframework delivered through CocoaPods. You instantiate OderoPaySDK, call showPaymentSheet with a sessionId, and observe the result via NotificationCenter.

Install

Reference the published podspec from Odero's Artifactory in your Podfile:

Podfile

platform :ios, '15.0'

target 'YourApp' do
  use_frameworks!
  pod 'OderoPaySDKIOS',
    :podspec => 'https://ro-artifactory.devtokeninc.com/artifactory/PublicLibraries/Odero/oderopaysdk/oderopaysdkios/1.0.0/OderoPaySDKIOS.podspec'
end

Then pod install and open the generated .xcworkspace.

Project setup

1. Select the environment with an sdk.plist. Add a file named exactly sdk.plist to your app bundle with an ENV key. The SDK reads it at init: "DEV" selects DEV, anything else (or a missing file) selects PROD.

sdk.plist

<dict>
    <key>ENV</key>
    <string>DEV</string>
</dict>

2. Apple Pay entitlement (only if you offer Apple Pay). In Xcode → Signing & Capabilities add the Apple Pay capability and your merchant id. The id here must match the applePayMerchantId you pass to showPaymentSheet, and the merchant certificate must be uploaded to Odero (see Merchant setup).

YourApp.entitlements

<key>com.apple.developer.in-app-payments</key>
<array>
    <string>merchant.com.yourapp</string>
</array>

3. Fonts. Nothing to do. The SDK bundles and auto-registers DM Sans. Only if you set custom font names in the appearance do you need to bundle/register those fonts yourself.

API

Public class OderoPaySDK:

  • init(): reads sdk.plist. Keep a strong reference for the sheet's lifetime.
  • showPaymentSheet(sessionId:appearance:applePayMerchantId:savedCards:): fetches the session and presents the sheet over the top-most view controller (no presenter argument needed).
  • closePaymentSheet(): dismiss programmatically.

Saved cards use SavedCardItem(cardToken:cvv:lastFourDigits:expirationMonth:expirationYear:).

Receiving the result

There is no completion handler. Observe the paymentStatusUpdated notification. Its userInfo carries a status of payment_processing, payment_success or payment_failure, plus referenceNumber on success and errorMessage on failure.

Full example

import UIKit
import OderoPaySDKIOS

final class CheckoutViewController: UIViewController {

    // Keep a strong reference for the sheet's lifetime.
    private let oderoPay = OderoPaySDK()   // reads sdk.plist ENV (DEV/PROD)

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handlePaymentStatus(_:)),
            name: OderoWebViewController.paymentStatusUpdated,   // "paymentStatusUpdated"
            object: nil
        )
    }

    // Call after YOUR backend created the session.
    func startPayment(sessionId: String) {

        // Optional saved cards (pass [] for none).
        let savedCards = [
            SavedCardItem(cardToken: "tok_…", cvv: "",
                          lastFourDigits: "8901",
                          expirationMonth: "06", expirationYear: "2027")
        ]

        // Default theme: each page appearance with its no-arg initializer.
        let appearance = OderoPaymentBottomSheetAppearance(
            oderoPaymentDetailsPageAppearance: OderoPaymentDetailsPageAppearance(),
            oderoPaymentProcessingPageAppearance: OderoPaymentProcessingPageAppearance(),
            oderoPaymentResultSuccessPageAppearance: OderoPaymentResultSuccessPageAppearance(),
            oderoPaymentResultFailurePageAppearance: OderoPaymentResultFailurePageAppearance(),
            oderoPaymentSummaryPageAppearance: OderoPaymentSummaryPageAppearance(),
            oderoPayWithAnotherCardPageAppearance: OderoPayWithAnotherCardPageAppearance(),
            oderoEnterCVCPageAppearance: OderoEnterCVCPageAppearance()
        )

        oderoPay.showPaymentSheet(
            sessionId: sessionId,
            appearance: appearance,
            applePayMerchantId: "merchant.com.yourapp",   // must match your entitlement
            savedCards: savedCards
        )
    }

    @objc private func handlePaymentStatus(_ note: Notification) {
        guard let status = note.userInfo?["status"] as? String else { return }
        switch status {
        case "payment_success":
            let ref = note.userInfo?["referenceNumber"] as? String ?? "-"
            print("Paid. reference: \(ref)")
        case "payment_failure":
            let msg = note.userInfo?["errorMessage"] as? String ?? "Payment failed"
            print("Failed: \(msg)")
        default:
            break   // payment_processing / payment_idle
        }
    }
}

Customising the look

Replace any OderoXxxPageAppearance() with a configured initializer. Colours are hex strings, sizes are numeric strings, fonts are font-name strings (see Customizing the sheet).

OderoPaymentDetailsPageAppearance(
    pageTitle: "Pay securely",
    pageTitleColorHex: "#101828",
    pageBackgroundColorHex: "#FFFFFF",
    bottomButtons_positiveButtonBgHex: "#199719",
    bottomButtons_positiveButtonTextColor: "#FFFFFF",
    bottomButtons_rightButtonLabel: "Pay now"
    // every other parameter keeps its default theme value
)

Android

The Android SDK is oderopaysdkandroid, a Maven (AAR) library. You instantiate OderoPaySdk, call showPaymentSheet with a sessionId, and read the result from the onPaymentStatusChanged callback.

Install

1. Add the Odero Artifactory repository to settings.gradle (it’s public, no credentials needed):

settings.gradle

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://ro-artifactory.devtokeninc.com/artifactory/PublicLibraries/") }
    }
}

2. Add the dependency. DEV and PROD are separate artifacts: the -dev build talks to the DEV API and Google Pay TEST; the plain build talks to PROD.

app/build.gradle

dependencies {
    // DEV (test Google Pay):
    implementation 'Odero.oderopaysdk:oderopaysdkandroid:1.0.0-dev'
    // PROD: implementation 'Odero.oderopaysdk:oderopaysdkandroid:1.0.0'

    implementation 'com.google.android.gms:play-services-wallet:20.0.0'
}

The shared core (oderopaysdkshared:shared-android) is pulled in transitively from the same repository.

Project setup

Mirror the environment in your app's BuildConfig. The SDK reads BuildConfig.ENVIRONMENT; set it to "DEV" in debug and "PROD" in release, and keep it in sync with the artifact you depend on.

app/build.gradle

android {
    buildFeatures { buildConfig true }
    defaultConfig {
        buildConfigField "String", "ENVIRONMENT", "\"DEV\""
    }
    buildTypes {
        debug   { buildConfigField "String", "ENVIRONMENT", "\"DEV\"" }
        release { buildConfigField "String", "ENVIRONMENT", "\"PROD\"" }
    }
}

The INTERNET permission, the Google Pay meta-data and the 3-D Secure web-view activity ship inside the AAR and merge in automatically. DM Sans fonts are bundled too.

API

  • OderoPaySdk(activity, loadPaymentDataRequestCode, screen3dsRequestCode): the host activity must be a ComponentActivity (e.g. AppCompatActivity); the two ints are request codes you choose.
  • showPaymentSheet(sessionId, appearance, savedCards): fetches the session and renders the sheet as a full-screen overlay.
  • closePaymentSheet(): remove the overlay.
  • var onPaymentStatusChanged: ((status, referenceNumber, errorMessage) -> Unit)?: set it before showPaymentSheet.

Saved cards use SavedCardItem(cardToken, cvv, lastFourDigits, expirationMonth, expirationYear).

Receiving the result

The onPaymentStatusChanged callback fires on the main thread with a status of payment_processing, payment_success (referenceNumber non-null) or payment_failure (errorMessage non-null). The sheet also shows its own success/failure pages, so the callback is optional for UI but recommended for your own bookkeeping.

Full example

import com.oderopaysdk.OderoPaySdk
import com.oderopaysdk.oderoui.models.SavedCardItem
import com.oderopaysdk.oderoui.paymentappearance.oderopaymentbottomsheetappearance.OderoPaymentBottomSheetAppearance
import com.oderopaysdk.oderoui.paymentappearance.oderopaymentdetailsappearance.OderoPaymentDetailsPageAppearance

class CheckoutActivity : AppCompatActivity() {        // must be a ComponentActivity

    private var sdk: OderoPaySdk? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sdk = OderoPaySdk(this, 1001, 1002)           // (activity, googlePayCode, 3dsCode)

        sdk?.onPaymentStatusChanged = { status, referenceNumber, errorMessage ->
            when (status) {
                "payment_success" -> Log.i("Pay", "OK ref=$referenceNumber")
                "payment_failure" -> Log.e("Pay", "FAIL $errorMessage")
            }
        }
    }

    // sessionId comes from YOUR backend.
    fun pay(sessionId: String) {
        val appearance = OderoPaymentBottomSheetAppearance.default().copy(
            oderoPaymentDetailsPageAppearance = OderoPaymentDetailsPageAppearance(
                pageTitle = "Pay securely",
                pageTitleColorHex = "#101828",
                bottomButtons_positiveButtonBgHex = "#199719",
                bottomButtons_rightButtonLabel = "Pay now"
            )
        )

        val savedCards = listOf(
            SavedCardItem(
                cardToken = "tok_…", cvv = "",
                lastFourDigits = "8901",
                expirationMonth = "06", expirationYear = "27"
            )
        )

        sdk?.showPaymentSheet(sessionId, appearance, savedCards)
    }

    fun dismiss() { sdk?.closePaymentSheet() }
}

React Native

The React Native SDK is react-native-oderopay, a pure-RN package. You wrap your app in <OderoPayProvider>, call showPaymentSheet from the useOderoPay() hook, and read the result from the same hook (or an event listener). The card flow is pure JavaScript plus react-native-webview; Apple Pay / Google Pay add an optional native wallet module.

Install

Add the package from Odero's Artifactory tarball, plus the react-native-webview peer dependency:

package.json

{
  "dependencies": {
    "react-native-oderopay": "https://ro-artifactory.devtokeninc.com/artifactory/PublicLibraries/Odero/oderopaysdk/react-native-oderopay/1.0.0/react-native-oderopay-1.0.0.tgz",
    "react-native-webview": ">=13.0.0"
  }
}
yarn install
cd ios && pod install

Both native modules autolink, so there's no manual linking step. (If Odero serves the package from an npm registry instead, install it by name: yarn add react-native-oderopay react-native-webview.)

Project setup

The card flow needs no native configuration. For wallets:

  • Apple Pay: add the Apple Pay capability + merchant id in Xcode, and upload your merchant certificate to Odero (see Merchant setup). Pass the id as applePayMerchantId. Test on a real device.
  • Google Pay: nothing to add manually. play-services-wallet and the wallet meta-data ship with the library, so just rebuild.

The environment is chosen in JS via the environment prop (default PROD). When you enable wallets, build a wallet adapter with the matching environment.

API

  • <OderoPayProvider environment defaultAppearance walletAdapter onPaymentEvent debug>: mount once near your app root.
  • useOderoPay(){ showPaymentSheet, closePaymentSheet, status, referenceNumber, errorMessage, isPresented }.
  • showPaymentSheet(sessionId, options?) where options = { appearance?, savedCards?, applePayMerchantId?, environment?, onEvent? }.
  • createNativeWalletAdapter({ environment }): pass as walletAdapter to enable Apple Pay / Google Pay; without it the wallet buttons stay hidden.

Receiving the result

Outcomes are OderoPaymentEvent objects: { status, referenceNumber?, errorMessage? } with status one of idle / processing / success / failure. Read it from the useOderoPay() hook (re-renders on change), the provider's onPaymentEvent, or a per-call onEvent.

Full example

App.tsx: mount the provider once

import { OderoPayProvider, createNativeWalletAdapter } from 'react-native-oderopay';
import { CheckoutScreen } from './CheckoutScreen';

// Keep the wallet env in sync with the provider env (DEV => Google Pay TEST).
const walletAdapter = createNativeWalletAdapter({ environment: 'DEV' });

// environment: 'DEV' or 'PROD' (default). walletAdapter is optional - omit for card-only.
export default function App() {
  return (
    <OderoPayProvider
      environment="DEV"
      walletAdapter={walletAdapter}
      onPaymentEvent={(e) => console.log('[odero]', e.status)}
    >
      <CheckoutScreen />
    </OderoPayProvider>
  );
}

CheckoutScreen.tsx: present the sheet for a server-created session

import React, { useEffect } from 'react';
import { Alert, Button } from 'react-native';
import { useOderoPay, type OderoSavedCard, type OderoAppearance } from 'react-native-oderopay';

const savedCards: OderoSavedCard[] = [
  { cardToken: 'tok_…', cvv: '', lastFourDigits: '8901', expirationMonth: '06', expirationYear: '2027' },
];

const appearance: OderoAppearance = {
  oderoPaymentDetailsPageAppearance: {
    pageTitle: 'Checkout',
    bottomButtons_positiveButtonBgHex: '#199719',
    bottomButtons_rightButtonLabel: 'Continue',
  },
  oderoPaymentResultSuccessPageAppearance: { successText: 'All done!' },
};

export function CheckoutScreen() {
  const { showPaymentSheet, status, referenceNumber, errorMessage } = useOderoPay();

  useEffect(() => {
    if (status === 'success') Alert.alert('Payment successful', `Ref: ${referenceNumber ?? ''}`);
    if (status === 'failure') Alert.alert('Payment error', errorMessage ?? 'Payment failed');
  }, [status]);

  const pay = async () => {
    const sessionId = await myBackend.createCheckoutSession(/* amount, products… */);
    showPaymentSheet(sessionId, {
      appearance,
      savedCards,                                  // optional one-tap cards
      applePayMerchantId: 'merchant.com.yourapp',  // optional (Apple Pay)
    });
  };

  return <Button title="Pay" onPress={pay} />;
}

Customizing the sheet

The payment sheet is fully themeable on all three platforms through the same model: a bottom-sheet appearance made of one appearance object per page. You override only the values you care about; everything else falls back to the Odero default theme. The default palette is led by Odero green (#199719) on white, with DM Sans typography.

The seven pages

Appearance objectPage it themes
oderoPaymentDetailsPageAppearanceThe main card-entry page (largest surface)
oderoPaymentSummaryPageAppearanceOrder summary before paying
oderoEnterCVCPageAppearanceCVC step for a selected saved card
oderoPayWithAnotherCardPageAppearanceNew-card form when saved cards are shown
oderoPaymentProcessingPageAppearanceProcessing / loading screen
oderoPaymentResultSuccessPageAppearanceSuccess result page
oderoPaymentResultFailurePageAppearanceFailure result page

Value conventions

Every styling value is a simple, serializable type, identical across iOS, Android and React Native:

  • Colours: hex strings, e.g. "#199719" (fields ending ColorHex / Color / BgHex).
  • Fonts: a font-family name string. Built-in: "DMSans-Regular", "DMSans-Medium", "DMSans-SemiBold" (fields ending FontName).
  • Sizes & widths: numeric strings, e.g. "18" (fields ending FontSize / Width).
  • Labels & placeholders: plain strings (e.g. pageTitle, bottomButtons_rightButtonLabel).
  • Toggles: booleans (e.g. ..._isEnabled, ..._isSecureTextEntry).
  • Icons: base64-encoded image strings (fields ending IconBase64 / leftIcon / rightIcon; empty = none).

What you can theme on the details page

The details page exposes the deepest set of knobs, grouped by section (the same names appear on every platform):

  • Page: pageTitle, pageTitleFontName/FontSize/ColorHex, pageBackgroundColorHex.
  • Payment-method selector: paymentMethodOrText, paymentMethodItem* (border / background / text / selected states).
  • Saved cards: savedCardsTitleText, card-item colours, show-more/less labels, savedCardsAnotherCardText.
  • Card inputs: cardInformation_* for card number, expiry, security code and cardholder: placeholders, colours, fonts, per-field icons, and the CVC tooltip.
  • Billing info: billingInformation_* for email, phone, country, city, address (enable + icons + styling).
  • Save-card checkbox: saveCardCheckboxTintColor, saveCardLabelText + font/colour.
  • Bottom buttons: bottomButtons_leftButtonLabel, bottomButtons_rightButtonLabel, positive/negative background + text colours + fonts.

How you pass it, per platform

iOS: build OderoPaymentBottomSheetAppearance with all seven page objects (use a page's no-arg initializer for defaults), then set the parameters you want:

let appearance = OderoPaymentBottomSheetAppearance(
    oderoPaymentDetailsPageAppearance: OderoPaymentDetailsPageAppearance(
        pageTitle: "Checkout",
        pageTitleColorHex: "#0B3D2E",
        bottomButtons_positiveButtonBgHex: "#0B7A4B",
        bottomButtons_rightButtonLabel: "Pay now"
    ),
    oderoPaymentProcessingPageAppearance: OderoPaymentProcessingPageAppearance(),
    oderoPaymentResultSuccessPageAppearance: OderoPaymentResultSuccessPageAppearance(),
    oderoPaymentResultFailurePageAppearance: OderoPaymentResultFailurePageAppearance(),
    oderoPaymentSummaryPageAppearance: OderoPaymentSummaryPageAppearance(),
    oderoPayWithAnotherCardPageAppearance: OderoPayWithAnotherCardPageAppearance(),
    oderoEnterCVCPageAppearance: OderoEnterCVCPageAppearance()
)

Android: start from OderoPaymentBottomSheetAppearance.default() and .copy(...) the pages you want; each page is a data class with defaulted, named parameters:

val appearance = OderoPaymentBottomSheetAppearance.default().copy(
    oderoPaymentDetailsPageAppearance = OderoPaymentDetailsPageAppearance(
        pageTitle = "Checkout",
        pageTitleColorHex = "#0B3D2E",
        bottomButtons_positiveButtonBgHex = "#0B7A4B",
        bottomButtons_rightButtonLabel = "Pay now"
    )
)

React Native: pass a partial OderoAppearance object; anything omitted uses the default. Set it per call, or as defaultAppearance on the provider:

const appearance: OderoAppearance = {
  oderoPaymentDetailsPageAppearance: {
    pageTitle: 'Checkout',
    pageTitleColorHex: '#0B3D2E',
    paymentMethodOrText: 'OR PAY WITH CARD',
    bottomButtons_positiveButtonBgHex: '#0B7A4B',
    bottomButtons_rightButtonLabel: 'Pay now',
  },
  oderoPaymentResultSuccessPageAppearance: {
    successText: 'All done!',
    returnToAppButtonText: 'Back to store',
  },
};

showPaymentSheet(sessionId, { appearance });

Sample apps

Three complete, runnable example apps show the SDK end to end (a shopping cart, session creation, the payment sheet, saved cards, and wallet buttons). Each is a standalone project you can download, build, and run.

PlatformDownloadSDK source
iOS OderoPaySDKIOSExampleApp.zip CocoaPods podspec from Artifactory
Android OderoPaySDKAndroidExampleApp.zip Maven (AAR) from Artifactory
React Native OderoPaySDKReactExampleApp.zip .tgz from Artifactory

iOS

unzip OderoPaySDKIOSExampleApp.zip
cd OderoPaySDKIOSExampleApp
pod install
open OderoInAppPaymentIOSExampleApp.xcworkspace

Set your environment in sdk.plist (ENV = DEV or PROD) and your Apple Pay merchant id in the entitlement. See iOS.

Android

unzip OderoPaySDKAndroidExampleApp.zip
cd OderoPaySDKAndroidExampleApp
./gradlew :app:installDebug

Or open the folder in Android Studio and run. See Android.

React Native

unzip OderoPaySDKReactExampleApp.zip
cd OderoPaySDKReactExampleApp
yarn install
cd ios && pod install && cd ..
yarn ios       # or: yarn android

See React Native for the full walkthrough.

Each zip contains source and build config only. Dependencies (the Odero SDK included) are fetched on the first pod install / yarn install / Gradle sync, so the downloads stay small.

Card Operations

Store Card

$billingAddress = new \Oderopay\Model\Address\BillingAddress();
$billingAddress
    ->setAddress('185 Berry St #550, San Francisco, CA 94107, USA')
    ->setCity('San Francisco')
    ->setCountry('USA');

$deliveryAddress = new \Oderopay\Model\Address\DeliveryAddress();
$deliveryAddress
    ->setAddress('185 Berry St #550, San Francisco, CA 94107, USA')
    ->setCity('San Francisco')
    ->setCountry('USA')
    ->setDeliveryType('Courier');

$customer = new \Oderopay\Model\Payment\Customer();
$customer
    ->setEmail('customer@email.com')
    ->setPhoneNumber('  +19159969739')
    ->setDeliveryInformation($deliveryAddress)
    ->setBillingInformation($billingAddress);

$card = new \Oderopay\Model\Card\SaveCard();

$card->setCustomer($customer);
$card->setCurrency('RON');
$card->setReturnUrl('https://my-store.com/');

$cardResponse = $oderopay->cards->create($card);  //CardSaveResponse

if($cardResponse->isSuccess()){
   return http_redirect($cardResponse->data['url'])
}else{
    log($cardResponse->message);
}
{
  "merchantId": "uuid",
  "returnUrl": "string",
  "currency": "string",
  "customer": {
    "email": "string",
    "phoneNumber": "string",
    "deliveryInformation": {
      "deliveryType": "string",
      "country": "string",
      "city": "string",
      "address": "string"
    },
    "billingInformation": {
      "country": "string",
      "city": "string",
      "address": "string"
    }
  }
}

The above command returns an object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string"
  }
}

This endpoint creates a card save request and returns the url of hosted card storage page if success.

HTTP Request

POST /api/cards

Body

Parameter Description
merchantId* Your unique merchant ID.
currency* Currency code (3 letters).
returnUrl* Base64 encoded URL where users will be redirected at the end or where they will be redirected if they choose to cancel current action.
customer.email* Customer email address".
customer.phoneNumber* Customer phone number (should start with country phone number prefix).
customer.deliveryInformation.deliveryType* Delivery type (e.g.: "Sameday").
customer.deliveryInformation.country* Alpha code of the country set for delivery - 3 letters.
customer.deliveryInformation.city* Where you will deliver requested products / services.
customer.deliveryInformation.address* Customer physical address set for delivery
customer.billingInformation.country* Alpha code of the country set for billing - 3 letters.
customer.billingInformation.city* City set for billing
customer.billingInformation.address* Customer physical address set for billing

Delete Card

$cardResponse = $oderopay->cards->delete('cardToken');  //CardSaveResponse

var_dump($cardResponse);

{
  "merchantId": "uuid"
}

The above command returns an object like this:

{
  "operationId": "uuid",
  "message": "string"
}

This endpoint deletes a saved card.

HTTP Request

DELETE /api/cards/{cardToken}

Payments

Create Payment Request

$billingAddress = new \Oderopay\Model\Address\BillingAddress();
$billingAddress
    ->setAddress('185 Berry St #550, San Francisco, CA 94107, USA')
    ->setCity('San Francisco')
    ->setCountry('USA');

$deliveryAddress = new \Oderopay\Model\Address\DeliveryAddress();
$deliveryAddress
    ->setAddress('185 Berry St #550, San Francisco, CA 94107, USA')
    ->setCity('San Francisco')
    ->setCountry('USA')
    ->setDeliveryType('Courier');

$customer = new \Oderopay\Model\Payment\Customer();
$customer
    ->setEmail('customer@email.com')
    ->setPhoneNumber('  +19159969739')
    ->setDeliveryInformation($deliveryAddress)
    ->setBillingInformation($billingAddress);

$products = []; 
$product1 = new \Oderopay\Model\Payment\BasketItem();
$product1
    ->setExtId('123')
    ->setImageUrl('https://site.com/image/product1.jpg')
    ->setName('Product Name')
    ->setPrice(99.99)
    ->setQuantity(1);

$products[] = $product1;

$product2 = new \Oderopay\Model\Payment\BasketItem();
$product2
    ->setExtId('123')
    ->setImageUrl('https://site.com/image/product1.jpg')
    ->setName('Product Name')
    ->setPrice(99.99)
    ->setQuantity(1);

$products[] = $product2;

$paymentRequest = new \Oderopay\Model\Payment\Payment();
$paymentRequest
    ->setAmount(100.00)
    ->setCurrency('USD')
    ->setExtOrderId('external-random-id')
    ->setExtOrderUrl('https://mystore.com/sample-product.html')
    ->setSuccessUrl('https://mystore.com/order/xxxx/?success=true')
    ->setFailUrl('https://mystore.com/order/xxxx/payment_failed')
    ->setMerchantId('{merchant-id}')
    ->setCustomer($customer)
    ->setProducts($products)
    ;

$payment = $oderopay->payments->create($paymentRequest); //PaymentIntentResponse

if($payment->isSuccess()){
   return http_redirect($payment->data['url'])
}else{
    log($payment->message);
}

{
  "merchantId": "uuid",
  "amount": "float",
  "description": "string",
  "currency": "string",
  "extOrderId": "string",
  "extOrderUrl": "string",
  "returnUrl": "string",
  "successUrl": "string",
  "failUrl": "string",
  "saveCard": "bool",
  "recurring": false,
  "submerchants": [
    {
      "extId": "uuid",
      "name": "string",
      "amount": "float",
      "commission": "float",
      "products": [
        {
          "extId": "string",
          "price": "float",
          "quantity": "float",
          "total": "float",
          "name": "string",
          "imageUrl": "string"
        }
      ]
    }
  ],
  "customer": {
    "email": "string",
    "phoneNumber": "string",
    "deliveryInformation": {
      "deliveryType": "string",
      "country": "string",
      "city": "string",
      "address": "string"
    },
    "billingInformation": {
      "country": "string",
      "city": "string",
      "address": "string"
    }
  }
}

The above command returns an object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string"
  }
}

This endpoint creates a payment request and returns the url of hosted payment page if success. You are an ecommerce company and selling your own products.

HTTP Request

POST /api/payments/one-time

Body

Parameter Description
merchantId* Your unique merchant ID.
cardToken Stored card token.
amount* Payment amount.
description Payment description, will be displayed on slip. (max 80 characters)
currency* Currency code (3 letters).
extOrderId* Your internal order ID.
extOrderUrl* Base64 encoded order URL
successUrl* Base64 encoded URL to return on success
failUrl* Base64 encoded URL to return on fail
returnUrl* Base64 encoded URL where users will be redirected at the end or where they will be redirected if they choose to cancel current action.
saveCard Would you like to save credit card and receive a specific stored card token?
recurring* boolean, default false.
customer.email* Customer email address".
customer.phoneNumber* Customer phone number (should start with country phone number prefix).
customer.deliveryInformation.deliveryType* Delivery type (e.g.: "Sameday").
customer.deliveryInformation.country* Alpha code of the country set for delivery - 3 letters.
customer.deliveryInformation.city* Where you will deliver requested products / services.
customer.deliveryInformation.address* Customer physical address set for delivery
customer.billingInformation.country* Alpha code of the country set for billing - 3 letters.
customer.billingInformation.city* City set for billing
customer.billingInformation.address* Customer physical address set for billing

$paymentRequest = new \Oderopay\Model\Payment\PaymentLink();
$paymentRequest
    ->setItemDescription('Sample Product')
    ->setCurrency('RON')
    ->setAmount(5.20)
    ->setExpireAt((new DateTime())->add(new DateInterval("P1Y")))
;

try {
    $payment = $oderopay->payments->create($paymentRequest); //PaymentIntentResponse
    var_dump($payment);
}catch (\Exception $e){
    echo $e->getMessage();
}

{
  "merchantId": "uuid",
  "amount": "float",
  "currency": "string",
  "expireAt": "string",
  "itemDescription": "string"
}

The above command returns an object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string",
    "qr" : "string"
  }
}

This endpoint creates a payment link request and returns the url of hosted payment page if success. Qr is the url of QrCode that could be used directly as image.

HTTP Request

POST /api/payments/link

Body

Parameter Description
merchantId* Your unique merchant ID.
amount* Payment amount.
currency* Currency code (3 letters).
itemDescription* Product or Service name
expireAt* Expiration date of the link
description Payment description, will be displayed on slip. (max 80 characters)

Get Payment

$payment = $oderopay->payments->get('payment-id');

The above command returns object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string"
  }
}

To get a single payment object, you can call this endpoint.

HTTP Request

GET /api/payments/{id}

Marketplace

$customer = new Customer();
.... 

$product1 = new \Oderopay\Model\Payment\BasketItem();
$product1
    ->setExtId('123')
    ->setImageUrl('https://site.com/image/product1.jpg')
    ->setName('Product Name')
    ->setPrice(99.99)
    ->setQuantity(1);

$merchant1Products[] = $product1;

//initialize first merchant
$merchant1 = new \Oderopay\Model\Payment\Merchant();
$merchant1
    ->setName('Sub Merchant Name')
    ->setExtId('uuid')
    ->setProducts($merchant1Products)
    ->setAmount(90)
    ->setCommission(0.2)

$product2 = new \Oderopay\Model\Payment\BasketItem();
$product2
    ->setExtId('123')
    ->setImageUrl('https://site.com/image/product2.jpg')
    ->setName('Product Name')
    ->setPrice(99.99)
    ->setQuantity(2);
$merchant2Products[] = $product2;

//initialize second merchant
$merchant2 = new \Oderopay\Model\Payment\Merchant();
$merchant2
    ->setName('Second Sub Merchant')
    ->setExtId('uuid')
    ->setAmount(180)
    ->setCommission(0.2)
    ->setProducts($merchant2Products)

$merchants[] = $merchant1;
$merchants[] = $merchant2;

$paymentRequest = new \Oderopay\Model\Payment\Payment();
$paymentRequest
    ->setAmount(100.00)
    ->setCurrency('USD')
    ->setExtOrderId('external-random-id')
    ->setExtOrderUrl('https://mystore.com/sample-product.html')
    ->setReturnUrl('https://mystore.com')
    ->setSuccessUrl('https://mystore.com/order/123213?success')
    ->setFailUrl('https://mystore.com/order/123213?failed')
    ->setMerchantId('{merchant-id}')
    ->setCustomer($customer)
    ->setMerchants($merchants) //add merchants data to payment
    ;

$payment = $oderopay->payments->create($paymentRequest);
{
  "merchantId": "uuid",
  "amount": "float",
  "currency": "string",
  "extOrderId": "string",
  "extOrderUrl": "string",
  "returnUrl": "string",
  "successUrl": "string",
  "failUrl": "string",
  "saveCard": "bool",
  "recurring": false,
  "submerchants": [
    {
      "extId": "uuid",
      "name": "string",
      "amount": "float",
      "commission": "float",
      "products": [
        {
          "extId": "string",
          "price": "float",
          "quantity": "float",
          "total": "float",
          "name": "string",
          "imageUrl": "string"
        }
      ]
    }
  ],
  "customer": {
    "email": "string",
    "phoneNumber": "string",
    "deliveryInformation": {
      "deliveryType": "string",
      "country": "string",
      "city": "string",
      "address": "string"
    },
    "billingInformation": {
      "country": "string",
      "city": "string",
      "address": "string"
    }
  }
}

The above command returns object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string"
  }
}

Lets assume you have multiple sub merchants. And your customer has multiple items from different submerchants in their basket. So that, you need to add each merchant with products to payment object

HTTP Request

POST /api/payments/one-time

URL Parameters

Parameter Description
merchantId* Your unique merchant ID.
cardToken Stored card token.
amount* Payment amount.
description Payment description, will be displayed on slip. (max 80 characters)
currency* Currency code (3 letters).
extOrderId* Your internal order ID.
extOrderUrl* Base64 encoded order URL.
returnUrl* Base64 encoded URL where users will be redirected at the end or where they will be redirected if they choose to cancel current action.
saveCard Would you like to save credit card and receive a specific stored card token?
submerchants[n].extId* Submerchant ID (from Odero PSP).
submerchants[n].name* Submerchant Name (from Odero PSP).
submerchants[n].amount* Amount to deposit
submerchants[n].commission* Submerchant commission.
submerchants[n].products[n].extId Product ID from your application. Products collection is not mandatory.
submerchants[n].products[n].price Product price from your application.
submerchants[n].products[n].quantity Product quantity.
submerchants[n].products[n].total Product price * product quantity.
submerchants[n].products[n].name Product name.
submerchants[n].products[n].image Base64 encoded image URL.
customer.email* Customer email address".
customer.phoneNumber* Customer phone number (should start with country phone number prefix).
customer.deliveryInformation.deliveryType* Delivery type (e.g.: "Sameday").
customer.deliveryInformation.country* Alpha code of the country set for delivery - 3 letters.
customer.deliveryInformation.city* Where you will deliver requested products / services.
customer.deliveryInformation.address* Customer physical address set for delivery
customer.billingInformation.country* Alpha code of the country set for billing - 3 letters.
customer.billingInformation.city* City set for billing
customer.billingInformation.address* Customer physical address set for billing

Pay With Saved Card

$customer = new Customer();
.... 
$merchants[] = $merchants;

$paymentRequest = new \Oderopay\Model\Payment\Payment();
$paymentRequest
    ->setAmount(100.00)
    ->setCurrency('USD')
    ->setExtOrderId('external-random-id')
    ->setExtOrderUrl('https://mystore.com/sample-product.html')
    ->setReturnUrl('https://mystore.com')
    ->setMerchantId('{merchant-id}')
    ->setCustomer($customer)
    ->setMerchants($merchants)
    ->setCardToken('savedCardToken') // 👈 add saved card token to payment
    ;

$payment = $oderopay->payments->create($paymentRequest);
{
  "merchantId": "uuid",
  "cardToken": "string",
  "amount": "float",
  "currency": "string",
  "extOrderId": "string",
  "extOrderUrl": "string",
  "returnUrl": "string",
  "saveCard": "bool",
  "recurring": false,
  "submerchants": [
    {
      "extId": "uuid",
      "name": "string",
      "amount": "float",
      "commission": "float",
      "products": [
        {
          "extId": "string",
          "price": "float",
          "quantity": "float",
          "total": "float",
          "name": "string",
          "imageUrl": "string"
        }
      ]
    }
  ],
  "customer": {
    "email": "string",
    "phoneNumber": "string",
    "deliveryInformation": {
      "deliveryType": "string",
      "country": "string",
      "city": "string",
      "address": "string"
    },
    "billingInformation": {
      "country": "string",
      "city": "string",
      "address": "string"
    }
  }
}

The above command returns object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string"
  }
}

If you want to make the payment with a saved card, you need to add cardToken parameter to payment object.

HTTP Request

POST /api/payments/stored-card

URL Parameters

Parameter Description
merchantId* Your unique merchant ID.
cardToken Stored card token.
amount* Payment amount.
description Payment description, will be displayed on slip. (max 80 characters)
currency* Currency code (3 letters).
extOrderId* Your internal order ID.
extOrderUrl* Base64 encoded order URL
successUrl* Base64 encoded URL to return on success
failUrl* Base64 encoded URL to return on fail
returnUrl* Base64 encoded URL where users will be redirected at the end or where they will be redirected if they choose to cancel current action.
saveCard Would you like to save credit card and receive a specific stored card token?
recurring* boolean, default false.
customer.email* Customer email address".
customer.phoneNumber* Customer phone number (should start with country phone number prefix).
customer.deliveryInformation.deliveryType* Delivery type (e.g.: "Sameday").
customer.deliveryInformation.country* Alpha code of the country set for delivery - 3 letters.
customer.deliveryInformation.city* Where you will deliver requested products / services.
customer.deliveryInformation.address* Customer physical address set for delivery
customer.billingInformation.country* Alpha code of the country set for billing - 3 letters.
customer.billingInformation.city* City set for billing
customer.billingInformation.address* Customer physical address set for billing

Deposit the Authorized Transaction

{
  "merchantId": "uuid",
  "paymentId": "uuid",
  "amount": "float",
  "submerchants": [
    {
      "extId": "uuid",
      "name": "string",
      "amount": "float",
      "commission": "float"
    }
  ]
}

The above command returns object like this:

{
  "operationId": "uuid",
  "message": "string"
}

If collection type is set as "two steps" under your merchant settings, then you can use this endpoint to deposit the amount to merchant accounts. The amount should be less than or equal to the total amount of payment.

HTTP Request

POST /api/deposits

Body Params

Field Type Description
merchantId* uuid Merchant identifier
paymentId* uuid Payment identifier
amount* float Total amount
submerchants.[n].extId* uuid External ID
submerchants.[n].name string Submerchant name
submerchants.[n].amount* float Submerchant's amount
submerchants.[n].commission float Submerchant's commission, you can put 0 for no commission

Reverse the Authorized Transaction

If collection type is set as "two steps" under your merchant settings, then you can use this endpoint to reverse the authorized transaction.

{
  "merchantId": "uuid",
  "paymentId": "uuid"
}

The above command returns object like this:

{
  "operationId": "uuid",
  "message": "string"
}

HTTP Request

POST /api/reverses

Body Params

Field Type Description
merchantId uuid Merchant identifier
paymentId uuid Payment identifier

Refund Deposited Transaction

Refund Deposited Transaction

Refund a previously deposited transaction. This transaction must have been completed with a payment type of "success". The amount of the refund cannot be greater than the original amount of the transaction.

{
  "merchantId": "uuid",
  "paymentId": "uuid",
  "amount": "float",
  "referenceId": "string",
  "submerchants": [
    {
      "extId": "uuid",
      "name": "string",
      "amount": "float",
      "commission": "float"
    }
  ]
}

The above command returns object like this:

{
  "operationId": "uuid",
  "message": "string"
}

HTTP Request

POST /api/refunds

Body Params

Field Type Description
merchantId uuid Merchant identifier
paymentId uuid Payment identifier
referenceId string reference identifier
amount float Refund amount
submerchants[n].extId* Submerchant ID (from Odero PSP).
submerchants[n].name* Submerchant Name (from Odero PSP).
submerchants[n].amount* Amount to deposit
submerchants[n].commission* Submerchant commission.

Subscriptions

Create Subscription

//subscription
$startDate = new DateTime();
$endDate = clone $startDate; $endDate->add(DateInterval::createFromDateString('1 years'));

$subscription = new \Oderopay\Model\Subscription\Subscription();
$subscription->setTimeForBillingUtc('15:00');
$subscription->setStartDate($startDate->format('Y-m-d H:i:s'));
$subscription->setEndDate($endDate->format('Y-m-d H:i:s'));
$subscription->monthly(); //set monthly, weekly or yearly

$paymentRequest = new \Oderopay\Model\Payment\Payment();
$paymentRequest
    ->setCurrency('USD')
    ->setExtOrderId('external-random-id')
    ->setExtOrderUrl('https://mystore.com/orders/3244234')
    ->setReturnUrl('https://mystore.com/sample-product.html')
    ->setSuccessUrl('https://mystore.com/order/xxxx/?success=true')
    ->setFailUrl('https://mystore.com/order/xxxx/payment_failed')
    ->setCustomer($customer)
    ->setProducts($products)
    ->setSubscription($subscription) // 👈 set the subscription
;

$subscriptionRequest = $oderopay->subscriptions->create($paymentRequest); //PaymentIntentResponse

dump($payment);
if($subscriptionRequest->isSuccess()){
    //redirect to $payment->data['url'];
}else{
   //log the message $subscriptionRequest->message
}


{
  "merchantId": "uuid",
  "cardToken": "string",
  "amount": "float",
  "currency": "string",
  "extOrderId": "string",
  "extOrderUrl": "string",
  "returnUrl": "string",
  "successUrl": "string",
  "failUrl": "string",
  "saveCard": "bool",
  "recurring": "bool",
  "recurringInformation": {
    "startDate": "string",
    "endDate": "string",
    "timeForBillingUtc": "string",
    "interval": "string"
  },
  "submerchants": [
    {
      "extId": "uuid",
      "name": "string",
      "amount": "float",
      "commission": "float",
      "products": [
        {
          "extId": "string",
          "price": "float",
          "quantity": "float",
          "total": "float",
          "name": "string",
          "imageUrl": "string"
        }
      ]
    }
  ],
  "customer": {
    "email": "string",
    "phoneNumber": "string",
    "deliveryInformation": {
      "deliveryType": "string",
      "country": "string",
      "city": "string",
      "address": "string"
    },
    "billingInformation": {
      "country": "string",
      "city": "string",
      "address": "string"
    }
  }
}

The above command returns an object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string"
  }
}

This endpoint creates a payment request and returns the url of hosted payment page if success.

HTTP Request

POST /api/payments/one-time

Body

Parameter Description
merchantId* Your unique merchant ID.
cardToken Stored card token.
amount* Payment amount.
currency* Currency code (3 letters).
extOrderId* Your internal order ID.
extOrderUrl* Base64 encoded order URL.
returnUrl* Base64 encoded URL where users will be redirected at the end or where they will be redirected if they choose to cancel current action.
saveCard Would you like to save credit card and receive a specific stored card token?
recurring* Setting a new recurring payment.
recurringInformation.startDate When will start the recurring payment. Datetime ATOM format expected. Required if recurring is true.
recurringInformation.endDate When will end the recurring payment. Datetime ATOM format expected. Required if recurring is true.
recurringInformation.timeForBillingUtc Hour and minute when we will debit client account. Required if recurring is true.
recurringInformation.interval How often you'd like to debit client account. Available options: Weekly, Monthly and Yearly. Required if recurring is true.
customer.email* Customer email address".
customer.phoneNumber* Customer phone number (should start with country phone number prefix).
customer.deliveryInformation.deliveryType* Delivery type (e.g.: "Sameday").
customer.deliveryInformation.country* Alpha code of the country set for delivery - 3 letters.
customer.deliveryInformation.city* Where you will deliver requested products / services.
customer.deliveryInformation.address* Customer physical address set for delivery
customer.billingInformation.country* Alpha code of the country set for billing - 3 letters.
customer.billingInformation.city* City set for billing
customer.billingInformation.address* Customer physical address set for billing

Retry

$subscription = $oderopay->subscriptions->retry('payment-id');
{
  "merchantId": "uuid"
}

The above command returns object like this:

{
  "message": "string"
}

If a recurring payment fails, you might want to retry for transaction manually. To do so, you can call the retry endpoint.

HTTP Request

POST /api/payments/{id}/recurring/retry

Cancel

$payment = $oderopay->subscriptions->cancel('payment-id');
{
  "merchantId": "uuid"
}

The above command returns object like this:

{
  "operationId": "uuid",
  "message": "string",
  "data": {
    "paymentId": "uuid",
    "url": "string"
  }
}

You might want to cancel the subscription. To do so, you can call cancel endpoint.

HTTP Request

POST /api/payments/{id}/recurring/cancel

Webhooks

$payload = @file_get_contents('php://input'); // or $_REQUEST
$message = $oderopay->webhooks->handle(json_decode($payload, true));

switch (true) {
    case $message instanceof \Oderopay\Model\Webhook\Payment:        
        /** @var  \Oderopay\Model\Webhook\Payment $message */
        $paymentId = $message->getPaymentId();

        break;
    case $message instanceof \Oderopay\Model\Webhook\Refund:
        /** @var  Refund $message */
        $operationId = $message->getOperationId();

        // Then update your data

    case $message instanceof \Oderopay\Model\Webhook\StoredCard:
        /** @var  \Oderopay\Model\Webhook\StoredCard $message */
        $cardToken = $message->getCardToken();


        // Then update your data
        break;
    // ... handle other event types
    default:
        echo 'Received unknown webhook ' . $message->getStatus(); //Should give ERROR
}

Odero uses webhooks (callback) to notify your application when an event happens in your account. Webhooks are particularly useful for asynchronous events like when a customer’s bank confirms a payment, a customer disputes a charge, a recurring payment succeeds, or when collecting subscription payments.

A webhook enables Odero to push real-time notifications to your app. Odero uses HTTPS to send these notifications to your app as a JSON payload. You can then use these notifications to execute actions in your backend systems.

How to create a webhook endpoint

Creating a webhook endpoint is no different from creating any other page on your website. It’s an HTTP or HTTPS endpoint on your server with a URL. If you’re still developing your endpoint on your local machine, it can be HTTP. After it’s publicly accessible, it must be HTTPS. You can use one endpoint to handle several different event types at once, or set up individual endpoints for specific events.

Then update your webhook (callback) url in merchant admin panel. Odero will send notifications to that url.

Webhook types

webhook types could be payment,deposit,refund,reverse, stored_card or remove_stored_card.

Payment

You will receive a payment message after handshake between customer and bank. It could be successful transaction or not.

$message = $oderopay->webhooks->handle(json_decode($_REQUEST, true));

/** @var  \Oderopay\Model\Webhook\Payment $message */
$isDepositMade = $message->isDepositMade();
$operationId = $message->getOperationId();
$cardToken = $message->getCardToken();
$cardExpirationMonth = $message->getExpirationMonth();
$cardExpirationYear = $message->getExpirationYear();
$cardLastFourDigits = $message->getLastFourDigits();
{
  "type": "payment",
  "operationId": "1ed4a356-55ea-67bc-b39f-83aa9247ea19",
  "status": "SUCCESS",
  "message": "Payment successful.",
  "data": {
    "depositMade": false,
    "cardToken": null,
    "lastFourDigits": null,
    "expirationMonth": null,
    "expirationYear": null,
    "paymentId": "7cbe2ee3-4324-4c0e-b2f6-4fe2920a77de",
    "extOrderId": "your order id, given on create payment"
  }
}
Parameter Description
type payment
operationId Each payment operation id is unique. you can use this value on admin panel.
status Status of operation
message Detailed message of operation
data.depositMade (bool) If false, you need to deposit manually. See marketplace documentation.
data.cardToken If customer checked the save card option, you will receive the card token to use in future.
data.lastFourDigits If customer checked the save card option, card's last for digits
data.expirationMonth If customer checked the save card option, card's expire month
data.expirationYear If customer checked the save card option, card's expire year
data.paymentId Unique payment ID
data.extOrderId Order ID at your end that provided on payment create

Deposit

You will receive deposit message when you manually deposit a payment.

$message = $oderopay->webhooks->handle(json_decode($_REQUEST, true));

/** @var  \Oderopay\Model\Webhook\Deposit $message */
$isDepositMade = $message->isDepositMade();
$operationId = $message->getOperationId();
$paymentId = $message->getPaymentId();
{
  "type": "deposit",
  "operationId": "1ed4a34f-60c8-687a-a14d-d53acf3cde4e",
  "status": "SUCCESS",
  "message": "Deposit successful.",
  "data": {
    "paymentId": "9e60d19a-62e0-4d6a-a482-922ff6f01933",
    "paymentOperationId": "1ed4a34b-79a1-6fee-ad98-e794f89a217a",
    "amount": "50.88"
  }
}
Parameter Description
type deposit
operationId Each payment operation id is unique. you can use this value on admin panel.
status Status of operation
message Detailed message of operation
data.paymentId Unique payment ID
data.paymentOperationId Operation ID of payment.
data.amount Deposited amount.

Reverse

If the payment is not deposited, you can reverse the payment.

$message = $oderopay->webhooks->handle(json_decode($_REQUEST, true));

/** @var  \Oderopay\Model\Webhook\Reverse $message */
$isDepositMade = $message->isDepositMade();
$operationId = $message->getOperationId();
$paymentId = $message->getPaymentId();
{
  "type": "reverse",
  "operationId": "1ed4a34f-60c8-687a-a14d-d53acf3cde4e",
  "status": "SUCCESS",
  "message": "Reverse successful.",
  "data": {
    "paymentId": "9e60d19a-62e0-4d6a-a482-922ff6f01933",
    "paymentOperationId": "1ed4a34b-79a1-6fee-ad98-e794f89a217a",
    "amount": "50.88"
  }
}
Parameter Description
type reverse
operationId Each reverse operation id is unique. you can use this value on admin panel.
status Status of operation
message Detailed message of operation
data.paymentId Unique payment ID
data.paymentOperationId Operation ID of payment.
data.amount Deposited amount.

Refund

If the payment is deposited, you can refund the payment.

$message = $oderopay->webhooks->handle(json_decode($_REQUEST, true));

/** @var  \Oderopay\Model\Webhook\Refund $message */
$isDepositMade = $message->isDepositMade();
$operationId = $message->getOperationId();
$paymentId = $message->getPaymentId();
{
  "type": "refund",
  "operationId": "1ed4a34f-60c8-687a-a14d-d53acf3cde4e",
  "status": "SUCCESS",
  "message": "Refund successful.",
  "data": {
    "paymentId": "9e60d19a-62e0-4d6a-a482-922ff6f01933",
    "paymentOperationId": "1ed4a34b-79a1-6fee-ad98-e794f89a217a",
    "amount": "50.88"
  }
}
Parameter Description
type refund
operationId Each refund operation id is unique. you can use this value on admin panel.
status Status of operation
message Detailed message of operation
data.paymentId Unique payment ID
data.paymentOperationId Operation ID of payment.
data.amount Refunded amount.

Stored Card

If you use stored card feature, you will receive the message as below

$message = $oderopay->webhooks->handle(json_decode($_REQUEST, true));

/** @var  \Oderopay\Model\Webhook\StoredCard $message */
$operationId = $message->getOperationId();
$cardToken = $message->getCardToken();
$cardExpirationMonth = $message->getExpirationMonth();
$cardExpirationYear = $message->getExpirationYear();
$cardLastFourDigits = $message->getLastFourDigits();
{
  "type": "stored_card",
  "operationId": "1ed4ac06-eb95-6a6a-89fb-9b562db4a5db",
  "status": "SUCCESS",
  "message": "Card saved successful.",
  "data": {
    "cardToken": "sample token",
    "lastFourDigits": "7499",
    "expirationMonth": 4,
    "expirationYear": 2023
  }
}
Parameter Description
type stored_card
operationId Each store card operation id is unique. you can use this value on admin panel.
status Status of operation
message Detailed message of operation
data.cardToken you will receive the card token to use in payment requests.
data.lastFourDigits card's last for digits
data.expirationMonth card's expire month
data.expirationYear card's expire year

Remove Card

If you want to remove stored card, you will receive the message below

$message = $oderopay->webhooks->handle(json_decode($_REQUEST, true));

/** @var  \Oderopay\Model\Webhook\StoredCard $message */
$cardToken = $message->getCardToken();
{
  "type": "remove_stored_card",
  "operationId": "1ed4ade7-c4a0-6d00-a09b-ed7e63f1aa16",
  "status": "SUCCESS",
  "message": "Remove card successful.",
  "data": {
    "storedCardToken": "7fbe06bc344424724e79703d39382dcfb542bb83217e11c2a1f465ce07268983"
  }
}
Parameter Description
type remove_stored_card
operationId Each store card operation id is unique. you can use this value on admin panel.
status Status of operation
message Detailed message of operation
data.storedCardToken you will receive the card token which has been removed

Status Codes

Webhook message status codes could be one of the below;

Code
SUCCESS
TRANSACTION_DECLINED
CANCELLED
REQUEST_IN_PROGRESS
COMPLETED_PARTIALLY
EXPIRED
INVALID_MERCHANT
INVALID_INPUT
INVALID_AMOUNT
INVALID_CARD_NUMBER
WRONG_CARD_EXPIRATION_DATE
EXPIRED_CARD
CVV_MISMATCH
INCORRECT_PIN
ALLOWABLE_NUMBER_OF_PIN_TRIES_EXCEEDED
RESTRICTED_CARD
INACTIVE_CARD
INVALID_CARD
INVALID_TRANSACTION
TRANSACTION_COULD_NOT_BE_ROUTED
TRANSACTION_LIMIT_EXCEEDED
DUPLICATE_TRANSACTION
REENTER_TRANSACTION
INSUFFICIENT_FUNDS
EXCEEDS_WITHDRAWAL_AMOUNT_LIMIT
NO_ACTION_TAKEN
ACTION_NOT_ALLOWED_FOR_PAYMENT
ALREADY_REVERSED
CARD_LOST_OR_STOLEN
SUSPECTED_FRAUD
ISSUER_DECLINED
SYSTEM_ERROR
DO_NOT_HONOR
AUTHENTICATION_FAILED_3DS2
UNHANDLED

Open Source

As Oderopay, we do support open source ecommerce and we created some extensions.

Opencart

After uploading files to your server, run this command

composer require oderopay/odero-php

Installation

After installation, run composer require oderopay/odero-php on your server where composer.json file is located.

Your extension is ready for configuration

Configuration

To access configuration list;

Parameter Description
Api Type Live or Testing (sandbox).
Merchant ID You Odero MerchantID, you can access this from Odero Merchant Admin
Token You Odero Token, you can access this from Odero Merchant Admin
Payment Value This value will be replaced on checkout page, you can use translations
Order Status The order status will be set after successful transaction
Order Refund Status The order status will be set after refund
Order Reverse Status The order status will be set after reverse
Cancel Order Status The order status will be set after cancel, or going back from payment page.
Extension Status Set if the extension is enabled or disabled.

Webhooks

After a successful installation, we generate url for webhooks. Copy the generated url from Opencart Oderopay extension page and paste to settings under Odero Merchant Admin

Woocommerce

Configuration

For Woocommerce settings please go to WooCommerce > Settings > Payments and enable OderoPay

Parameter Description
Enable/Disable If yes, customers will see the option for Oderopay on checkout page.
Title This is the title for customers to see on checkout page.
Description This is the title for customers to see on checkout page.
Merchant ID You Odero MerchantID, you can access this from Odero Merchant Admin
Merchant Token You Odero Token, you can access this from Odero Merchant Admin
Sandbox Mode For development purpose to use Odero staging environment
Merchant ID You Odero MerchantID, you can access this from Odero Merchant Admin (STG)
Merchant Token You Odero Token, you can access this from Odero Merchant Admin (STG)
Order Status You should set order statuses during the payment for your custom checkout flow
Status On Process This status is set when customer is redirected to Odero Payment Page (default on-hold)
On Payment Failed This status is set when payment is failed (default failed)
On Payment Success This status is set when payment is success. (default processing)
Secret Key A random pasphrase to secure webhook endpoint for IPN
Enable Logging Enable loging for debugging, you can access to logs on WooCommerce > Status > Logs

Test Cards

Card Number Date CVV Description
5299121035338204 05/26 063 SUCCESS

Errors

The Odero API uses the following error codes:

Error Code Meaning
400 Bad Request -- Your request is invalid.
401 Unauthorized -- Your API key is wrong.
403 Forbidden -- The value requested is hidden for administrators only.
404 Not Found -- The specified value could not be found.
405 Method Not Allowed -- You tried to access a value with an invalid method.
406 Not Acceptable -- You requested a format that isn't json.
429 Too Many Requests -- You're requesting too many values! Slow down!
500 Internal Server Error -- We had a problem with our server. Try again later.
503 Service Unavailable -- We're temporarily offline for maintenance. Please try again later.