Skip to content

PayPal

Integrates the Payment actor with PayPal. The core of the app is the Payment-actor integration — capturing and refunding PayPal payments and reconciling them onto the Hantera Payment via webhooks.

The app also ships with a self-contained, redirect-based checkout flow (start → PayPal approval → return). It's a convenience entry point that any redirect-capable checkout can drive, and it works particularly well as a Kustom External Payment Method (EPM) — Kustom collects the address and redirects the customer to the start ingress, then the PayPal app owns the rest of the completion flow (there is no postback from Kustom for external payments).

Key features:

  • Payment-actor capture & refund driven by the OnPaymentCapture rule hook
  • Unified settlement: a single settle rule both captures outstanding authorizations and refunds over-charged captures, based on the capture's remaining balance
  • Idempotent webhook receiver with PayPal signature verification that reconciles dashboard-initiated actions (manual captures, refunds, voids) back onto the Hantera Payment
  • Manual Sync from PayPal button in the portal order view as a fallback for missed or late webhook deliveries
  • Optional redirect-driven checkout (start → PayPal approval → return) that creates the Payment, links it to the cart, completes the cart, and returns the customer to the channel confirmation page — intent=AUTHORIZE, captured later

Checkout Flow

The confirmation page is responsible for detecting the order's payment type and rendering accordingly.

Settlement (Capture & Refund)

PayPal Orders v2 is a hierarchy of resources, each with its own id:

  • Order — the checkout session the customer approves.
  • Authorization — a hold on the funds (created at return). Needed to capture.
  • Capture — the actual movement of money. Needed to refund.
  • Refund — issued against a specific capture.

The settle rule listens on OnPaymentCapture. When a capture is pending it inspects the remaining balance:

Because refunds are per-capture, the refund job walks the payment's charge journal entries newest-first to find the capture ids to refund against.

PayPal ↔ Hantera mapping

PayPalHantera Payment
Order idexternalReference
Authorization idauthorization authorizationNumber
Capture idcharge journal transactionReference
Refund idrefund journal transactionReference

Settings

SettingSecretDescription
clientIdnoPayPal REST app Client ID
clientSecretyesPayPal REST app Client Secret
environmentnosandbox or live
webhookIdyesPayPal webhook ID. Required for signature verification on incoming webhooks; without it every webhook is rejected with INVALID_SIGNATURE and dashboard-initiated changes won't sync back.

Webhook configuration

The webhook ingress is what keeps the Hantera Payment in sync when an action happens outside Hantera — most commonly when a backoffice agent captures or refunds the transaction directly in the PayPal dashboard. Configuring it is a one-time setup in two places.

1. In the PayPal Developer dashboard

  1. Go to PayPal Developer → Apps & Credentials and select the REST app whose Client ID / Secret you used for the clientId / clientSecret settings. Make sure the Sandbox / Live toggle matches the environment setting.

  2. Scroll to Webhooks on the app's page and click Add Webhook.

  3. Set the Webhook URL to:

    https://<your-hantera-host>/ingress/paypal/webhook
  4. Subscribe to the following event types (the ingress reconciles these by re-fetching the parent order from PayPal; other event types are accepted and logged but otherwise ignored):

    • Checkout order approvedCHECKOUT.ORDER.APPROVED
    • Checkout order completedCHECKOUT.ORDER.COMPLETED
    • Payment authorization createdPAYMENT.AUTHORIZATION.CREATED
    • Payment authorization voidedPAYMENT.AUTHORIZATION.VOIDED
    • Payment capture completedPAYMENT.CAPTURE.COMPLETED
    • Payment capture deniedPAYMENT.CAPTURE.DENIED
    • Payment capture pendingPAYMENT.CAPTURE.PENDING
    • Payment capture refundedPAYMENT.CAPTURE.REFUNDED
    • Payment capture reversedPAYMENT.CAPTURE.REVERSED
  5. Save the webhook. PayPal generates a short opaque Webhook ID for the subscription — copy it.

2. In Hantera

Paste the Webhook ID into the app's webhookId setting. On every incoming webhook the ingress POSTs the transmission headers and raw body to PayPal's /v1/notifications/verify-webhook-signature endpoint, which checks them against this ID; verified events are forwarded to the syncPayment job and unverified ones are rejected.

What the webhook does

The webhook extracts the parent PayPal Order id from the event resource and delegates to the syncPayment job. That job fetches the authoritative state from PayPal and reconciles it onto the Hantera Payment — a single code path that also powers the manual "Sync from PayPal" portal button.

Manual sync

The portal renders a Sync from PayPal button on every PayPal payment in the order view. It schedules the same syncPayment job the webhook uses and is the canonical recovery path when webhook delivery missed an event — for example a transient PayPal outage, an event raised before the webhook was configured, or an agent performing a refund inside the PayPal dashboard before signature verification was set up.

Sync only ever adds missing journal entries and updates the authorization state. It never removes or reverses an existing entry, so running it repeatedly is safe.

Using PayPal inside Kustom

The Kustom app exposes the OnKustomExternalPaymentMethods hook. A merchant rule returns a custom effect whose redirect_url points at this app's start ingress for the channels that should offer PayPal.

See Also

© 2024 Hantera AB. All rights reserved.