Proforma orders
A proforma order is a priced offer that comes before the order: a pro-forma invoice. It freezes the prices of a basket, reserves the stock, and gets a number and a bank payment reference. The customer pays it, your warehouse confirms what it can deliver, and you convert it into an order. A proforma that is not paid in time expires.
Lifecycle#
A proforma is made from a basket that checkout has priced, with its promotions and benefits applied: the order evaluation, named by order_processing_id. From there it moves through these statuses:
| Status | What it means |
|---|---|
| draft | Created. Prices are frozen, stock is reserved, the amount is owed. |
| awaiting_payment | A card payment is in progress, for example waiting for 3-D Secure. |
| paid | What was paid covers the total. Partial payments add up until it does. |
| confirmed | The warehouse confirmed every line in full. |
| partial_confirmed | The warehouse confirmed less on some lines. The proforma was repriced and the difference refunded. |
| converted | Turned into an order. Final. |
| canceled | Cancelled; captured payments were refunded and the stock released. Final. |
| expired | Not finished in time and cancelled automatically, without a message to the customer. Final. |
A proforma lives 72 hours unless your app is set otherwise; expires_at says when. Its lines, prices, customer and delivery cannot be changed: to change them, cancel the proforma and make a new one. Only its tags, comment and alerts can be edited.
The proforma in lists#
What GET /proforma_orders returns for each proforma. Amounts are numbers in the proforma's currency.
| Field | Description |
|---|---|
| idinteger | Our id of the proforma. |
| proforma_numberstring | Its number, such as P-2026-7KQ2XD. Unique within your app, and not sequential. |
| proforma_status_id, proforma_status_name, proforma_status_color, proforma_status_final | The status. |
| processing_datedatetime | When the prices were frozen. |
| expires_at, expires_in_seconds | When the proforma expires, and how long is left. |
| customer_id, customer_email, customer_first_name, customer_last_name | The customer. anonymous is true for a guest. |
| sales, discount, net_sales, vat, end_salesnumber | Gross, discount, net, VAT and the total to pay. |
| paid, debtnumber | What has been paid and what is still owed. |
| market_id, currency_id, sales_channel_id, sales_channel | Where the proforma was made. |
| warehouse_confirmed_at, converted_at, order_idnullable | When the warehouse confirmed it, when it was converted, and the order it became. |
| canceled, canceled_at, cancel_reason | Whether, when and why it was cancelled. |
| tags, alerts_count, open_alerts_count | Your tags, and how many alerts it has and how many are unresolved. An alert records something that needs a person, such as a refund that failed. |
| inserted_at, updated_atdatetime · ISO 8601, UTC | When it was created and last changed. Sent without an offset. |
List proformas#
Your app's proformas, a page at a time. Use the filters to find what needs action: proformas about to expire, paid ones waiting for the warehouse, ones with open alerts.
Query parameters
| Parameter | Description |
|---|---|
| statusstring | One status name, or several separated by commas: status=paid,confirmed. |
| expiring_withininteger | Open proformas that expire within this many hours. |
| has_alertsboolean | true: at least one unresolved alert. false: none. |
| anonymousboolean | Only guest proformas, or only customers' ones. |
| customer_idinteger | One customer's proformas. |
| date_from, date_todate or datetime | By processing_date, inclusive. A date alone covers the whole day. |
| orderBystring | expires_at, end_sales, paid, debt, customer_name, processing_date, inserted_at, updated_at, proforma_number or status, optionally followed by asc or desc. |
| offset, limitinteger · default 0, 30 | Paging. See Pagination & filtering. |
A filter value we cannot use is ignored rather than refused.
Returns
data: a list of proformas. GET /proforma_orders/stats returns the counts behind a dashboard instead: open proformas and their sum, those awaiting payment, those expiring within 24 hours, paid but not converted, converted in the last 7 days, and the conversion rate between date_from and date_to (default: the last 30 days).
curl -G "https://api.morffeus.com/api/v2/proforma_orders" \ -H "Authorization: Bearer $MORFFEUS_TOKEN" \ -d "status=paid" -d "orderBy=expires_at"
{
"data": [
{
"id": 2207,
"proforma_number": "P-2026-7KQ2XD",
"proforma_status_name": "paid",
"proforma_status_final": false,
"processing_date": "2026-10-01T09:12:40",
"expires_at": "2026-10-04T09:12:40",
"expires_in_seconds": 203400,
"customer_id": 5521,
"end_sales": 5490.0,
"paid": 5490.0,
"debt": 0.0,
"order_id": null,
"open_alerts_count": 0,
…
}
]
}
Read one proforma#
One proforma in full, shaped like an order so the same code can read both.
Returns
data: the proforma's fields and amounts, plus:
itemsandfee_items: the lines, each with the quantity requested and, after the warehouse step, the quantity confirmed.payments: each payment withamount,payment_method,payment_status,payment_dateandfinalized.payment_instructions: what a bank transfer needs: the beneficiary and bank account your app has set up, the amount still owed, and the payment reference (model97).tax_brackets,transaction_reference,delivery_area, andalertswith their resolution.converted_order_numberonce it is converted.
A proforma that is not in your app returns 404 with an empty body.
{
"data": {
"id": 2207,
"proforma_number": "P-2026-7KQ2XD",
"proforma_status": "paid",
"order_processing_id": 88123,
"end_sales": 5490.0,
"paid": 5490.0,
"payments": [
{
"amount": 5490.0,
"payment_method": "Bank transfer",
"payment_status": "Captured",
"finalized": true,
…
}
],
"payment_instructions": { … },
"alerts": [],
…
}
}
Create a proforma#
Turns an order evaluation into a proforma. The prices, promotions and benefits of the evaluation are frozen, the stock is reserved, and the evaluation is used up: it cannot make a second proforma or an order.
Body parameters
| Parameter | Description |
|---|---|
| order_processing_idrequiredinteger | The order evaluation, from checkout. Not wrapped in an object. |
Returns
201 and the proforma in data, in the shape of Read one proforma.
Errors
| Status | When |
|---|---|
| 422 | The evaluation does not exist or was used: API.OrderProcessings.StaleEvaluation. It belongs to another app: API.ProformaOrders.WrongApp. A product has no VAT: API.Products.MissingVat. A guest's delivery data is incomplete: API.Orders.GuestDeliveryDataIncomplete. A pickup point is missing or unavailable: API.ShipmentMethods.PickupPointRequired, API.ShipmentMethods.PickupPointUnavailable. There is not enough stock: Insufficient stock for product: …. |
curl -X POST "https://api.morffeus.com/api/v2/proforma_orders" \ -H "Authorization: Bearer $MORFFEUS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "order_processing_id": 88123 }'
{
"errors": {
"OrderProcessings": ["API.OrderProcessings.StaleEvaluation"]
}
}
Pay, confirm, convert#
The three steps that move a proforma forward. Each returns the proforma, or for convert the order, and fails with API.ProformaOrders.AlreadyFinal on a proforma that is converted, cancelled or expired.
| Call | Description |
|---|---|
| POST /proforma_orders/{id}/pay | Records a payment. Body: {"payment": {"payment_method_id": …, "amount": …}}. An amount of 0 or less pays the whole remaining debt. Use it for payments that settle at once, such as cash or a bank transfer you received; card payments that need the customer's browser go through the storefront checkout. When the payments cover the total, the status becomes paid. |
| POST /proforma_orders/{id}/warehouse_confirm | Confirms what the warehouse can deliver. Body: {"adjustments": [{"proforma_order_item_id": …, "qty_confirmed": …}]}. Lines you leave out are confirmed in full. When a line is reduced, the proforma is repriced with the promotions it had when it was made, the stock no longer needed is released, the overpaid difference is refunded, and the status becomes partial_confirmed; otherwise confirmed. |
| POST /proforma_orders/{id}/convert | Creates the order: its lines carry the confirmed quantities, the payments move to the order, and the customer's earned benefits are booked. The stock stays reserved as it was. Calling it again returns the same order. The response is the order's record in data. |
Errors
| Status | When |
|---|---|
| 404 | No such proforma in your app. The body is empty. |
| 422 | The proforma is final: API.ProformaOrders.AlreadyFinal. The payment method is not supported: API.PaymentMethod.NotSupported. The payment is larger than the debt, or the debt is already paid: Payment amount exceeds order debt, Order debt is already paid. A cash payment needs an amount: API.Payments.CashAmountRequired. |
A refund that fails during the warehouse step does not fail the call: it is added to the proforma's alerts for someone to follow up.
curl -X POST "https://api.morffeus.com/api/v2/proforma_orders/2207/warehouse_confirm" \ -H "Authorization: Bearer $MORFFEUS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "adjustments": [ { "proforma_order_item_id": 9911, "qty_confirmed": 1 } ] }'
{
"errors": {
"ProformaOrders": ["API.ProformaOrders.AlreadyFinal"]
}
}
Tag, comment or cancel#
| Call | Description |
|---|---|
| PUT /proforma_orders/{id}, PATCH | Changes the only editable parts. Body: tags (a list of strings, which replaces the old one), comment, and alerts to resolve, each as {"index": …, "resolved": true}. Tags and comment cannot change on a final proforma; alerts can always be resolved. Returns the proforma. |
| DELETE /proforma_orders/{id} | Cancels the proforma: captured payments are refunded, the stock and any held benefits are released, and the status becomes canceled. Optional body: reason. Returns 204 with no body. |
Errors
| Status | When |
|---|---|
| 404 | No such proforma in your app. |
| 422 | The proforma is final: API.ProformaOrders.AlreadyFinal. Tags or comment are not valid: API.ProformaOrders.InvalidTags, API.ProformaOrders.InvalidComment. An alert to resolve does not exist: API.ProformaOrders.AlertNotFound, and nothing is written. |
curl -X DELETE "https://api.morffeus.com/api/v2/proforma_orders/2207" \ -H "Authorization: Bearer $MORFFEUS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "reason": "Customer changed the order" }'
Related#
- Orders: what a proforma becomes.
- Checkout & payments: how the customer makes and pays a proforma.
- Stock: a proforma reserves stock until it is converted or cancelled.