Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PayPal Checkout Demo

A minimal plain PHP app demonstrating a complete PayPal Checkout flow using the official paypal/paypal-server-sdk on the server and the PayPal JavaScript SDK v6 web components on a plain HTML front-end. No framework required.

It shows the full server-side pattern: create an order, let the buyer approve it in the PayPal UI, then capture the payment — with prices held on the server so they can't be tampered with from the browser.

Setup

  1. Create a sandbox app at developer.paypal.com to get a Client ID and Secret.

  2. Copy the env template and fill in your credentials:

    cp .env.example .env
    PAYPAL_CLIENT_ID=your_sandbox_client_id
    PAYPAL_CLIENT_SECRET=your_sandbox_client_secret
    PAYPAL_ENV=sandbox   # or "live"
  3. Install and run:

    composer install
    composer start
  4. Open http://localhost:3000 and pay with a sandbox test account or a generated test card.

Using GitHub Codespaces

Opening this repo in a Codespace uses the .devcontainer config to start a PHP 8.3 container, run composer install, and copy .env.example to .env for you automatically. You still need to open .env and swap in your own Sandbox PAYPAL_CLIENT_ID/PAYPAL_CLIENT_SECRET before running composer start — the placeholder values won't authenticate.

How it works

The checkout is a three-legged handshake between the browser, this server, and PayPal. The browser never talks to PayPal's REST API directly — it only ever calls this server, which holds the secret and the prices.

Browser                      This server                    PayPal
  │  GET /api/config            │                              │
  │ ───────────────────────────▶│  clientId, env, sdkUrl       │
  │ ◀───────────────────────────│                              │
  │  (loads PayPal SDK, renders <paypal-button>)               │
  │                             │                              │
  │  click → POST /api/orders   │  createOrder(item)           │
  │ ───────────────────────────▶│ ────────────────────────────▶│
  │ ◀───────────────────────────│ ◀────────────────────────────│  order id
  │  (buyer approves in PayPal UI)                             │
  │                             │                              │
  │  POST /api/orders/:id/capture  captureOrder(order)         │
  │ ───────────────────────────▶│ ────────────────────────────▶│
  │ ◀───────────────────────────│ ◀────────────────────────────│  COMPLETED

Files

File Responsibility
router.php The whole server: PayPal client, routes, and the in-memory item catalogue. Also the front controller PHP's built-in server checks before serving a static file.
public/index.html Loads the PayPal SDK v6, renders the <paypal-button> web component, and drives the checkout.

Everything lives in router.php:

  • An $items array — the stub catalogue. Prices are held server-side so the browser can't change them.
  • A PayPal client from paypal/paypal-server-sdk, driven through its OrdersController.
  • Hand-rolled routing on $_SERVER['REQUEST_METHOD'] and the request path — there's no framework here, and anything that doesn't match falls through to the built-in server's static file handling in public/.

Because PHP's built-in server (and PHP-FPM) starts a fresh process per request, there's no in-memory store for created orders the way a long-running Node process could keep one. The order id round-trips through the browser instead: /api/orders returns it, and the client sends it straight back to /api/orders/:orderId/capture.

Routes

  • GET /api/config — hands the front-end the public clientId, env, and the correct sdkUrl. Loading the SDK URL from the server lets you switch between sandbox and live without touching client code.
  • POST /api/orders — looks up the item by itemId, asks PayPal to create an order, and returns the order id.
  • POST /api/orders/:orderId/capture — captures the previously approved order.

Talking to PayPal

The paypal/paypal-server-sdk handles the REST calls and OAuth for you. A single client is built with PaypalServerSdkClientBuilder, configured with client-credentials auth and the environment, then $client->getOrdersController() exposes:

  • createOrder(...) — creates an order with CheckoutPaymentIntent::CAPTURE and the item's amount pulled from the server-side $items array.
  • captureOrder(['id' => ...]) — captures the previously approved order.

PAYPAL_ENV drives both the SDK's Environment (Sandbox vs Production) and the front-end sdkUrl returned by /api/config, so switching between sandbox and live is a single env change.

Notes for a real application

This is a demo. Before using anything like it in production you'd want to:

  • Track order and capture status in a real database instead of trusting the round trip through the browser.
  • Compute order amounts from a real cart rather than a single hardcoded demo-product (currently $9.99 USD).
  • Add input validation, authentication, idempotency, and verification of PayPal webhooks for reliable payment confirmation.
  • Swap the hand-rolled routing in router.php for a framework (Laravel, Slim, Symfony) if the app grows beyond a couple of routes.

Production

php -S is for local development only. In production, run this behind PHP-FPM with Nginx or Apache: serve static files from public/ directly, and route everything else to router.php the same way -t public router.php does locally.

Scripts

Script Description
composer start Run with PHP's built-in server on http://localhost:3000.

About

Minimal plain PHP + PayPal Checkout demo

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages