Skip to main content

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​

ProductPurseCheckoutPayPal (brings PayPal's iOS SDK)
InstallPluginRegistry.shared.install(PayPalModule()), once, before you build a checkout
DashboardPayPal 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
KeysNone in the app
EnvironmentPurseEnvironment.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​

OutcomeWhen
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
canceledThe 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​

SymptomCause
failed with PARTNER_UI_ERROR, message mentions merchant_specifics.client_idThe 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 successfulThe /init was refused for this item
The button never appears, the method shows as a plain rowPayPal is in redirection mode on this entity
The method is listed but has no UI and submitting reports METHOD_NOT_IMPLEMENTEDPayPalModule was never installed. See Payment methods