# Invoice a customer and get paid

Create a customer, write them an invoice, issue it, and record the payment when it arrives, so you know what the platform does at each stage of an invoice's life.

9 steps, 6 API calls

**Products:** Invoicing, Customers, Payments

Samples use {{API_KEY}} for your API key and {{BASE_URL}} for this instance's API address. Anything else in double braces is a value an earlier step gave you.

## Set up the customer

### 1. Read the merchant id off a sandbox sale

API call

`POST /api/transactions`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/transactionsCreate.md)

Every invoicing call names the merchant it bills as, and your key belongs to exactly one. There is no call that answers with that id on its own, so read it off the first merchant-scoped record your key creates: here, a card sale at the amount the sandbox always approves. If you have already run another flow on this site, the merchantId on any of its responses is the same value.

**Values this step gives you**

- `{{merchantId}}`: The merchant the sale belongs to, from the response body's merchantId property. Every invoicing call below bills as this merchant.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/transactions" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "cardData": {
      "cardNumber": "4111111111111111",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

.NET:

```csharp
using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");

var response = await http.PostAsJsonAsync("/api/transactions", new
{
    transactionType = "Sale",
    cardData = new
    {
        cardNumber = "4111111111111111",
        nameOnCard = "Jane Doe",
        expirationMonth = 12,
        expirationYear = 2030,
        cvv = 123
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

response.EnsureSuccessStatusCode();

var sale = await response.Content.ReadFromJsonAsync<JsonElement>();
var merchantId = sale.GetProperty("merchantId").GetString();
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
  "transactionType": "Sale",
  "resultCode": "Ok",
  "authorizedAmount": 10.00
}
```

### 2. Create the customer you are invoicing

API call

`POST /api/customers`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/customersCreate.md)

An invoice is addressed to a customer record, so the customer comes first. Give the address list exactly one default entry with a street line, a city, a state and a postal code; the list may be left empty, but a partial address is refused. Keep the id: it's the recipient of the invoice below, and it's also how a customer portal or a payment link knows whose invoice it's showing.

**Values this step gives you**

- `{{customerId}}`: The id of the created customer, from the response body's id property. The invoice names it as its recipient.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/customers" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "{{merchantId}}",
    "name": "Jane Doe",
    "isActive": true,
    "contactDetail": {
      "primaryContactName": "Jane Doe",
      "emailAddress": "jane.doe@example.com",
      "phone": "5555550123",
      "addresses": [
        {
          "address1": "100 Main Street",
          "city": "Minneapolis",
          "state": "MN",
          "zip": "55401",
          "isDefault": true
        }
      ]
    }
  }'
```

.NET:

```csharp
var created = await http.PostAsJsonAsync("/api/customers", new
{
    merchantId,
    name = "Jane Doe",
    isActive = true,
    contactDetail = new
    {
        primaryContactName = "Jane Doe",
        emailAddress = "jane.doe@example.com",
        phone = "5555550123",
        addresses = new[]
        {
            new
            {
                address1 = "100 Main Street",
                city = "Minneapolis",
                state = "MN",
                zip = "55401",
                isDefault = true
            }
        }
    }
});

created.EnsureSuccessStatusCode();

var customer = await created.Content.ReadFromJsonAsync<JsonElement>();
var customerId = customer.GetProperty("id").GetString();
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "7b2c4d6e-8f0a-4c3d-9e5f-6a7b8c9d0e1f",
  "merchantId": "{{merchantId}}",
  "name": "Jane Doe",
  "isActive": true
}
```

## Write the invoice

### 3. Create the invoice as a draft

API call

`POST /api/invoicing/invoices`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/invoiceCrudCreate.md)

Send the biller, the recipient, the currency, the terms and the lines. The platform answers with a draft: totals are computed, the status is Draft, and there is no invoice number yet. A draft is the one stage you can still edit, so this is where your own review or approval step belongs. Lines need only a description, a quantity and a unit price; link one to a catalog product when you sell the same thing again and again, and leave productId off when you don't.

**Values this step gives you**

- `{{invoiceId}}`: The id of the draft, from the response body's id property. Every later call addresses the invoice by it; the invoice number is for people and isn't assigned until issue.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/invoicing/invoices" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "billerId": "{{merchantId}}",
    "billerType": "Merchant",
    "recipientId": "{{customerId}}",
    "recipientType": "Customer",
    "currency": "USD",
    "paymentTermsValue": "Net30",
    "recipientSnapshot": {
      "name": "Jane Doe",
      "email": "jane.doe@example.com"
    },
    "lineItems": [
      { "description": "Website redesign", "quantity": 1, "unitPrice": 1200.00 },
      { "description": "Managed hosting, monthly", "quantity": 12, "unitPrice": 25.00 }
    ]
  }'
```

