upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/15.md
diff options
context:
space:
mode:
Diffstat (limited to '15.md')
-rw-r--r--15.md316
1 files changed, 220 insertions, 96 deletions
diff --git a/15.md b/15.md
index f8df328..55814fb 100644
--- a/15.md
+++ b/15.md
@@ -1,14 +1,14 @@
1NIP-15 1NIP-15
2====== 2======
3 3
4Nostr Marketplace (for resilient marketplaces) 4Nostr Marketplace
5----------------------------------- 5-----------------
6 6
7`draft` `optional` `author:fiatjaf` `author:benarc` `author:motorina0` `author:talvasconcelos` 7`draft` `optional`
8 8
9> Based on https://github.com/lnbits/Diagon-Alley 9Based on https://github.com/lnbits/Diagon-Alley.
10 10
11> Implemented here https://github.com/lnbits/nostrmarket 11Implemented in [NostrMarket](https://github.com/lnbits/nostrmarket) and [Plebeian Market](https://github.com/PlebeianTech/plebeian-market).
12 12
13## Terms 13## Terms
14 14
@@ -33,88 +33,113 @@ The `merchant` admin software can be purely clientside, but for `convenience` an
33## `Merchant` publishing/updating products (event) 33## `Merchant` publishing/updating products (event)
34 34
35A merchant can publish these events: 35A merchant can publish these events:
36| Kind | | Description | NIP | 36| Kind | | Description |
37|---------|------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------| 37| --------- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
38| `0 ` | `set_meta` | The merchant description (similar with any `nostr` public key). | [NIP01 ](https://github.com/nostr-protocol/nips/blob/master/01.md) | 38| `0` | `set_meta` | The merchant description (similar with any `nostr` public key). |
39| `30017` | `set_stall` | Create or update a stall. | [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md) (Parameterized Replaceable Event) | 39| `30017` | `set_stall` | Create or update a stall. |
40| `30018` | `set_product` | Create or update a product. | [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md) (Parameterized Replaceable Event) | 40| `30018` | `set_product` | Create or update a product. |
41| `4 ` | `direct_message` | Communicate with the customer. The messages can be plain-text or JSON. | [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md) | 41| `4` | `direct_message` | Communicate with the customer. The messages can be plain-text or JSON. |
42| `5 ` | `delete` | Delete a product or a stall. | [NIP09](https://github.com/nostr-protocol/nips/blob/master/09.md) | 42| `5` | `delete` | Delete a product or a stall. |
43 43
44### Event `30017`: Create or update a stall. 44### Event `30017`: Create or update a stall.
45 45
46**Event Content**: 46**Event Content**
47
47```json 48```json
48{ 49{
49 "id": <String, UUID generated by the merchant. Sequential IDs (`0`, `1`, `2`...) are discouraged>, 50 "id": <string, id generated by the merchant. Sequential IDs (`0`, `1`, `2`...) are discouraged>,
50 "name": <String, stall name>, 51 "name": <string, stall name>,
51 "description": <String (optional), stall description>, 52 "description": <string (optional), stall description>,
52 "currency": <String, currency used>, 53 "currency": <string, currency used>,
53 "shipping": [ 54 "shipping": [
54 { 55 {
55 "id": <String, UUID of the shipping zone, generated by the merchant>, 56 "id": <string, id of the shipping zone, generated by the merchant>,
56 "name": <String (optional), zone name>, 57 "name": <string (optional), zone name>,
57 "cost": <float, cost for shipping. The currency is defined at the stall level>, 58 "cost": <float, base cost for shipping. The currency is defined at the stall level>,
58 "countries": [<String, countries included in this zone>], 59 "regions": [<string, regions included in this zone>]
59 } 60 }
60 ] 61 ]
61} 62}
62``` 63```
63 64
64Fields that are not self-explanatory: 65Fields that are not self-explanatory:
65 - `shipping`: 66 - `shipping`:
66 - an array with possible shipping zones for this stall. The customer MUST choose exactly one shipping zone. 67 - an array with possible shipping zones for this stall.
68 - the customer MUST choose exactly one of those shipping zones.
67 - shipping to different zones can have different costs. For some goods (digital for example) the cost can be zero. 69 - shipping to different zones can have different costs. For some goods (digital for example) the cost can be zero.
68 - the `id` is an internal value used by the merchant. This value must be sent back as the customer selection. 70 - the `id` is an internal value used by the merchant. This value must be sent back as the customer selection.
71 - each shipping zone contains the base cost for orders made to that shipping zone, but a specific shipping cost per
72 product can also be specified if the shipping cost for that product is higher than what's specified by the base cost.
73
74**Event Tags**
69 75
70**Event Tags**:
71```json 76```json
72 "tags": [["d", <String, id of stall]] 77{
78 "tags": [["d", <string, id of stall]],
79 ...
80}
73``` 81```
74 - the `d` tag is required by [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md). Its value MUST be the same as the stall `id`. 82 - the `d` tag is required, its value MUST be the same as the stall `id`.
75 83
76### Event `30018`: Create or update a product 84### Event `30018`: Create or update a product
77 85
78**Event Content**: 86**Event Content**
87
79```json 88```json
80{ 89{
81 "id": <String, UUID generated by the merchant.Sequential IDs (`0`, `1`, `2`...) are discouraged>, 90 "id": <string, id generated by the merchant (sequential ids are discouraged)>,
82 "stall_id": <String, UUID of the stall to which this product belong to>, 91 "stall_id": <string, id of the stall to which this product belong to>,
83 "name": <String, product name>, 92 "name": <string, product name>,
84 "description": <String (optional), product description>, 93 "description": <string (optional), product description>,
85 "images": <[String], array of image URLs, optional>, 94 "images": <[string], array of image URLs, optional>,
86 "currency": <String, currency used>, 95 "currency": <string, currency used>,
87 "price": <float, cost of product>, 96 "price": <float, cost of product>,
88 "quantity": <int, available items>, 97 "quantity": <int or null, available items>,
89 "specs": [ 98 "specs": [
90 [ <String, spec key>, <String, spec value>] 99 [<string, spec key>, <string, spec value>]
91 ] 100 ],
101 "shipping": [
102 {
103 "id": <string, id of the shipping zone (must match one of the zones defined for the stall)>,
104 "cost": <float, extra cost for shipping. The currency is defined at the stall level>
105 }
106 ]
92} 107}
93``` 108```
94 109
95Fields that are not self-explanatory: 110Fields that are not self-explanatory:
111 - `quantity` can be null in the case of items with unlimited availability, like digital items, or services
96 - `specs`: 112 - `specs`:
97 - an array of key pair values. It allows for the Customer UI to present present product specifications in a structure mode. It also allows comparison between products 113 - an optional array of key pair values. It allows for the Customer UI to present product specifications in a structure mode. It also allows comparison between products
98 - eg: `[["operating_system", "Android 12.0"], ["screen_size", "6.4 inches"], ["connector_type", "USB Type C"]]` 114 - eg: `[["operating_system", "Android 12.0"], ["screen_size", "6.4 inches"], ["connector_type", "USB Type C"]]`
99 115
100_Open_: better to move `spec` in the `tags` section of the event? 116 _Open_: better to move `spec` in the `tags` section of the event?
117
118- `shipping`:
119 - an _optional_ array of extra costs to be used per shipping zone, only for products that require special shipping costs to be added to the base shipping cost defined in the stall
120 - the `id` should match the id of the shipping zone, as defined in the `shipping` field of the stall
121 - to calculate the total cost of shipping for an order, the user will choose a shipping option during checkout, and then the client must consider this costs:
122 - the `base cost from the stall` for the chosen shipping option
123 - the result of multiplying the product units by the `shipping costs specified in the product`, if any.
124
125**Event Tags**
101 126
102**Event Tags**:
103```json 127```json
104 "tags": [ 128 "tags": [
105 ["d", <String, id of product], 129 ["d", <string, id of product],
106 ["t", <String (optional), product category], 130 ["t", <string (optional), product category],
107 ["t", <String (optional), product category], 131 ["t", <string (optional), product category],
108 ... 132 ...
109 ] 133 ],
134 ...
110``` 135```
111 136
112 - the `d` tag is required by [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md). Its value MUST be the same as the product `id`. 137 - the `d` tag is required, its value MUST be the same as the product `id`.
113 - the `t` tag is as searchable tag ([NIP12](https://github.com/nostr-protocol/nips/blob/master/12.md)). It represents different categories that the product can be part of (`food`, `fruits`). Multiple `t` tags can be present. 138 - the `t` tag is as searchable tag, it represents different categories that the product can be part of (`food`, `fruits`). Multiple `t` tags can be present.
114 139
115## Checkout events 140## Checkout events
116 141
117All checkout events are sent as JSON strings using ([NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md)). 142All checkout events are sent as JSON strings using ([NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md)).
118 143
119The `merchant` and the `customer` can exchange JSON messages that represent different actions. Each `JSON` message `MUST` have a `type` field indicating the what the JSON represents. Possible types: 144The `merchant` and the `customer` can exchange JSON messages that represent different actions. Each `JSON` message `MUST` have a `type` field indicating the what the JSON represents. Possible types:
120 145
@@ -124,41 +149,40 @@ The `merchant` and the `customer` can exchange JSON messages that represent diff
124| 1 | Merchant | Payment Request | 149| 1 | Merchant | Payment Request |
125| 2 | Merchant | Order Status Update | 150| 2 | Merchant | Order Status Update |
126 151
127
128### Step 1: `customer` order (event) 152### Step 1: `customer` order (event)
129The below json goes in content of [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md). 153The below JSON goes in content of [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md).
130 154
131```json 155```json
132{ 156{
133 "id": <String, UUID generated by the customer>, 157 "id": <string, id generated by the customer>,
134 "type": 0, 158 "type": 0,
135 "name": <String (optional), ???>, 159 "name": <string (optional), ???>,
136 "address": <String (optional), for physical goods an address should be provided> 160 "address": <string (optional), for physical goods an address should be provided>,
137 "message": "<String (optional), message for merchant>, 161 "message": "<string (optional), message for merchant>,
138 "contact": { 162 "contact": {
139 "nostr": <32-bytes hex of a pubkey>, 163 "nostr": <32-bytes hex of a pubkey>,
140 "phone": <String (optional), if the customer wants to be contacted by phone>, 164 "phone": <string (optional), if the customer wants to be contacted by phone>,
141 "email": <String (optional), if the customer wants to be contacted by email>, 165 "email": <string (optional), if the customer wants to be contacted by email>
142 }, 166 },
143 "items": [ 167 "items": [
144 { 168 {
145 "product_id": <String, UUID of the product>, 169 "product_id": <string, id of the product>,
146 "quantity": <int, how many products the customer is ordering> 170 "quantity": <int, how many products the customer is ordering>
147 } 171 }
148 ], 172 ],
149 "shipping_id": <String, UUID of the shipping zone> 173 "shipping_id": <string, id of the shipping zone>
150} 174}
151 175
152``` 176```
153 177
154_Open_: is `contact.nostr` required? 178_Open_: is `contact.nostr` required?
155 179
156 180
157### Step 2: `merchant` request payment (event) 181### Step 2: `merchant` request payment (event)
158 182
159Sent back from the merchant for payment. Any payment option is valid that the merchant can check. 183Sent back from the merchant for payment. Any payment option is valid that the merchant can check.
160 184
161The below json goes in `content` of [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md). 185The below JSON goes in `content` of [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md).
162 186
163`payment_options`/`type` include: 187`payment_options`/`type` include:
164 188
@@ -169,46 +193,146 @@ The below json goes in `content` of [NIP04](https://github.com/nostr-protocol/ni
169 193
170```json 194```json
171{ 195{
172 "id": <String, UUID of the order>, 196 "id": <string, id of the order>,
173 "type": 1, 197 "type": 1,
174 "message": <String, message to customer, optional>, 198 "message": <string, message to customer, optional>,
175 "payment_options": [ 199 "payment_options": [
176 { 200 {
177 "type": <String, option type>, 201 "type": <string, option type>,
178 "link": <String, url, btc address, ln invoice, etc> 202 "link": <string, url, btc address, ln invoice, etc>
179 }, 203 },
204 {
205 "type": <string, option type>,
206 "link": <string, url, btc address, ln invoice, etc>
207 },
208 {
209 "type": <string, option type>,
210 "link": <string, url, btc address, ln invoice, etc>
211 }
212 ]
213}
214```
215
216### Step 3: `merchant` verify payment/shipped (event)
217
218Once payment has been received and processed.
219
220The below JSON goes in `content` of [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md).
221
222```json
223{
224 "id": <string, id of the order>,
225 "type": 2,
226 "message": <string, message to customer>,
227 "paid": <bool: has received payment>,
228 "shipped": <bool: has been shipped>,
229}
230```
231
232## Customize Marketplace
233
234Create a customized user experience using the `naddr` from [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md#shareable-identifiers-with-extra-metadata). The use of `naddr` enables easy sharing of marketplace events while incorporating a rich set of metadata. This metadata can include relays, merchant profiles, and more. Subsequently, it allows merchants to be grouped into a market, empowering the market creator to configure the marketplace's user interface and user experience, and share that marketplace. This customization can encompass elements such as market name, description, logo, banner, themes, and even color schemes, offering a tailored and unique marketplace experience.
235
236### Event `30019`: Create or update marketplace UI/UX
237
238**Event Content**
239
240```json
241{
242 "name": <string (optional), market name>,
243 "about": <string (optional), market description>,
244 "ui": {
245 "picture": <string (optional), market logo image URL>,
246 "banner": <string (optional), market logo banner URL>,
247 "theme": <string (optional), market theme>,
248 "darkMode": <bool, true/false>
249 },
250 "merchants": [array of pubkeys (optional)],
251 ...
252}
253```
254
255This event leverages naddr to enable comprehensive customization and sharing of marketplace configurations, fostering a unique and engaging marketplace environment.
256
257## Auctions
258
259### Event `30020`: Create or update a product sold as an auction
260
261**Event Content**:
262```json
263{
264 "id": <String, UUID generated by the merchant. Sequential IDs (`0`, `1`, `2`...) are discouraged>,
265 "stall_id": <String, UUID of the stall to which this product belong to>,
266 "name": <String, product name>,
267 "description": <String (optional), product description>,
268 "images": <[String], array of image URLs, optional>,
269 "starting_bid": <int>,
270 "start_date": <int (optional) UNIX timestamp, date the auction started / will start>,
271 "duration": <int, number of seconds the auction will run for, excluding eventual time extensions that might happen>,
272 "specs": [
273 [<String, spec key>, <String, spec value>]
274 ],
275 "shipping": [
180 { 276 {
181 "type": <String, option type>, 277 "id": <String, UUID of the shipping zone. Must match one of the zones defined for the stall>,
182 "link": <String, url, btc address, ln invoice, etc> 278 "cost": <float, extra cost for shipping. The currency is defined at the stall level>
183 },
184 {
185 "type": <String, option type>,
186 "link": <String, url, btc address, ln invoice, etc>
187 } 279 }
188 ] 280 ]
189} 281}
190``` 282```
191 283
192### Step 3: `merchant` verify payment/shipped (event) 284> [!NOTE]
285> Items sold as an auction are very similar in structure to fixed-price items, with some important differences worth noting.
193 286
194Once payment has been received and processed. 287* The `start_date` can be set to a date in the future if the auction is scheduled to start on that date, or can be omitted if the start date is unknown/hidden. If the start date is not specified, the auction will have to be edited later to set an actual date.
288
289* The auction runs for an initial number of seconds after the `start_date`, specified by `duration`.
290
291### Event `1021`: Bid
292
293```json
294{
295 "content": <int, amount of sats>,
296 "tags": [["e", <event ID of the auction to bid on>]],
297}
298```
299
300Bids are simply events of kind `1021` with a `content` field specifying the amount, in the currency of the auction. Bids must reference an auction.
301
302> [!NOTE]
303> Auctions can be edited as many times as desired (they are "parameterized replaceable events") by the author - even after the start_date, but they cannot be edited after they have received the first bid! This is enforced by the fact that bids reference the event ID of the auction (rather than the product UUID), which changes with every new version of the auctioned product. So a bid is always attached to one "version". Editing the auction after a bid would result in the new product losing the bid!
195 304
196The below json goes in `content` of [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md). 305### Event `1022`: Bid confirmation
306
307**Event Content**:
197 308
198```json 309```json
199{ 310{
200 "id": <String, UUID of the order>, 311 "status": <String, "accepted" | "rejected" | "pending" | "winner">,
201 "type": 2, 312 "message": <String (optional)>,
202 "message": <String, message to customer>, 313 "duration_extended": <int (optional), number of seconds>
203 "paid": <Bool, true/false has received payment>,
204 "shipped": <Bool, true/false has been shipped>,
205} 314}
206``` 315```
207 316
317**Event Tags**:
318```json
319 "tags": [["e" <event ID of the bid being confirmed>], ["e", <event ID of the auction>]],
320```
321
322Bids should be confirmed by the merchant before being considered as valid by other clients. So clients should subscribe to *bid confirmation* events (kind `1022`) for every auction that they follow, in addition to the actual bids and should check that the pubkey of the bid confirmation matches the pubkey of the merchant (in addition to checking the signature).
323
324The `content` field is a JSON which includes *at least* a `status`. `winner` is how the *winning bid* is replied to after the auction ends and the winning bid is picked by the merchant.
325
326The reasons for which a bid can be marked as `rejected` or `pending` are up to the merchant's implementation and configuration - they could be anything from basic validation errors (amount too low) to the bidder being blacklisted or to the bidder lacking sufficient *trust*, which could lead to the bid being marked as `pending` until sufficient verification is performed. The difference between the two is that `pending` bids *might* get approved after additional steps are taken by the bidder, whereas `rejected` bids can not be later approved.
327
328An additional `message` field can appear in the `content` JSON to give further context as of why a bid is `rejected` or `pending`.
329
330Another thing that can happen is - if bids happen very close to the end date of the auction - for the merchant to decide to extend the auction duration for a few more minutes. This is done by passing a `duration_extended` field as part of a bid confirmation, which would contain a number of seconds by which the initial duration is extended. So the actual end date of an auction is always `start_date + duration + (SUM(c.duration_extended) FOR c in all confirmations`.
331
208## Customer support events 332## Customer support events
209 333
210Customer support is handled over whatever communication method was specified. If communicating via nostr, NIP-04 is used https://github.com/nostr-protocol/nips/blob/master/04.md. 334Customer support is handled over whatever communication method was specified. If communicating via nostr, NIP-04 is used https://github.com/nostr-protocol/nips/blob/master/04.md.
211 335
212## Additional 336## Additional
213 337
214Standard data models can be found here <a href="https://raw.githubusercontent.com/lnbits/nostrmarket/main/models.py">here</a> 338Standard data models can be found <a href="https://raw.githubusercontent.com/lnbits/nostrmarket/main/models.py">here</a>