# Receipt Service

Receipt Service turns a successful payment into a customer-facing receipt. It does not price, allocate, or capture money. It stores enough payment and invoice context to render HTML or PDF, optionally email the payer, and keep an immutable receipt number for audit.

Receipt Service is where payers get proof of payment. From your team's perspective, this is the system that helps you:

- issue a receipt after Invoice Service or Payment Widget completes a payment
- brand the artifact with merchant logo, colors, and legal footer
- control layout with Liquid templates
- deliver the receipt by email and resend it later
- look up receipts by payer, account, invoice, payment, or date range


## What Receipt Service Is (and Is Not)

**Receipt Service is responsible for:**

- Creating receipt records from already-computed payment data
- Generating sequential, immutable receipt numbers per merchant
- Rendering HTML and PDF from Liquid templates plus merchant branding
- Optional delivery through NPS Push Notifications (email)
- Listing, retrieving, and resending stored receipts


**Receipt Service is not responsible for:**

- Defining rates, discounts, or allocations (Billing & Ledger)
- Generating invoices or applying payments to invoices (Invoice Service)
- Authorizing or capturing payments (Payment Widget and Payments)
- Owning payer profiles (Profile Service)
- Replacing your general ledger


## How It Fits Your Workflow

```mermaid
flowchart LR
  Pay[Successful payment] --> Callers
  Callers[Invoice Service or Payment Widget] --> RS[Receipt Service]
  RS --> Store[Store receipt and number]
  Store --> Render[Render HTML or PDF]
  Render --> Send[Optional email delivery]
```

1. A payment succeeds upstream (invoice pay, standalone widget pay, or another caller).
2. The caller sends `POST /receipts` with payment details and, when relevant, the invoices that payment covered.
3. Receipt Service assigns a receipt number, stores the record, and renders the active Liquid template.
4. If send is requested, it emails the payer and records the delivery attempt.
5. Partners retrieve or resend later with `GET /receipts`, `GET /receipts/{receiptId}/render`, `POST /receipts/{receiptId}/deliveries`, or `POST /receipts/{receiptId}/resend`.


Responses use the NPS envelope. HTTP status is typically 200. Branch on `responseCode` (`A100` for success, `E###` for errors), not on the HTTP code.

All paths below are relative to `https://api.nelnetpay.com/receipts`.

## Core Domain Concepts

| Concept | What it is | Why it matters | Key IDs |
|  --- | --- | --- | --- |
| **Receipt** | Payment confirmation document | Customer-facing proof of payment with an audit number | `receiptId`, `receiptNumber` |
| **Receipt number** | Sequential, immutable, non-reusable number per merchant | Independent of invoice numbers; required for audit | format like `RCP-{YEAR}{MONTH}-{SEQ}` |
| **Receipt template** | Liquid markup for the HTML receipt or the email body | Controls layout and which fields print | `templateId` |
| **Receipt config** | Per-merchant branding, numbering, and send defaults | Selects the active templates and auto-send behavior | `entityId` |
| **Delivery** | One send attempt (email) | Tracks SENT vs FAILED and supports resend | delivery id on the receipt |


### Invoice Receipt vs. Standalone Receipt

A receipt can cover invoices, or it can be payment-only.

- **Invoice-based:** Invoice Service (or another caller) includes one or more invoices and the amount applied on this payment. The rendered receipt shows invoice numbers and remaining balance.
- **Standalone:** Payment Widget or another caller omits invoices (one-time or ad-hoc pay). The receipt still shows amount, method, date, and payer.


Both paths use the same `POST /receipts` contract. `payment` is always required. `invoices` is optional.

## Receipt Lifecycle

| Status | Meaning |
|  --- | --- |
| `CREATED` | Stored and numbered. Not yet delivered, or send was not requested. |
| `SENT` | At least one delivery succeeded. |
| `FAILED` | Delivery was attempted and did not succeed. |


Receipt numbers are never reused, including for failed or resent receipts.

Payment method on the receipt is a display summary only (`CARD`, `ACH`, `APPLE_PAY`, `PAYPAL`). Full PAN or account numbers are never stored. Card and ACH show last four (and brand or bank details when the caller supplied them).

## How Clients Typically Use Receipt Service

### Pattern 1: Invoice Payment

- Invoice Service applies the payment, then calls `POST /receipts` with invoices and `sendOptions.send = true`.
- Payer receives the branded receipt. Invoice records can keep `receiptId` for cross-linking.