.NET:

```csharp
var drafted = await http.PostAsJsonAsync("/api/invoicing/invoices", new
{
    billerId = merchantId,
    billerType = "Merchant",
    recipientId = customerId,
    recipientType = "Customer",
    currency = "USD",
    paymentTermsValue = "Net30",
    recipientSnapshot = new { name = "Jane Doe", email = "jane.doe@example.com" },
    lineItems = new[]
    {
        new { description = "Website redesign", quantity = 1m, unitPrice = 1200.00m },
        new { description = "Managed hosting, monthly", quantity = 12m, unitPrice = 25.00m }
    }
});

drafted.EnsureSuccessStatusCode();

var draft = await drafted.Content.ReadFromJsonAsync<JsonElement>();
var invoiceId = draft.GetProperty("id").GetString();
var status = draft.GetProperty("status").GetString(); // "Draft"
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "8c0d2e4f-6a7b-4c9d-8e1f-2a3b4c5d6e7f",
  "billerId": "{{merchantId}}",
  "billerType": "Merchant",
  "recipientId": "{{customerId}}",
  "recipientType": "Customer",
  "status": "Draft",
  "invoiceNumber": null,
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "issueDate": null,
  "dueDate": null,
  "subtotal": 1500.00,
  "taxTotal": 0.00,
  "grandTotal": 1500.00,
  "amountPaid": 0.00,
  "balanceDue": 1500.00,
  "paidAt": null,
  "isLocked": false,
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00,
      "lineTotal": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00,
      "lineTotal": 300.00
    }
  ]
}
```

### 4. Issue the invoice

API call

`POST /api/invoicing/invoices/{{invoiceId}}/issue`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/invoiceLifecycleIssue.md)

Issuing is the point of no return. The platform assigns the invoice number from the biller's sequence, stamps the issue date, computes the due date from the Net 30 terms, freezes the biller and recipient snapshots, and locks the document: the status is Issued and an update from here on is refused. The body is empty; the invoice id in the route is the whole request. Do this when the invoice is final, and not before.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/invoicing/invoices/{{invoiceId}}/issue" \
  -H "api-key: {{API_KEY}}"
```

.NET:

```csharp
var issuing = await http.PostAsync(
    $"/api/invoicing/invoices/{invoiceId}/issue", content: null);

issuing.EnsureSuccessStatusCode();

var issued = await issuing.Content.ReadFromJsonAsync<JsonElement>();
var invoiceNumber = issued.GetProperty("invoiceNumber").GetString();
var balanceDue = issued.GetProperty("balanceDue").GetDecimal();
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "{{invoiceId}}",
  "billerId": "{{merchantId}}",
  "billerType": "Merchant",
  "recipientId": "{{customerId}}",
  "recipientType": "Customer",
  "status": "Issued",
  "invoiceNumber": "INV-00001",
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "issueDate": "2026-02-04T18:22:43.062Z",
  "dueDate": "2026-03-06T18:22:43.062Z",
  "subtotal": 1500.00,
  "taxTotal": 0.00,
  "grandTotal": 1500.00,
  "amountPaid": 0.00,
  "balanceDue": 1500.00,
  "paidAt": null,
  "isLocked": true,
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00,
      "lineTotal": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00,
      "lineTotal": 300.00
    }
  ]
}
```

## Get paid

### 5. Record the payment

API call

`POST /api/invoicing/invoices/{{invoiceId}}/record-payment`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/invoiceLifecycleRecordPayment.md)

When the customer pays outside the platform, by check or by bank transfer, tell the invoice about it. Send the amount received and how it arrived. The platform applies it to the balance and moves the status: to Paid when the balance reaches zero, as here, or to PartiallyPaid when it doesn't. A partial amount is refused unless the invoice allows partial payment, and any amount above the balance is refused outright. A payment the customer makes through a payment link is applied for you by the same rule, so your reconciliation reads one status whichever way the money came.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/invoicing/invoices/{{invoiceId}}/record-payment" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1500.00,
    "paymentMethod": "Check",
    "notes": "Check received by mail."
  }'
```

.NET:

