Skip to content

Android SDK Integration

The xMoney Android SDK provides a drop-in Payment Sheet, an embeddable Payment Element, and standalone Google Pay. All surfaces use PaymentConfig, PaymentIntent, and PaymentResult.

Current release: 1.0.0.

Requirements

  • Android Gradle Plugin 8.5 or later
  • Kotlin 2.0 or later
  • Java 17
  • minSdk 23 and compileSdk 36
  • A host screen based on FragmentActivity
  • A publishable xMoney key and a backend that signs orders
Do not ship a secret key

Store the xMoney private key on your backend. The app should receive only the publishable key, order payload, and checksum.

1. Install the SDK

Make sure mavenCentral() is configured, then add Payment Sheet:

dependencies {
    implementation("com.xmoney:paymentsheet:1.0.0")
}

Payment Sheet brings in Payment Element and Google Pay at runtime. To choose surfaces separately:

dependencies {
    implementation("com.xmoney:paymentelement:1.0.0")
    implementation("com.xmoney:googlepay:1.0.0")
}

Payment Element does not include the Google Pay module automatically.

2. Create a PaymentIntent

Your backend creates and signs the order. Build an intent from the returned values:

val intent = PaymentIntent(
    OrderPayload(payloadFromYourServer),
    OrderChecksum(checksumFromYourServer),
)

See Create the order on your backend for the shared server responsibilities.

3. Choose a payment surface

Payment Sheet

Payment Sheet owns the complete checkout UI and Pay button.

val sheet = rememberPaymentSheet(
    configuration = PaymentConfig(
        publicKey = "pk_test_...",
        paymentMethods = PaymentMethodsConfig(
            googlePay = GooglePayConfig(enabled = true),
        ),
        card = CardConfig(
            savedCards = SavedCardsConfig(enabled = true),
        ),
    ),
    onResult = ::handlePaymentResult,
)

sheet.present(intent) { event ->
    if (event is PaymentSheetEvent.Ready) {
        hideCheckoutLoader()
    }
}

Idle close, back, drag, or scrim dismissal returns Canceled without consuming the order. Once payment or 3DS starts, terminal results consume the signed order.

Payment Element

Payment Element places the form in your Compose layout. Keep it composed while it binds the order.

var ready by remember { mutableStateOf(false) }

val embedded = rememberEmbeddedPayment(
    configuration = configuration,
    onResult = ::handlePaymentResult,
)

if (!ready) {
    CheckoutLoader()
}

PaymentElement(
    controller = embedded,
    intent = intent,
    modifier = Modifier.padding(horizontal = 20.dp),
    onEvent = { event ->
        if (event is EmbeddedEvent.Ready) {
            ready = true
        }
    },
)

Payment Element supplies no outer page padding or background. Keep it mounted, and apply spacing and surrounding layout in your app.

To use your own Pay button, hide the SDK button and call confirm() after Ready:

val configuration = PaymentConfig(
    publicKey = "pk_test_...",
    card = CardConfig(
        submitButton = SubmitButtonConfig(visible = false),
    ),
)

Button(
    onClick = { embedded.confirm() },
    enabled = embedded.isInteractionEnabled,
) {
    Text("Pay")
}

Standalone Google Pay

val googlePay = rememberGooglePay(
    configuration = configuration,
    onResult = ::handlePaymentResult,
)

GooglePayButton(
    controller = googlePay,
    intent = intent,
    onEvent = { event ->
        if (event is GooglePayEvent.Ready) {
            hideWalletLoader()
        }
    },
)

Use isAvailable, isReady, isInteractionEnabled, and isOrderConsumed to control surrounding wallet UI. For Activity integrations, call GooglePay.availability(activity, intent) before present.

A wallet dismissal before authorization returns Canceled and does not consume the intent.

See Google Pay for activation and integration options.

Handle PaymentResult

fun handlePaymentResult(result: PaymentResult) {
    when (result) {
        is PaymentResult.Complete -> confirmOrderOnBackend(result.transaction)
        is PaymentResult.Failed -> showPaymentError(result.error.message)
        PaymentResult.Canceled -> showCheckout()
    }
}

Use the result to update the app, then confirm final payment status on your backend before fulfillment.

Update a mounted Payment Element

When the amount, currency, or cart changes, request a new signed order and rebind the mounted element:

embedded.updateOrder(
    PaymentIntent(
        OrderPayload(nextPayload),
        OrderChecksum(nextChecksum),
    ),
)

Changing the intent passed to PaymentElement also updates the order. Do not unmount the element for a loader. isInteractionEnabled remains false until the new intent is ready.

For live styling changes, keep the controller and call updateAppearance, updateStyle, updateLocale, or updateWalletAppearance.

Configuration

PaymentConfig controls saved cards, card validation, field grouping, submit-button intent, locale, light/dark style, colors, and radii.

val configuration = PaymentConfig(
    publicKey = "pk_test_...",
    card = CardConfig(
        savedCards = SavedCardsConfig(
            enabled = true,
            optInVisible = true,
        ),
        validationMode = ValidationMode.ON_TOUCHED,
        inputs = CardInputsConfig(
            grouping = CardGrouping.CONDENSED,
        ),
    ),
    options = OptionsConfig(
        locale = "en-US",
        style = UserInterfaceStyle.AUTOMATIC,
    ),
)

Supported UI languages are English (en), Greek (el), Romanian (ro), Bulgarian (bg), Hungarian (hu), and Polish (pl). Region tags such as pl-PL are supported.

For cardholder name verification, provide CardHolderVerification and return true to continue:

card = CardConfig(
    cardHolderVerification = CardHolderVerification(
        name = CardHolderName(
            firstName = "John",
            lastName = "Doe",
        ),
        onCardHolderVerification = { result ->
            result.status == CardHolderMatchStatus.MATCHED
        },
    ),
)

The site must have cardholder name verification enabled.

Next steps