ArtisanPack UI
DocsEcommerce PaypalConfiguration

Configuration

Ecommerce Paypal v1.0.0

publishes the config to . You can also publish it with . It loads under the runtime key : Every key has a default, so you only need the keys you change...

php artisan ecommerce-paypal:install publishes the config to config/artisanpack/ecommerce-paypal.php. You can also publish it with php artisan vendor:publish --tag=ecommerce-paypal-config. It loads under the runtime key artisanpack.ecommerce-paypal:

config( 'artisanpack.ecommerce-paypal.methods.card.enabled' ); // false

Every key has a default, so you only need the keys you change. Most keys are also store settings the admin can change without touching config; a value saved there overrides the config value.

Core

Key Env Default What it controls
enabled PAYPAL_ENABLED false Register the PayPal gateways. They register only when this is true and both client_id and client_secret are set.
mode PAYPAL_MODE 'sandbox' sandbox or live. Picks the API host (api-m.sandbox.paypal.com or api-m.paypal.com), the web host, the webhook certificate host, and the dashboard links. An unknown value falls back to sandbox.
client_id PAYPAL_CLIENT_ID null The REST app's client id. Public: the JS SDK loads with it.
client_secret PAYPAL_CLIENT_SECRET null The REST app's secret. Never leaves the server.
webhook_id PAYPAL_WEBHOOK_ID null The id ecommerce-paypal:webhooks:register prints. Comma-separate several ids during a rotation; a delivery signed for any of them is accepted.
webhook_verification PAYPAL_WEBHOOK_VERIFICATION 'local' local checks the signature against PayPal's certificate with no API call. postback asks PayPal's verify-webhook-signature endpoint instead. Any other value means local. See Webhooks.
webhook_tolerance_seconds PAYPAL_WEBHOOK_TOLERANCE_SECONDS 259200 (3 days) How far a webhook's transmission time may be from now before it is refused as stale_transmission. The default is PayPal's redelivery horizon, so PayPal's retries aren't refused. 0 turns the check off; a non-numeric value means the default. See Webhooks.
intent PAYPAL_INTENT 'capture' capture or authorize. See Intent.
brand_name PAYPAL_BRAND_NAME null The name on PayPal's pages and the Apple Pay sheet. Empty falls back to the engine's store name (artisanpack.ecommerce.notifications.store_name), then app.name.
soft_descriptor PAYPAL_SOFT_DESCRIPTOR null Text on the buyer's card statement, 22 characters at most. Empty sends none.

Payment methods

Each method is its own checkout choice and its own gateway. See Payment Methods.

Key Env Default What it controls
methods.paypal.enabled true Offer the PayPal buttons (gateway paypal).
methods.paypal.pay_later true Offer Pay Later in the buttons where the buyer is eligible. Also required for Pay Later messages.
methods.paypal.venmo true Offer Venmo in the buttons where the buyer is eligible (US, USD).
methods.paypal.button [ 'layout' => 'vertical', 'color' => 'gold', 'shape' => 'rect', 'label' => 'paypal' ] The buttons' style, passed to PayPal's Buttons( { style } ). Only layout, color, shape, label, height, and tagline are passed.
methods.card.enabled false Offer card fields hosted by PayPal (gateway paypal-card). While it is on, the PayPal buttons hide their own card button.
methods.card.three_d_secure 'SCA_WHEN_REQUIRED' SCA_WHEN_REQUIRED runs 3-D Secure when the issuer or regulation asks for it; SCA_ALWAYS runs it on every card payment.
methods.card.require_liability_shift true Refuse card, Apple Pay, and Google Pay payments whose 3-D Secure result is NO or UNKNOWN. With SCA_ALWAYS, a new card paid through card fields with no 3-D Secure result is refused too. See Card Fields.
methods.apple_pay.enabled false Offer Apple Pay (gateway paypal-apple-pay).
methods.apple_pay.domain_association PAYPAL_APPLE_PAY_DOMAIN_ASSOCIATION null Apple's domain association file: its contents, an absolute path to it, or an HTTPS URL. Empty uses storage/app/ecommerce-paypal/apple-developer-merchantid-domain-association. See Apple Pay.
methods.apple_pay.serve_domain_association PAYPAL_APPLE_PAY_SERVE_DOMAIN_ASSOCIATION true Register the /.well-known/apple-developer-merchantid-domain-association route. Turn it off when your web server serves the file.
methods.google_pay.enabled false Offer Google Pay (gateway paypal-google-pay).

