Skip to main content

Android App Integration

Accept NFC payments from your Android app using the MONEI Pay Android SDK. The SDK handles intent launching, result parsing, and error handling.

Source: github.com/MONEI/monei-pay-android-sdk

No special signing needed

Uses standard Android intents — no special signing, entitlements, or platform approvals required.

Payment Modes

The SDK supports two modes:

  • Direct (PaymentMode.DIRECT) — Launches CloudCommerce directly. MONEI Pay is not required.
  • Via MONEI Pay (PaymentMode.VIA_MONEI_PAY) — Launches MONEI Pay, which handles the NFC payment.
DirectVia MONEI Pay
POS auth tokenRequiredRequired
MONEI Pay appNot neededRequired
CloudCommerce appRequiredNot required
Best forCloudCommerce-only setupsFull MONEI Pay merchant flow

Download Required Apps

MONEI Pay (Via MONEI Pay mode)

Get MONEI Pay on Google Play

CloudCommerce (Direct mode)

Get CloudCommerce on Google Play

Prerequisites

Integration

1. Add the Dependency

Requires a PAT with read:packages (or GITHUB_TOKEN in CI):

gradle.properties
gpr.user=YOUR_GITHUB_USERNAME
gpr.key=YOUR_GITHUB_TOKEN
settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/MONEI/monei-pay-android-sdk")
credentials {
username = providers.gradleProperty("gpr.user")
.orElse(providers.environmentVariable("GITHUB_ACTOR"))
.get()
password = providers.gradleProperty("gpr.key")
.orElse(providers.environmentVariable("GITHUB_TOKEN"))
.get()
}
}
}
}
app/build.gradle.kts
dependencies {
implementation("com.monei:monei-pay-sdk:1.0.0")
}

2. Configure AndroidManifest

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<queries>
<intent>
<action android:name="com.monei.pay.ACCEPT_PAYMENT" />
</intent>
<package android:name="com.mastercard.cpos" />
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="cloud_payment" />
</intent>
</queries>

</manifest>

3. Accept a Payment

import com.monei.pay.sdk.MoneiPay
import com.monei.pay.sdk.PaymentMode
import com.monei.pay.sdk.MoneiPayException

try {
val result = MoneiPay.acceptPayment(
context = this,
token = "eyJ...",
amount = 1500,
description = "Order #123",
customerName = "John Doe",
customerEmail = "john@example.com",
customerPhone = "+34600000000",
callbackUrl = "https://merchant.com/webhook/monei",
mode = PaymentMode.DIRECT
)

if (result.success) {
println("Payment approved: ${result.transactionId}")
println("Card: ${result.cardBrand} ${result.maskedCardNumber}")
}
} catch (e: MoneiPayException.CloudCommerceNotInstalled) {
// Prompt user to install CloudCommerce
} catch (e: MoneiPayException.PaymentCancelled) {
// User cancelled
} catch (e: MoneiPayException.InvalidToken) {
// Token expired or invalid
} catch (e: MoneiPayException.PaymentFailed) {
println("Payment failed: ${e.reason}")
} catch (e: MoneiPayException) {
println("Error: ${e.message}")
}
Trust model

callbackUrl is a trusted, signed webhook delivered server-to-server — use it to fulfill orders. The SDK's sync PaymentResult is also trustworthy because it comes back through the Android intent system from the originating payment app, but it should still be reconciled via the webhook or Get Payment for record-keeping. See Result Delivery.

SDK Reference

MoneiPay.acceptPayment(...)

Suspending function — call from a coroutine scope.

ParameterTypeRequiredDescription
contextContextYesActivity or Application context
tokenStringYesRaw JWT auth token (no "Bearer " prefix)
amountIntYesAmount in cents
descriptionString?NoPayment description
customerNameString?NoCustomer name
customerEmailString?NoCustomer email
customerPhoneString?NoCustomer phone
callbackUrlString?NoTrusted signed webhook URL. Must be strict https://, max 2048 chars. See Result Delivery.
orderIdString?NoYour merchant order reference. Surfaced in the webhook callback for reconciliation. Max 2048 chars. If omitted, the SDK generates one.
transactionTypeString?NoOptional transaction type: SALE (default), AUTH, REFUND, CAPTURE, CANCEL, PAYOUT, VERIF. Server-validated.
modePaymentModeNoDIRECT (default) or VIA_MONEI_PAY

PaymentResult

PropertyTypeDescription
transactionIdStringMONEI transaction ID
successBooleanWhether the payment was approved
amountInt?Payment amount in cents
cardBrandString?Card brand (e.g. visa, mastercard)
maskedCardNumberString?Masked card number (e.g. ****1234)

Error Types

ExceptionDescription
MoneiPayNotInstalledMONEI Pay not installed (VIA_MONEI_PAY mode)
CloudCommerceNotInstalledCloudCommerce not installed (DIRECT mode)
PaymentInProgressAnother payment is already active
PaymentCancelledUser cancelled
PaymentFailedPayment declined or failed (has reason)
InvalidParametersInvalid input (e.g. non-positive amount)
InvalidTokenAuth token invalid or expired

Manual Integration (Intent)

For integration without the SDK using Android intents directly.

Show manual intent integration

Configure Your Manifest

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="com.monei.pay.permission.ACCEPT_PAYMENT" />

<queries>
<intent>
<action android:name="com.monei.pay.ACCEPT_PAYMENT" />
</intent>
</queries>

</manifest>

Launch the Payment Intent

class MainActivity : AppCompatActivity() {

private val paymentLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.resultCode) {
RESULT_OK -> handleSuccess(result.data)
RESULT_CANCELED -> handleError(result.data)
}
}