```csharp
var recording = await http.PostAsJsonAsync(
    $"/api/invoicing/invoices/{invoiceId}/record-payment",
    new
    {
        amount = 1500.00m,
        paymentMethod = "Check",
        notes = "Check received by mail."
    });

recording.EnsureSuccessStatusCode();

var paid = await recording.Content.ReadFromJsonAsync<JsonElement>();
var paidStatus = paid.GetProperty("status").GetString(); // "Paid"
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "{{invoiceId}}",
  "billerId": "{{merchantId}}",
  "billerType": "Merchant",
  "recipientId": "{{customerId}}",
  "recipientType": "Customer",
  "status": "Paid",
  "invoiceNumber": "INV-00001",
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "issueDate": "2026-02-04T18:22:43.062Z",
  "dueDate": "2026-03-06T18:22:43.062Z",
  "subtotal": 1500.00,
  "taxTotal": 0.00,
  "grandTotal": 1500.00,
  "amountPaid": 1500.00,
  "balanceDue": 0.00,
  "paidAt": "2026-02-04T18:22:43.062Z",
  "isLocked": true,
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00,
      "lineTotal": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00,
      "lineTotal": 300.00
    }
  ]
}
```

### 6. Read the invoice back

API call

`GET /api/invoicing/invoices/{{invoiceId}}`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/invoiceCrudGet.md)

Read the invoice you just settled. The status is Paid, the balance due is zero, the amount paid equals the grand total, and paidAt records when the balance cleared. Reconcile against this record rather than against the response you got from recording the payment, so a request that timed out on your side still has somewhere to recover the outcome from.

cURL:

```bash
curl "{{BASE_URL}}/api/invoicing/invoices/{{invoiceId}}" \
  -H "api-key: {{API_KEY}}"
```

.NET:

```csharp
var invoice = await http.GetFromJsonAsync<JsonElement>(
    $"/api/invoicing/invoices/{invoiceId}");

var settled = invoice.GetProperty("status").GetString() == "Paid"
              && invoice.GetProperty("balanceDue").GetDecimal() == 0m;
var paidAt = invoice.GetProperty("paidAt").GetString();
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "{{invoiceId}}",
  "billerId": "{{merchantId}}",
  "billerType": "Merchant",
  "recipientId": "{{customerId}}",
  "recipientType": "Customer",
  "status": "Paid",
  "invoiceNumber": "INV-00001",
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "issueDate": "2026-02-04T18:22:43.062Z",
  "dueDate": "2026-03-06T18:22:43.062Z",
  "subtotal": 1500.00,
  "taxTotal": 0.00,
  "grandTotal": 1500.00,
  "amountPaid": 1500.00,
  "balanceDue": 0.00,
  "paidAt": "2026-02-04T18:22:43.062Z",
  "isLocked": true,
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00,
      "lineTotal": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00,
      "lineTotal": 300.00
    }
  ]
}
```

## Go further

### 7. Send the invoice to the customer

On your side

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/invoiceLifecycleSend.md)

In production you send the invoice rather than recording a payment by hand. The send call runs the whole delivery workflow: it issues the invoice if it's still a draft, creates a payment link and a view token for the recipient, marks the status Sent, and delivers the notification. That last part needs a merchant with a configured email destination, which a fresh sandbox may not have, and it's why this flow records a payment as its runnable path instead. Once you have configured delivery, call send from your own integration and let the payment link do the collecting.

### 8. Download the invoice as a PDF

On your side

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/invoicePdfDownload.md)

Any issued invoice can be rendered as a PDF from the snapshots and lines frozen at issue. Fetch it when your customer asks for a copy or when your own records need the document rather than the data. The response is the file itself, not JSON.

### 9. Offer a payment plan

On your side

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/invoicePaymentPlanCreate.md)

A larger invoice can be split into scheduled installments the platform collects against a stored payment method. Create the plan on an issued invoice; each installment is applied to the balance the same way the payment above was, so the invoice reaches Paid when the last one clears. Payment plans, credit notes and recurring invoices each have their own reference pages.

## Run the whole flow as one script

Every step above in one script you can copy and run. Replace {{API_KEY}} with your own API key and {{BASE_URL}} with this instance's API address, and set any value the script asks you for at the top. A step that happens outside the API stays a comment.

### cURL

```bash
brew install jq
```

