ArtisanPack UI
DocsEcommerce PaypalPayment Methods Overview

Payment Methods Overview

Ecommerce Paypal v1.0.0

The package registers four gateways with the engine. Each is a separate checkout choice, can be switched on separately, and has its own storefront driver...

The package registers four gateways with the engine. Each is a separate checkout choice, can be switched on separately, and has its own storefront driver.

In this section

  • PayPal Buttons - PayPal, Pay Later, and Venmo, the decline restart flow, and button style
  • Card Fields - PayPal-hosted card fields, 3-D Secure, and the liability-shift policy
  • Apple Pay - Domain registration, the domain association file, and going live
  • Google Pay - Readiness, 3-D Secure, and going live
  • Pay Later Messaging - PayPal's Pay Later messages on product, cart, and checkout pages

The gateway family

Gateway key Checkout label Config key Driver PayPal payment_source Saved methods
paypal PayPal methods.paypal paypal-buttons paypal (and Venmo, Pay Later) With vault.enabled
paypal-card Credit or debit card methods.card paypal-card-fields card With vault.enabled
paypal-apple-pay Apple Pay methods.apple_pay paypal-apple-pay apple_pay No
paypal-google-pay Google Pay methods.google_pay paypal-google-pay google_pay No

All four support full and partial refunds. Only the paypal gateway receives webhooks: PayPal posts every event to ecommerce/webhooks/paypal, and that gateway reconciles checkouts placed with any of the four. See Webhooks.

How a payment runs

  1. Create. When the shopper reaches the payment step, the gateway creates a PayPal Order on the server (POST /v2/checkout/orders) with the cart's items, totals, shipping address, and brand. The Order id is the engine's payment session reference.
  2. Approve. The driver mounts PayPal's UI with the gateway's client config. The buyer approves in PayPal (the buttons, the card fields, or the wallet sheet). The browser never creates the Order: createOrder hands back the server's id.
  3. Confirm. The driver checks the approved Order belongs to the session and reports it to the checkout. With require_liability_shift on, a card or wallet payment without a liability shift is refused here.
  4. Finalize. The engine retrieves the session, runs its fraud check, and calls capturePayment(). The gateway captures (or authorizes and captures; see Intent) with an idempotent PayPal-Request-Id, and stores the capture id, authorization, and payer details on orders.meta.paypal.

Before capturing, the gateway checks the PayPal Order against the checkout. An Order whose custom_id names another cart fails with paypal_order_mismatch, and one paid with a source the gateway doesn't take (a card on the paypal gateway while card fields are on, a wallet on paypal-card) fails with payment_source_mismatch, so a switched-off method can't be paid through a sibling. If the shopper changed the shipping address after the Order was created, PayPal and Venmo Orders get the new address (PATCH /v2/checkout/orders/{id}) before capture, so the transaction keeps PayPal Seller Protection; a PATCH PayPal refuses is logged with a paypal.shipping_patch_failed timeline entry, and the capture still goes ahead.

A PENDING capture (an eCheck or a review hold) places the order as paid and is settled later by webhook. A decline returns a failed result with a translated message; nothing throws.

Session statuses

PayPal Order status Engine session status
CREATED, SAVED requires_payment_method
PAYER_ACTION_REQUIRED requires_action
APPROVED authorized (ready for finalize)
COMPLETED with a COMPLETED capture succeeded
COMPLETED with a PENDING capture processing
COMPLETED with a CREATED authorization authorized
VOIDED canceled

Money

Amounts are sent as PayPal's decimal strings. HUF, JPY, and TWD are sent without decimals. When that rounding makes the line items and breakdown differ from the engine total, the difference goes into discount or handling, so PayPal's breakdown check passes.

The total itself is never rounded. The engine keeps HUF and TWD in hundredths, but PayPal only takes whole forints and dollars, so a cart whose HUF or TWD total has a fraction isn't offered any PayPal method, and createPaymentSession() throws for one ("PayPal only accepts whole :currency amounts."). For every cart PayPal is offered, amount.value equals the engine's total. JPY is stored in whole yen, so it is unaffected.

Refunds

refund() refunds the order's capture (POST /v2/payments/captures/{id}/refund) with the engine's idempotency key as PayPal-Request-Id, invoice_id set to refund-{refund id}, custom_id set to the engine refund id (so the webhook matches it back), and the reason as note_to_payer (up to 255 characters). PayPal shows note_to_payer to the buyer, so the engine's reason codes are mapped first: fraudulent and duplicate send no note, requested_by_customer sends "Refund requested by the customer.", and any other text is sent as is. A HUF or TWD amount with a fraction fails with amount_not_representable before PayPal is called. PayPal's declines (CAPTURE_FULLY_REFUNDED, REFUND_AMOUNT_EXCEEDED, REFUND_TIME_LIMIT_EXCEEDED, and others) return a failed result with a translated message. A PENDING refund succeeds and the PAYMENT.CAPTURE.REFUNDED webhook settles it. Refunds made in the PayPal dashboard are recorded on the order from the webhook.

Without storefront-livewire

The gateways, refunds, and webhooks work without any storefront. A React or Vue storefront renders the payment step with the JavaScript module, using the client config the engine's checkout API returns.

Replacing a driver

The storefront's payments.drivers config wins over the package's drivers, whichever provider boots first. To use your own Livewire component for a PayPal driver:

// config/artisanpack/ecommerce-storefront-livewire.php
'payments' => [
    'drivers' => [
        'paypal-buttons' => \App\Livewire\MyPayPalButtons::class,
    ],
],

Stay in the Loop

Monthly tips, tutorials and package updates — plus a free Quick Start cheat sheet.

Subscribe
Edit this page on GitHub