### Pattern 2: Widget Payment Without an Invoice

- Payment Widget calls `POST /receipts` after a successful standalone charge.
- Pass payer `name` and `email` directly when there is no `personId`.
- Use `sendOptions.send = true` to email, or `false` if the widget only needs the artifact URL or HTML.


### Pattern 3: Preview, Then Publish a Custom Look

- Validate Liquid with `POST /receipts/templates/validate`.
- Create a `DRAFT` template, preview it, activate it, then point config at it.
- Leave system templates in place as fallback.


## Create a Receipt Template

Templates are Liquid, not a portal-only theme. There are two types:

| `type` | Used for |
|  --- | --- |
| `RECEIPT` | The receipt document (HTML, and PDF generated from that HTML) |
| `RECEIPT_EMAIL` | The notification email body |


System templates ship with the service (`isSystem: true`). You can list them. You cannot update or delete them. Custom templates start as `DRAFT`. Rendering and config will only use a custom template after you set `status` to `ACTIVE` and reference it from config.

### Step 1. Validate Liquid (optional, no persist)

`POST /receipts/templates/validate`

```json
{
  "entityId": 1234567890,
  "type": "RECEIPT",
  "content": "<h1>Receipt {{ receipt.receiptNumber }}</h1><p>{{ payment.amount }} {{ payment.currency }}</p>"
}
```

Use this to catch syntax errors before save. Unrecognized variables may come back as warnings.

### Step 2. Create the template

`POST /receipts/templates`

Required: `entityId`, `name`, `type`, `content`. Optional: `description`.

```json
{
  "entityId": 1234567890,
  "name": "Custom branded receipt",
  "type": "RECEIPT",
  "description": "Logo header and invoice table",
  "content": "<!DOCTYPE html><html><body><h1>{{ merchant.merchantName }}</h1><p>Receipt {{ receipt.receiptNumber }}</p><p>{{ payment.amount }} {{ payment.currency }} on {{ payment.processedAt }}</p></body></html>"
}
```

The new row is `DRAFT`. Liquid is validated on create. Content max length is 65536 characters.

### Step 3. Preview with sample data

`POST /receipts/templates/{templateId}/preview`

The response is `text/html`. If you omit `sampleData`, the service fills a sample receipt, payment, person, invoices, merchant, and tags.

```json
{
  "entityId": 1234567890
}
```

### Step 4. Activate

`PUT /receipts/templates/{templateId}`

```json
{
  "entityId": 1234567890,
  "status": "ACTIVE"
}
```

You can also change `name`, `content`, or `description` on this call. Changing `content` re-validates Liquid and increments the template version.

### Step 5. Point merchant config at the template

`PUT /receipts/config`

Set `branding.receiptTemplateId` for the document and `branding.emailTemplateId` for the email. The referenced template must exist, match the expected type (`RECEIPT` vs `RECEIPT_EMAIL`), and be `ACTIVE`. If you omit those IDs, Receipt Service uses the active system template.

```json
{
  "entityId": 1234567890,
  "enabled": true,
  "defaultCurrency": "USD",
  "branding": {
    "merchantName": "Example Childcare",
    "logoUrl": "https://example.com/logo.png",
    "primaryColor": "#003366",
    "receiptTemplateId": "<template-uuid>",
    "emailTemplateId": "<email-template-uuid>"
  },
  "receiptNumberFormat": "RCP-{YEAR}{MONTH}-{SEQ}",
  "receiptNumberPrefix": "RCP",
  "minDigits": 6,
  "autoSendOnPayment": true,
  "defaultSendChannels": ["EMAIL"]
}
```

**Fallback:** if a custom template is missing or not active, rendering uses the system default so receipt delivery is not blocked.

**Delete:** `DELETE /receipts/templates/{templateId}?entityId=` soft-deletes a custom template. If config still points at it, the envelope uses `responseCode` `E409`. Switch config off the template first.

## Liquid Variables

These objects are passed into `RECEIPT` rendering. Missing values render empty. Do not assume a portal field exists unless it is in this list.