Pay Later messaging

Where PayPal's Pay Later messages may render. They also need the paypal method and its pay_later option on. See Pay Later Messaging.

Key Default Placement
pay_later_messaging.checkout true payment (and checkout)
pay_later_messaging.cart false cart
pay_later_messaging.product false product

Vault

Key Env Default What it controls
vault.enabled false Let signed-in shoppers save PayPal and cards, and show the saved payment methods account page. Needs the paypal or card method on. See Saved Payment Methods.
vault.prune_after_days PAYPAL_VAULT_PRUNE_AFTER_DAYS 30 How many days a removed saved method's row (its display data) is kept before the daily ecommerce-paypal:prune-vault command deletes it; 0 deletes them at the next run, and a value that isn't a number falls back to 30. See Saved Payment Methods.

HTTP

Key Default What it controls
http.timeout 15 Request timeout in seconds.
http.connect_timeout 5 Connection timeout in seconds.
http.retries 2 How many times a request that timed out, or got a 429 or 5xx, is retried. Retries back off exponentially (from 250 ms, capped at 8 s, honoring Retry-After) and reuse the same PayPal-Request-Id, so a retry never moves money twice. Other 4xx responses are never retried. TLS verification is always on.

Logging

Key Env Default What it controls
logging.channel PAYPAL_LOG_CHANNEL null The log channel. null uses the default channel.
logging.debug PAYPAL_LOG_DEBUG false Also log every request and response body, redacted. Failed calls are always logged with PayPal's debug id. See Troubleshooting.

Intent: capture or authorize

capture (default) authorize
At finalize The approved PayPal Order is captured (POST /v2/checkout/orders/{id}/capture). The approved Order is authorized (/authorize), then the authorization is captured with final_capture: true.
Stored on the order The capture id The authorization id and status, then the capture id
voidPendingPayment() A no-op: nothing is authorized Voids an open authorization; throws if it was already captured (refund it instead)
The SDK Loads with intent=capture Loads with intent=authorize

The engine's fraud check runs on the approved session before capturePayment() is called, in both intents, so a blocked payment is never captured; an approved Order that is never captured expires at PayPal on its own. In this release, authorize still captures in the same finalize step once the authorization succeeds. An authorization stays open only when its capture doesn't complete then: PayPal returned it as PENDING, or the capture failed with a retryable error and the order was left pending for a retry.

Honor period. PayPal guarantees an authorization's funds for 3 days and keeps it valid for 29. For authorizations left open, ecommerce-paypal:authorizations:reauthorize runs daily (scheduled by the package, withoutOverlapping()):

  • once the 3-day honor period is over, it reauthorizes the authorization once (PayPal allows one reauthorization) while it is still valid, and adds an order note;
  • 2 days before the authorization expires, it fires ap.ecommerce.paypal.authorizationExpiring (Order $order, CarbonImmutable $expiresAt) once, so you can capture it or contact the shopper.

Progress is kept on orders.meta.paypal.authorization (reauthorized_at, expiry_notified_at), so running the command twice on the same day changes nothing. It exits non-zero when a reauthorization fails, and does nothing while PayPal is disabled. Make sure the Laravel scheduler runs.

Store settings

The package adds a PayPal group to the engine's settings registry (shown on the admin-livewire settings screen). Each setting overlays its config key, so a saved value wins over the config file and the environment.