```bash
#!/usr/bin/env bash
# Invoice a customer and get paid
#
# Create a customer, write them an invoice, issue it, and record the payment when it arrives, so you
# know what the platform does at each stage of an invoice's life.
#
# Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
# it. A step that happens outside the API is a comment, and any failed call stops the script.

set -euo pipefail

BASE_URL="{{BASE_URL}}"
API_KEY="{{API_KEY}}"

# Sends one request and prints the response body. A failed call prints the API's answer and stops
# the script.
call() {
  local method="$1" path="$2" body="${3:-}" out
  local args=(-sS --fail-with-body -X "$method" "$BASE_URL$path" -H "api-key: $API_KEY")
  if [ -n "$body" ]; then
    args+=(-H "Content-Type: application/json" -d "$body")
  fi
  if ! out=$(curl "${args[@]}"); then
    printf '%s\n' "$out" >&2
    return 1
  fi
  printf '%s' "$out"
}

# Percent-encodes a value for use in a URL.
urlencode() {
  jq -rn --arg value "$1" '$value | @uri'
}

# Phase 1: Set up the customer

# Step 1: Read the merchant id off a sandbox sale
step1=$(call POST "/api/transactions" '{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}')
merchantId=$(jq -er '.merchantId | if . == null then error("The response carried no value for merchantId.") else tostring end' <<< "$step1")

# Step 2: Create the customer you are invoicing
body=$(jq -n --arg merchantId "$merchantId" '{
  "merchantId": $merchantId,
  "name": "Jane Doe",
  "isActive": true,
  "contactDetail": {
    "primaryContactName": "Jane Doe",
    "emailAddress": "jane.doe@example.com",
    "phone": "5555550123",
    "addresses": [
      {
        "address1": "100 Main Street",
        "city": "Minneapolis",
        "state": "MN",
        "zip": "55401",
        "isDefault": true
      }
    ]
  }
}')
step2=$(call POST "/api/customers" "$body")
customerId=$(jq -er '.id | if . == null then error("The response carried no value for customerId.") else tostring end' <<< "$step2")

# Phase 2: Write the invoice

# Step 3: Create the invoice as a draft
body=$(jq -n --arg merchantId "$merchantId" --arg customerId "$customerId" '{
  "billerId": $merchantId,
  "billerType": "Merchant",
  "recipientId": $customerId,
  "recipientType": "Customer",
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "recipientSnapshot": {
    "name": "Jane Doe",
    "email": "jane.doe@example.com"
  },
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00
    }
  ]
}')
step3=$(call POST "/api/invoicing/invoices" "$body")
invoiceId=$(jq -er '.id | if . == null then error("The response carried no value for invoiceId.") else tostring end' <<< "$step3")

# Step 4: Issue the invoice
call POST "/api/invoicing/invoices/$(urlencode "$invoiceId")/issue" > /dev/null

# Phase 3: Get paid

# Step 5: Record the payment
call POST "/api/invoicing/invoices/$(urlencode "$invoiceId")/record-payment" '{
  "amount": 1500.00,
  "paymentMethod": "Check",
  "notes": "Check received by mail."
}' > /dev/null

# Step 6: Read the invoice back
call GET "/api/invoicing/invoices/$(urlencode "$invoiceId")" > /dev/null

# Phase 4: Go further

# Step 7: Send the invoice to the customer
# In production you send the invoice rather than recording a payment by hand. The send call runs the
# whole delivery workflow: it issues the invoice if it's still a draft, creates a payment link and a
# view token for the recipient, marks the status Sent, and delivers the notification. That last part
# needs a merchant with a configured email destination, which a fresh sandbox may not have, and it's
# why this flow records a payment as its runnable path instead. Once you have configured delivery,
# call send from your own integration and let the payment link do the collecting.

# Step 8: Download the invoice as a PDF
# Any issued invoice can be rendered as a PDF from the snapshots and lines frozen at issue. Fetch it
# when your customer asks for a copy or when your own records need the document rather than the
# data. The response is the file itself, not JSON.

# Step 9: Offer a payment plan
# A larger invoice can be split into scheduled installments the platform collects against a stored
# payment method. Create the plan on an issued invoice; each installment is applied to the balance
# the same way the payment above was, so the invoice reaches Paid when the last one clears. Payment
# plans, credit notes and recurring invoices each have their own reference pages.
```

### PowerShell

