@spree/docs 0.1.286 → 0.1.288
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-reference/store.yaml +52 -0
- package/dist/developer/core-concepts/fees.md +1 -0
- package/dist/developer/core-concepts/reporting.md +4 -0
- package/dist/developer/core-concepts/returns-exchanges-claims.md +43 -2
- package/dist/developer/core-concepts/sellers.md +2 -0
- package/dist/developer/core-concepts/taxes.md +28 -0
- package/dist/developer/upgrades/5.6-to-6.0.md +1 -1
- package/package.json +1 -1
|
@@ -12427,6 +12427,14 @@ components:
|
|
|
12427
12427
|
type: string
|
|
12428
12428
|
paid_amount:
|
|
12429
12429
|
type: string
|
|
12430
|
+
pre_tax_amount:
|
|
12431
|
+
type: string
|
|
12432
|
+
included_tax_total:
|
|
12433
|
+
type: string
|
|
12434
|
+
additional_tax_total:
|
|
12435
|
+
type: string
|
|
12436
|
+
tax_total:
|
|
12437
|
+
type: string
|
|
12430
12438
|
display_refund_amount:
|
|
12431
12439
|
type: string
|
|
12432
12440
|
variant_id:
|
|
@@ -12447,6 +12455,10 @@ components:
|
|
|
12447
12455
|
- description
|
|
12448
12456
|
- refund_amount
|
|
12449
12457
|
- paid_amount
|
|
12458
|
+
- pre_tax_amount
|
|
12459
|
+
- included_tax_total
|
|
12460
|
+
- additional_tax_total
|
|
12461
|
+
- tax_total
|
|
12450
12462
|
- display_refund_amount
|
|
12451
12463
|
- variant_id
|
|
12452
12464
|
- replacement_variant_id
|
|
@@ -13246,6 +13258,7 @@ components:
|
|
|
13246
13258
|
- cod
|
|
13247
13259
|
- payment
|
|
13248
13260
|
- duty
|
|
13261
|
+
- exchange
|
|
13249
13262
|
- type: string
|
|
13250
13263
|
description: What sort of charge this is. `duty` is a customs duty on a
|
|
13251
13264
|
cross-border order and is not taxed; the other kinds are. Extensions may
|
|
@@ -15019,6 +15032,18 @@ components:
|
|
|
15019
15032
|
type: string
|
|
15020
15033
|
display_pre_tax_amount:
|
|
15021
15034
|
type: string
|
|
15035
|
+
included_tax_total:
|
|
15036
|
+
type: string
|
|
15037
|
+
additional_tax_total:
|
|
15038
|
+
type: string
|
|
15039
|
+
tax_total:
|
|
15040
|
+
type: string
|
|
15041
|
+
display_tax_total:
|
|
15042
|
+
type: string
|
|
15043
|
+
refund_amount:
|
|
15044
|
+
type: string
|
|
15045
|
+
display_refund_amount:
|
|
15046
|
+
type: string
|
|
15022
15047
|
variant_id:
|
|
15023
15048
|
type: string
|
|
15024
15049
|
nullable: true
|
|
@@ -15037,6 +15062,12 @@ components:
|
|
|
15037
15062
|
- resellable
|
|
15038
15063
|
- pre_tax_amount
|
|
15039
15064
|
- display_pre_tax_amount
|
|
15065
|
+
- included_tax_total
|
|
15066
|
+
- additional_tax_total
|
|
15067
|
+
- tax_total
|
|
15068
|
+
- display_tax_total
|
|
15069
|
+
- refund_amount
|
|
15070
|
+
- display_refund_amount
|
|
15040
15071
|
- variant_id
|
|
15041
15072
|
- line_item_id
|
|
15042
15073
|
- fulfillment_item_id
|
|
@@ -15084,6 +15115,10 @@ components:
|
|
|
15084
15115
|
type: string
|
|
15085
15116
|
display_refund_total:
|
|
15086
15117
|
type: string
|
|
15118
|
+
refund_tax_total:
|
|
15119
|
+
type: string
|
|
15120
|
+
display_refund_tax_total:
|
|
15121
|
+
type: string
|
|
15087
15122
|
approved_at:
|
|
15088
15123
|
type: string
|
|
15089
15124
|
nullable: true
|
|
@@ -15110,6 +15145,8 @@ components:
|
|
|
15110
15145
|
- reason_id
|
|
15111
15146
|
- refund_total
|
|
15112
15147
|
- display_refund_total
|
|
15148
|
+
- refund_tax_total
|
|
15149
|
+
- display_refund_tax_total
|
|
15113
15150
|
- approved_at
|
|
15114
15151
|
- received_at
|
|
15115
15152
|
- refunded_at
|
|
@@ -15391,6 +15428,8 @@ components:
|
|
|
15391
15428
|
type: string
|
|
15392
15429
|
included:
|
|
15393
15430
|
type: boolean
|
|
15431
|
+
credit:
|
|
15432
|
+
type: boolean
|
|
15394
15433
|
rate:
|
|
15395
15434
|
type: string
|
|
15396
15435
|
tax_rate_id:
|
|
@@ -15405,6 +15444,15 @@ components:
|
|
|
15405
15444
|
fee_id:
|
|
15406
15445
|
type: string
|
|
15407
15446
|
nullable: true
|
|
15447
|
+
return_line_item_id:
|
|
15448
|
+
type: string
|
|
15449
|
+
nullable: true
|
|
15450
|
+
claim_line_item_id:
|
|
15451
|
+
type: string
|
|
15452
|
+
nullable: true
|
|
15453
|
+
exchange_line_item_id:
|
|
15454
|
+
type: string
|
|
15455
|
+
nullable: true
|
|
15408
15456
|
amount:
|
|
15409
15457
|
type: string
|
|
15410
15458
|
nullable: true
|
|
@@ -15415,11 +15463,15 @@ components:
|
|
|
15415
15463
|
- id
|
|
15416
15464
|
- label
|
|
15417
15465
|
- included
|
|
15466
|
+
- credit
|
|
15418
15467
|
- rate
|
|
15419
15468
|
- tax_rate_id
|
|
15420
15469
|
- line_item_id
|
|
15421
15470
|
- fulfillment_id
|
|
15422
15471
|
- fee_id
|
|
15472
|
+
- return_line_item_id
|
|
15473
|
+
- claim_line_item_id
|
|
15474
|
+
- exchange_line_item_id
|
|
15423
15475
|
- amount
|
|
15424
15476
|
- display_amount
|
|
15425
15477
|
x-typelizer: true
|
|
@@ -44,6 +44,7 @@ When neither ID is set, the fee applies to the whole order.
|
|
|
44
44
|
| `cod` | Cash on delivery |
|
|
45
45
|
| `payment` | A payment method surcharge |
|
|
46
46
|
| `duty` | Customs duty — see [below](#customs-duties) |
|
|
47
|
+
| `exchange` | What a customer owes when an [exchange](returns-exchanges-claims.md#settling-the-difference)'s replacement costs more. Spree adds it; you don't |
|
|
47
48
|
|
|
48
49
|
The list is open — an extension can add its own kind.
|
|
49
50
|
|
|
@@ -129,6 +129,10 @@ the period of the original order. A report over a past range can therefore
|
|
|
129
129
|
change after the fact, which is the conventional treatment and the only one an
|
|
130
130
|
open accounting period can produce.
|
|
131
131
|
|
|
132
|
+
A refund from a return, claim or exchange gives tax back with the goods, so it
|
|
133
|
+
is split: the goods come off `returns` and `net_sales`, and the tax comes off
|
|
134
|
+
`taxes`. `total_sales` loses the whole refund.
|
|
135
|
+
|
|
132
136
|
`cost_of_goods`, `gross_profit` and `gross_margin` read the line item's
|
|
133
137
|
`cost_price`. That column is nullable, so a variant with no cost contributes
|
|
134
138
|
zero and margin over an incompletely-costed catalogue reads high.
|
|
@@ -116,6 +116,14 @@ Receiving takes the quantities the warehouse actually counted, because partial a
|
|
|
116
116
|
|
|
117
117
|
Leave `items` out to receive everything as requested. Refunds default to whatever the return is still owed, and can go back to the original payment method or to store credit. A returned item is worth what the customer paid for it after discounts, not its list price, so a free gift sent back refunds nothing. Refunding a return that is owed nothing completes it with no money moved and no refund email; an amount of zero is refused on any return the customer is still owed money on.
|
|
118
118
|
|
|
119
|
+
### Tax on returns
|
|
120
|
+
|
|
121
|
+
A return gives back the tax the customer paid on what came back. A $25.00 shirt sold with 10% tax added on top refunds $27.50, and on a store whose prices include VAT the VAT inside the price goes back with it. Delivery and its tax are not refunded.
|
|
122
|
+
|
|
123
|
+
Each return line shows its worth the way an order line does: `pre_tax_amount` before tax, `included_tax_total` and `additional_tax_total` beside it, and `refund_amount` with the tax included. The return's `refund_tax_total` says how much of `refund_total` is tax, and `expand=return_line_items.tax_lines` (Admin API) lists the tax given back rate by rate.
|
|
124
|
+
|
|
125
|
+
The store's [tax provider](taxes.md) works this out when the return opens, and again just before the money goes back, for the units the warehouse counted. Refunding less than the return is worth — keeping a restocking fee, say — gives the tax back in the same proportion as the goods. If the tax service cannot answer, the return is not opened (or not refunded) and the customer can try again. Claims and exchanges follow the same rules.
|
|
126
|
+
|
|
119
127
|
### Where the goods come back to
|
|
120
128
|
|
|
121
129
|
A return is received at a stock location. By default that is wherever the goods
|
|
@@ -191,9 +199,32 @@ curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/claims/claim_x
|
|
|
191
199
|
|
|
192
200
|
A replacement creates a new [fulfillment](fulfillments.md) on the original order, so the customer doesn't have to place a second one.
|
|
193
201
|
|
|
194
|
-
The replacement is filed under the order line it replaces, but it doesn't count toward that line's quantity. Editing the line later, from a claim or an [exchange](#exchanges), adds or removes only the goods the customer paid for and leaves the replacement where it is.
|
|
202
|
+
The replacement is filed under the order line it replaces, but it doesn't count toward that line's quantity. Editing the line later, from a claim or an [exchange](#exchanges), adds or removes only the goods the customer paid for and leaves the replacement where it is. Adding a fulfillment for that line works the same way: it takes only the goods the customer paid for, and the replacement stays in its own fulfillment.
|
|
203
|
+
|
|
204
|
+
If a replacement's fulfillment is canceled, for example because the courier lost the booking, send the goods again by naming that fulfillment as the source of a new one. The new fulfillment takes everything the canceled one held and reserves its stock again. It is priced like any new fulfillment, so pass `cost: 0` to carry over the canceled fulfillment's delivery cost instead, which keeps a free replacement free.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
```typescript Admin SDK
|
|
208
|
+
await adminClient.orders.fulfillments.create('or_xxx', {
|
|
209
|
+
stock_location_id: 'sloc_xxx',
|
|
210
|
+
source_fulfillment_id: 'ful_xxx', // the canceled replacement fulfillment
|
|
211
|
+
cost: 0,
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```bash cURL
|
|
216
|
+
curl -X POST 'https://api.mystore.com/api/v3/admin/orders/or_xxx/fulfillments' \
|
|
217
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
218
|
+
-H 'Content-Type: application/json' \
|
|
219
|
+
-d '{
|
|
220
|
+
"stock_location_id": "sloc_xxx",
|
|
221
|
+
"source_fulfillment_id": "ful_xxx",
|
|
222
|
+
"cost": 0
|
|
223
|
+
}'
|
|
224
|
+
```
|
|
195
225
|
|
|
196
|
-
|
|
226
|
+
|
|
227
|
+
A claim never refunds more than the customer paid for the claimed items after discounts, tax included. Each claim line reports that ceiling as `paid_amount`, and the `refund_amount` you enter for a line includes its tax. The tax goes back in proportion to the money: refunding half of what a line is worth gives back half its tax, and resolving with a replacement alone gives none back.
|
|
197
228
|
|
|
198
229
|
## Exchanges
|
|
199
230
|
|
|
@@ -218,6 +249,16 @@ curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch
|
|
|
218
249
|
```
|
|
219
250
|
|
|
220
251
|
|
|
252
|
+
### Settling the difference
|
|
253
|
+
|
|
254
|
+
An exchange credits what came back and sells the replacement, each with its tax:
|
|
255
|
+
|
|
256
|
+
- `original_price` is what the customer paid for the items coming back, after discounts and with their tax.
|
|
257
|
+
- `new_variant_price` is the replacement, with its own tax. It keeps the deal the customer had: an item bought at 10% off is replaced at 10% off its price, so swapping a size costs nothing.
|
|
258
|
+
- `price_difference` is the second less the first.
|
|
259
|
+
|
|
260
|
+
When you fulfill, a cheaper replacement refunds the difference, to store credit or the original payment. A dearer one adds the difference to the order as an `exchange` [fee](fees.md), so the order shows a balance due. Spree never charges a stored card for it; take the payment through the order's [payments](payments.md).
|
|
261
|
+
|
|
221
262
|
## Reporting
|
|
222
263
|
|
|
223
264
|
Because each one is its own record, you can query them directly:
|
|
@@ -325,6 +325,8 @@ The amount is payment-source-agnostic — store credit and gift cards are how th
|
|
|
325
325
|
|
|
326
326
|
A refund never edits an earning. `refund.created` writes a second row of kind `refund_reversal` against the same order, so what a seller has earned is always the sum of their transfers, and a reversal that lands after a payout closed falls into the next period rather than rewriting a settlement that already happened.
|
|
327
327
|
|
|
328
|
+
A reversal takes back only the seller's share of what was refunded. When a return or claim refunds goods and their tax, the goods come back less the commission charged on them; the tax comes back from the seller only if the seller collected it, and stays with the marketplace when the marketplace remits it.
|
|
329
|
+
|
|
328
330
|
### Payouts — what was sent
|
|
329
331
|
|
|
330
332
|
On schedule, a seller's confirmed, unsettled earnings are batched into one `Spree::SellerPayout` per currency and handed to the provider. A payout names exactly which transfers it settled, which is what a seller needs to reconcile a deposit.
|
|
@@ -148,6 +148,8 @@ Whatever works out the tax, the record is the same: a tax line per charge, sayin
|
|
|
148
148
|
| `included` | Whether the tax was already inside the displayed price |
|
|
149
149
|
| `amount` / `display_amount` | The tax charged |
|
|
150
150
|
| `line_item_id` / `fulfillment_id` / `fee_id` | What was taxed — exactly one is set |
|
|
151
|
+
| `return_line_item_id` / `claim_line_item_id` / `exchange_line_item_id` | After the sale: the return, claim or exchange line the tax belongs to |
|
|
152
|
+
| `credit` | `true` for tax given back on a return, claim or exchange |
|
|
151
153
|
|
|
152
154
|
|
|
153
155
|
```typescript Admin SDK
|
|
@@ -166,6 +168,10 @@ Each line also keeps its own copy of the rate and label. If someone edits that t
|
|
|
166
168
|
|
|
167
169
|
You never create tax lines yourself; they're written whenever the total is worked out.
|
|
168
170
|
|
|
171
|
+
### Tax given back
|
|
172
|
+
|
|
173
|
+
When a customer returns something, the tax they paid on it goes back with the money. That tax is recorded as tax lines too, marked `credit`, on the [return, claim or exchange](returns-exchanges-claims.md) line rather than the order line. Each one names the sale's tax line it gives back (`original_tax_line_id`, Admin API), keeps that line's rate and label, and covers only the units that came back. They never change the order's own tax totals, and the order's tax lines list above shows only what was charged.
|
|
174
|
+
|
|
169
175
|
## When tax is worked out
|
|
170
176
|
|
|
171
177
|
Tax is recalculated whenever the answer could have changed — an item added, an address entered, a delivery option chosen. Every one of those requests returns the updated cart, so a storefront never needs a separate "refresh the tax" call.
|
|
@@ -204,6 +210,27 @@ A provider declares up front what it cannot do — no US local tax, no reverse c
|
|
|
204
210
|
|
|
205
211
|
Whichever provider is in use, the result is the same: tax lines on the order, in the same shape. Your storefront code doesn't change.
|
|
206
212
|
|
|
213
|
+
The provider also decides the tax given back on returns, claims and exchanges, and the refund pays exactly what it answers. Spree's own rates give back each returned unit's share of the tax the sale actually charged, so the customer gets back what they paid even if a rate has been edited since. An external service may calculate it itself instead, for example at the rates in force on the day of the sale. If it cannot be reached, the return is refused until it can, rather than refunding tax nobody worked out.
|
|
214
|
+
|
|
215
|
+
If you are writing a provider, these are the calls Spree makes after the sale:
|
|
216
|
+
|
|
217
|
+
| Method | When | What it does |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `estimate_refund` | A return, claim or exchange opens, and again before its money moves | Writes the credit tax lines the refund will carry |
|
|
220
|
+
| `estimate_replacement` | An exchange opens and is fulfilled | Taxes the replacement as a new sale |
|
|
221
|
+
| `refund` | After the money has moved | Files the credit with your service — no more than the credit lines hold |
|
|
222
|
+
| `commit_replacement` | After an exchange is fulfilled | Files the replacement sale |
|
|
223
|
+
|
|
224
|
+
`estimate_refund` and `estimate_replacement` have no default — a provider without them cannot open a return, claim or exchange. To give back exactly what the sale charged, as Spree's own rates do, answer `estimate_refund` with `Spree::TaxProvider::RecordedShare`:
|
|
225
|
+
|
|
226
|
+
```ruby server/app/models/my_app/tax_provider.rb
|
|
227
|
+
def estimate_refund(order, items, amounts: nil, tax_date: nil)
|
|
228
|
+
Spree::TaxProvider::RecordedShare.new(order: order, items: items, amounts: amounts).call
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`refund` and `commit_replacement` are optional: by default they do nothing, which suits a service that keeps no record of sales.
|
|
233
|
+
|
|
207
234
|
## Tax-exempt customers
|
|
208
235
|
|
|
209
236
|
Business customers are often exempt, and the paperwork is real. Spree records a customer's or company's **tax identifier** — a VAT number, an ABN — and can validate it. Companies can also hold exemption certificates, scoped to the country or state that issued them.
|
|
@@ -214,5 +241,6 @@ Exemption is decided when tax is worked out, not stored as a flag on the custome
|
|
|
214
241
|
|
|
215
242
|
- [Order totals](order-totals.md) — how tax rolls into what a customer pays
|
|
216
243
|
- [Markets](markets.md) — where tax treatment and provider are chosen
|
|
244
|
+
- [Returns, exchanges and claims](returns-exchanges-claims.md) — how tax goes back with a refund
|
|
217
245
|
- [Addresses](addresses.md) — what determines the rate
|
|
218
246
|
- [Companies](companies.md) — B2B tax identifiers and exemptions
|
|
@@ -366,7 +366,7 @@ A handler receives the workflow (so it can read `order`, `items`, `created_by`,
|
|
|
366
366
|
### Other behavioral changes
|
|
367
367
|
|
|
368
368
|
- **`Return#refunded_total` counts store credits.** Store credit is a separate ledger and never creates a `Spree::Refund` row, so the old sum reported zero for a store-credit refund.
|
|
369
|
-
- **`Order#outstanding_balance`
|
|
369
|
+
- **`Order#outstanding_balance` replaced its reimbursement term** with refunds made for returns, claims and exchanges (`returned_items_refund_total`). Those refunds net out of `payment_total`, and adding them back is what keeps a refunded return from reading as a balance due. A refund made for nothing in return still reads as owed, as in 5.x.
|
|
370
370
|
- **The reimbursement email is now `Spree::ReturnMailer#refunded_email`**, sent on `return.refunded` (in the optional `spree_emails` gem, like the other transactional mail).
|
|
371
371
|
- **`Refund#originator`** points at the new records.
|
|
372
372
|
- Events are `return.requested` / `.approved` / `.received` / `.refunded` / `.canceled`, and the matching `exchange.*` and `claim.*` families.
|