هذه الصفحة متاحة بالإنجليزية فقط حاليًا.
PayPal
PayPal balance and card payments through the Orders API; the customer approves on PayPal's page, the capture is made server-side from the webhook.
What you need
developer.paypal.com → Apps & Credentials → your app (or Create App):
client_id: the app's Client IDclient_secret: the app's Secret (click Show)webhook_secret: the Webhook ID of the webhook you create under that app's Webhooks section (PayPal has no shared signing secret; verification is a call back to PayPal with this id, see below)
In your panel choose Settings → Payments → Add method → PayPal and enter all three.
Sandbox: enter the keys from the app's Sandbox tab and tick sandbox (test) mode; the panel then talks to api-m.sandbox.paypal.com and you pay with a sandbox buyer account. Going live, enter the Live tab's keys and that app's webhook id, and untick the box. Sandbox and Live apps have separate webhooks; each set of keys needs its own.
Webhook, must be configured on PayPal's side
On your app's page, Webhooks → Add Webhook:
https://<your-panel-domain>/payments/paypal/callback
Subscribe to at least:
CHECKOUT.ORDER.APPROVED: the buyer approved; this is where we capturePAYMENT.CAPTURE.COMPLETED: the money reached your accountPAYMENT.CAPTURE.PENDING: the capture is on hold (eCheck, review, a currency you do not hold)PAYMENT.CAPTURE.DENIED(also listed asPAYMENT.CAPTURE.DECLINED), the capture was refused
"All events" works too: unrelated events are verified, recorded and acknowledged without touching any balance. After saving, copy the Webhook ID from the list into the panel's webhook_secret field.
Without the webhook a payment never completes: even after the buyer approves on PayPal, no money moves until we call capture from the server, and that call is made in the CHECKOUT.ORDER.APPROVED handler. PayPal reverses an approval that is not captured within 3 hours (CHECKOUT.PAYMENT-APPROVAL.REVERSED).
Flow
- The customer enters an amount and picks PayPal. The panel opens an
intent: CAPTUREorder withPOST /v2/checkout/orders; our payment reference (publicRef) goes intocustom_idandinvoice_id. No shipping, a "Pay Now" button, immediate payment only (IMMEDIATE_PAYMENT_REQUIRED). - The customer is sent to PayPal's approval page (the
payer-actionlink) and pays with a PayPal balance or a card. - After approval PayPal brings the customer's browser back to the panel's return route (
/payments/paypal/return?ref=…). That return only redirects, it credits nothing; the customer lands on "Add funds". - At the same time PayPal sends the
CHECKOUT.ORDER.APPROVEDwebhook. The panel has PayPal verify the signature, then callsPOST /v2/checkout/orders/{id}/capturewith its own credentials. If the returned capture's status isCOMPLETED, the balance is credited. PAYMENT.CAPTURE.COMPLETEDarrives as well; it carries the same capture id, so nothing is credited twice. If it arrives beforeCHECKOUT.ORDER.APPROVED, it does the crediting and the later capture call gets "already captured", after which the order is simply read back.
The webhook usually arrives within seconds, so the balance shows up shortly after the customer is back on the page.
Verification
The panel trusts no webhook on its own: it sends PayPal's PAYPAL-TRANSMISSION-ID, PAYPAL-TRANSMISSION-TIME, PAYPAL-TRANSMISSION-SIG, PAYPAL-CERT-URL and PAYPAL-AUTH-ALGO headers, the body and your Webhook ID to POST /v1/notifications/verify-webhook-signature; unless PayPal answers SUCCESS the request is rejected (HTTP 400, PayPal retries). Every API call uses a Bearer token obtained from POST /v1/oauth2/token.
Statuses
| PayPal capture status | In the panel |
|---|---|
COMPLETED |
completed, balance credited |
PENDING (ECHECK, PENDING_REVIEW, RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION …) |
pending; the reason is in the payment memo. The PAYMENT.CAPTURE.COMPLETED that follows when it clears credits it |
DECLINED, FAILED |
failed |
REFUNDED, PARTIALLY_REFUNDED |
a completed payment is left untouched; handle the refund by hand under Admin → Payments |
If the capture call fails on the network, the webhook is rejected with 400 and PayPal re-sends the event; the payment stays "waiting" until then.
Currencies
PayPal's REST API accepts AUD, BRL, CAD, CNY, CZK, DKK, EUR, HKD, HUF, ILS, JPY, MYR, MXN, TWD, NZD, NOK, PHP, PLN, GBP, RUB, SGD, SEK, CHF, THB and USD. TRY is not on the list; define the PayPal method in USD or EUR on a Turkish-lira panel. HUF, JPY and TWD take no decimals; the amount is rounded to a whole number.
If you receive a currency you do not hold, PayPal may hold the capture depending on your Payment Receiving Preferences; such a payment shows as pending in the panel and is credited once you accept it in your PayPal account.
Common problems
- Customer paid, no balance, payment "waiting": the webhook id is missing, or the webhook is not defined on PayPal / points at the wrong URL. Check delivery under Developer Dashboard → Webhooks → Events; if the panel answers 400 the id does not match.
- "AUTHENTICATION_FAILURE" / "invalid_client" in sandbox: Live keys with sandbox mode (or the reverse). The mode must match the tab the keys came from.
- "CURRENCY_NOT_SUPPORTED": the method's currency is not on PayPal's list (usually TRY). Re-create the method in USD/EUR.
- "DUPLICATE_INVOICE_ID": your PayPal account has "Block duplicate invoice IDs" on and this reference was captured before. The panel generates a fresh reference for every payment; this only occurs when two orders were opened for the same payment, have the customer start a new one.
- "CHECKOUT.PAYMENT-APPROVAL.REVERSED" 3 hours after approval: the capture call never went through (no webhook, or it kept failing). The money has been returned to the buyer; fix the webhook and ask for a new payment.
FollowerHQ