```powershell
# Invoice a customer and get paid
#
# Create a customer, write them an invoice, issue it, and record the payment when it arrives, so you
# know what the platform does at each stage of an invoice's life.
#
# Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
# it. A step that happens outside the API is a comment, and any failed call stops the script.

$ErrorActionPreference = 'Stop'

$baseUrl = '{{BASE_URL}}'
$apiKey = '{{API_KEY}}'

# Sends one request and returns the parsed response body. A failed call stops the script.
function Invoke-BlueprintCall([string] $Method, [string] $Path, [string] $Body) {
    $arguments = @{
        Method  = $Method
        Uri     = $baseUrl + $Path
        Headers = @{ 'api-key' = $apiKey }
    }
    if ($Body) {
        $arguments.ContentType = 'application/json'
        $arguments.Body = [System.Text.Encoding]::UTF8.GetBytes($Body)
    }
    Invoke-RestMethod @arguments
}

# Turns a value read off a response back into the text the API sent. A missing value stops the
# script.
function ConvertTo-CaptureValue($Value, [string] $Name) {
    if ($null -eq $Value) { throw "The response carried no value for $Name." }
    if ($Value -is [datetime] -and $Value.Kind -eq 'Unspecified') { return $Value.ToString('yyyy-MM-ddTHH:mm:ss.FFFFFFF', [cultureinfo]::InvariantCulture) }
    if ($Value -is [datetime]) { return $Value.ToUniversalTime().ToString('o') }
    if ($Value -is [bool]) { return $Value.ToString().ToLowerInvariant() }
    [string]$Value
}

# Phase 1: Set up the customer

# Step 1: Read the merchant id off a sandbox sale
$body = @'
{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}
'@
$step1 = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body
$merchantId = ConvertTo-CaptureValue $step1.merchantId 'merchantId'

# Step 2: Create the customer you are invoicing
$body = @"
{
  "merchantId": $(ConvertTo-Json -InputObject ([string]($merchantId))),
  "name": "Jane Doe",
  "isActive": true,
  "contactDetail": {
    "primaryContactName": "Jane Doe",
    "emailAddress": "jane.doe@example.com",
    "phone": "5555550123",
    "addresses": [
      {
        "address1": "100 Main Street",
        "city": "Minneapolis",
        "state": "MN",
        "zip": "55401",
        "isDefault": true
      }
    ]
  }
}
"@
$step2 = Invoke-BlueprintCall -Method 'POST' -Path '/api/customers' -Body $body
$customerId = ConvertTo-CaptureValue $step2.id 'customerId'

# Phase 2: Write the invoice

# Step 3: Create the invoice as a draft
$body = @"
{
  "billerId": $(ConvertTo-Json -InputObject ([string]($merchantId))),
  "billerType": "Merchant",
  "recipientId": $(ConvertTo-Json -InputObject ([string]($customerId))),
  "recipientType": "Customer",
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "recipientSnapshot": {
    "name": "Jane Doe",
    "email": "jane.doe@example.com"
  },
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00
    }
  ]
}
"@
$step3 = Invoke-BlueprintCall -Method 'POST' -Path '/api/invoicing/invoices' -Body $body
$invoiceId = ConvertTo-CaptureValue $step3.id 'invoiceId'

# Step 4: Issue the invoice
$null = Invoke-BlueprintCall -Method 'POST' -Path "/api/invoicing/invoices/$([uri]::EscapeDataString($invoiceId))/issue"

# Phase 3: Get paid

# Step 5: Record the payment
$body = @'
{
  "amount": 1500.00,
  "paymentMethod": "Check",
  "notes": "Check received by mail."
}
'@
$null = Invoke-BlueprintCall -Method 'POST' -Path "/api/invoicing/invoices/$([uri]::EscapeDataString($invoiceId))/record-payment" -Body $body

# Step 6: Read the invoice back
$null = Invoke-BlueprintCall -Method 'GET' -Path "/api/invoicing/invoices/$([uri]::EscapeDataString($invoiceId))"

# Phase 4: Go further

# Step 7: Send the invoice to the customer
# In production you send the invoice rather than recording a payment by hand. The send call runs the
# whole delivery workflow: it issues the invoice if it's still a draft, creates a payment link and a
# view token for the recipient, marks the status Sent, and delivers the notification. That last part
# needs a merchant with a configured email destination, which a fresh sandbox may not have, and it's
# why this flow records a payment as its runnable path instead. Once you have configured delivery,
# call send from your own integration and let the payment link do the collecting.

# Step 8: Download the invoice as a PDF
# Any issued invoice can be rendered as a PDF from the snapshots and lines frozen at issue. Fetch it
# when your customer asks for a copy or when your own records need the document rather than the
# data. The response is the file itself, not JSON.

# Step 9: Offer a payment plan
# A larger invoice can be split into scheduled installments the platform collects against a stored
# payment method. Create the plan on an issued invoice; each installment is applied to the balance
# the same way the payment above was, so the invoice reaches Paid when the last one clears. Payment
# plans, credit notes and recurring invoices each have their own reference pages.
```

### TypeScript

