Update Checkout Amount
How to Update the Checkout Amount After Session Creation
Change the checkout amount (and optionally currency) at runtime — for example, when the user selects a tip, applies a discount code, or when shipping costs are calculated after address entry.
Prerequisites
- An active Payrails session (see Quick Start)
- At least one payment button already created (
CardPaymentButtonorGooglePayButton) - Your backend can call the Payrails lookup action on an execution
How amount updates work
Updating the checkout amount requires two coordinated steps — one on your backend and one on the client. Both must happen before the payment is triggered, and they must agree on the same amount.
- Backend: Call the Payrails lookup action on the execution with the new amount. This authorizes the updated amount on the Payrails side.
- Client: Call
Payrails.update()with the same new amount. This tells the SDK what amount to send in the next payment request.
Important: If the amount authorized by your backend (via lookup) and the amount sent by the SDK (via
update()) do not match, Payrails will reject the payment with a401HTTP error. Always keep the two in sync.
Steps
1. Calculate the new amount in your UI logic
Compute the final amount before calling your backend. The value must be a numeric string matching the format your backend expects.
val originalAmount = 45.99
val tipPercent = 0.15
val totalAmount = originalAmount + (originalAmount * tipPercent) // 52.89
val totalAmountString = "%.2f".format(totalAmount) // "52.89"2. Call your backend to update the execution via the lookup action
Your backend must call the Payrails lookup action with the new amount before the client
updates the SDK. Pass the executionId from the active session so your backend knows
which execution to update.
// Get the execution ID from the active session
val executionId = Payrails.query(PayrailsQuery.ExecutionId)
// Call your own backend endpoint, which in turn calls the Payrails lookup action
myApiClient.updateExecutionAmount(
executionId = executionId,
amount = totalAmountString,
currency = "EUR"
)Your backend endpoint should call the Payrails lookup action:
POST /v1/merchant/executions/{executionId}/lookup
{
"amount": {
"value": "52.89",
"currency": "EUR"
}
}
Wait for your backend to confirm success before proceeding to step 3.
3. Call Payrails.update() with the same new amount
Payrails.update() with the same new amountOnce your backend has successfully updated the execution, update the SDK with the matching amount. The value and currency must be identical to what was sent to the backend.
Payrails.update(
PayrailsUpdate(
amount = AmountUpdate(
value = totalAmountString,
currency = "EUR"
)
)
)update() is synchronous and requires no suspend context.
4. Trigger payment as normal
The updated amount is applied automatically on the next pay() call. No additional
configuration is required.
cardPaymentButton.pay()Full example — tip selection before payment
@Composable
fun CheckoutScreen(myApiClient: MyApiClient) {
val scope = rememberCoroutineScope()
var selectedTipPercent by remember { mutableStateOf(0.0) }
var isUpdatingAmount by remember { mutableStateOf(false) }
val baseAmount = 45.99
val executionId = remember { Payrails.query(PayrailsQuery.ExecutionId) }
val cardPaymentButton = remember {
Payrails.createCardPaymentButton(
translations = CardPaymenButtonTranslations(label = "Pay")
)
}
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
// Tip selector
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
listOf(0.0, 0.10, 0.15, 0.20).forEach { tip ->
Button(
enabled = !isUpdatingAmount,
onClick = {
scope.launch {
isUpdatingAmount = true
val total = baseAmount + (baseAmount * tip)
val totalString = "%.2f".format(total)
// Step 1: update the execution on your backend first
myApiClient.updateExecutionAmount(
executionId = executionId,
amount = totalString,
currency = "EUR"
)
// Step 2: update the SDK with the same amount
Payrails.update(
PayrailsUpdate(
amount = AmountUpdate(
value = totalString,
currency = "EUR"
)
)
)
selectedTipPercent = tip
isUpdatingAmount = false
}
}
) {
Text(if (tip == 0.0) "No tip" else "${(tip * 100).toInt()}%")
}
}
}
cardPaymentButton.Render()
}
}Important: update() is reset on redirect session recovery
If the session is refreshed during a redirect flow (via onSessionExpired), the amount
override is cleared and the value from the new session init payload takes effect. If your
checkout flow involves redirects, re-call Payrails.update() after session recovery if a
merchant-side amount override is still needed.
Troubleshooting
Problem: Payment fails with a 401 HTTP error after calling update()
Solution: The amount set on the SDK and the amount authorized on the backend via the
lookup action do not match. Ensure both use exactly the same value and currency
strings, and that your backend lookup call succeeded before Payrails.update() was
called.
Problem: IllegalStateException — "No active Payrails session"
Solution: Payrails.update() was called before createSession() completed. Ensure
the session is fully initialized before calling update().
Problem: IllegalArgumentException — "AmountUpdate.value must be a valid positive number"
Solution: The value string is not a valid positive number (it may be empty, zero,
negative, or contain non-numeric characters). Validate your amount calculation before
passing it to AmountUpdate.
See also
Payrails.update()API Reference — full parameter and exception referencePayrailsUpdateAPI Reference — extensible update containerAmountUpdateAPI Reference — amount and currency fields
Updated about 3 hours ago