mirror of
https://github.com/nostr-protocol/nips.git
synced 2025-01-12 16:42:10 -05:00
clean up, rewrite, clarify specification
This commit is contained in:
parent
d851ed802a
commit
598682be74
103
15.md
103
15.md
|
@ -12,33 +12,41 @@ Implemented in [NostrMarket](https://github.com/lnbits/nostrmarket) and [Plebeia
|
|||
|
||||
## Terms
|
||||
|
||||
- `merchant` - seller of products with NOSTR key-pair
|
||||
- `customer` - buyer of products with NOSTR key-pair
|
||||
- `product` - item for sale by the `merchant`
|
||||
- `stall` - list of products controlled by `merchant` (a `merchant` can have multiple stalls)
|
||||
- `merchant` - Seller of products with NOSTR key-pair
|
||||
- `customer` - Buyer of products with NOSTR key-pair
|
||||
- `stall` - List of products controlled by `merchant` (a `merchant` can have multiple stalls)
|
||||
- `product` - Item for sale by the `merchant`
|
||||
- `auction` - Item for sale, as auction, by the `merchant`
|
||||
- `bid` - Bids from users participating in a auction.
|
||||
- `marketplace` - clientside software for searching `stalls` and purchasing `products`
|
||||
- `checkout events` - A series of private messages exchanged between `merchant` and `customer`, representing the checkout process.
|
||||
|
||||
> [!NOTE]
|
||||
> Nostr Marketplaces operates using Bitcoin as its currency, but `merchants` have the flexibility to set prices in their preferred currency for their `stalls`. This means they can convert from their chosen currency to set a fixed price. Alternatively, merchants can opt to use Bitcoin as their currency, ensuring a fixed price in BTC.
|
||||
|
||||
## Nostr Marketplace Clients
|
||||
|
||||
### Merchant admin
|
||||
|
||||
Where the `merchant` creates, updates and deletes `stalls` and `products`, as well as where they manage sales, payments and communication with `customers`.
|
||||
|
||||
The `merchant` admin software can be purely clientside, but for `convenience` and uptime, implementations will likely have a server client listening for NOSTR events.
|
||||
The `merchant` admin software is where the `merchant` creates, updates, and deletes `stalls` and `products`, as well as manages sales, payments, and communication with `customers`. While the `merchant` admin software can be purely client-side, implementations will likely have a server client listening for NOSTR events for convenience and uptime. The `merchant` admin software can be integrated into the marketplace client.
|
||||
|
||||
### Marketplace
|
||||
|
||||
`Marketplace` software should be entirely clientside, either as a stand-alone app, or as a purely frontend webpage. A `customer` subscribes to different merchant NOSTR public keys, and those `merchants` `stalls` and `products` become listed and searchable. The marketplace client is like any other ecommerce site, with basket and checkout. `Marketplaces` may also wish to include a `customer` support area for direct message communication with `merchants`.
|
||||
`Marketplace` software should be entirely client-side, either as a standalone app or as a purely frontend webpage. A `customer` subscribes to different merchant NOSTR public keys, and those `merchants'` `stalls` and `products` become listed and searchable. The marketplace client is similar to any other e-commerce site, with a basket and checkout. `Marketplaces` may also wish to include a `customer` support area for direct message communication with `merchants`.
|
||||
|
||||
## `Merchant` publishing/updating products (event)
|
||||
|
||||
A merchant can publish these events:
|
||||
| Kind | | Description |
|
||||
| --------- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `0` | `set_meta` | The merchant description (similar with any `nostr` public key). |
|
||||
| `30017` | `set_stall` | Create or update a stall. |
|
||||
| `30018` | `set_product` | Create or update a product. |
|
||||
| `4` | `direct_message` | Communicate with the customer. The messages can be plain-text or JSON. |
|
||||
| `30020` | `set_auction` | Create or update a product selled as auction. |
|
||||
|
||||
Alongside other kinds of events that can be involved.
|
||||
| Kind | | Description |
|
||||
| --------- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `4` | `direct_message` | Used for `checkout events` and communication between `merchant` - `customer`. |
|
||||
| `5` | `delete` | Delete a product or a stall. |
|
||||
|
||||
### Event `30017`: Create or update a stall.
|
||||
|
@ -60,6 +68,9 @@ A merchant can publish these events:
|
|||
]
|
||||
}
|
||||
```
|
||||
Observations:
|
||||
- `currency` defines the currency in which the merchant wishes to operate. If it's `BTC`, no conversion is required; if it's different from `BTC`, the platform should perform the necessary conversion to `BTC` at the time of purchase, ensuring accurate price conversion for each buyer.
|
||||
- Ideally `currency` and `regions` values for shipping costs will utilize ISO 3166 codes to ensure accurate and consistent calculations.
|
||||
|
||||
Fields that are not self-explanatory:
|
||||
- `shipping`:
|
||||
|
@ -86,12 +97,11 @@ Fields that are not self-explanatory:
|
|||
|
||||
```json
|
||||
{
|
||||
// "stall_id": <string, id of the stall to which this product belong to>,
|
||||
"stall_id": <string, id of the stall to which this product belong to>,
|
||||
"name": <string, product name>,
|
||||
"type": <string, "simple" | "variable" | "variation">,
|
||||
"description": <string (optional), product description>,
|
||||
"images": <[string], array of image URLs, optional>,
|
||||
"currency": <string, currency used>,
|
||||
"price": <float, cost of product>,
|
||||
"quantity": <int or null, available items>,
|
||||
"specs": [
|
||||
|
@ -106,29 +116,28 @@ Fields that are not self-explanatory:
|
|||
}
|
||||
```
|
||||
|
||||
Observations:
|
||||
- The product inherits its currency from the stall it belongs to.
|
||||
|
||||
Fields that are not self-explanatory:
|
||||
- `quantity`: can be null in the case of items with unlimited availability, like digital items, or services
|
||||
- `specs`:
|
||||
- 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
|
||||
- eg: `[["operating_system", "Android 12.0"], ["screen_size", "6.4 inches"], ["connector_type", "USB Type C"]]`
|
||||
|
||||
_Open_: better to move `spec` in the `tags` section of the event?
|
||||
|
||||
- `shipping`:
|
||||
- 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
|
||||
- the `id` should match the id of the shipping zone, as defined in the `shipping` field of the stall
|
||||
- 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:
|
||||
- the `base cost from the stall` for the chosen shipping option
|
||||
- the result of multiplying the product units by the `shipping costs specified in the product`, if any.
|
||||
- `type`: It determines whether a product is a simple product, a variable product, or a variation of a variable product. (i.e.: a shirt sold in a single colour is a simple product, a shirt sold in more than one colour is a variable product, and has variations). To link the variable product within its variations, the variation product event should contain an `a` tag pointing to the event coordinate of the variable product.
|
||||
- `type`:This determination categorizes a product as either a `simple` product, a `variable` product, or a `variation` of a variable product. For example, a shirt sold in a single color is a `simple` product, while a shirt offered in multiple colors is a `variable` product with `variations`. To establish a link between a variable product and its variations, the variation product event should include an `a` tag that references the event coordinate of the corresponding variable product.
|
||||
|
||||
**Event Tags**
|
||||
|
||||
```json
|
||||
"tags": [
|
||||
["d", <string, id of product>],
|
||||
["a", <event coordinate (naddr) of the stall to which the product belongs>],
|
||||
["a", <event coordinate (naddr) of the variable product (optional, only for variations)>],
|
||||
["a", <event coordinate (decoded naddr) of the variable product (optional, only for variations)>, <recommended relay URL, optional>, <string, "variation">],
|
||||
["t", <string (optional), product category],
|
||||
["t", <string (optional), product category],
|
||||
...
|
||||
|
@ -138,8 +147,7 @@ Fields that are not self-explanatory:
|
|||
|
||||
- The `d` tag is required.
|
||||
- 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.
|
||||
- The `a` tag is used to link the product to the stall to which it belongs.
|
||||
- The `a` tag is used in variation products. It is used to link products that belong to a variable product.
|
||||
- The `a` tag is employed in products that are variations of a variable product, serving as a linking mechanism. It follows the conventions outlined in [nip01](https://github.com/nostr-protocol/nips/blob/master/01.md#tags)
|
||||
|
||||
## Checkout events
|
||||
|
||||
|
@ -162,7 +170,7 @@ The below JSON goes in content of [NIP-04](https://github.com/nostr-protocol/nip
|
|||
"type": 0,
|
||||
"name": <string (optional), ???>,
|
||||
"address": <string (optional), for physical goods an address should be provided>,
|
||||
"message": "<string (optional), message for merchant>,
|
||||
"message": <string (optional), message for merchant>,
|
||||
"contact": {
|
||||
"nostr": <32-bytes hex of a pubkey>,
|
||||
"phone": <string (optional), if the customer wants to be contacted by phone>,
|
||||
|
@ -179,9 +187,6 @@ The below JSON goes in content of [NIP-04](https://github.com/nostr-protocol/nip
|
|||
|
||||
```
|
||||
|
||||
_Open_: is `contact.nostr` required?
|
||||
|
||||
|
||||
### Step 2: `merchant` request payment (event)
|
||||
|
||||
Sent back from the merchant for payment. Any payment option is valid that the merchant can check.
|
||||
|
@ -234,12 +239,12 @@ The below JSON goes in `content` of [NIP-04](https://github.com/nostr-protocol/n
|
|||
}
|
||||
```
|
||||
Possible order statuses meaning:
|
||||
- Pending - The order has been created there is something that maintains it on hold, i.e: payment has not been received yet, or the stock of the product has been reduced but the merchant needs to approve the payment.
|
||||
- Processing - The payment has been received and the order is awaiting fulfillment (i.e. shipping products)
|
||||
- Completed - The payment is successful and the order has been fulfilled.
|
||||
- Cancelled - The order has been manually cancelled by the admin or the customer.
|
||||
- Refunded - The order has been refunded by the admin.
|
||||
- Failed - The order has failed due to technical issues or the payment being declined.
|
||||
- `Pending` - The order has been created there is something that maintains it on hold, i.e: payment has not been received yet, or the stock of the product has been reduced but the merchant needs to approve the payment.
|
||||
- `Processing` - The payment has been received and the order is awaiting fulfillment (i.e. shipping products)
|
||||
- `Completed` - The payment is successful and the order has been fulfilled.
|
||||
- `Cancelled` - The order has been manually cancelled by the admin or the customer.
|
||||
- `Refunded` - The order has been refunded by the admin.
|
||||
- `Failed` - The order has failed due to technical issues or the payment being declined.
|
||||
|
||||
## Customize Marketplace
|
||||
|
||||
|
@ -273,9 +278,10 @@ This event leverages naddr to enable comprehensive customization and sharing of
|
|||
**Event Content**:
|
||||
```json
|
||||
{
|
||||
// "stall_id": <String, UUID of the stall to which this product belong to>,
|
||||
"stall_id": <String, UUID of the stall to which this product belong to>,
|
||||
"name": <String, product name>,
|
||||
"description": <String (optional), product description>,
|
||||
"type": <string, "simple" | "variable" | "variation">,
|
||||
"images": <[String], array of image URLs, optional>,
|
||||
"starting_bid": <int>,
|
||||
"start_date": <int (optional) UNIX timestamp, date the auction started / will start>,
|
||||
|
@ -302,7 +308,7 @@ This event leverages naddr to enable comprehensive customization and sharing of
|
|||
```json
|
||||
"tags": [
|
||||
["d", <string, id of product auction>],
|
||||
["a", <event coordinate (naddr) of the stall to which the product belongs>]
|
||||
["a", <event coordinate (decoded naddr) of the variable product (optional, only for variations)>, <recommended relay URL, optional>, <string,"variation">],
|
||||
["t", <string (optional), product category],
|
||||
["t", <string (optional), product category],
|
||||
...
|
||||
|
@ -314,20 +320,6 @@ This event leverages naddr to enable comprehensive customization and sharing of
|
|||
|
||||
### Event `1021`: Bid
|
||||
|
||||
```json
|
||||
{
|
||||
"content": <int, amount of sats>,
|
||||
"tags": [["e", <event ID of the auction to bid on>]],//NOTE: Tag "e" should be "a" since products are kind:30020 ?
|
||||
}
|
||||
```
|
||||
|
||||
Bids are simply events of kind `1021` with a `content` field specifying the amount, in the currency of the auction. Bids must reference an auction.
|
||||
|
||||
> [!NOTE]
|
||||
> 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!
|
||||
|
||||
(NOTE: Should the bid event contain a stringified version of the kind:30020 auction event that its bidding for? in order to have a snapshot of it that can be verified with the signature, and not relay in the tag:`e` id of a parametriced replaceable event since its not mandatory that relays stores it. Stringified version of the auction event can be on the content field, and amount in sats using a tag:`amount` as its standariced in the protocol)
|
||||
|
||||
```json
|
||||
{
|
||||
"content": <stringified auction event>,
|
||||
|
@ -338,6 +330,7 @@ Bids are simply events of kind `1021` with a `content` field specifying the amou
|
|||
}
|
||||
```
|
||||
Bids are simply events of kind `1021` with a `content` field containing the strigified version of the auction event, and tags consisting of a tag:`a` pointing to the auction event coordinates and a tag:`amount` containing the bid amount expressed as an integer in the auction currency. Bids must refer to an auction.
|
||||
|
||||
### Event `1022`: Bid confirmation
|
||||
|
||||
**Event Content**:
|
||||
|
@ -346,24 +339,28 @@ Bids are simply events of kind `1021` with a `content` field containing the stri
|
|||
{
|
||||
"status": <String, "accepted" | "rejected" | "pending" | "winner">,
|
||||
"message": <String (optional)>,
|
||||
"duration_extended": <int (optional), number of seconds>//NOTE: Is this necessary? It can be simplified by simply increasing the duration of the kind:30020 auction event.
|
||||
}
|
||||
```
|
||||
|
||||
**Event Tags**:
|
||||
```json
|
||||
"tags": [["e" <event ID of the bid being confirmed>], ["e", <event ID of the auction>]],
|
||||
{
|
||||
"tags": [
|
||||
["e" <event ID of the bid being confirmed>],
|
||||
["a", <event coordinate of the auction>]
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Bids 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).
|
||||
To ensure the validity of bids, merchants must confirm them before they can be considered valid by other clients. Therefore, clients should subscribe to 'bid confirmation' events (kind `1022`) for every auction they follow, in addition to the actual bids. When checking the bid confirmation, clients must verify that the pubkey of the bid confirmation matches the pubkey of the merchant, in addition to checking the signature.
|
||||
|
||||
The `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.
|
||||
The `content` field is a JSON object that includes, at a minimum, a `status` field. The `winner` field is used to respond to the winning bid after the auction ends and the merchant has selected the winner.
|
||||
|
||||
The 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. (NOTE: pending action can happen again after and update of the auction event, the merchant dedices publish an update in the starting bid field of the auction event and can mark as pending bid that have a lower value)
|
||||
The reasons for marking a bid as `rejected` or `pending` are determined by the merchant's implementation and configuration. These reasons can range from basic validation errors (e.g., amount too low) to the bidder being blacklisted or lacking sufficient trust, which may lead to the bid being marked as `pending` until additional verification is performed. The key difference between `rejected` and `pending` bids is that `pending` bids may be approved after the bidder takes additional steps, whereas `rejected` bids cannot be later approved. Note that pending bids can be re-evaluated after an update to the auction event, at which point the merchant may publish an update to the starting bid field and mark bids with lower values as pending.
|
||||
|
||||
An additional `message` field can appear in the `content` JSON to give further context as of why a bid is `rejected` or `pending`.
|
||||
An optional `message` field can appear in the `content` JSON to provide further context for why a bid is `rejected` or `pending`.
|
||||
|
||||
Another 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`.
|
||||
If bids are placed very close to the auction's end date, the merchant can extend the auction duration by updating the `duration` field in the auction event.
|
||||
|
||||
## Customer support events
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user