```typescript
// Invoice a customer and get paid
//
// Create a customer, write them an invoice, issue it, and record the payment when it arrives, so
// you know what the platform does at each stage of an invoice's life.
//
// Every API call in this blueprint, in order. Each value a call returns is passed to the calls
// after it. A step that happens outside the API is a comment, and any failed call stops the script.

export {};

const baseUrl = '{{BASE_URL}}';
const apiKey = '{{API_KEY}}';

// Sends one request and returns the parsed response body. A failed call throws.
async function call(method: string, path: string, body?: unknown): Promise<any> {
  const headers: Record<string, string> = { 'api-key': apiKey };
  if (body !== undefined) {
    headers['Content-Type'] = 'application/json';
  }

  const response = await fetch(baseUrl + path, {
    method,
    headers,
    body: body === undefined ? undefined : JSON.stringify(body),
  });

  const text = await response.text();
  if (!response.ok) {
    throw new Error(`${method} ${path} answered ${response.status}: ${text}`);
  }

  return text ? JSON.parse(text) : null;
}

// Turns a value read off a response into the text the API sent. A missing value throws.
function capture(value: unknown, name: string): string {
  if (value === undefined || value === null) {
    throw new Error(`The response carried no value for ${name}.`);
  }

  return typeof value === 'string' ? value : JSON.stringify(value);
}

// Phase 1: Set up the customer

// Step 1: Read the merchant id off a sandbox sale
const step1 = await call('POST', '/api/transactions', {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
});
const merchantId = capture(step1.merchantId, 'merchantId');

// Step 2: Create the customer you are invoicing
const step2 = await call('POST', '/api/customers', {
  "merchantId": merchantId,
  "name": "Jane Doe",
  "isActive": true,
  "contactDetail": {
    "primaryContactName": "Jane Doe",
    "emailAddress": "jane.doe@example.com",
    "phone": "5555550123",
    "addresses": [
      {
        "address1": "100 Main Street",
        "city": "Minneapolis",
        "state": "MN",
        "zip": "55401",
        "isDefault": true
      }
    ]
  }
});
const customerId = capture(step2.id, 'customerId');

// Phase 2: Write the invoice

// Step 3: Create the invoice as a draft
const step3 = await call('POST', '/api/invoicing/invoices', {
  "billerId": merchantId,
  "billerType": "Merchant",
  "recipientId": customerId,
  "recipientType": "Customer",
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "recipientSnapshot": {
    "name": "Jane Doe",
    "email": "jane.doe@example.com"
  },
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00
    }
  ]
});
const invoiceId = capture(step3.id, 'invoiceId');

// Step 4: Issue the invoice
await call('POST', `/api/invoicing/invoices/${encodeURIComponent(invoiceId)}/issue`);

// Phase 3: Get paid

// Step 5: Record the payment
await call('POST', `/api/invoicing/invoices/${encodeURIComponent(invoiceId)}/record-payment`, {
  "amount": 1500.00,
  "paymentMethod": "Check",
  "notes": "Check received by mail."
});

// Step 6: Read the invoice back
await call('GET', `/api/invoicing/invoices/${encodeURIComponent(invoiceId)}`);

// Phase 4: Go further

// Step 7: Send the invoice to the customer
// In production you send the invoice rather than recording a payment by hand. The send call runs
// the whole delivery workflow: it issues the invoice if it's still a draft, creates a payment link
// and a view token for the recipient, marks the status Sent, and delivers the notification. That
// last part needs a merchant with a configured email destination, which a fresh sandbox may not
// have, and it's why this flow records a payment as its runnable path instead. Once you have
// configured delivery, call send from your own integration and let the payment link do the
// collecting.

// Step 8: Download the invoice as a PDF
// Any issued invoice can be rendered as a PDF from the snapshots and lines frozen at issue. Fetch
// it when your customer asks for a copy or when your own records need the document rather than the
// data. The response is the file itself, not JSON.

// Step 9: Offer a payment plan
// A larger invoice can be split into scheduled installments the platform collects against a stored
// payment method. Create the plan on an issued invoice; each installment is applied to the balance
// the same way the payment above was, so the invoice reaches Paid when the last one clears. Payment
// plans, credit notes and recurring invoices each have their own reference pages.
```

### C#

