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
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.
| Direct | Via MONEI Pay | |
|---|---|---|
| POS auth token | Required | Required |
| MONEI Pay app | Not needed | Required |
| CloudCommerce app | Required | Not required |
| Best for | CloudCommerce-only setups | Full MONEI Pay merchant flow |
Download Required Apps
Prerequisites
- MONEI account
- Android 8.0+ (API 26), NFC-capable device
- POS auth token from your backend — see Getting Started
- Direct mode: CloudCommerce installed
- Via MONEI Pay mode: MONEI Pay installed
Integration
1. Add the Dependency
- GitHub Packages (recommended)
- JitPack (no auth)
Requires a PAT with read:packages (or GITHUB_TOKEN in CI):
gpr.user=YOUR_GITHUB_USERNAME
gpr.key=YOUR_GITHUB_TOKEN
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()
}
}
}
}
dependencies {
implementation("com.monei:monei-pay-sdk:1.0.0")
}
2. Configure AndroidManifest
<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
- Direct Mode
- Via MONEI Pay
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}")
}
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.VIA_MONEI_PAY
)
if (result.success) {
println("Payment approved: ${result.transactionId}")
println("Card: ${result.cardBrand} ${result.maskedCardNumber}")
}
} catch (e: MoneiPayException.MoneiPayNotInstalled) {
// Prompt user to install MONEI Pay
} catch (e: MoneiPayException.PaymentCancelled) {
// User cancelled
} catch (e: MoneiPayException.InvalidToken) {
// Token expired or invalid
} catch (e: MoneiPayException) {
println("Payment failed: ${e.message}")
}
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.
| Parameter | Type | Required | Description |
|---|---|---|---|
context | Context | Yes | Activity or Application context |
token | String | Yes | Raw JWT auth token (no "Bearer " prefix) |
amount | Int | Yes | Amount in cents |
description | String? | No | Payment description |
customerName | String? | No | Customer name |
customerEmail | String? | No | Customer email |
customerPhone | String? | No | Customer phone |
callbackUrl | String? | No | Trusted signed webhook URL. Must be strict https://, max 2048 chars. See Result Delivery. |
orderId | String? | No | Your merchant order reference. Surfaced in the webhook callback for reconciliation. Max 2048 chars. If omitted, the SDK generates one. |
transactionType | String? | No | Optional transaction type: SALE (default), AUTH, REFUND, CAPTURE, CANCEL, PAYOUT, VERIF. Server-validated. |
mode | PaymentMode | No | DIRECT (default) or VIA_MONEI_PAY |
PaymentResult
| Property | Type | Description |
|---|---|---|
transactionId | String | MONEI transaction ID |
success | Boolean | Whether the payment was approved |
amount | Int? | Payment amount in cents |
cardBrand | String? | Card brand (e.g. visa, mastercard) |
maskedCardNumber | String? | Masked card number (e.g. ****1234) |
Error Types
| Exception | Description |
|---|---|
MoneiPayNotInstalled | MONEI Pay not installed (VIA_MONEI_PAY mode) |
CloudCommerceNotInstalled | CloudCommerce not installed (DIRECT mode) |
PaymentInProgress | Another payment is already active |
PaymentCancelled | User cancelled |
PaymentFailed | Payment declined or failed (has reason) |
InvalidParameters | Invalid input (e.g. non-positive amount) |
InvalidToken | Auth token invalid or expired |
Manual Integration (Intent)
For integration without the SDK using Android intents directly.
Show manual intent integration
Configure Your Manifest
<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
| Extra | Type | Required | Description |
|---|---|---|---|
amount_cents | Int | Yes | Payment amount in cents (e.g. 1500 = 15.00 EUR) |
auth_token | String | Yes | POS auth token (raw JWT) |
description | String | No | Payment description |
customer_name | String | No | Customer name |
customer_email | String | No | Customer email |
customer_phone | String | No | Customer phone |
Result Extras (RESULT_OK)
| Extra | Type | Description |
|---|---|---|
transaction_id | String | MONEI transaction ID |
success | Boolean | true if approved, false if declined |
amount | Int | Payment amount in cents |
card_brand | String | Card brand (e.g. visa, mastercard) |
masked_card_number | String | Masked card number (e.g. ****1234) |
Result Extras (RESULT_CANCELED)
| Extra | Type | Description |
|---|---|---|
error_code | String | Error code (e.g. PAYMENT_FAILED, NOT_AUTHENTICATED) |
error_message | String | Human-readable error description |
Manual Integration (Deep Link)
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
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | Integer | Yes | Payment amount in cents (e.g. 1500 = 15.00 EUR) |
auth_token | String | Yes | POS auth token (raw JWT) |
callback_url | String | No | Trusted signed webhook URL. Must be strict https://, max 2048 chars. Receives a signed POST with MONEI-Signature HMAC on completion. |
complete_url | String | No | URL opened on the device after the payment completes. Accepts https://, custom URL schemes, App Links. Max 2048 chars. Not signed. |
description | String | No | Payment description |
customer_name | String | No | Customer name |
customer_email | String | No | Customer email |
customer_phone | String | No | Customer phone |
order_id | String | No | Merchant order reference. Surfaced in the webhook callback for reconciliation. Max 2048 chars. If omitted, MONEI generates one. |
transaction_type | String | No | Optional transaction type: SALE, AUTH, REFUND, CAPTURE, CANCEL, PAYOUT, VERIF. Server-validated. |
complete_url Redirect Parameters (Success)
| Parameter | Type | Description |
|---|---|---|
success | String | "true" |
transaction_id | String | MONEI transaction ID |
amount | String | Payment amount in cents |
card_brand | String | Card brand (e.g. visa, mastercard) |
masked_card_number | String | Masked card number (e.g. ****1234) |
complete_url Redirect Parameters (Error)
| Parameter | Type | Description |
|---|---|---|
success | String | "false" |
error | String | Error code — see error code list |
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(), andhttps://jitpack.iotosettings.gradle.ktsunderdependencyResolutionManagement. - GitHub Packages: Check
gpr.user/gpr.keyingradle.propertiesand themaven.pkg.github.com/MONEI/monei-pay-android-sdkrepository block.