# Create Payment Link API

Create a Herepay **payment link** programmatically and get back a public `pay_url`
that you redirect your customer to. The pay page is the same dynamic checkout a
dashboard‑created link uses (FPX bank selection → payment → receipt), so no UI
work is needed on the integrator side — just create the link and redirect.

---

## Endpoint

```
POST /api/integration/create-payment-link
```

| Environment | Base URL |
|---|---|
| UAT | `https://uat.herepay.org` |
| Production | `https://app.herepay.org` |

Full URL (UAT): `https://uat.herepay.org/api/integration/create-payment-link`

---

## Authentication

Authenticate with your team's **Secret Key**, sent as a request header. This is the
same `SecretKey` used by the other `/api/integration/*` endpoints.

```
SecretKey: <your-team-secret-key>
```

A missing or wrong key returns `401 Unauthorized`.

> Find your Secret Key in the Herepay dashboard under your team's API/Integration
> settings. Treat it as a credential — never expose it in client‑side code.

---

## Request

### Headers

| Header | Value | Required |
|---|---|---|
| `SecretKey` | Your team secret key | ✅ |
| `Content-Type` | `application/json` | ✅ |
| `Accept` | `application/json` | ✅ |

### Body parameters

Only `title` and `amount` are required. Everything else is optional and has a
sensible default.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `title` | string (≤255) | ✅ | — | Name of what's being paid for. Shown on the pay page. |
| `amount` | number | ✅ | — | Amount in **MYR**. Range `1` – `99,999,999`. Two decimals. |
| `description` | string (≤65000) | — | `null` | HTML allowed. Rendered in the pay page's "Additional Information". |
| `usage_type` | string | — | `multiple` | `multiple` = reusable by any number of payers. `single` = closes after the first successful payment. |
| `expires_at` | datetime | — | `null` | Link stops accepting payments after this time. Must be in the future. Format `YYYY-MM-DD HH:MM:SS`. |
| `team_bank_account_id` | integer | — | team default | Settlement account. Must be one of your team's **verified** bank accounts. |
| `collect_phone` | boolean | — | `true` | Whether the payer must enter a phone number. |
| `note_enabled` | boolean | — | `false` | Show an extra note field to the payer. |
| `note_label` | string (≤100) | — | `null` | Label for the note field. **Required when `note_enabled` is `true`.** |
| `redirect_url` | url (≤2048) | — | `null` | Browser is redirected here after payment. Falls back to the Herepay receipt page if empty. |
| `callback_url` | url (≤2048) | — | `null` | Server‑to‑server webhook fired when the payment settles (see [Callback](#callback-webhook)). |
| `notify_email` | email (≤255) | — | `null` | Email to notify about the link (reserved). |
| `payer_name` | string (≤255) | — | `null` | Prefills the **Full Name** field on the pay page. |
| `payer_email` | email (≤255) | — | `null` | Prefills the **Email** field on the pay page. |
| `payer_phone` | string (≤32) | — | `null` | Prefills the **Phone Number** field on the pay page. |

> **Payer prefill.** When `payer_name` / `payer_email` / `payer_phone` are set, the
> pay page opens with the payer form already filled in — the payer can still edit
> the values before paying. The prefill is stored on the link itself, so it's
> intended for **`usage_type: single`** links created for a specific customer; on a
> `multiple` link every payer would see the same prefilled details.

---

## Examples

### Minimal

```bash
curl -X POST https://uat.herepay.org/api/integration/create-payment-link \
  -H "SecretKey: <your-team-secret-key>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "title": "Consultation Fee",
    "amount": 10.50
  }'
```

### Full

```bash
curl -X POST https://uat.herepay.org/api/integration/create-payment-link \
  -H "SecretKey: <your-team-secret-key>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "title": "Order #A1042",
    "amount": 89.90,
    "description": "<p>1x Premium Plan — annual</p>",
    "usage_type": "single",
    "expires_at": "2026-12-31 23:59:59",
    "collect_phone": true,
    "note_enabled": true,
    "note_label": "Booking reference",
    "redirect_url": "https://integrator.example.com/return",
    "callback_url": "https://integrator.example.com/webhook/herepay",
    "notify_email": "sales@integrator.example.com",
    "payer_name": "Aisyah binti Ahmad",
    "payer_email": "aisyah@example.com",
    "payer_phone": "0123456789"
  }'
```

---

## Response

### Success — `200 OK`

```json
{
    "status": 200,
    "data": {
        "id": 6,
        "code": "539ace2630bf7fa2",
        "title": "Order #A1042",
        "amount": 89.9,
        "usage_type": "single",
        "status": "active",
        "expires_at": null,
        "redirect_url": "https://integrator.example.com/return",
        "callback_url": "https://integrator.example.com/webhook/herepay",
        "payer_name": "Aisyah binti Ahmad",
        "payer_email": "aisyah@example.com",
        "payer_phone": "0123456789",
        "pay_url": "https://uat.herepay.org/herepay/pay/EQFYRVJXFhhCVUwoXwMlYQ"
    }
}
```

| Field | Description |
|---|---|
| `id` | Internal payment‑link id. |
| `code` | Public link code. |
| `status` | `active`, `inactive`, `expired`, or `completed`. |
| `expires_at` | ISO‑8601 or `null`. |
| `pay_url` | **Redirect your customer here to pay.** |

### Errors

| HTTP | Body | When |
|---|---|---|
| `401` | `{ "status": "Unauthorized", "status_code": "41", "error": "..." }` | Missing / invalid `SecretKey`. |
| `422` | `{ "status": 422, "error": { "field": ["message"] } }` | Validation failed. |
| `500` | `{ "status": 500, "error": "Failed to create payment link." }` | Server error. |

Validation example:

```json
{
    "status": 422,
    "error": {
        "title": ["The title field is required."],
        "amount": ["The amount field is required."]
    }
}
```

---

## Payment flow

1. **Create** — `POST` this endpoint → receive `pay_url`.
2. **Redirect** — send your customer to `pay_url` (HTTP `302`, or open in a browser).
3. **Pay** — the customer completes the payment on the Herepay pay page via FPX.
4. **Return** — after payment, Herepay:
   - redirects the customer's browser to your `redirect_url` (or the Herepay
     receipt page if you didn't set one), **and**
   - sends a server‑to‑server **callback** to your `callback_url` (if set).

Each link uses its **own** `redirect_url` / `callback_url`. If a link doesn't set
them, Herepay falls back to your team‑level URLs.

---

## Callback (webhook)

When a link payment settles, Herepay `POST`s (as `application/x-www-form-urlencoded`)
to the link's `callback_url`.

### Payload

| Field | Description |
|---|---|
| `reference_code` | Invoice reference code. |
| `payment_code` | Payment reference code. |
| `transaction_id` | FPX transaction id (may be empty). |
| `status` | `Success` / `Failed` / `Pending`. |
| `status_code` | `00` success, `30` failed, `29` pending. |
| `message` | Gateway remark. |
| `amount` | Paid amount (MYR). |
| `currency` | `MYR`. |
| `payment_method` | Payment gateway name. |
| `fpx_type` | FPX type (may be empty). |
| `bank_name` | Payer's bank (may be empty). |
| `checksum` | HMAC signature — see below. |

### Verifying the checksum

The `checksum` lets you confirm the callback really came from Herepay:

1. Take all payload fields **except** `checksum`.
2. Sort them by key (ascending).
3. Join the values with a comma `,` (in the sorted‑key order). Array values are
   `json_encode`d first.
4. Compute `HMAC‑SHA256` of that string using **your team's private key**.
5. Compare (constant‑time) against the received `checksum`.

```
checksum = HMAC_SHA256( implode(",", values_sorted_by_key), team_private_key )
```

Respond `200 OK` to acknowledge. Herepay logs a failure if your endpoint errors or
times out (5s).

---

## Notes

- **Amounts are in MYR** with two decimals.
- **`usage_type: single`** links close after the first successful payment; reusable
  links (`multiple`) can be paid any number of times.
- The `pay_url` code is encrypted — it reveals nothing about the underlying record
  and is the same URL space used by custom invoices.
- The link's optional tip/contribution section is shown on the pay page.