```csharp
// Invoice a customer and get paid
//
// Create a customer, write them an invoice, issue it, and record the payment when it arrives, so
// you know what the platform does at each stage of an invoice's life.
//
// Every API call in this blueprint, in order. Each value a call returns is passed to the calls
// after it. A step that happens outside the API is a comment, and any failed call stops the script.

using System.Text;
using System.Text.Json;

var baseUrl = "{{BASE_URL}}";
var apiKey = "{{API_KEY}}";

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("api-key", apiKey);

// Sends one request and returns the parsed response body. A failed call throws.
async Task<JsonElement> CallAsync(string method, string path, string? body = null)
{
    using var request = new HttpRequestMessage(new HttpMethod(method), baseUrl + path);
    if (body is not null)
    {
        request.Content = new StringContent(body, Encoding.UTF8, "application/json");
    }

    using var response = await http.SendAsync(request);
    var json = await response.Content.ReadAsStringAsync();
    if (!response.IsSuccessStatusCode)
    {
        throw new HttpRequestException($"{method} {path} answered {(int)response.StatusCode}: {json}");
    }

    return json.Length == 0 ? default : JsonSerializer.Deserialize<JsonElement>(json);
}

// Turns a value read off a response into the text the API sent. A missing value throws.
static string Capture(JsonElement value, string name) => value.ValueKind switch
{
    JsonValueKind.String => value.GetString()!,
    JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False => value.GetRawText(),
    _ => throw new InvalidOperationException($"The response carried no value for {name}.")
};

// Phase 1: Set up the customer

// Step 1: Read the merchant id off a sandbox sale
var step1 = await CallAsync("POST", "/api/transactions", """
    {
      "transactionType": "Sale",
      "cardData": {
        "cardNumber": "4111111111111111",
        "nameOnCard": "Jane Doe",
        "expirationMonth": 12,
        "expirationYear": 2030,
        "cvv": 123
      },
      "invoiceData": {
        "amounts": { "base": 10.00, "total": 10.00 }
      }
    }
    """);
var merchantId = Capture(step1.GetProperty("merchantId"), "merchantId");

// Step 2: Create the customer you are invoicing
var step2 = await CallAsync("POST", "/api/customers", $$"""
    {
      "merchantId": {{JsonSerializer.Serialize(merchantId)}},
      "name": "Jane Doe",
      "isActive": true,
      "contactDetail": {
        "primaryContactName": "Jane Doe",
        "emailAddress": "jane.doe@example.com",
        "phone": "5555550123",
        "addresses": [
          {
            "address1": "100 Main Street",
            "city": "Minneapolis",
            "state": "MN",
            "zip": "55401",
            "isDefault": true
          }
        ]
      }
    }
    """);
var customerId = Capture(step2.GetProperty("id"), "customerId");

// Phase 2: Write the invoice

// Step 3: Create the invoice as a draft
var step3 = await CallAsync("POST", "/api/invoicing/invoices", $$"""
    {
      "billerId": {{JsonSerializer.Serialize(merchantId)}},
      "billerType": "Merchant",
      "recipientId": {{JsonSerializer.Serialize(customerId)}},
      "recipientType": "Customer",
      "currency": "USD",
      "paymentTermsValue": "Net30",
      "recipientSnapshot": {
        "name": "Jane Doe",
        "email": "jane.doe@example.com"
      },
      "lineItems": [
        {
          "description": "Website redesign",
          "quantity": 1,
          "unitPrice": 1200.00
        },
        {
          "description": "Managed hosting, monthly",
          "quantity": 12,
          "unitPrice": 25.00
        }
      ]
    }
    """);
var invoiceId = Capture(step3.GetProperty("id"), "invoiceId");

// Step 4: Issue the invoice
await CallAsync("POST", $"/api/invoicing/invoices/{Uri.EscapeDataString(invoiceId)}/issue");

// Phase 3: Get paid

// Step 5: Record the payment
await CallAsync("POST", $"/api/invoicing/invoices/{Uri.EscapeDataString(invoiceId)}/record-payment", """
    {
      "amount": 1500.00,
      "paymentMethod": "Check",
      "notes": "Check received by mail."
    }
    """);

// Step 6: Read the invoice back
await CallAsync("GET", $"/api/invoicing/invoices/{Uri.EscapeDataString(invoiceId)}");

// Phase 4: Go further

// Step 7: Send the invoice to the customer
// In production you send the invoice rather than recording a payment by hand. The send call runs
// the whole delivery workflow: it issues the invoice if it's still a draft, creates a payment link
// and a view token for the recipient, marks the status Sent, and delivers the notification. That
// last part needs a merchant with a configured email destination, which a fresh sandbox may not
// have, and it's why this flow records a payment as its runnable path instead. Once you have
// configured delivery, call send from your own integration and let the payment link do the
// collecting.

// Step 8: Download the invoice as a PDF
// Any issued invoice can be rendered as a PDF from the snapshots and lines frozen at issue. Fetch
// it when your customer asks for a copy or when your own records need the document rather than the
// data. The response is the file itself, not JSON.

// Step 9: Offer a payment plan
// A larger invoice can be split into scheduled installments the platform collects against a stored
// payment method. Create the plan on an issued invoice; each installment is applied to the balance
// the same way the payment above was, so the invoice reaches Paid when the last one clears. Payment
// plans, credit notes and recurring invoices each have their own reference pages.
```