| Object | Fields |
|  --- | --- |
| `receipt` | `receiptNumber`, `status`, `createdAt`, `description` |
| `payment` | `amount`, `paymentMethod`, `lastFour`, `processedAt`, `currency`, `authorizationCode`, `status`, `paymentMethodDetails.brand`, `paymentMethodDetails.cardholderName`, `paymentMethodDetails.accountType`, `paymentMethodDetails.accountHolderName` |
| `person` | `personName`, `personEmail`, `personPhone` |
| `account` | `accountId`, `accountName` (only present when the receipt has account context) |
| `invoices[]` | `invoiceId`, `invoiceNumber`, `amountApplied`, `currency`, `invoiceTotalAmount`, `previouslyPaidAmount`, `paidThisPaymentAmount`, `remainingBalance`, `paymentStatus`, `paymentStatusLabel`, `billableEntities[].billableEntityId`, `billableEntities[].billableEntityName` |
| `merchant` | `merchantName`, `logoUrl`, `primaryColor`, `footerText`, `contactSupportEmail`, `contactSupportPhone`, `contactSupportUrl`, `contactDisplayName`, optional `taxIdentifier.type`, `taxIdentifier.maskedValue`, `taxIdentifier.displayLabel` |
| `tags` | string map of tag key to value |


`invoices` is an empty list for standalone receipts. Dates in the live render path are formatted as `MMM d, yyyy h:mm a UTC`.

Always-useful fields to print: amount and currency, payment date, payment method plus last four, payer name, merchant name and contact.

## Create a Receipt

`POST /receipts`

`payment` is required. Identify the payer with `personId` or with `person.name` and `person.email`. Pass `Idempotency-Key` (UUID) when the same payment should not create a second receipt.

Invoice-based example:

```json
{
  "entityId": 1234567890,
  "personId": "11111111-1111-1111-1111-111111111111",
  "account": {
    "accountId": "22222222-2222-2222-2222-222222222222",
    "accountName": "Household account"
  },
  "payment": {
    "paymentId": "pay_456",
    "transactionId": "txn_789",
    "amount": 250.00,
    "currency": "USD",
    "paymentMethod": "CARD",
    "lastFour": "4242",
    "processedAt": "2026-01-19T14:30:00Z",
    "authorizationCode": "AUTH123",
    "paymentMethodDetails": {
      "brand": "VISA",
      "cardholderName": "Jane Doe"
    }
  },
  "invoices": [
    {
      "invoiceId": "33333333-3333-3333-3333-333333333333",
      "invoiceNumber": "INV-2026-000123",
      "amountApplied": 150.00
    }
  ],
  "sendOptions": {
    "send": true,
    "channels": ["EMAIL"]
  }
}
```

Standalone example: omit `invoices`, set `description`, and supply `person` when you do not have a `personId`.

## Retrieve and Deliver

| Method | Path | Purpose |
|  --- | --- | --- |
| GET | `/receipts?entityId=` | List. Filters: `personId`, `accountId`, `invoiceId`, `paymentId`, `status`, `from`/`to` (also `createdAtFrom`/`createdAtTo`) |
| GET | `/receipts/{receiptId}?entityId=` | Structured receipt |
| GET | `/receipts/{receiptId}/render?entityId=&format=HTML` | HTML artifact (default format) |
| GET | `/receipts/{receiptId}/render?entityId=&format=PDF` | PDF from the same HTML |
| GET | `/receipts/{receiptId}/html?entityId=` | Legacy HTML path. Prefer `/render?format=HTML` |
| POST | `/receipts/{receiptId}/deliveries` | Send or add recipients |
| POST | `/receipts/{receiptId}/resend` | Resend with stored recipients |


PDF is rendered on demand from the current HTML. It is not stored as a separate cached file.

List and get return JSON in the NPS envelope. `/render` and `/html` return the artifact body (`text/html` or `application/pdf`).

## Configuration

`GET /receipts/config?entityId=` and `PUT /receipts/config` store receipt settings on Receipt Service itself. OSI Config is only used for service enablement.

| Setting | Role |
|  --- | --- |
| `enabled` | Master switch for receipt generation |
| `defaultCurrency` | ISO 4217, required on upsert |
| `branding.*` | Logo, color, merchant name, active template IDs |
| `receiptNumberFormat` / `receiptNumberPrefix` / `minDigits` | Number pattern. Placeholders: `{PREFIX}`, `{YEAR}`, `{MONTH}`, `{SEQ}` |
| `autoSendOnPayment` | Default send-on-create behavior |
| `defaultSendChannels` | `EMAIL` (and `SMS` when that channel is enabled) |


Receipts and send history are retained for at least 7 years so you can re-render throughout the retention window.