fun acceptPayment(amountInCents: Int) {
val intent = Intent("com.monei.pay.ACCEPT_PAYMENT").apply {
setPackage("com.monei.moneibusiness")
putExtra("amount_cents", amountInCents)
putExtra("auth_token", "eyJ...")
}

if (intent.resolveActivity(packageManager) == null) {
return
}

paymentLauncher.launch(intent)
}
}

Handle the Result

private fun handleSuccess(data: Intent?) {
val success = data?.getBooleanExtra("success", false) ?: false
val transactionId = data?.getStringExtra("transaction_id")
val amount = data?.getIntExtra("amount", 0)
val cardBrand = data?.getStringExtra("card_brand")
val maskedCard = data?.getStringExtra("masked_card_number")
}

private fun handleError(data: Intent?) {
val errorCode = data?.getStringExtra("error_code") ?: "UNKNOWN"
val errorMessage = data?.getStringExtra("error_message") ?: "Payment failed"
}

Intent Parameters

Request Extras

ExtraTypeRequiredDescription
amount_centsIntYesPayment amount in cents (e.g. 1500 = 15.00 EUR)
auth_tokenStringYesPOS auth token (raw JWT)
descriptionStringNoPayment description
customer_nameStringNoCustomer name
customer_emailStringNoCustomer email
customer_phoneStringNoCustomer phone

Result Extras (RESULT_OK)

ExtraTypeDescription
transaction_idStringMONEI transaction ID
successBooleantrue if approved, false if declined
amountIntPayment amount in cents
card_brandStringCard brand (e.g. visa, mastercard)
masked_card_numberStringMasked card number (e.g. ****1234)

Result Extras (RESULT_CANCELED)

ExtraTypeDescription
error_codeStringError code (e.g. PAYMENT_FAILED, NOT_AUTHENTICATED)
error_messageStringHuman-readable error description

For non-Android callers — including web apps (browser-based merchant terminals) — MONEI Pay accepts a deep link with the same shape as the iOS URL scheme. See Web App Integration for the full browser-driven flow.

Show manual deep-link integration

Open the Payment URL

<a
href="monei-pay://accept-payment
?amount=1500
&auth_token=eyJ...
&callback_url=https://merchant.com/webhook/monei
&complete_url=https://merchant.com/checkout/done"
>
Pay with MONEI Pay
</a>

Or programmatically:

window.location.href =
'monei-pay://accept-payment' +
'?amount=1500' +
'&auth_token=' +
encodeURIComponent(token) +
'&callback_url=' +
encodeURIComponent('https://merchant.com/webhook/monei') +
'&complete_url=' +
encodeURIComponent('https://merchant.com/checkout/done');

Android resolves monei-pay:// via the installed MONEI Pay app. The user taps the link (or the browser opens it), MONEI Pay handles the NFC payment, then redirects to complete_url with the result.

URL Parameters

ParameterTypeRequiredDescription
amountIntegerYesPayment amount in cents (e.g. 1500 = 15.00 EUR)
auth_tokenStringYesPOS auth token (raw JWT)
callback_urlStringNoTrusted signed webhook URL. Must be strict https://, max 2048 chars. Receives a signed POST with MONEI-Signature HMAC on completion.
complete_urlStringNoURL opened on the device after the payment completes. Accepts https://, custom URL schemes, App Links. Max 2048 chars. Not signed.
descriptionStringNoPayment description
customer_nameStringNoCustomer name
customer_emailStringNoCustomer email
customer_phoneStringNoCustomer phone
order_idStringNoMerchant order reference. Surfaced in the webhook callback for reconciliation. Max 2048 chars. If omitted, MONEI generates one.
transaction_typeStringNoOptional transaction type: SALE, AUTH, REFUND, CAPTURE, CANCEL, PAYOUT, VERIF. Server-validated.

complete_url Redirect Parameters (Success)

ParameterTypeDescription
successString"true"
transaction_idStringMONEI transaction ID
amountStringPayment amount in cents
card_brandStringCard brand (e.g. visa, mastercard)
masked_card_numberStringMasked card number (e.g. ****1234)

complete_url Redirect Parameters (Error)

ParameterTypeDescription
successString"false"
errorStringError code — see error code list
Trust model

The query parameters on the complete_url redirect are not signed — they can be spoofed. Use callback_url (signed webhook) or Get Payment to confirm the actual outcome before fulfilling an order. See Result Delivery.

Example App

The examples/merchant-demo/ directory contains a minimal app demonstrating the full flow. Run ./gradlew assembleDebug and install on an NFC-capable device. The project uses includeBuild to reference the SDK locally, so SDK changes apply immediately.

Troubleshooting

CloudCommerceNotInstalled

CloudCommerce is a separate app from MONEI Pay. Install from Google Play and verify your manifest includes the <package> and cloud_payment:// scheme in <queries>.

MoneiPayNotInstalled

Install MONEI Pay from Google Play. Verify your manifest includes the com.monei.pay.ACCEPT_PAYMENT action in <queries>.

InvalidToken

The POS auth token has a 24h lifetime. Generate a fresh token from your backend. Pass the raw JWT string — not "Bearer ...".

Payment fails on API 30+

Android 11+ requires <queries> for package visibility. Ensure your manifest has the full queries block from Configure AndroidManifest.

Dependency resolution fails

  • JitPack: Add google(), mavenCentral(), and https://jitpack.io to settings.gradle.kts under dependencyResolutionManagement.
  • GitHub Packages: Check gpr.user / gpr.key in gradle.properties and the maven.pkg.github.com/MONEI/monei-pay-android-sdk repository block.