### Python

```bash
pip install requests
```

```python
# Invoice a customer and get paid
#
# Create a customer, write them an invoice, issue it, and record the payment when it arrives, so you
# know what the platform does at each stage of an invoice's life.
#
# Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
# it. A step that happens outside the API is a comment, and any failed call stops the script.

from urllib.parse import quote

import json
import requests

BASE_URL = "{{BASE_URL}}"
API_KEY = "{{API_KEY}}"


# Sends one request and returns the parsed response body. A failed call raises.
def call(method, path, body=None):
    response = requests.request(
        method,
        BASE_URL + path,
        headers={"api-key": API_KEY},
        json=body,
    )
    response.raise_for_status()
    return response.json() if response.content else None


# Turns a value read off a response into the text the API sent. A missing value raises.
def capture(value, name):
    if value is None:
        raise ValueError(f"The response carried no value for {name}.")
    return value if isinstance(value, str) else json.dumps(value)


# Phase 1: Set up the customer

# Step 1: Read the merchant id off a sandbox sale
step1 = call("POST", "/api/transactions", {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
})
merchant_id = capture(step1["merchantId"], "merchantId")

# Step 2: Create the customer you are invoicing
step2 = call("POST", "/api/customers", {
  "merchantId": merchant_id,
  "name": "Jane Doe",
  "isActive": True,
  "contactDetail": {
    "primaryContactName": "Jane Doe",
    "emailAddress": "jane.doe@example.com",
    "phone": "5555550123",
    "addresses": [
      {
        "address1": "100 Main Street",
        "city": "Minneapolis",
        "state": "MN",
        "zip": "55401",
        "isDefault": True
      }
    ]
  }
})
customer_id = capture(step2["id"], "customerId")

# Phase 2: Write the invoice

# Step 3: Create the invoice as a draft
step3 = call("POST", "/api/invoicing/invoices", {
  "billerId": merchant_id,
  "billerType": "Merchant",
  "recipientId": customer_id,
  "recipientType": "Customer",
  "currency": "USD",
  "paymentTermsValue": "Net30",
  "recipientSnapshot": {
    "name": "Jane Doe",
    "email": "jane.doe@example.com"
  },
  "lineItems": [
    {
      "description": "Website redesign",
      "quantity": 1,
      "unitPrice": 1200.00
    },
    {
      "description": "Managed hosting, monthly",
      "quantity": 12,
      "unitPrice": 25.00
    }
  ]
})
invoice_id = capture(step3["id"], "invoiceId")

# Step 4: Issue the invoice
call("POST", f"/api/invoicing/invoices/{quote(invoice_id, safe='')}/issue")

# Phase 3: Get paid

# Step 5: Record the payment
call("POST", f"/api/invoicing/invoices/{quote(invoice_id, safe='')}/record-payment", {
  "amount": 1500.00,
  "paymentMethod": "Check",
  "notes": "Check received by mail."
})

# Step 6: Read the invoice back
call("GET", f"/api/invoicing/invoices/{quote(invoice_id, safe='')}")

# Phase 4: Go further

# Step 7: Send the invoice to the customer
# In production you send the invoice rather than recording a payment by hand. The send call runs the
# whole delivery workflow: it issues the invoice if it's still a draft, creates a payment link and a
# view token for the recipient, marks the status Sent, and delivers the notification. That last part
# needs a merchant with a configured email destination, which a fresh sandbox may not have, and it's
# why this flow records a payment as its runnable path instead. Once you have configured delivery,
# call send from your own integration and let the payment link do the collecting.

# Step 8: Download the invoice as a PDF
# Any issued invoice can be rendered as a PDF from the snapshots and lines frozen at issue. Fetch it
# when your customer asks for a copy or when your own records need the document rather than the
# data. The response is the file itself, not JSON.

# Step 9: Offer a payment plan
# A larger invoice can be split into scheduled installments the platform collects against a stored
# payment method. Create the plan on an issued invoice; each installment is applied to the balance
# the same way the payment above was, so the invoice reaches Paid when the last one clears. Payment
# plans, credit notes and recurring invoices each have their own reference pages.
```

- [Blueprints](https://devportal-simpay-sbx.winkpg.io/docs/blueprints.md): every blueprint this instance publishes.

## See also

- [All documentation](https://devportal-simpay-sbx.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
