Orders and buy again
Show the logged-in customer's orders and quotes. Covers the order list and its pages, the order page with its products and shipments, status labels, printing, the Buy again button, return and removal requests, and the list of quotes.
Customers see their past orders in their account. This page builds the order list, the page of a single order with its products and shipments, and a Buy again button that puts the products of an order back in the cart. It also covers the forms where customers ask for a return or for products to be removed, and the list of the quotes the store has sent them.
Note
Customer accounts must be set up first: see Customer accounts. Put these parts on account pages, since they need a logged-in customer.
How it fits together
What Smootify needs:
- for the list: a
customer-orderselement with onecustomer-orderinside, the template of each order; - for the order page: a page at
/account/orderwith anorder-pageelement on it; - inside an order, a
line-itemfor its products and anorder-fulfillmentfor its shipments, if you show them; - for returns and removals: an
order-returnororder-edit-requestinside an order; - for quotes: a
customer-draft-orderselement with onecustomer-draft-orderinside.
What is up to you:
- which details each order shows, and the design;
- whether you build an order page at all.
order="statusUrl"can link to Shopify’s own page for the order instead.
1. The order list
- Add a
customer-orderselement. - Inside it, add a
customer-orderelement. Design it as one order: Smootify repeats it once per order, newest first. - Inside it, add the order values of step 2, and a link to the order page with
order="url".
<customer-orders limit="10">
<customer-order>
<div order="name">#1001</div>
<div order="processedAt">1/1/2026</div>
<div order="fulfillmentStatus">Status</div>
<div order="totalPrice">$0.00</div>
<a href="#" order="url">View order</a>
</customer-order>
<button data-action="load">Load more</button>
</customer-orders>- How many.
limitoncustomer-orderssets the orders per page. Without it, the list shows 5. - No orders. Show an empty state with
customer-condition="no-orders"on the same page. The list is what counts the orders, so this condition works only on a page that has it. If the list cannot load, neitherhas-ordersnorno-ordersshows, and a next page that fails to load leaves the orders already on screen. - While it loads. Add
skeleton="text"orskeleton="block"to the placeholders you want to shimmer until the orders arrive. See loading.
More orders. Pagination buttons are <button> elements, inside customer-orders and outside customer-order. There are two kinds:
Load more
- Does
- Adds the next orders below the ones shown.
- Hidden
- When there are no more orders.
Previous and next
- Does
- Shows the previous or the next page of orders instead.
- Hidden
- Each one, when there is no page in its direction. Here, on the last page, Next.
2. Order values
These work in a row of the list and on the order page.
| Attribute | What it shows |
|---|---|
order="name" | the order name, such as #1001 |
order="processedAt" | the order date, in the visitor’s date format |
order="financialStatus" | the payment status |
order="fulfillmentStatus" | the shipping status: UNFULFILLED until the first shipment, then that shipment’s status |
order="subtotalPrice", order="totalShipping" | the subtotal, the shipping |
order="totalTax", order="totalPrice" | the taxes, the total |
order="totalDiscounts" | the discounts of the order, on its lines and on shipping; removed when the order has none |
order="totalRefunded" | the amount refunded |
order="shippingAddress", order="billingAddress" | the addresses, one line under the other |
order="note", order="email", order="phone" | what the customer entered at checkout |
order="cancelReason" | why the order was cancelled |
Three values go on an a link:
order="url": your order page for this order;order="statusUrl": Shopify’s own page for this order, in the customer’s Shopify account;order="customerUrl": the customer’s profile in their Shopify account.
An element whose value is missing, such as a cancel reason on an order that was not cancelled, is taken out. The full list is in order values.
Status labels. The statuses show Shopify’s codes, such as PAID. To show your own words, add a script with the labels to the <head> of your layout, src/layouts/Base.astro. Write every label in the same script:
<script is:inline>
window.SmootifyStatusLabels = {
PAID: "Paid",
PENDING: "Awaiting payment",
UNFULFILLED: "Being prepared",
};
</script>The keys are the codes as Shopify sends them. The same script holds the statuses of subscriptions. The codes of each status:
Payment
Codes
PENDINGAUTHORIZEDPAIDPARTIALLY_PAIDPARTIALLY_REFUNDEDREFUNDEDVOIDEDEXPIREDShipping
Codes
UNFULFILLEDSUCCESSCANCELLEDERRORFAILUREUNFULFILLED until the first shipment, then the status of that shipment.
Subscription
Codes
ACTIVEPAUSEDCANCELLEDEXPIREDFAILEDSTALEWithout a label, the code with a capital letter only at the start.
3. The order page
- Create the page
src/pages/account/order.astro, so its address is/account/order. For another address, set it in the optionorderPageUrl. - Add an
order-pageelement, and put the order values inside it.
The page shows the order that order="url" links to: the link adds the order number to the address. A visitor who is not logged in gets the same treatment as on the other account pages: with the option accountPageRedirectTo: "passwordless-login", the Shopify login opens and then comes back to the same order, so a link to an order in an email works. Opened by a logged-in customer without an order in the address, or for an order that is not theirs, the page goes to your home page.
Products. Inside order-page, add a line-item element. Design it as one product: Smootify repeats it once per product of the order. Design only one: the other copies you make are removed.
<order-page>
<h1 order="name">#1001</h1>
<line-item>
<img line-item="image" alt="">
<div line-item="name">Product</div>
<div line-item="option"><span option="name">Size</span>: <span option="value">M</span></div>
<div line-item="quantity">1</div>
<div line-item="totalPrice">$0.00</div>
</line-item>
<div order="totalPrice">$0.00</div>
</order-page>Inside line-item:
line-item="name",line-item="title",line-item="sku",line-item="vendor",line-item="quantity": texts;line-item="variant-title": the variant in one text, such asRed / L, taken out for a product without variants;line-item="image"on animg, taken out when the product has no image;line-item="price",line-item="totalPrice",line-item="totalDiscount": amounts;line-item="option"on an element: one copy per option and per property of the line, such as a size or an engraving, withoption="name"andoption="value"inside. Properties whose name starts with_are left out.
The full list is in order line values.
Shipments. Add an order-fulfillment element, the template of one shipment. Inside it, these values fill a shipment:
fulfillment="status"The status of the shipment: success, cancelled, error or failure.fulfillment="latestShipmentStatus"The latest delivery status, such as in transit or delivered.fulfillment="estimatedDeliveryAt"The expected date, or TBD when Shopify does not know it.fulfillment="trackingCompany"The carrier.fulfillment="trackingNumber"The tracking number.fulfillment="trackingUrl"On a link: the tracking page. Taken out when there is none.fulfillment="updatedAt"The date of the last update.Wrap the shipments section in an element with order-condition="has-fulfillments": it hides while the order has no shipment.
<div order-condition="has-fulfillments">
<order-fulfillment>
<div fulfillment="status">Status</div>
<a href="#" fulfillment="trackingUrl">Track the parcel</a>
</order-fulfillment>
</div>Printing. To print the order, wrap the part to print in a printable-element element, and give it an id, such as order. A print-button element with data-target="order" prints that part alone. See print-button.
The rows of the order list can hold line-item and order-fulfillment too, for example to show small product images in each order.
4. Buy again
A <button> with data-action="buy-again" puts the products of an order back in the cart, each with its variant, its quantity and its properties, such as an engraving or an uploaded file. It works inside customer-order, in the list, and inside order-page.
<button data-action="buy-again"><span data-state="default">Buy again</span><span data-state="loading">Adding…</span></button>- Straight to checkout. Add
data-buy-now="true"to the button to send the customer to checkout instead of the cart. - While it works. The elements inside the button with
data-state="default"anddata-state="loading"swap, and the button gets the classis-loading. Then the mini cart opens, as after any addition. - What stays out. Products that no longer exist, the products of a Shopify bundle (the order does not say which bundle they formed), and products priced by the product configurator or name your price. A product Shopify refuses, such as a sold-out one, is not added either.
- Subscriptions come back as one-time purchases.
- Nothing to buy. When nothing in the order can be bought again, the button hides itself.
5. Returns and removals
Customers can ask to return products they received, or to remove products that are not shipped yet, from the order on your site. The request goes to Shopify. A return lands in the admin as requested, and you approve it there. A removal you resolve or decline in the admin.
Note
Requests need the new customer accounts, which Smootify 2.0 already uses. No app and no plan. What can be returned follows the return rules of the store in Shopify, under Settings › Returns.
Returns. Add an order-return element inside order-page, or inside a customer-order row of the list. Inside it, put a <form>. In the form, add a request-line-item element: the template of one product that can be returned. Smootify repeats it once per product, with the line-item values of step 3. There, line-item="quantity" is how many can still be returned.
<order-return>
<div order-return="reason">Return window expired</div>
<form>
<request-line-item>
<div line-item="name">Product</div>
<div line-item="variant-title">Red / L</div>
<select name="quantity"><option value="0">0</option></select>
<select name="reason"><option value="">Choose a reason</option></select>
<textarea name="note"></textarea>
</request-line-item>
<button type="submit"><span data-state="default">Ask for a return</span><span data-state="loading">Sending…</span></button>
</form>
<div form-done hidden>Return requested</div>
<div form-fail hidden>Something went wrong</div>
<div order-return="request">Last return: <span order-return="name">#1001-R1</span> <span order-return="status">Requested</span></div>
</order-return>The fields of a row are read by their name, and every one is optional:
quantity, aninputwithtype="number"or aselect: how many to return. Aselectgets the options from 0 to what can be returned, from 1 when the row has a checkbox.- A checkbox, with any name: the row is sent only while it is ticked. Without a
quantityfield, a ticked row returns the whole product. reason, aselect: Smootify fills it with the return reasons Shopify suggests for the product’s category. A first option with an empty value, such as “Choose a reason”, stays first. When Shopify suggests reasons, one is required: without it,form-failreads “Choose a reason for” and the product’s title, and nothing is sent. When Shopify suggests none, the field hides.note, atextarea: the customer’s note on the product, up to 300 characters.
A row with neither a quantity field nor a checkbox only shows the product. When nothing is picked, form-fail reads “Choose at least one item”. Shopify’s own errors show there too. Once the request is sent, form-done shows and Smootify reads the order again.
Around the form. These values go inside order-return, outside the <form>, which hides when nothing can be returned:
order-return="reason": why nothing can be returned, such as “Return window expired”. It is taken out when something can. Shopify’s codes, such asRETURN_WINDOW_EXPIRED, take your own words in theSmootifyStatusLabelsscript of step 2.order-return="request": wraps the last return, and is taken out when there is none. Inside it,order-return="name"(#1001-R1),order-return="status"andorder-return="created-at". The statuses areREQUESTED,OPEN,CLOSED,DECLINEDandCANCELED, with labels in the same script.
order-return gets the class is-requestable while something can be returned, has-request once a return was asked, and loaded once Smootify has read the order. Style them from those classes.
Removals. order-edit-request is built the same way, for products not shipped yet: a <form> with a request-line-item row, its quantity field or checkbox. It has no reason and no note, and it only asks to remove products. order-edit-request="reason" (such as “Pending edit request”), order-edit-request="request" and order-edit-request="status" work as for returns. The statuses are REQUESTED, RESOLVED and DECLINED.
6. Quotes
Customers can see the quotes the store has sent them, with a link to pay each one. A quote is a Shopify draft order: one the customer asked for from the cart (see Quotes and B2B), or one you create in the Shopify admin.
Which quotes show. Shopify shows the customer only the quotes the store has sent:
- The customer asks for a quote from the cart.
- You review it in the Shopify admin and click Send invoice.
- The quote shows in the account as «Invoice sent», with its pay link. Once paid, it reads «Completed».
An open quote, including one just asked for from the cart, never shows. B2B orders that a company submitted for review show too.
«Invoice sent» INVOICE_SENT
You sent the invoice from Shopify
draft-order="invoice-url" - On a link: the page where the customer pays. Taken out once the quote is an order.
draft-order-condition="has-discounts" - Shows the discount line. Hidden when the quote has no discount.
«Completed» COMPLETED
The customer paid: the quote is an order
draft-order="order-url" - On a link: your order page for that order. Taken out until the quote is paid.
«Open · In review» OPEN
A company submitted it for review
draft-order-condition="in-review" - Shows only for a B2B order submitted for review and not approved yet.
An open quote never shows, including one just asked for from the cart: the account lists only the quotes you sent with Send invoice, plus B2B orders in review.
Note
In Shopify, in the Headless channel, the permissions of the Customer Account API must include draft orders. Without them the list stays empty.
The list. Add a customer-draft-orders element, and inside it one customer-draft-order, the template of one quote. limit and the pagination buttons work as in the order list of step 1.
<customer-draft-orders limit="10">
<customer-draft-order>
<div draft-order="name">#D12</div>
<div draft-order="status">Invoice sent</div>
<div draft-order-condition="in-review">Waiting for approval</div>
<div draft-order-condition="has-discounts">Discount: <span draft-order="total-discounts">$0.00</span></div>
<div draft-order="total-price">$0.00</div>
<a href="#" draft-order="invoice-url">Pay now</a>
</customer-draft-order>
<button data-action="load">Load more</button>
</customer-draft-orders>
<div customer-condition="no-draft-orders">No quotes yet</div>Inside customer-draft-order, draft-order="…" takes these values:
- Texts:
name,status,order-number,email,phone,item-count(the pieces),created-at,updated-at. - Amounts:
subtotal-price,total-price,total-tax,total-shipping,total-discounts(taken out when the quote has no discount). - Addresses:
billing-address,shipping-address, one line under the other. - On an
alink:invoice-urlopens the page where the customer pays the quote, and is taken out once the quote became an order.order-urlopens your order page for that order, and is taken out until then.
A line-item inside the row shows the quote’s products as in step 3, with their prices after the quote’s discounts.
- Status labels. The status reads
Open,Invoice sentorCompleted. Write your own words in theSmootifyStatusLabelsscript of step 2, with the keysOPEN,INVOICE_SENTandCOMPLETED. The status element also gets the status as a class, such asinvoiceSent. - In review.
draft-order-condition="in-review"shows an element only for a B2B order submitted for review and not yet approved. - Discount. Put the label and
draft-order="total-discounts"inside one element withdraft-order-condition="has-discounts", as above: it hides when the quote has no discount. Do not puttotal-discountson that element itself: the amount replaces its text, label included. - No quotes.
customer-condition="has-draft-orders"andcustomer-condition="no-draft-orders"show an element when the customer has quotes, or none. The list counts them, so they work only on a page that has it. If the list cannot load, neither shows.
Before you publish
Check these first
- The order page sends you to the home page. It was opened without an order in its address, or by a customer the order does not belong to. Open it from an
order="url"link. A visitor who is not logged in goes to the home page too unlessaccountPageRedirectTois"passwordless-login". - The order link leads to a missing page. The order page is not at
/account/order. Move it there, or setorderPageUrl. - Statuses show codes such as
PAID. Add theSmootifyStatusLabelsscript with your words. - Buy again or the pagination does nothing. It is a link, not a
<button>. Use a<button>. - The Buy again button is missing on an order. Nothing in that order can be bought again: its products are deleted, part of a bundle, or had a custom price.
- The products you designed disappear. Only the first
line-itemis the template. Design one; Smootify repeats it. - A quote the customer asked for is not in the account. Shopify shows only the quotes the store has sent: open it in Orders › Drafts and click Send invoice. If the list is always empty and neither
has-draft-ordersnorno-draft-ordersshows, the Customer Account API of the Headless channel lacks the draft orders permission.