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
composer require oderopay/odero-php
Minimum Requirements;
- PHP 7.2 or later
- curl
- json
- mbstring
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:
- Merchant ID;
- Payload (encoded as json);
- Merchant API Token.
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:
-
A small piece of backend code that creates a payment session with your secret merchant credentials and returns a
sessionIdto the app. See Merchant setup & sessions. -
The app integration: hand that
sessionIdto 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
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:
- The customer picks a method and confirms.
- The SDK creates the payment with Odero.
- 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.
- 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: processing →
success 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
| Requirement | Value |
|---|---|
| Deployment target | iOS 15.0+ |
| Swift | 5.0 |
| Distribution | CocoaPods (binary xcframework) |
| Links | PassKit (Apple Pay), C++ standard library (handled by the pod) |
| Apple Pay testing | A real device (the simulator cannot authorize) |
Android
| Requirement | Value |
|---|---|
minSdk | 23 |
compileSdk / targetSdk | 35 |
| JDK | 17 |
| Android Gradle Plugin | 8.7.3+ |
| Kotlin | 2.1.20 (don't force a newer stdlib) |
| UI | Jetpack Compose (pulled in transitively; you don't need Compose yourself) |
| Host Activity | Must be a ComponentActivity (e.g. AppCompatActivity), required for Google Pay |
| Google Pay | com.google.android.gms:play-services-wallet:20.0.0 |
React Native
| Requirement | Value |
|---|---|
| React Native | Any modern RN (built and tested on 0.81) |
| React | Any (tested on 19.x) |
| Peer dependency | react-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
| Item | What it is | Where 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 asapplePayMerchantIdand register in your app's Apple Pay entitlement.
Environments
There are two environments, selected per platform (see each SDK page):
| Environment | API host |
|---|---|
| DEV | https://api-dev.pay.odero.ro/ |
| PROD | https://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 appWhich 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.
| Method | iOS | Android | React Native | Shown when |
|---|---|---|---|---|
| Card (new card) | Yes | Yes | Yes | Always |
| Saved / stored card | Yes | Yes | Yes | You pass savedCards |
| Apple Pay | Yes | — | iOS only | Session allows apple_pay (+ wallet set up) |
| Google Pay | — | Yes | Android 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'
endThen 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(): readssdk.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 hostactivitymust be aComponentActivity(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 beforeshowPaymentSheet.
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 installBoth 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-walletand 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?)whereoptions={ appearance?, savedCards?, applePayMerchantId?, environment?, onEvent? }.createNativeWalletAdapter({ environment }): pass aswalletAdapterto 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 object | Page it themes |
|---|---|
oderoPaymentDetailsPageAppearance | The main card-entry page (largest surface) |
oderoPaymentSummaryPageAppearance | Order summary before paying |
oderoEnterCVCPageAppearance | CVC step for a selected saved card |
oderoPayWithAnotherCardPageAppearance | New-card form when saved cards are shown |
oderoPaymentProcessingPageAppearance | Processing / loading screen |
oderoPaymentResultSuccessPageAppearance | Success result page |
oderoPaymentResultFailurePageAppearance | Failure 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 endingColorHex/Color/BgHex). - Fonts: a font-family name string. Built-in:
"DMSans-Regular","DMSans-Medium","DMSans-SemiBold"(fields endingFontName). - Sizes & widths: numeric strings, e.g.
"18"(fields endingFontSize/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.
| Platform | Download | SDK 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.xcworkspaceSet 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:installDebugOr 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 androidSee 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 |
Create Payment Link
$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
- Download the latest version of extension from Github.
- Upload the all files in
uploaddirectory to your server. - Login to your admin panel and go to Extensions
- List Payment extensions and install Oderopay from the list.
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;
- Login to your admin panel and go to Extensions
- List Payment extensions and select Oderopay from the 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
- Download the latest version of extension (zip) from Github.
- Go to Wordpress Admin > Plugins > Add New
- Click Upload Plugin on top
- Select the downloaded zip file and click Install Now button
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 |
- step 1. SMS code: 12345
- step 2. clients password: Parola123$
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. |