PayPal
PayPal is self-submitting: its branded button is the Pay button. The tap creates the PayPal order, opens the approval in the PayPal app or a Custom Tab, and the return runs /validate, which captures.
Setup
| Artifact | eu.purse:headless-checkout-paypal (brings the PayPal Android SDK) |
| Dashboard | PayPal wallet activated on the entity, in popup integration mode, with the merchant's PayPal REST client id. The client id reaches the app inside the client session |
| Manifest | Nothing. The return Activity and its deep link, scheme ${applicationId}.paypal, ship in the artifact |
| Keys | None in the app |
| Environment | PurseEnvironment.PRODUCTION maps to PayPal live; every other environment to the PayPal sandbox |
// app/build.gradle.kts
dependencies {
implementation("eu.purse:headless-checkout-paypal:${SDK_VERSION}")
}
A PayPal method configured in redirection mode is not driven by this artifact: it renders as a row and pays through the PSP page, see Redirection methods.
Render
Render the element as soon as the method is listed. Never call setPrimary for it, and never place a Pay button of yours next to it.
val element = rememberPaymentElement(checkout, paypalMethod, listener)
PaymentElementUi(element, Modifier.fillMaxWidth().height(56.dp)) // the branded button is the submit
Creating the element costs nothing: /init, which creates the PayPal order, runs on the tap, so a button the shopper never uses leaves no orphan order behind. element.ready is true immediately.
Outcomes
| Outcome | When |
|---|---|
Completed | The shopper approved in PayPal and /validate captured. status is the API's verdict |
ReturnedFromRedirect | /validate chained a further step (rare) and the shopper came back. Not an outcome. Show a processing state; your backend confirms the payment |
Canceled | The shopper dismissed PayPal without approving |
Failed | /init failed, the approval could not open, or /validate threw |
onRequiresAction() fires when the approval is being opened: a good moment to show that the shopper is leaving the app.
Saved PayPal accounts
A PayPal account the shopper vaulted earlier arrives on paymentTokens as a PaymentToken with isVaultedCard == false. It has nothing to approve and no CVV: select it with setPrimaryToken, its element has no UI, and submit() captures directly. See Saved cards and accounts. Creating such a token from the app (vaulting) is not available in this SDK today.
PayPal Pay Later (BNPL) and vaulting are not built. A BNPL method configured in redirection mode works through the redirection guide.
Troubleshooting
| Symptom | Cause |
|---|---|
Failed with API_ERROR, message mentions merchant_specifics.client_id | The session offers PayPal without a REST client id. Configure it in the dashboard |
Failed with API_ERROR, message mentions no order_id | /init did not create a PayPal order. Check the method's configuration |
Canceled right after PayPal opens | The shopper dismissed the Custom Tab or the PayPal app |
| PayPal opens but never returns to the app | The artifact's return Activity was overridden in your manifest. Remove the override |
| The button never appears, the method shows as a row | PayPal is in redirection mode on this entity |
The method is listed but has no UI, submit() fails with METHOD_NOT_IMPLEMENTED | The -paypal artifact is missing. See Payment methods |