Skip to content

PayPal

PayPal integration uses the Orders API v2 with a two-step flow: create order → buyer approves → capture.

Getting Started — Step by Step

flowchart TB
    A["1. Create PayPal Developer Account"] --> B["2. Create App & Get Keys"]
    B --> C["3. Get API Key from Payments Service"]
    C --> D["4. Configure PayPal Credentials"]
    D --> E["5. Set Webhook URL"]
    E --> F["6. Create an Order"]
    F --> G["7. Redirect Buyer to PayPal"]
    G --> H["8. Capture the Payment"]

    style A fill:#e3f2fd
    style F fill:#e8f5e9
    style H fill:#e8f5e9

Step 1: Create a PayPal Developer Account

  1. Go to PayPal Developer Dashboard
  2. Log in or create an account

Step 2: Create an App & Get Keys

  1. Go to Apps & Credentials
  2. Click Create App
  3. Copy your Client ID and Client Secret
  4. Toggle between Sandbox and Live as needed

Sandbox accounts

PayPal provides sandbox buyer/seller accounts for testing. Find them under SandboxAccounts.

Step 3: Get an API Key from Payments Service

make createapikey
# Scopes: payments:read,payments:write,merchant:read,merchant:write,webhooks:read

Step 4: Configure PayPal Credentials

PUT /v1/merchant/providers
X-API-Key: sk_live_your_key
Content-Type: application/json
{
  "provider": "paypal",
  "paypal": {
    "env": "sandbox",
    "client_id": "your_paypal_client_id",
    "client_secret": "your_paypal_client_secret"
  }
}

Step 5: Set Your Webhook URL

PUT /v1/merchant/webhook
X-API-Key: sk_live_your_key
{
  "webhook_url": "https://yourapp.com/webhooks/payments"
}

Step 6: Create an Order

POST /v1/payments
X-API-Key: sk_live_your_key
Idempotency-Key: test-paypal-001
{
  "amount": 25.00,
  "currency": "USD",
  "provider": "paypal",
  "return_url": "https://yourapp.com/payment/success",
  "cancel_url": "https://yourapp.com/payment/cancel"
}

Response includes status: "pending" and a provider_ref (PayPal order ID).

Step 7: Redirect Buyer

Redirect the buyer to PayPal's approval page. After they approve, PayPal redirects them to your return_url.

Step 8: Capture the Payment

POST /v1/payments/{payment_id}/capture
X-API-Key: sk_live_your_key

The payment status changes to completed.


Payment Flow

sequenceDiagram
    participant Merchant
    participant API as Payments API
    participant PayPal
    participant Buyer
    participant Webhook as Merchant Webhook

    Merchant->>API: POST /v1/payments<br/>{ provider: "paypal", return_url, cancel_url }
    API->>PayPal: OAuth token (client_credentials)
    PayPal-->>API: { access_token: "..." }
    API->>PayPal: POST /v2/checkout/orders
    PayPal-->>API: { id: "ORDER_ID", status: "CREATED", links: [...] }
    API-->>Merchant: { status: "pending", provider_ref: "ORDER_ID" }

    Merchant->>Buyer: Redirect to approval_url
    Buyer->>PayPal: Approves payment
    PayPal->>Buyer: Redirect to return_url

    Merchant->>API: POST /v1/payments/{id}/capture
    API->>PayPal: POST /v2/checkout/orders/ORDER_ID/capture
    PayPal-->>API: { status: "COMPLETED" }
    API-->>Merchant: { status: "completed" }

    PayPal->>API: POST /v1/webhooks/paypal (callback)
    API->>Webhook: { type: "payment.completed" }

Create Payment

Request

POST /v1/payments
X-API-Key: sk_live_...
Idempotency-Key: order-789
{
  "amount": 75.00,
  "currency": "USD",
  "provider": "paypal",
  "return_url": "https://merchant.com/success",
  "cancel_url": "https://merchant.com/cancel"
}
Field Type Required Description
amount decimal Amount to charge
currency string 3-letter ISO code
provider string Must be paypal
return_url string Where buyer returns after approval
cancel_url string Where buyer returns if they cancel

Response

{
  "success": true,
  "data": {
    "id": "txn-uuid-...",
    "merchant_id": "m1m2m3-...",
    "amount": "75.00",
    "currency": "USD",
    "provider": "paypal",
    "status": "pending",
    "provider_ref": "5O190127TN364715T",
    "idempotency_key": "order-789",
    "created_at": "2025-01-01T12:00:00Z"
  }
}

Getting the approval URL

The approval_url is in the raw provider response. Use GET /v1/payments/{id} if you need to retrieve it later.

Capture Payment

After the buyer approves on PayPal, capture the order:

POST /v1/payments/{payment_id}/capture
X-API-Key: sk_live_...

Response

{
  "success": true,
  "data": {
    "id": "txn-uuid-...",
    "status": "completed",
    "provider_ref": "5O190127TN364715T",
    "amount": "75.00",
    "currency": "USD",
    "provider": "paypal",
    "created_at": "2025-01-01T12:00:00Z"
  }
}

Refund

POST /v1/refunds
X-API-Key: sk_live_...
{
  "payment_id": "txn-uuid-...",
  "amount": 30.00
}
Field Type Required Description
payment_id UUID Original payment to refund
amount decimal Partial amount. Omit for full refund

Response

{
  "success": true,
  "data": {
    "id": "refund-uuid-...",
    "payment_id": "txn-uuid-...",
    "status": "completed",
    "provider_ref": "REFUND_ID",
    "amount": "30.00",
    "created_at": "2025-01-01T12:00:00Z"
  }
}

Inbound Webhook (PayPal → Us)

PayPal sends events to POST /v1/webhooks/paypal with transmission headers.

Verification headers:

Header Description
Paypal-Transmission-Id Unique transmission ID
Paypal-Transmission-Time Timestamp
Paypal-Webhook-Id Webhook configuration ID
Paypal-Transmission-Sig HMAC signature

Events handled:

PayPal Event Our Status
PAYMENT.CAPTURE.COMPLETED completed
PAYMENT.CAPTURE.DENIED failed
PAYMENT.CAPTURE.REFUNDED refunded

PayPal Status Mapping

PayPal Status Our Status
COMPLETED completed
APPROVED pending
CREATED pending
VOIDED failed
PAYER_ACTION_REQUIRED pending

Viewing PayPal Transactions

List and filter all PayPal transactions:

GET /v1/payments?provider=paypal&status=completed
GET /v1/payments?provider=paypal&currency=USD&min_amount=50
GET /v1/payments?provider=paypal&since=2025-06-01T00:00:00Z

See Payments API → List & Filter for all available filters.