PayPal Payments
Take payments at checkout with PayPal — buyers pay with a PayPal account or with a debit/credit card through PayPal, without leaving the checkout page. Card details never reach your server.
How it works
The integration uses PayPal’s JavaScript SDK v6 in the browser and the Orders v2 REST API on the server:
- The buyer fills in contact and shipping details and clicks PayPal or Debit or Credit Card.
- The server prices the cart (the same calculation as Cash on Delivery: items, promo code, delivery charge) and creates a PayPal order for that amount.
- The buyer approves the payment in PayPal’s popup.
- The server checks the cart hasn’t changed, captures the payment, and only then saves the order, reduces stock, empties the cart and sends the usual emails.
Nothing is saved before the money is captured, so abandoned PayPal popups leave no half-orders. If the cart or its prices changed while PayPal was open, the payment is never captured. If saving the order fails after the capture, the payment is refunded automatically.
Setup
- Sign in at developer.paypal.com → Apps & Credentials. Use the Sandbox toggle for testing (PayPal creates sandbox buyer and business accounts for you).
- Create a REST API app (or open the default one) and copy its Client ID and Secret.
- In the Dashboard, open /dashboard/configurations and set the keys below. Changes take effect immediately, without a restart.
- Import the latest language pack (Dashboard → Languages) so the new PayPal messages are translated; without it they fall back to short English labels.
| Key | Default | Purpose |
|---|---|---|
EnablePayPalMethod | false | Shows PayPal at checkout. It only appears once Client ID and Secret are also set. |
PayPal_Mode | Sandbox | Sandbox for test accounts, Live for real payments. Credentials and webhook ID must come from the same mode. |
PayPal_ClientID | empty | Your REST app’s Client ID (public; it is sent to the browser). |
PayPal_ClientSecret | empty | Your REST app’s Secret. Shown masked and never sent back to the browser; leave the field empty when editing to keep the current value. |
PayPal_CurrencyCode | USD | Three-letter ISO code PayPal charges in. Keep it consistent with CurrencySymbol. Some currencies (for example INR) only work for PayPal business accounts registered in that country. |
PayPal_WebhookID | empty | Enables webhooks (see below). Webhooks are ignored while it’s empty. |
Existing databases get these rows automatically on the next start; values you already edited are never overwritten.
Webhooks
Webhooks keep orders in sync with what happens on PayPal’s side after checkout. In your PayPal app, under Webhooks, add https://{your-site}/paypal/webhook, subscribe to the events below, and copy the generated Webhook ID into PayPal_WebhookID. Every message’s signature is verified with PayPal before anything is changed. Webhooks need a public HTTPS URL, so they can’t reach a plain localhost run.
| Event | Effect on the order |
|---|---|
PAYMENT.CAPTURE.COMPLETED | A payment that was pending at checkout cleared: On Hold → Placed. |
PAYMENT.CAPTURE.DENIED | A pending payment was refused: On Hold → Failed. |
PAYMENT.CAPTURE.REFUNDED | Refund made in your PayPal account: Refunded (a partial refund only adds a history note). |
PAYMENT.CAPTURE.REVERSED | Chargeback or reversal: Refunded. |
Customers with an account get the usual status-update email for each change.
Orders and refunds in the Dashboard
PayPal orders show the PayPal Transaction ID (capture ID) and PayPal Order ID on their details page. Most payments complete immediately and the order starts as Placed; a payment PayPal is still reviewing starts as On Hold until the webhook moves it on.
Refund via PayPal on the order details page refunds the full amount to the buyer and marks the order Refunded. Stock is not put back, the same as changing the status by hand. Partial refunds can be made in your PayPal account; with webhooks set up they are noted on the order.
Testing in Sandbox
Sandbox payments are never real. With PayPal_Mode set to Sandbox, both checkout buttons can be tested without spending money.
Pay with PayPal
Sign in with the sandbox personal account listed under developer.paypal.com → Testing Tools → Sandbox Accounts (the business account is the one receiving the money).
Debit or Credit Card
Use one of PayPal’s sandbox test cards. Any future expiry date works (for example 12/30), with any 3-digit CSC (123), or 4 digits for American Express (1234). Name, billing address, mobile and email can be any made-up values.
| Card type | Test card number | Result |
|---|---|---|
| Visa | 4012 0000 3333 0026 | Approved (verified with this app) |
| Visa | 4005 5192 0000 0004 | Approved |
| Visa | 4012 8888 8888 1881 | Approved |
| Mastercard | 2223 0000 4840 0011 | Approved |
| American Express | 3714 496353 98431 | Approved |
| Visa | 4111 1111 1111 1111 | Declined by PayPal sandbox — useful for checking the “payment was declined” message |
Use an approved card and put a trigger value in the card’s First name field (case-sensitive): CCREJECT-REFUSED (do not honor), CCREJECT-IF (insufficient funds), CCREJECT-EC (expired card) or CCREJECT-CVV_F (CVV check fails). The buyer sees the declined message and no order is created. The full list is on PayPal’s card testing page.
After a successful test payment, the order appears in the Dashboard with payment method PayPal and its PayPal Transaction ID, and the transaction is listed in the sandbox business account at sandbox.paypal.com.
Going live
Switch PayPal_Mode to Live and replace PayPal_ClientID, PayPal_ClientSecret and PayPal_WebhookID with your live app’s values. Test cards don’t work in Live mode.
Mobile API
The API offers the same flow for the mobile client: POST /api/{lang}/paypal/create-order returns a PayPalOrderID and an ApproveURL, and POST /api/{lang}/paypal/capture-order places the order. See API Reference.