Setting key Config key Type
paypal.enabled enabled boolean
paypal.mode mode select: sandbox, live
paypal.client_id client_id string (max 255)
paypal.intent intent select: capture, authorize
paypal.brand_name brand_name string (max 127)
paypal.soft_descriptor soft_descriptor string (max 22)
paypal.methods.paypal.enabled methods.paypal.enabled boolean
paypal.methods.paypal.pay_later methods.paypal.pay_later boolean
paypal.methods.paypal.venmo methods.paypal.venmo boolean
paypal.methods.card.enabled methods.card.enabled boolean
paypal.methods.apple_pay.enabled methods.apple_pay.enabled boolean
paypal.methods.apple_pay.domain_association methods.apple_pay.domain_association text
paypal.methods.google_pay.enabled methods.google_pay.enabled boolean
paypal.pay_later_messaging.checkout pay_later_messaging.checkout boolean
paypal.pay_later_messaging.cart pay_later_messaging.cart boolean
paypal.pay_later_messaging.product pay_later_messaging.product boolean
paypal.vault.enabled vault.enabled boolean

Config-only keys (no setting): webhook_verification, webhook_tolerance_seconds, methods.paypal.button, methods.card.three_d_secure, methods.card.require_liability_shift, methods.apple_pay.serve_domain_association, vault.prune_after_days, http.*, and logging.*.

Secrets. client_secret (PAYPAL_CLIENT_SECRET) and webhook_id (PAYPAL_WEBHOOK_ID) are registered as setting secrets: the screen shows whether each is configured, and never shows, returns, or stores the value. Set them in the environment.

Validation. A paypal constraint runs on the combined settings before every save and refuses:

Code When
paypal-not-configured PayPal is enabled without both a client id and PAYPAL_CLIENT_SECRET.
paypal-live-credentials-invalid PayPal is enabled in live mode and PayPal refuses a live token for the credentials. The answer is cached for 10 minutes per credential fingerprint. When PayPal can't be reached, the save isn't blocked.
paypal-vault-unavailable Saved payment methods are on, but neither PayPal nor cards are.
paypal-apple-pay-domain-missing Apple Pay is on with no domain association value and no file in storage/app/ecommerce-paypal/.
paypal-apple-pay-domain-invalid The domain association value can't be used: contents that aren't Apple's hex, an http:// URL, or a path that isn't a readable apple-developer-merchantid-domain-association file holding Apple's contents. While Apple Pay is on with the value empty, the file in storage/app/ecommerce-paypal/ is checked the same way. See Apple Pay.

Per-method enablement

Every gateway in the family is registered while PayPal is enabled, even a method that is switched off, so orders already paid with it can still be refunded and reconciled. Switched-off methods are hidden from checkout by the availability filter. A method is offered for a cart only when:

  • its methods.*.enabled is on;
  • PayPal accepts the cart's currency for that method;
  • the cart total is above zero in PayPal's units;
  • the cart total is a whole amount in PayPal's units (see HUF and TWD).

Device and buyer eligibility (Apple Pay on Apple devices, Google Pay readiness, Venmo and Pay Later by buyer country) is decided by PayPal's SDK in the browser; an ineligible method hides itself. See Availability.

Supported currencies

Method Currencies
paypal AUD, BRL, CAD, CNY, CZK, DKK, EUR, HKD, HUF, ILS, JPY, MYR, MXN, TWD, NZD, NOK, PHP, PLN, GBP, SGD, SEK, CHF, THB, USD
card AUD, CAD, CHF, CZK, DKK, EUR, GBP, HKD, HUF, JPY, NOK, NZD, PLN, SEK, SGD, USD
apple_pay, google_pay AUD, BRL, CAD, CHF, CZK, DKK, EUR, GBP, HKD, HUF, ILS, JPY, MXN, NOK, NZD, PHP, PLN, SEK, SGD, THB, TWD, USD
venmo (inside the buttons) USD
pay_later (inside the buttons and messages) AUD, EUR, GBP, USD

HUF, JPY, and TWD are sent to PayPal without decimals. Change any list with the ap.ecommerce.paypal.supportedCurrencies filter.

HUF and TWD

The engine stores HUF and TWD in hundredths, but PayPal only takes whole forints and New Taiwan dollars. A cart whose total has a fraction (for example 1234.50 HUF, which 27% VAT easily produces) isn't offered any PayPal method, because PayPal would charge a rounded amount the checkout could never finalize against. Refunds of a HUF or TWD amount with a fraction fail with amount_not_representable. Round your HUF and TWD prices, taxes, and shipping so totals come out whole if you sell in them. JPY is stored in whole yen, so it is unaffected.