# Piprapay Integration

> Implements the PipraPay payment gateway for any web project. Use when the user wants to integrate PipraPay payments, handle checkout redirects, verify transactions server-side, process refunds, or set up PipraPay webhooks. Supports Next.js, Node.js/Express, PHP, and plain HTML/JS. Trigger on phrases like "PipraPay", "payment gateway", "bKash checkout", "payment integration", "pay now button", or any request to add, fix, or extend a PipraPay integration.

- Skill: `shovonsheikh/piprapay-integration` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add shovonsheikh/piprapay-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/shovonsheikh/piprapay-integration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: shovonsheikh (https://skillmd.com/u/shovonsheikh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/shovonsheikh/piprapay-integration

---


# PipraPay Integration Skill

PipraPay is a Bangladeshi payment gateway supporting bKash, Nagad, Rocket, and card payments. It works via a **redirect-based checkout model**: your server creates a payment session, receives a `pp_url`, and redirects the user there. After payment, the user is sent to your `return_url` and PipraPay calls your `webhook_url`.

---

## Environment Setup

| Variable | Value |
|---|---|
| Sandbox base URL | `https://sandbox.piprapay.com/api` |
| Production base URL | `https://piprapay.com/api` (confirm with PipraPay docs) |
| API Key header | `MHS-PIPRAPAY-API-KEY` |

Always use sandbox during development. Switch the base URL and API key for production.

---

## The 3-Endpoint API

### 1. Create Payment (`POST /checkout/redirect`)

Initiates a new payment session and returns a redirect URL.

**Request body:**
```json
{
  "full_name": "Customer Name",
  "email_address": "customer@email.com",
  "mobile_number": "01XXXXXXXXX",
  "amount": "500",
  "currency": "BDT",
  "metadata": "{\"order_id\": \"123\", \"product_id\": \"456\"}",
  "return_url": "https://yoursite.com/payment/success",
  "webhook_url": "https://yoursite.com/api/webhooks/piprapay"
}
```

**Success response (200):**
```json
{
  "pp_id": "349452200706799329851862826",
  "pp_url": "https://pay.demo.com/checkout/2134123412341234231"
}
```

→ Redirect the user to `pp_url` immediately after receiving this.

**Key notes:**
- `metadata` must be a **JSON-encoded string**, not an object. Use `JSON.stringify()` or `json_encode()`.
- `amount` is a string, not a number.
- Store `pp_id` in your database immediately — you'll need it to verify and refund.

---

### 2. Verify Payment (`POST /verify-payment`)

Call this to confirm a payment after the user returns to your `return_url`, or within your webhook handler.

**Request body:**
```json
{ "pp_id": "349452200706799329851862826" }
```

**Success response (200):**
```json
{
  "pp_id": "...",
  "status": "completed",
  "amount": "6",
  "fee": "0.68",
  "discount_amount": "0.34",
  "total": 6.34,
  "gateway": "Bkash Personal",
  "sender": "01300000000",
  "transaction_id": "LSKDJCVNNVHG",
  "currency": "USD",
  "local_currency": "BDT",
  "local_net_amount": "774.99",
  "metadata": { "order_id": "123" },
  "date": "Jan 30, 2026 07:01 PM"
}
```

**Always verify server-side** — never trust the return URL alone. A user can manually visit your return URL without paying.

---

### 3. Refund Payment (`POST /refund-payment`)

Initiates a refund for a completed payment.

**Request body:**
```json
{ "pp_id": "349452200706799329851862826" }
```

Returns the same structure as verify. Check `status` field in response.
On `400`, the refund was rejected (already refunded, not completed, etc.).

---

## Webhook Handling

PipraPay POSTs a JSON payload to your `webhook_url` when a payment status changes.

**Payload shape:**
```json
{
  "pp_id": "349452200706799329851862826",
  "status": "completed",
  "amount": "6",
  "transaction_id": "LSKDJCVNNVHG",
  "metadata": { "order_id": "123" }
}
```

**Webhook handler requirements:**
1. Respond with HTTP 200 immediately — do heavy work async.
2. **Always re-verify** by calling `/verify-payment` with the received `pp_id`. Don't trust the webhook payload alone.
3. Use `metadata` (e.g. `order_id`) to find and update the correct order in your DB.
4. Make your handler **idempotent** — PipraPay may retry; the same `pp_id` arriving twice should not double-fulfill an order.

---

## Implementation by Stack

For stack-specific code, read only the file matching the user's stack from `references/`:

- **Next.js / TypeScript** → `references/nextjs.md`
- **Node.js (Express/Vanilla)** → `references/nodejs.md`
- **PHP** → `references/php.md`
- **Plain HTML + fetch** (frontend-only demo) → `references/html.md`

## Decision Tree

```
User wants to integrate PipraPay?
├── What stack?
│   ├── Next.js     → read references/nextjs.md
│   ├── Node.js     → read references/nodejs.md
│   ├── PHP         → read references/php.md
│   └── HTML/JS     → read references/html.md
└── What action?
    ├── Create checkout  → POST /checkout/redirect
    ├── Verify payment   → POST /verify-payment (always server-side)
    ├── Handle webhook   → POST /verify-payment inside handler
    └── Refund           → POST /refund-payment
```

---

## Security Checklist

- [ ] API key stored in environment variable, never in frontend code
- [ ] Payment created server-side only (never expose API key to client)
- [ ] `pp_id` stored in DB at checkout creation time
- [ ] Payment verified server-side via `/verify-payment` before fulfilling order
- [ ] Webhook handler re-verifies before acting on status
- [ ] Webhook handler is idempotent (safe to call multiple times with same `pp_id`)
- [ ] Using HTTPS for `return_url` and `webhook_url` in production

---

## Common Mistakes

| Mistake | Fix |
|---|---|
| Sending `metadata` as an object | Must be `JSON.stringify({...})` — a string |
| Trusting `return_url` params without verifying | Always call `/verify-payment` server-side |
| Not storing `pp_id` before redirect | Store it right after `/checkout/redirect` succeeds |
| Fulfilling order in webhook without re-verify | Call `/verify-payment` inside the webhook handler |
| Hardcoding API key in frontend JS | Move payment creation to a backend API route |

