Appearance
Transactions (Orders)
Overview
Transactions represent all orders placed through any channel. This is where you view, manage, and process orders.
Key Purpose: View and manage all orders from all channels.
Purpose
This page lets you view, filter, and manage all orders across every sales channel, including reviewing order details, resending receipts, exporting data, and tracking payment and fulfillment status.
Key Concepts
- Transaction: A complete order record stored in MongoDB, containing items, pricing, customer data, and an order channel identifier (Kiosk, POS, Online Ordering, Table Qr Ordering, Uber Eats, Takeaway, Shopify).
- Order Status: A single
statusfield tracks the combined payment and fulfillment state of an order (Unpaid, Awaiting Capture, Awaiting Invoice, Awaiting Payment, Pending, Complete, Queued, In Progress, Ready, Cancelled). There is no separate payment-status field; all status filtering uses this one enum. - Idempotency Key: A unique key per transaction that prevents duplicate order creation; the system checks for an existing transaction with the same key before processing a new one.
- Stats Aggregation: The stats endpoint aggregates completed transactions for a date range and optional location, returning completed transaction count, total item quantity, total revenue, and currency.
- Spreadsheet Export: Transactions can be exported to Excel spreadsheets in paginated chunks of 500 records, including columns for order number, date, location, customer, payment status, total, items, dining option, type, device, receipt number, and channel.
Actions
View Transaction Details
Open any transaction to see the full order breakdown including items, modifiers, pricing, customer information, payment method, and status history.
Resend Receipt
Re-send a receipt for a completed transaction via the ReceiptService; only transactions with a "Complete" status are eligible for receipt resend.
Export Transactions
Download transaction data as a spreadsheet filtered by date range, location, and other criteria; the export processes records in chunks of 500 for efficiency.
Filter and Search Orders
Filter orders by date range, status (via the All / Paid / Unpaid / Stuck POS tabs), channel, dining option, order type, device, and location, or search by order number, customer name, email, or phone.
View Transaction Statistics
Access aggregated stats showing completed transaction count, total quantity sold, and total revenue for a selected date range and location. The stats response also carries total_pending — the summed total of orders awaiting invoice or awaiting payment — which is surfaced as a fourth summary card only when Invoice Collection is enabled. total_pending is location-scoped but deliberately not date-scoped. (Verified: TransactionService::stats, upvendo-backend app/Services/BackOffice/TransactionService.php lines 206-254.)
Retry POS Sync
For orders that failed to sync to the POS (shown under the "Stuck" tab), re-dispatch the POS sync job so the order is sent again.
Delete Test Transaction
Delete a transaction that was created in test mode. Only test-mode transactions can be deleted; deleting a live transaction is blocked.
Location
- Backoffice Route:
/transactions - Backend Controller:
app/Http/Controllers/Api/BackOffice/TransactionController.php - Vue Component:
src/views/transactions/Transactions.vue
Order Fields
Order Number
| Property | Value |
|---|---|
| Field ID | order_number |
| Label | Order # |
| Type | String |
| Auto-generated | Yes |
Description: Unique identifier for the order.
Format: Sequential number or custom format (e.g., #1234, ORD-2024-001)
Order Status
| Property | Value |
|---|---|
| Field ID | status |
| Label | Status |
| Type | Select |
| Options | Unpaid, Awaiting Capture, Awaiting Invoice, Awaiting Payment, Pending, Complete, Queued, In Progress, Ready, Cancelled |
Description: Combined payment and fulfillment status of the order (single status field; there is no separate payment-status field).
Statuses:
- Unpaid: Order placed but not yet paid
- Awaiting Capture: Payment authorized, awaiting capture
- Awaiting Invoice: Awaiting an invoice payment
- Awaiting Payment: Awaiting payment
- Pending: Order is in but no payment is made/requested (e.g. third-party orders)
- Complete: Order paid and fulfilled
- Queued: Order queued for processing
- In Progress: Order is being processed/prepared
- Ready: Order ready for pickup/delivery
- Cancelled: Order cancelled
Order Channel
| Property | Value |
|---|---|
| Field ID | order_channel |
| Label | Order Channel |
| Type | String |
Description: Where the order originated.
Channels (from App\Enums\ChannelOptions):
Kiosk- Self-service kioskPOS- Point of saleOnline Ordering- Online ordering websiteTable Qr Ordering- QR table orderingUber Eats- Uber Eats integrationTakeaway- TakeawayShopify- Shopify integration
Note: the Channel filter dropdown in the Transactions list currently only offers "All", "Online Ordering", and "Kiosk", even though more channel values can appear on records.
Dining Option
| Property | Value |
|---|---|
| Field ID | dining_option |
| Label | Type |
| Type | Select |
| Options | Delivery, For Here, Pickup, Takeout |
Description: How the customer will receive the order. (Note: "Dine In" is deprecated and replaced by "For Here".)
Order Date/Time
| Property | Value |
|---|---|
| Field ID | created_at |
| Label | Order Time |
| Type | DateTime |
Description: When the order was placed.
Scheduled Time
| Property | Value |
|---|---|
| Field ID | scheduled_time |
| Label | Scheduled For |
| Type | DateTime |
Description: When the order should be ready (pickup) or delivered.
Customer
| Property | Value |
|---|---|
| Field ID | customer |
| Label | Customer |
| Type | Object |
Description: Customer information.
Fields:
name- Customer nameemail- Email addressphone- Phone numbercustomer_id- Account ID (if logged in)
Delivery Address
| Property | Value |
|---|---|
| Field ID | delivery_address |
| Label | Delivery Address |
| Type | Object |
| Depends On | dining_option = delivery |
Description: Address for delivery orders.
Items
| Property | Value |
|---|---|
| Field ID | items |
| Label | Order Items |
| Type | Array |
Description: Items in the order.
persons is the party size on a per-person (MPLUS bestelbeperking) line; the key is absent on the stored snapshot line for every other item and is emitted as null in the order payload. On such a line quantity holds article units (party size × how many of the dish), because that is what the money and any re-order are computed from. The order-detail line-items table shows the dish count instead: it divides quantity by persons only when persons > 1 and quantity divides evenly, otherwise it shows quantity unchanged. A "For N people" note appears under the item name whenever persons is set (including persons: 1, where no division happens). So a line displayed as 2x Gourmet — For 6 people has quantity: 12, persons: 6 in the payload. Display only — no stored value changes, and the per-unit price shown is still per article unit.
Item Structure:
json
{
"item_id": "item-123",
"name": "Cheeseburger",
"quantity": 2,
"persons": null,
"unit_price": 8.95,
"modifiers": [
{ "name": "Large", "price": 2.00 },
{ "name": "Extra Cheese", "price": 1.50 }
],
"notes": "No pickles",
"line_total": 24.90
}Subtotal
| Property | Value |
|---|---|
| Field ID | subtotal |
| Label | Subtotal |
| Type | Currency |
Description: Total of all items before fees and discounts.
Delivery Fee
| Property | Value |
|---|---|
| Field ID | delivery_fee |
| Label | Delivery Fee |
| Type | Currency |
Description: Delivery fee (for delivery orders).
Discount
| Property | Value |
|---|---|
| Field ID | discount |
| Label | Discount |
| Type | Currency |
Description: Discount applied (from offers/promo codes).
Tip
| Property | Value |
|---|---|
| Field ID | tip |
| Label | Tip |
| Type | Currency |
Description: Tip amount (if tips enabled).
Tax
| Property | Value |
|---|---|
| Field ID | tax |
| Label | Tax |
| Type | Currency |
Description: Total tax amount.
Total
| Property | Value |
|---|---|
| Field ID | total |
| Label | Total |
| Type | Currency |
Description: Final order total.
Calculation: Subtotal + Delivery Fee - Discount + Tip + Tax
Payment Method
| Property | Value |
|---|---|
| Field ID | payment_method |
| Label | Payment |
| Type | String |
Description: How the order was paid.
Methods (the stored value is set by the payment provider / POS, so exact casing varies by source — e.g. Card, iDEAL, Bancontact, Cash):
card- Credit/debit cardideal- iDEAL (NL)bancontact- Bancontact (BE)cash- Cash payment
Note: there are no distinct
apple_pay/google_paypayment_methodvalues — the code never sets them. Wallet payments made through Stripe are recorded under the underlying card method, not as separate values.
Notes
| Property | Value |
|---|---|
| Field ID | notes |
| Label | Order Notes |
| Type | Text |
Description: Special instructions from customer.
Order Actions
The back-office Transactions feature is read-and-manage only. There are no manual fulfilment-progression actions (no Confirm / Mark as Preparing / Mark as Ready / Complete buttons) — fulfilment status changes are driven by payment events and the ordering/KDS flow, not by the back office. The one manual action that can change payment state is Verify payment status. The TransactionController exposes: show, dataTable, stats, export, resendReceipt, retrySync, verifyPaymentStatus, deleteTestTransaction.
Resend Receipt
Re-send a receipt for a transaction (only when its status is "Complete").
Verify Payment Status
Re-check an order's true payment status at the payment provider and settle it if the money was actually taken.
- Where: the order-detail 3-dots overflow menu (the same menu as Resend receipt).
- When it is shown: only when the order status is Unpaid, Awaiting Payment, Awaiting Capture or Pending, and the payment method is not Cash.
- Endpoint:
POST /back-office/transactions/{id}/verify-payment-status(permissionview-transactions). - Outcomes:
settled,settlement_queued,already_settledornot_paid. Each raises its own toast; the first three reload the order.
This is the one back-office action that can move an order out of Unpaid.
Retry POS Sync
Re-dispatch the POS sync job for an order that failed to sync (shown under the "Stuck" tab).
Delete Test Transaction
Delete a transaction that was created in test mode. Live transactions cannot be deleted.
Transaction Lifecycle Timeline
Order detail renders a Transaction Lifecycle card whenever the order's lifecycle_logs array is non-empty. It is a read-only audit trail — nothing on it changes the order. The only interactive control is the Load More (N) button that appears when the timeline is collapsed.
- One entry per lifecycle log, drawn as a Vuetify timeline. The dot colour comes from
log.level:success,info,warningorerror; anything else falls back toinfo. - Event label is the
transactions.lifecycle.events.<event>translation. The template also writes|| log.label, but that branch is unreachable: on a missing key the translation helper returns the key path itself, which is truthy. An event with noenentry therefore renders as the literal stringtransactions.lifecycle.events.<event>. Only theenfile has to carry the key — a locale that is missing it falls back to English. - Elapsed delta from the first entry is shown next to each timestamp (
+12s,+1m 4s,+1h 3m). It is blank for the first entry and for any non-positive or invalid delta. - Metadata chips: a tonal chip per shown metadata key. Two filters run first: (1) keys with no translation under
transactions.lifecycle.metadata.<key>are dropped entirely, and (2) a givenkey=valuepair is shown only the first time it appears in the timeline, so unchanging context (e.g.Provider: hendrickxon every Kassanet step) appears once and later entries show only what actually changed. The chip label is the translated key name (Provider,Order number,Idempotency key, …), not the raw key. Values:nullandundefinedare skipped; objects and arrays are JSON-encoded and truncated to 60 characters with an ellipsis. The de-duplication is computed over the currently visible entries, so pressing Load More re-evaluates it across the full timeline. - Long timelines collapse: with more than 8 entries only the 6 most recent render, behind a Load More (N) control.
- On mobile the timestamp moves inline underneath the event label.
- Backend cap: only the most recent 200 lifecycle entries are stored per transaction.
Timestamps render in the venue's timezone, taken from the order's
location_timezone(the timezone on the order's location snapshot). Every time carries an explicit timezone label, so a support agent and the merchant read the same clock time off the same order — e.g.Jul 17, 20:40:36 CEST. The label's form follows the viewer's locale (an en-US viewer seesGMT+2and a 12-hour clock for the same instant), but the underlying wall-clock time is always the venue's. If an order has nolocation_timezone(older orders with no location-snapshot timezone), the time falls back to the viewer's local timezone — still labelled — so the two cases are distinguishable on screen. The year is deliberately dropped from the format.
Refund Order (not currently available)
A RefundDialog exists in the shared OrderDetailView.vue, but it is not wired up: no refund button is ever rendered (the dialog's showRefundDialog flag is never set to true, so it is unreachable), and the store's refundOrder action is a simulated mock (a setTimeout that returns a fake REF_<timestamp> id, no Axios call, no backend endpoint). Refunds are not processed from the back office today; handle refunds through your payment provider (e.g. the Stripe dashboard).
Business Logic
Item counts: dishes vs article units
The Item column in the transactions list and its Excel export count dishes: Transaction::getQuantity() divides a per-person line by its party size and passes every other line through untouched. The Total Items stats card is a separate aggregation (sumTransactionQuantity) and still sums article units, so it can exceed the sum of the Item column when per-person lines are present.
Order Status
An order's lifecycle is reflected by the single status field. Typical paid orders move toward Complete; unpaid/awaiting-payment orders sit in the Unpaid / Awaiting Payment / Awaiting Capture / Awaiting Invoice / Pending states; fulfillment progresses through Queued → In Progress → Ready; and orders can end in Cancelled. These transitions are driven by payment events and the ordering/KDS flow rather than by manual back-office actions.
Filtering & Search
Filter Options
- Date range
- Status (via the All / Paid / Unpaid / Stuck POS tabs)
- Channel
- Dining option
- Order type
- Device
- Location
Search
- Order number
- Customer name
- Customer email
- Customer phone
Customer Impact
Order Confirmation
Customer receives:
- Confirmation email
- Order number
- Estimated time
- Order details
Order Updates
Customer notified when:
- Order is placed/paid
- Order ready
- Order cancelled
Relations
Depends On
- Locations: Orders belong to locations
- Menu Items: Items in orders
- Customers: Order attribution
- Payments: Payment processing
Affects
- Inventory: Stock decremented
- Loyalty: Points earned
- Reports: Sales data
- KDS: Orders displayed
Related Features
Business Rules
- Only transactions with a status of "Complete" can have their receipt resent; attempting to resend for an incomplete transaction returns a 400 error with "Transaction is incomplete".
- There are no separate payment-status and order-status fields — a single
statusenum captures both the payment and fulfillment state. - Refunds are not processed from the back office: no refund button is rendered (the
RefundDialoginOrderDetailView.vueis unreachable), the store'srefundOrderaction is a mock, and there is no refund endpoint on theTransactionController. - There are no manual fulfilment-progression actions in the back office (no Confirm / Mark as Preparing / Mark as Ready / Complete). Fulfilment changes are driven by payment events and the ordering/KDS flow. The single exception on the payment side is Verify payment status, which can settle an order that was actually paid.
- Only test-mode transactions can be deleted; deleting a live transaction is blocked with a 403 error, "Only test mode transactions can be deleted".
- Transaction stats only count orders with status "Complete" and apply date-range filtering on the
order_datefield, with timezone-aware parsing via the request'stimezoneparameter (falling back to the app default timezone). - The spreadsheet export aborts with a 400 error if no transactions match the given filters, rather than producing an empty file.
- Tip validation enforces mutual exclusivity: a transaction request cannot specify both a tip percentage and a tip amount; providing both throws an InvalidArgumentException.
- When a customer places an order and no existing customer record matches their email or phone, a new customer and customer-vendor record are created automatically, and any applicable loyalty sign-up bonus is applied.
FAQs
"Why can I not resend a receipt for an order?" Receipts can only be resent for transactions with a "Complete" status; if the order is still pending or was cancelled, the resend option is blocked.
"How does the export handle large volumes of transactions?" The export processes records in chunks of 500, writing each batch sequentially to the spreadsheet until all matching records are retrieved, preventing memory issues.
"What timezone are transaction dates displayed in?" Transaction dates are stored in UTC (MongoDB UTCDateTime) but are displayed and filtered using the location's configured timezone.
"Can a transaction belong to multiple locations?" No, each transaction is tied to exactly one location via its location_id; filtering by location returns only that location's transactions.
"How are duplicate orders prevented?" Each transaction carries an idempotency key; before creating a new transaction, the system checks for an existing record with the same key and returns the existing order if found.
"Which sales channels can I filter transactions by, and what are they called?" Transactions carry an order channel; the recognised values are Online Ordering, Kiosk, POS, Table Qr Ordering, Uber Eats, Takeaway, and Shopify. Note the Channel filter dropdown currently only offers "All", "Online Ordering", and "Kiosk".
"What do the status tabs at the top of the Transactions list mean?" Four tabs: All, Paid, Unpaid, and Stuck POS. The Paid tab shows orders with status Complete, Unpaid shows orders with status Unpaid, and Stuck POS shows orders whose POS sync failed.
"What does the 'Stuck POS' tab show and how do I fix a stuck order?" The Stuck POS tab lists transactions that failed to sync to your POS. Each stuck row has a retry icon; clicking it (Retry Sync) clears the failed flag and re-dispatches the POS sync job so the order is sent again.
"Why can't I resend a receipt for some orders?" Resend Receipt only works for transactions whose status is Complete. For any other status you get "Transaction is incomplete", so pending, unpaid, or cancelled orders can't have a receipt resent.
"What columns are included when I export transactions?" The Excel export includes Order No, Date, Location, Customer, Payment Status, Total, Items, Dining Option, Type, Device, Receipt No, and Channel.
"My export downloaded nothing / gave an error — why?" If no transactions match your current filters, the export stops with a "No transactions found" error instead of an empty file. Adjust your date range, location, or filters so at least one order matches, then export again.
"What do the summary cards at the top mean?" Normally three cards: Completed Transactions (count of fully paid orders), Total Items (quantity sold), and Total Collected (amount collected), all scoped to your current filters (date range, location, etc.). A fourth card, Total Pending, appears when Invoice Collection is enabled for your account; with it the row switches from a 3-up to a 4-up layout. Total Pending sums the totals of orders awaiting invoice or awaiting payment and, unlike the other three, is not limited by your date range — an invoice raised months ago is still outstanding today. It does respect the location filter. (Verified: card definitions in upvendo-backoffice
src/utils/constants.tslines 153-188 --{ id: 4, title: 'Total Pending', key: 'total_pending' }; gating and layout insrc/views/transactions/Transactions.vuelines 107-119 and 571-575; feature flag getterisInvoiceCollectionEnabledinsrc/store/modules/app.tsline 323, defaulting tofalseat line 246; the figure itself in upvendo-backendapp/Services/BackOffice/TransactionService.phplines 234-253.)"Can I filter transactions to a single location?" Yes. If your account has more than one location, a Location filter appears; each transaction belongs to exactly one location. The export and stats respect the same filter.
"Which transaction date and timezone are used for filtering?" Date filtering uses the order date, evaluated in your location's configured timezone. Dates are stored in UTC but parsed against the location timezone, so "today" reflects your local trading day.
"Can I delete a transaction?" Only test-mode transactions can be deleted; deleting a real (live) transaction is blocked with "Only test mode transactions can be deleted".
Troubleshooting
Problem: Order stuck in an unpaid/pending status
Causes:
- Payment not completed
- Payment failed
- System error
Solutions:
- Check the payment in your payment provider (e.g. Stripe)
- Contact the customer to retry payment
- Note: the back office has no "manually confirm" action — status is updated by the payment flow, not manually
Problem: Order stuck (failed POS sync)
Causes:
- POS offline or unreachable
- Transient sync error
- POS integration misconfiguration
Solutions:
- Open the "Stuck" tab, which lists orders whose POS sync failed
- Click the retry icon (Retry Sync) on the row to re-dispatch the POS sync job
- If it keeps failing, check the POS connection and integration settings
Problem: Order not appearing on KDS
Causes:
- KDS not connected
- Channel filter on KDS
- Order status not yet eligible for KDS
Solutions:
- Check KDS connection
- Review KDS channel settings
- Confirm the order has reached a status that routes it to the KDS
Problem: Customer didn't receive an order email
Causes:
- Wrong email address
- Email in spam
- Email service issue
Solutions:
- Verify the email address on the order
- Ask the customer to check spam
- For receipts on completed orders, use Resend Receipt from the order detail (there is no separate "resend confirmation" action)
Problem: Need to refund an order
Note: Refunds are not processed from the back office today — the refund UI is disabled and there is no refund endpoint.
Solutions:
- Process the refund directly in your payment provider (e.g. the Stripe dashboard)
- For cash payments, handle the refund at the point of sale
- Contact your payment provider if the refund fails
Examples
Online Order (Delivery)
json
{
"order_number": "1234",
"status": "Complete",
"order_channel": "Online Ordering",
"dining_option": "Delivery",
"created_at": "2024-01-15T12:30:00Z",
"scheduled_time": "2024-01-15T13:15:00Z",
"customer": {
"name": "Jan de Vries",
"email": "jan@example.com",
"phone": "+31612345678"
},
"delivery_address": {
"address_line_1": "Kalverstraat 123",
"city": "Amsterdam",
"postal_code": "1012 AB"
},
"items": [
{
"name": "Margherita Pizza",
"quantity": 1,
"unit_price": 12.50,
"modifiers": [
{ "name": "Large", "price": 3.00 }
],
"line_total": 15.50
},
{
"name": "Garlic Bread",
"quantity": 1,
"unit_price": 4.50,
"line_total": 4.50
}
],
"subtotal": 20.00,
"delivery_fee": 3.50,
"discount": 0,
"tip": 2.00,
"tax": 1.89,
"total": 27.39,
"payment_method": "ideal"
}Kiosk Order (For Here)
json
{
"order_number": "42",
"status": "In Progress",
"order_channel": "Kiosk",
"dining_option": "For Here",
"created_at": "2024-01-15T12:45:00Z",
"items": [
{
"name": "Cheeseburger",
"quantity": 2,
"unit_price": 8.95,
"modifiers": [
{ "name": "Extra Cheese", "price": 1.50 }
],
"line_total": 20.90
},
{
"name": "French Fries",
"quantity": 2,
"unit_price": 3.50,
"line_total": 7.00
},
{
"name": "Cola",
"quantity": 2,
"unit_price": 2.50,
"line_total": 5.00
}
],
"subtotal": 32.90,
"tip": 5.00,
"tax": 2.81,
"total": 40.71,
"payment_method": "card"
}Order Summary View
Order #1234 - Jan de Vries
Status: Complete
Channel: Online Ordering
Type: Delivery
Items:
1x Margherita Pizza (Large) €15.50
1x Garlic Bread €4.50
─────────────
Subtotal €20.00
Delivery Fee €3.50
Tip €2.00
Tax €1.89
─────────────
Total €27.39
Payment: iDEAL ✓ Paid
Delivery: Kalverstraat 123, Amsterdam
Scheduled: 13:15