@spree/docs 0.1.181 → 0.1.183
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/seller-api/errors.md +1 -1
- package/dist/api-reference/store.yaml +78 -91
- package/dist/developer/core-concepts/addresses.md +106 -198
- package/dist/developer/core-concepts/architecture.md +97 -126
- package/dist/developer/core-concepts/calculators.md +75 -252
- package/dist/developer/core-concepts/carts.md +1 -1
- package/dist/developer/core-concepts/channels.md +0 -4
- package/dist/developer/core-concepts/companies-and-catalogs.md +1 -1
- package/dist/developer/core-concepts/customers.md +0 -3
- package/dist/developer/core-concepts/discounts.md +133 -0
- package/dist/developer/core-concepts/events.md +83 -576
- package/dist/developer/core-concepts/fees.md +144 -0
- package/dist/developer/core-concepts/imports-exports.md +105 -679
- package/dist/developer/core-concepts/inventory.md +114 -248
- package/dist/developer/core-concepts/markets.md +9 -12
- package/dist/developer/core-concepts/media.md +9 -11
- package/dist/developer/core-concepts/metafields.md +123 -200
- package/dist/developer/core-concepts/order-totals.md +110 -0
- package/dist/developer/core-concepts/orders.md +1 -1
- package/dist/developer/core-concepts/payments.md +11 -14
- package/dist/developer/core-concepts/pricing.md +11 -13
- package/dist/developer/core-concepts/products.md +173 -19
- package/dist/developer/core-concepts/promotions.md +12 -11
- package/dist/developer/core-concepts/search-filtering.md +2 -4
- package/dist/developer/core-concepts/store-credits-gift-cards.md +0 -3
- package/dist/developer/core-concepts/taxes.md +125 -113
- package/dist/developer/core-concepts/translations.md +61 -68
- package/dist/developer/core-concepts/webhooks.md +25 -59
- package/dist/developer/how-to/custom-promotion.md +3 -3
- package/package.json +1 -1
- package/dist/developer/core-concepts/taxes-discounts-fees.md +0 -199
|
@@ -1,303 +1,126 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Calculators
|
|
3
|
+
description: The small pieces of arithmetic behind delivery charges and promotion discounts — what each one does and how to choose between them.
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
## Overview
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```mermaid
|
|
10
|
-
erDiagram
|
|
11
|
-
Calculator {
|
|
12
|
-
string type
|
|
13
|
-
string calculable_type
|
|
14
|
-
text preferences
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
TaxRate {
|
|
18
|
-
string name
|
|
19
|
-
decimal amount
|
|
20
|
-
boolean included_in_price
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
ShippingMethod {
|
|
24
|
-
string name
|
|
25
|
-
string display_on
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
PromotionAction {
|
|
29
|
-
string type
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
Adjustment {
|
|
33
|
-
decimal amount
|
|
34
|
-
string label
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
TaxRate ||--|| Calculator : "has one"
|
|
38
|
-
ShippingMethod ||--|| Calculator : "has one"
|
|
39
|
-
PromotionAction ||--|| Calculator : "has one"
|
|
40
|
-
TaxRate ||--o{ Adjustment : "creates"
|
|
41
|
-
ShippingMethod ||--o{ ShippingRate : "calculates"
|
|
42
|
-
PromotionAction ||--o{ Adjustment : "creates"
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Key relationships:**
|
|
46
|
-
- **Calculator** computes amounts for various features
|
|
47
|
-
- Used by **[Tax Rates](taxes.md)** to calculate tax amounts
|
|
48
|
-
- Used by **[Shipping Methods](fulfillments.md)** to calculate shipping costs
|
|
49
|
-
- Used by **[Promotion Actions](promotions.md)** to calculate discounts
|
|
50
|
-
- Calculators store [preferences (rates, percentages, etc.)](../customization/model-preferences.md) for their calculations
|
|
51
|
-
|
|
52
|
-
Spree makes extensive use of the `Spree::Calculator` model and there are several subclasses provided to deal with various types of calculations flat rate, percentage discount, sales tax, VAT, etc. All calculators extend the `Spree::Calculator` class and must provide the following methods:
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
def self.description
|
|
56
|
-
# Human readable description of the calculator
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def compute(object=nil)
|
|
60
|
-
# Returns the value after performing the required calculation
|
|
61
|
-
end
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Calculators link to a `calculable` object, which are typically one of `Spree::ShippingMethod`, `Spree::TaxRate`, or `Spree::Promotion::Actions::CreateAdjustment`. These three classes use the `Spree::CalculatedAdjustments` module described below to provide an easy way to calculate adjustments for their objects.
|
|
65
|
-
|
|
66
|
-
## Available Calculators
|
|
67
|
-
|
|
68
|
-
The following are descriptions of the currently available calculators in Spree. If you would like to add your own, please see the [Creating a New Calculator](#creating-a-new-calculator) section.
|
|
69
|
-
|
|
70
|
-
### Default Tax
|
|
71
|
-
|
|
72
|
-
For information about this calculator, please read the [Taxes](taxes.md) guide.
|
|
73
|
-
|
|
74
|
-
### Flat Percent Per Item Total
|
|
75
|
-
|
|
76
|
-
This calculator has one preference: `flat_percent` and can be set like this:
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
calculator.preferred_flat_percent = 10
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
This calculator takes an order and calculates an amount using this calculation:
|
|
83
|
-
|
|
84
|
-
```ruby
|
|
85
|
-
[item total] x [flat percentage]
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
For example, if an order had an item total of `$31` and the calculator was configured to have a flat percent amount of `10`, the discount would be `$3.10`, because `$31 x 10% = $3.10`.
|
|
89
|
-
|
|
90
|
-
### Flat Rate
|
|
91
|
-
|
|
92
|
-
This calculator can be used to provide a flat rate discount.
|
|
93
|
-
|
|
94
|
-
This calculator has two preferences: `amount` and `currency`. These can be set like this:
|
|
95
|
-
|
|
96
|
-
```ruby
|
|
97
|
-
calculator.preferred_amount = 10
|
|
98
|
-
calculator.preferred_currency = "USD"
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
The currency for this calculator is used to check to see if a shipping method is available for an order. If an order's currency does not match the shipping method's currency, then that shipping method will not be displayed on the frontend.
|
|
102
|
-
|
|
103
|
-
This calculator can take any object and will return simply the preferred amount.
|
|
104
|
-
|
|
105
|
-
### Flexi Rate
|
|
106
|
-
|
|
107
|
-
This calculator is typically used for promotional discounts when you want a specific discount for the first product, and then subsequent discounts for other products, up to a certain amount.
|
|
8
|
+
A calculator works out an amount. "Charge $5 per item." "Take 15% off." "Free over $50, otherwise $7."
|
|
108
9
|
|
|
109
|
-
|
|
10
|
+
They exist because the rule and the number are different questions. A promotion decides *whether* a discount applies; a calculator decides *how much*. Keeping them apart means a merchant can change "10% off" to "$10 off" without touching the conditions that decide who qualifies.
|
|
110
11
|
|
|
111
|
-
|
|
112
|
-
* `additional_item`: The discounted price of subsequent items.
|
|
113
|
-
* `max_items`: The maximum number of items this discount applies to.
|
|
12
|
+
Two things use calculators:
|
|
114
13
|
|
|
115
|
-
|
|
14
|
+
- **[Delivery methods](fulfillments.md)** — what delivery costs
|
|
15
|
+
- **[Promotions](promotions.md)** — how big a discount is
|
|
116
16
|
|
|
117
|
-
|
|
118
|
-
[first item discount] + (([items_count*] - 1) x [additional item discount])
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
* up to the `max_items`
|
|
122
|
-
|
|
123
|
-
Thus, if you have ten items in your shopping cart, your `first_item` preference is set to `$10`, your `additional_items` preference is set to `$5`, and your `max_items` preference is set to `4`, the total discount would be `$25`:
|
|
124
|
-
|
|
125
|
-
* `$10` for the first item
|
|
126
|
-
* `$5` for each of the `3` subsequent items: `$5 \* 3 = $15`
|
|
127
|
-
* `$0` for the remaining `6` items
|
|
128
|
-
|
|
129
|
-
### Per Item
|
|
130
|
-
|
|
131
|
-
The Per Item calculator (`Spree::Calculator::Shipping::PerItem`) is a shipping calculator that charges a flat amount for every item in a shipment.
|
|
132
|
-
|
|
133
|
-
This calculator takes two preferences:
|
|
134
|
-
|
|
135
|
-
* `amount`: The flat amount charged per item.
|
|
136
|
-
* `currency`: The currency for this calculator.
|
|
137
|
-
|
|
138
|
-
It computes a flat rate per item by multiplying the `amount` preference by the total item quantity in the shipment package:
|
|
139
|
-
|
|
140
|
-
```text
|
|
141
|
-
[amount] x [total item quantity in package]
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
For example, with an `amount` of `5` and a package containing `3` items in total, the calculator computes an amount of `15` (`5 x 3`).
|
|
145
|
-
|
|
146
|
-
### Percent Per Item
|
|
147
|
-
|
|
148
|
-
The Percent Per Item calculator (`Spree::Calculator::PercentOnLineItem`) applies a percentage discount to a single line item. It takes two preferences:
|
|
17
|
+
> **INFO:** Tax does **not** use calculators. Tax is worked out by a [tax provider](taxes.md), which can be Spree's own rate tables or an external service.
|
|
149
18
|
|
|
150
|
-
|
|
151
|
-
* `apply_only_on_full_priced_items`: When enabled, skips line items that are already on sale.
|
|
19
|
+
## Choosing a calculator
|
|
152
20
|
|
|
153
|
-
|
|
21
|
+
Each calculator takes a few settings — the amount, the percentage, the threshold. A merchant picks one and fills in the settings when setting up a delivery method or a promotion.
|
|
154
22
|
|
|
155
|
-
For
|
|
23
|
+
### For delivery charges
|
|
156
24
|
|
|
157
|
-
|
|
25
|
+
| Calculator | What it charges | Settings |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| **Flat rate** | The same amount every time | `amount`, `currency` |
|
|
28
|
+
| **Per item** | An amount for each item in the parcel | `amount`, `currency` |
|
|
29
|
+
| **Percent of item total** | A percentage of what's in the parcel | `flat_percent` |
|
|
30
|
+
| **Price sack** | One amount above a threshold, another below — the usual "free delivery over $50" | `minimal_amount`, `discount_amount`, `normal_amount` |
|
|
31
|
+
| **Flexi rate** | A charge for the first item, less for each one after | `first_item`, `additional_item`, `max_items` |
|
|
32
|
+
| **Digital delivery** | Nothing, for downloads | — |
|
|
158
33
|
|
|
159
|
-
|
|
34
|
+
### For promotion discounts
|
|
160
35
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
36
|
+
| Calculator | What it takes off | Settings |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| **Flat rate** | A fixed amount | `amount`, `currency` |
|
|
39
|
+
| **Percent of item total** | A percentage of the order | `flat_percent` |
|
|
40
|
+
| **Percent on line item** | A percentage of one item | `percent` |
|
|
41
|
+
| **Flexi rate** | A sliding amount by quantity | `first_item`, `additional_item`, `max_items` |
|
|
42
|
+
| **Tiered percent** | A percentage that grows with order value | tiers |
|
|
43
|
+
| **Tiered flat rate** | A fixed amount that grows with order value | tiers |
|
|
165
44
|
|
|
166
|
-
|
|
45
|
+
## Worked examples
|
|
167
46
|
|
|
168
|
-
|
|
47
|
+
<details>
|
|
48
|
+
<summary>Free delivery over $50, otherwise $7</summary>
|
|
169
49
|
|
|
170
|
-
|
|
50
|
+
**Price sack**, with `minimal_amount: 50`, `discount_amount: 0`, `normal_amount: 7`.
|
|
171
51
|
|
|
172
|
-
|
|
173
|
-
class CustomCalculator < Spree::Calculator
|
|
174
|
-
def self.description
|
|
175
|
-
# Human readable description of the calculator
|
|
176
|
-
end
|
|
52
|
+
A $60 basket pays nothing. A $20 basket pays $7.
|
|
177
53
|
|
|
178
|
-
|
|
179
|
-
# Returns the value after performing the required calculation
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
```
|
|
54
|
+
</details>
|
|
183
55
|
|
|
184
|
-
|
|
56
|
+
<details>
|
|
57
|
+
<summary>$3 to ship the first item, $1 for each extra</summary>
|
|
185
58
|
|
|
186
|
-
|
|
187
|
-
class CustomCalculator < Spree::ShippingCalculator
|
|
188
|
-
def self.description
|
|
189
|
-
# Human readable description of the calculator
|
|
190
|
-
end
|
|
59
|
+
**Flexi rate**, with `first_item: 3`, `additional_item: 1`.
|
|
191
60
|
|
|
192
|
-
|
|
193
|
-
# Returns the value after performing the required calculation
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
```
|
|
61
|
+
Four items cost `$3 + (3 × $1)` = **$6**. Set `max_items` to stop charging beyond a point.
|
|
197
62
|
|
|
198
|
-
|
|
63
|
+
</details>
|
|
199
64
|
|
|
200
|
-
|
|
65
|
+
<details>
|
|
66
|
+
<summary>10% off the order</summary>
|
|
201
67
|
|
|
202
|
-
|
|
203
|
-
Rails.application.config.after_initialize do
|
|
204
|
-
Spree.calculators.tax_rates << CustomCalculator
|
|
205
|
-
Spree.calculators.shipping_methods << CustomCalculator
|
|
206
|
-
Spree.calculators.promotion_actions_create_adjustments << CustomCalculator
|
|
207
|
-
end
|
|
208
|
-
```
|
|
68
|
+
**Percent of item total**, with `flat_percent: 10`.
|
|
209
69
|
|
|
210
|
-
|
|
70
|
+
A $31 order gets **$3.10** off.
|
|
211
71
|
|
|
212
|
-
|
|
213
|
-
Rails.application.config.spree.calculators.tax_rates << CustomCalculator
|
|
214
|
-
Rails.application.config.spree.calculators.shipping_methods << CustomCalculator
|
|
215
|
-
Rails.application.config.spree.calculators.promotion_actions_create_adjustments << CustomCalculator
|
|
216
|
-
```
|
|
72
|
+
</details>
|
|
217
73
|
|
|
74
|
+
<details>
|
|
75
|
+
<summary>Spend more, save more</summary>
|
|
218
76
|
|
|
219
|
-
|
|
77
|
+
**Tiered percent** — 5% over $100, 10% over $250, 15% over $500.
|
|
220
78
|
|
|
221
|
-
|
|
79
|
+
The order total picks the tier. One promotion covers the whole ladder instead of three competing ones.
|
|
222
80
|
|
|
223
|
-
|
|
224
|
-
Rails.application.config.after_initialize do
|
|
225
|
-
Spree.calculators.shipping_methods << Spree::Calculator::Shipping::MyOwnCalculator
|
|
226
|
-
end
|
|
227
|
-
```
|
|
81
|
+
</details>
|
|
228
82
|
|
|
229
|
-
**Spree 5.1 and below:**
|
|
230
83
|
|
|
231
|
-
|
|
232
|
-
Rails.application.config.spree.calculators.shipping_methods << Spree::Calculator::Shipping::MyOwnCalculator
|
|
233
|
-
```
|
|
84
|
+
## Setting one up
|
|
234
85
|
|
|
86
|
+
Calculators are configured as part of the thing that uses them. The available calculators for each are discoverable, so a dashboard or a script can present the real list rather than a hardcoded one:
|
|
235
87
|
|
|
236
|
-
### Determining Availability
|
|
237
88
|
|
|
238
|
-
|
|
89
|
+
```typescript Admin SDK
|
|
90
|
+
// What can price a delivery method?
|
|
91
|
+
const calculators = await adminClient.deliveryMethods.calculators()
|
|
239
92
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
93
|
+
// Set up a delivery method with a flat rate
|
|
94
|
+
await adminClient.deliveryMethods.create({
|
|
95
|
+
name: 'Standard shipping',
|
|
96
|
+
delivery_zone_id: 'dz_xxx',
|
|
97
|
+
calculator_type: 'Spree::Calculator::Shipping::FlatRate',
|
|
98
|
+
calculator_attributes: {
|
|
99
|
+
preferences: { amount: '7.00', currency: 'USD' },
|
|
100
|
+
},
|
|
101
|
+
})
|
|
246
102
|
```
|
|
247
103
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
If you wish to use Spree's calculator functionality for your own application, you can include the `Spree::CalculatedAdjustments` module into a model of your choosing.
|
|
251
|
-
|
|
252
|
-
```ruby app/models/plan.rb
|
|
253
|
-
class Plan < ActiveRecord::Base
|
|
254
|
-
include Spree::CalculatedAdjustments
|
|
255
|
-
end
|
|
104
|
+
```bash CLI
|
|
105
|
+
spree api get /delivery_methods/calculators
|
|
256
106
|
```
|
|
257
107
|
|
|
258
|
-
To have calculators available for this class, you will need to register them. `Spree.calculators` is a fixed-member struct (`SpreeCalculators`, defined in `spree/core/lib/spree/core/engine.rb`) that exposes only the four built-in buckets:
|
|
259
|
-
|
|
260
|
-
* `shipping_methods`
|
|
261
|
-
* `tax_rates`
|
|
262
|
-
* `promotion_actions_create_adjustments`
|
|
263
|
-
* `promotion_actions_create_item_adjustments`
|
|
264
|
-
|
|
265
|
-
`Plan.calculators` internally calls `Spree.calculators.send(:plans)` (the tableized model name), so both registration and lookup raise `NoMethodError` until you extend the struct to add a matching `plans` member. Ruby structs cannot gain members at runtime, so this means redefining the `SpreeCalculators` struct (or reassigning `Spree.calculators` to an object that responds to `plans`) before you register anything onto it.
|
|
266
|
-
|
|
267
|
-
Once the struct exposes a `plans` member you can register calculators:
|
|
268
108
|
|
|
269
|
-
|
|
109
|
+
Each calculator declares which settings it takes, so a form can be built for it without knowing the calculator in advance — which is how the dashboard renders these.
|
|
270
110
|
|
|
271
|
-
|
|
272
|
-
Rails.application.config.after_initialize do
|
|
273
|
-
Spree.calculators.plans << CustomCalculator
|
|
274
|
-
end
|
|
275
|
-
```
|
|
111
|
+
> **WARNING:** A calculator with an `amount` also has a **currency**. If it doesn't match the order's currency, the calculator contributes nothing rather than converting. Set up one calculator per currency you sell in, or a promotion will silently do nothing for some customers.
|
|
276
112
|
|
|
277
|
-
|
|
113
|
+
## Writing your own
|
|
278
114
|
|
|
279
|
-
|
|
280
|
-
Rails.application.config.spree.calculators.plans << CustomCalculator
|
|
281
|
-
```
|
|
115
|
+
When none of the built-in calculators expresses your pricing — dimensional weight, a contract rate card, a rule your finance team invented — you can add one. A calculator is a small class with a name and a method that returns an amount, registered so it appears alongside the built-in options.
|
|
282
116
|
|
|
117
|
+
For delivery specifically, consider whether you want a calculator or a **delivery rate provider**. A calculator computes a number from what's in the parcel. A provider asks a carrier for real, live rates. If you want what UPS would actually charge today, that's a provider.
|
|
283
118
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
```ruby
|
|
287
|
-
Plan.calculators
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Using this method, you can then display the calculators as you please. Each object for this new class will need to have a calculator associated so that adjustments can be calculated on them.
|
|
291
|
-
|
|
292
|
-
`Spree::CalculatedAdjustments` provides a `has_one :calculator` association (with `accepts_nested_attributes_for` and a presence validation), delegates `compute` to that calculator, exposes a `with_calculator` scope, `calculator_type` / `calculator_type=` accessors, and the `Plan.calculators` registry shown above. To work out what the calculator would compute an amount to be, call `compute` on an instance:
|
|
293
|
-
|
|
294
|
-
```ruby
|
|
295
|
-
plan.compute(<calculable object>)
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
The module does not define `create_adjustment`, `update_adjustment`, or `compute_amount`. If you also need to build adjustments, include `Spree::AdjustmentSource`, which adds `create_adjustment(order, adjustable, included = false)`. That method calls a `compute_amount` you define on the including model (as `Spree::TaxRate` and the `Spree::Promotion::Actions` classes do). There is no `update_adjustment` method in core.
|
|
119
|
+
See [Custom promotions](../how-to/custom-promotion.md) and [Providers](../providers/overview.md).
|
|
299
120
|
|
|
300
|
-
## Related
|
|
121
|
+
## Related
|
|
301
122
|
|
|
302
|
-
- [
|
|
303
|
-
- [
|
|
123
|
+
- [Fulfillments](fulfillments.md) — delivery methods and rates
|
|
124
|
+
- [Promotions](promotions.md) — the rules that decide when a discount applies
|
|
125
|
+
- [Discounts](discounts.md) — the rows a promotion produces
|
|
126
|
+
- [Taxes](taxes.md) — worked out by providers, not calculators
|
|
@@ -136,7 +136,7 @@ This is what lets you design your own checkout. Spree tells you what's missing;
|
|
|
136
136
|
shipping_address: {
|
|
137
137
|
first_name: 'John', last_name: 'Doe',
|
|
138
138
|
address1: '123 Main St', city: 'Los Angeles',
|
|
139
|
-
country_code: 'US',
|
|
139
|
+
country_code: 'US', state_code: 'CA', postal_code: '90001',
|
|
140
140
|
},
|
|
141
141
|
})
|
|
142
142
|
```
|
|
@@ -3,9 +3,6 @@ title: Channels
|
|
|
3
3
|
description: Per-store distribution surfaces — online storefront, POS, marketplace, wholesale — each with its own product catalog and order attribution.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { Since } from '/snippets/since.mdx';
|
|
7
|
-
|
|
8
|
-
|
|
9
6
|
## Overview
|
|
10
7
|
|
|
11
8
|
Channels segment a single [Store](stores.md) into distinct selling surfaces. A channel represents *where* an order originates from — the online storefront, an in-person point-of-sale till, a marketplace integration (Amazon, eBay), a B2B wholesale portal, a mobile app — and *which* subset of the store's products is available there.
|
|
@@ -93,7 +90,6 @@ This attribution drives reporting (best-selling by channel, revenue per channel)
|
|
|
93
90
|
|
|
94
91
|
### Storefront Access Gating
|
|
95
92
|
|
|
96
|
-
|
|
97
93
|
A channel's `storefront_access` decides what an **anonymous** visitor — a request with no authenticated customer — may see. Logged-in customers are never gated. The posture is one of three values:
|
|
98
94
|
|
|
99
95
|
| Mode | Guest sees catalog | Guest sees prices | Use case |
|
|
@@ -54,7 +54,7 @@ Memberships stay always-active and always customer-backed: the pending state liv
|
|
|
54
54
|
|
|
55
55
|
Carts and orders carry a `company_id` pointing at any node — buying *for* a division means pointing at it. A buyer with exactly one membership resolves to it automatically; a buyer with several names the node on their cart (the `company_id` param on cart update, validated against their standing). The node is frozen onto the order at completion, like every other order attribute, so a placed order's tax treatment stays explainable no matter how the tree changes later.
|
|
56
56
|
|
|
57
|
-
Exemption certificates and the company's tax registration always resolve through `company.legal_entity` — see [Taxes
|
|
57
|
+
Exemption certificates and the company's tax registration always resolve through `company.legal_entity` — see [Taxes](taxes.md) for how they reach the tax provider.
|
|
58
58
|
|
|
59
59
|
## Catalogs
|
|
60
60
|
|
|
@@ -3,8 +3,6 @@ title: Customers
|
|
|
3
3
|
description: How Spree models customer accounts — registration, authentication, addresses, order history, store credits, and guest checkout behavior.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { Since } from '/snippets/since.mdx';
|
|
7
|
-
|
|
8
6
|
## Overview
|
|
9
7
|
|
|
10
8
|
Customers interact with your store through the Store API. They can register, log in, manage their profile, and view order history.
|
|
@@ -193,7 +191,6 @@ On success, the user is automatically logged in and a JWT token is returned. The
|
|
|
193
191
|
|
|
194
192
|
## Newsletter Subscriptions
|
|
195
193
|
|
|
196
|
-
|
|
197
194
|
Headless storefronts often need to collect newsletter signups before account creation (footer forms, popup overlays). The Store API exposes a double opt-in subscription flow that mirrors the password reset webhook pattern.
|
|
198
195
|
|
|
199
196
|
### Step 1: Subscribe
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Discounts
|
|
3
|
+
description: The record of money taken off one order — where a discount comes from, and why it is spread across the items it applied to.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
A discount is one line on one order saying money came off, and why.
|
|
9
|
+
|
|
10
|
+
> **NOTE:** **A discount is not a promotion.** A [promotion](promotions.md) is a *rule* — "20% off shoes in July" — that exists on its own, before anybody buys anything. A discount is the *result* on a particular order.
|
|
11
|
+
>
|
|
12
|
+
> Think of the promotion as the sign in the shop window and the discount as the line on the receipt. One promotion produces thousands of discounts over its life.
|
|
13
|
+
|
|
14
|
+
That separation is what lets an order stay truthful. The promotion can be edited, paused or deleted next month; the discount row on last month's order still says exactly what that customer was given.
|
|
15
|
+
|
|
16
|
+
It also means **not every discount has a promotion behind it.** When a support agent takes $10 off as a goodwill gesture, that's a discount with no campaign attached — which is what the `kind` field distinguishes.
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
erDiagram
|
|
20
|
+
Promotion ||--o{ Discount : "produces many, over time"
|
|
21
|
+
Order ||--o{ Discount : "has many"
|
|
22
|
+
LineItem ||--o{ Discount : "reduced by"
|
|
23
|
+
Fulfillment ||--o{ Discount : "reduced by"
|
|
24
|
+
|
|
25
|
+
Promotion {
|
|
26
|
+
string name
|
|
27
|
+
string code
|
|
28
|
+
datetime expires_at
|
|
29
|
+
}
|
|
30
|
+
Discount {
|
|
31
|
+
string label
|
|
32
|
+
string kind
|
|
33
|
+
string code
|
|
34
|
+
string value
|
|
35
|
+
string amount
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| | [Promotion](promotions.md) | Discount |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| What it is | A campaign rule | A row on one order |
|
|
42
|
+
| Exists | Before any order | Only once money comes off |
|
|
43
|
+
| How many | One | One per order it applies to |
|
|
44
|
+
| Can change later | Yes — edit or end it | No — it's a financial record |
|
|
45
|
+
| Set up by | A merchant, in advance | Created automatically, or by staff |
|
|
46
|
+
|
|
47
|
+
## Discount attributes
|
|
48
|
+
|
|
49
|
+
| Attribute | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `label` | What the customer sees — `Summer Sale` |
|
|
52
|
+
| `kind` | `promotion` or `manual` |
|
|
53
|
+
| `code` | The coupon code used, if there was one |
|
|
54
|
+
| `value` / `value_type` | The rule behind it — `"10"` and `percent` |
|
|
55
|
+
| `amount` / `display_amount` | What actually came off, as a negative amount |
|
|
56
|
+
| `promotion_id` | The promotion responsible, for promotion discounts |
|
|
57
|
+
| `line_item_id` / `fulfillment_id` | What it reduced |
|
|
58
|
+
|
|
59
|
+
Note the difference between `value` and `amount`. `value` is the rule — "10 percent". `amount` is the money — "-$12.00". You need the rule to explain the discount and the amount to add up the order.
|
|
60
|
+
|
|
61
|
+
## Reading discounts on an order
|
|
62
|
+
|
|
63
|
+
Whatever produced them, the rows read the same way:
|
|
64
|
+
|
|
65
|
+
```typescript Store SDK
|
|
66
|
+
const cart = await client.carts.get(cartId)
|
|
67
|
+
|
|
68
|
+
cart.display_discount_total // "-$12.00"
|
|
69
|
+
|
|
70
|
+
cart.discounts.forEach((discount) => {
|
|
71
|
+
discount.label // "Summer Sale"
|
|
72
|
+
discount.kind // "promotion" or "manual"
|
|
73
|
+
discount.code // "SUMMER20", when a code was used
|
|
74
|
+
discount.display_amount // "-$12.00"
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Applying a coupon code, and what decides whether it's accepted, belongs to [Promotions](promotions.md).
|
|
79
|
+
|
|
80
|
+
## Manual discounts
|
|
81
|
+
|
|
82
|
+
Sometimes there's no promotion — a price match, an apology for a late delivery. Staff can add a discount directly:
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
```typescript Admin SDK
|
|
86
|
+
// 10% off one line item
|
|
87
|
+
await adminClient.orders.discounts.create('or_xxx', {
|
|
88
|
+
label: 'Goodwill discount',
|
|
89
|
+
value: '10',
|
|
90
|
+
value_type: 'percent',
|
|
91
|
+
line_item_id: 'li_xxx',
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// A flat amount across the order
|
|
95
|
+
await adminClient.orders.discounts.create('or_xxx', {
|
|
96
|
+
label: 'Price match',
|
|
97
|
+
value: '15.00',
|
|
98
|
+
value_type: 'flat',
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```bash cURL
|
|
103
|
+
curl -X POST 'https://api.mystore.com/api/v3/admin/orders/or_xxx/discounts' \
|
|
104
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
105
|
+
-H 'Content-Type: application/json' \
|
|
106
|
+
-d '{ "label": "Goodwill discount", "value": "10", "value_type": "percent" }'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
> **WARNING:** Only **manual** discounts can be edited or deleted. Changing one that came from a promotion returns a `422`.
|
|
111
|
+
>
|
|
112
|
+
> That row belongs to the promotion that created it. If staff could edit it, an order would start disagreeing with the promotion it claims to have used — and the reporting behind "how did the summer sale do" stops adding up.
|
|
113
|
+
|
|
114
|
+
## How a discount is spread
|
|
115
|
+
|
|
116
|
+
An order-level discount isn't kept as one lump sum. It's divided across the items it applied to, in proportion to what they cost.
|
|
117
|
+
|
|
118
|
+
This looks like an implementation detail until someone returns one item out of three. Then the question is: how much of that $30 discount belonged to the returned shirt? If the discount were one lump, you'd have to guess. Because it was spread at the time, the answer is already recorded, and the refund is right without anyone doing arithmetic on the phone.
|
|
119
|
+
|
|
120
|
+
The same reasoning applies to tax — the tax on a partially returned order follows the same split.
|
|
121
|
+
|
|
122
|
+
## Discounts on placed orders
|
|
123
|
+
|
|
124
|
+
Once an order exists, its discounts stop being regenerated. Editing a placed order re-applies the rows it already has rather than re-running today's promotions.
|
|
125
|
+
|
|
126
|
+
The alternative would mean an order quietly changing because a sale ended overnight — which is indefensible to a customer holding a confirmation email.
|
|
127
|
+
|
|
128
|
+
## Related
|
|
129
|
+
|
|
130
|
+
- [Promotions](promotions.md) — the rules that create most discounts
|
|
131
|
+
- [Order totals](order-totals.md) — how discounts roll into the total
|
|
132
|
+
- [Gift cards & store credit](store-credits-gift-cards.md) — payment, not discount
|
|
133
|
+
- [Returns](returns-exchanges-claims.md) — how a discount is unwound on a return
|