# Payment links

> Create payment links with the Qualy API: one-off links for a payment, and reusable payment links from a public service and price template, like Stripe Payment Links.

Qualy has two kinds of payment link. Pick by whether you already know who's paying.

| You want… | Use | Like |
| --- | --- | --- |
| A link for one student and one bill | The payment's own link (`links.short`) | An invoice link |
| One link you can post anywhere, that anyone can pay | A public **service** with a price **template** | Stripe Payment Links, a "pay here" button |

---

## One-off links

Every payment intent comes with a link. Create the payment and send `links.short`:

```javascript
const res = await fetch('https://api.qualyhq.com/v1/payment-intents/create', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'ApiKey your-api-key-here',
    'X-TENANT-ID': 'your-tenant-id-here',
  },
  body: JSON.stringify({
    intentType: 'ad-hoc',
    contact: 'john.doe@example.com',
    currency: 'AUD',
    items: [{ name: 'Tuition fee', amount: 135000 }],
  }),
});

const { data: payment } = await res.json();
console.log(payment.links.short);
```

See [Collecting payments](/docs/guides/collecting-payments.md) for what the student sees.

---

## Reusable links

A reusable link is not its own object. It's three things working together:

1. **A service** — what's being sold (an application fee, a deposit, a short course).
2. **A channel flag on the service** — `displayEcommerce: true` makes it public, so anyone with the link can buy it. `displayPortal: true` shows it only to contacts signed in to your portal.
3. **A template** — the price. It belongs to the service and must carry the same channel flag.

The link is your portal address plus the service:

```
https://{subdomain}.qualyhqportal.com/services/{serviceId}?action=buy-now
```

`{subdomain}` is your account's nickname (or your tenant id). With `?action=buy-now` the visitor goes straight to checkout; without it they see the service page first.

> **Warning — Both flags, both places**
>
> A service that isn't public produces a link that leads nowhere for someone who isn't signed in, and a template without the matching flag isn't offered on that channel. Set `displayEcommerce` (or `displayPortal`) on the service **and** on its template.

### Step 1: create the service

```javascript
const res = await fetch('https://api.qualyhq.com/v1/services/create', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'ApiKey your-api-key-here',
    'X-TENANT-ID': 'your-tenant-id-here',
  },
  body: JSON.stringify({
    name: 'Application fee',
    displayEcommerce: true, // anyone with the link
    displayPortal: false,   // set true to also list it in the signed-in portal
  }),
});

const { data: service } = await res.json();
```

If the money belongs to a school or another partner, set `supplier` on the service (a partnership id, code or name). Payments made through the link are then routed to that supplier.

### Step 2: give it a price

A fixed price is a `payment-plan-list` template with one payment and one item:

```javascript
await fetch('https://api.qualyhq.com/v1/templates/create', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'ApiKey your-api-key-here',
    'X-TENANT-ID': 'your-tenant-id-here',
  },
  body: JSON.stringify({
    name: 'Application fee',
    category: 'payment-plan-list',
    services: [service._id],
    displayEcommerce: true,
    displayPortal: false,
    payments: [
      {
        currency: 'AUD',
        items: [{ name: 'Application fee', amount: 25000 }],
      },
    ],
  }),
});
```

For **pay-what-you-owe** links (deposits, top-ups) use `category: 'payment-ad-hoc-variable-amount'` and leave `amount` off the item. The payer types the amount at checkout.

For a **payment plan** (a deposit now and the rest later), add more entries to `payments`, one per instalment. Templates can also carry `splits`, so a link can pay a school and keep your commission automatically. See [Creating payment splits](/docs/guides/creating-payment-splits.md).

### Step 3: share it

```javascript
const link = `https://yourcompany.qualyhqportal.com/services/${service._id}?action=buy-now`;
```

### What happens when someone pays

1. The visitor enters their name, email and phone (plus any [custom fields](https://v1-spec.qualyhq.com/#post-/v1/fields/create) you've set up).
2. Qualy creates a **contact**, an **order** with the service as its item, and a **payment intent** from the template. Visitors get `intentType: "ecommerce"`; signed-in portal contacts get `"portal"`.
3. The visitor is taken to the payment and pays the usual way.

Your integration sees the usual `contacts.create`, `paymentIntents.create` and, once paid, `paymentIntents.update` [webhooks](/docs/webhooks/events.md), so link payments need no special handling.

### Using a template from the API

You can also create a payment straight from a service and its template, without the checkout:

```javascript
body: JSON.stringify({
  contact: 'john.doe@example.com',
  services: [{ service: '6634f56c7dbbc16e07b5025e', template: '6634f5a17dbbc16e07b50261' }],
}),
```

---

## Turning a link off

Set the service's `status` to `archived` (or set `displayEcommerce: false`) with `POST /v1/services/{serviceId}/update`. Payments already made aren't affected.
