PayPal
PayPal is self-submitting: its branded button is the Pay button. The tap creates the PayPal order, opens the approval in a browser session, and the return runs /validate, which captures.
Setup
| Product | PurseCheckoutPayPal (brings PayPal's iOS SDK) |
| Install | PluginRegistry.shared.install(PayPalModule()), once, before you build a checkout |
| 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 |
| Keys | None in the app |
| Environment | PurseEnvironment.production maps to PayPal live; every other environment (sandbox, development, test) to the PayPal sandbox |
A PayPal method configured in redirection mode is not driven by this product: 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: submit() is a no-op for a self-submitting element.
PaymentElementView(element)
.frame(height: 48) // the branded button is the submit
.task { await element.prepare() }
Creating the element costs nothing: prepare() makes no network call and element.isReady turns true immediately. /init, which creates the PayPal order, runs on the tap, so a button the shopper never uses leaves no orphan order behind. The client id is read from the session before that call, so a misconfigured entity fails before an order exists.
Outcomes
| Outcome | When |
|---|---|
completed(ValidationResult) | The shopper approved in PayPal and /validate captured. result.status is the API's verdict |
returnedFromRedirect(url:) | /validate chained a further step (rare) and the shopper came back. Confirm the payment status on your backend |
canceled | The shopper dismissed PayPal without approving. Nothing was charged; let them try again |
failed(PurseError) | /init failed, the approval could not open or PayPal reported an error, or /validate threw |
A failed /init or a PayPal SDK error is also reported as a plugin event (initFailed, clientError): the method moves into PARTNER_UI_FATAL_ERROR and stays unavailable for the session (Method states).
onRequiresAction() fires when a redirect from /validate is about to be presented. PayPal's own approval page echoed back in the /validate answer is recognised and suppressed, so the shopper is never sent to PayPal twice.
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:
try checkout.setPrimaryToken(token)
// element.hasUI == false, element.selfSubmitting == false
Button("Pay") { element.submit() }.disabled(!element.isReady)
Its element renders nothing and pays from your Pay button: submit() runs its own /init, patches the token into the payload and captures. See Saved cards and accounts. Creating such a token from the app (vaulting) is not available in this SDK today.
Troubleshooting
| Symptom | Cause |
|---|---|
failed with PARTNER_UI_ERROR, message mentions merchant_specifics.client_id | The session offers PayPal without a REST client id. Configure it in the dashboard |
failed with PARTNER_UI_ERROR, message PayPal init returned no order_id | /init did not create a PayPal order. Check the method's configuration |
failed with API_ERROR, message paypal init not successful | The /init was refused for this item |
| The button never appears, the method shows as a plain row | PayPal is in redirection mode on this entity |
The method is listed but has no UI and submitting reports METHOD_NOT_IMPLEMENTED | PayPalModule was never installed. See Payment methods |