fix: shipping cost; update UI on new order
This commit is contained in:
parent
f80a75e90c
commit
547c477ce6
11 changed files with 57 additions and 23 deletions
6
crud.py
6
crud.py
|
|
@ -393,12 +393,13 @@ async def create_order(merchant_id: str, o: Order) -> Order:
|
||||||
address,
|
address,
|
||||||
contact_data,
|
contact_data,
|
||||||
extra_data,
|
extra_data,
|
||||||
order_items,
|
order_items,
|
||||||
|
shipping_id,
|
||||||
stall_id,
|
stall_id,
|
||||||
invoice_id,
|
invoice_id,
|
||||||
total
|
total
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT(event_id) DO NOTHING
|
ON CONFLICT(event_id) DO NOTHING
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
|
|
@ -412,6 +413,7 @@ async def create_order(merchant_id: str, o: Order) -> Order:
|
||||||
json.dumps(o.contact.dict() if o.contact else {}),
|
json.dumps(o.contact.dict() if o.contact else {}),
|
||||||
json.dumps(o.extra.dict()),
|
json.dumps(o.extra.dict()),
|
||||||
json.dumps([i.dict() for i in o.items]),
|
json.dumps([i.dict() for i in o.items]),
|
||||||
|
o.shipping_id,
|
||||||
o.stall_id,
|
o.stall_id,
|
||||||
o.invoice_id,
|
o.invoice_id,
|
||||||
o.total,
|
o.total,
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,6 @@ def copy_x(output, x32, y32, data):
|
||||||
def order_from_json(s: str) -> Tuple[Optional[Any], Optional[str]]:
|
def order_from_json(s: str) -> Tuple[Optional[Any], Optional[str]]:
|
||||||
try:
|
try:
|
||||||
order = json.loads(s)
|
order = json.loads(s)
|
||||||
return (
|
return (order, s) if (type(order) is dict) and "items" in order else (None, s)
|
||||||
(order, s) if (type(order) is dict) and "items" in order else (None, s)
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None, s
|
return None, s
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ async def m001_initial(db):
|
||||||
order_items TEXT NOT NULL,
|
order_items TEXT NOT NULL,
|
||||||
address TEXT,
|
address TEXT,
|
||||||
total REAL NOT NULL,
|
total REAL NOT NULL,
|
||||||
|
shipping_id TEXT NOT NULL,
|
||||||
stall_id TEXT NOT NULL,
|
stall_id TEXT NOT NULL,
|
||||||
invoice_id TEXT NOT NULL,
|
invoice_id TEXT NOT NULL,
|
||||||
paid BOOLEAN NOT NULL DEFAULT false,
|
paid BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
|
|
||||||
18
models.py
18
models.py
|
|
@ -2,7 +2,7 @@ import json
|
||||||
import time
|
import time
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
@ -313,6 +313,8 @@ class OrderExtra(BaseModel):
|
||||||
products: List[ProductOverview]
|
products: List[ProductOverview]
|
||||||
currency: str
|
currency: str
|
||||||
btc_price: str
|
btc_price: str
|
||||||
|
shipping_cost: float = 0
|
||||||
|
shipping_cost_sat: float = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_products(cls, products: List[Product]):
|
async def from_products(cls, products: List[Product]):
|
||||||
|
|
@ -329,6 +331,7 @@ class PartialOrder(BaseModel):
|
||||||
event_created_at: Optional[int]
|
event_created_at: Optional[int]
|
||||||
public_key: str
|
public_key: str
|
||||||
merchant_public_key: str
|
merchant_public_key: str
|
||||||
|
shipping_id: str
|
||||||
items: List[OrderItem]
|
items: List[OrderItem]
|
||||||
contact: Optional[OrderContact]
|
contact: Optional[OrderContact]
|
||||||
address: Optional[str]
|
address: Optional[str]
|
||||||
|
|
@ -356,20 +359,25 @@ class PartialOrder(BaseModel):
|
||||||
f"Order ({self.id}) has products from different stalls"
|
f"Order ({self.id}) has products from different stalls"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def total_sats(self, products: List[Product]) -> float:
|
async def costs_in_sats(
|
||||||
|
self, products: List[Product], shipping_cost: float
|
||||||
|
) -> Tuple[float, float]:
|
||||||
product_prices = {}
|
product_prices = {}
|
||||||
for p in products:
|
for p in products:
|
||||||
product_prices[p.id] = p
|
product_prices[p.id] = p
|
||||||
|
|
||||||
amount: float = 0 # todo
|
product_cost: float = 0 # todo
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
price = product_prices[item.product_id].price
|
price = product_prices[item.product_id].price
|
||||||
currency = product_prices[item.product_id].config.currency or "sat"
|
currency = product_prices[item.product_id].config.currency or "sat"
|
||||||
if currency != "sat":
|
if currency != "sat":
|
||||||
price = await fiat_amount_as_satoshis(price, currency)
|
price = await fiat_amount_as_satoshis(price, currency)
|
||||||
amount += item.quantity * price
|
product_cost += item.quantity * price
|
||||||
|
|
||||||
return amount
|
if currency != "sat":
|
||||||
|
shipping_cost = await fiat_amount_as_satoshis(shipping_cost, currency)
|
||||||
|
|
||||||
|
return product_cost, shipping_cost
|
||||||
|
|
||||||
|
|
||||||
class Order(PartialOrder):
|
class Order(PartialOrder):
|
||||||
|
|
|
||||||
23
services.py
23
services.py
|
|
@ -20,6 +20,7 @@ from .crud import (
|
||||||
get_products_by_ids,
|
get_products_by_ids,
|
||||||
get_stalls,
|
get_stalls,
|
||||||
get_wallet_for_product,
|
get_wallet_for_product,
|
||||||
|
get_zone,
|
||||||
increment_customer_unread_messages,
|
increment_customer_unread_messages,
|
||||||
update_customer_profile,
|
update_customer_profile,
|
||||||
update_order_paid_status,
|
update_order_paid_status,
|
||||||
|
|
@ -60,8 +61,12 @@ async def create_new_order(
|
||||||
merchant.id, [p.product_id for p in data.items]
|
merchant.id, [p.product_id for p in data.items]
|
||||||
)
|
)
|
||||||
data.validate_order_items(products)
|
data.validate_order_items(products)
|
||||||
|
shipping_zone = await get_zone(merchant.id, data.shipping_id)
|
||||||
|
assert shipping_zone, f"Shipping zone not found for order '{data.id}'"
|
||||||
|
|
||||||
total_amount = await data.total_sats(products)
|
product_cost_sat, shipping_cost_sat = await data.costs_in_sats(
|
||||||
|
products, shipping_zone.cost
|
||||||
|
)
|
||||||
|
|
||||||
wallet_id = await get_wallet_for_product(data.items[0].product_id)
|
wallet_id = await get_wallet_for_product(data.items[0].product_id)
|
||||||
assert wallet_id, "Missing wallet for order `{data.id}`"
|
assert wallet_id, "Missing wallet for order `{data.id}`"
|
||||||
|
|
@ -75,7 +80,7 @@ async def create_new_order(
|
||||||
|
|
||||||
payment_hash, invoice = await create_invoice(
|
payment_hash, invoice = await create_invoice(
|
||||||
wallet_id=wallet_id,
|
wallet_id=wallet_id,
|
||||||
amount=round(total_amount),
|
amount=round(product_cost_sat + shipping_cost_sat),
|
||||||
memo=f"Order '{data.id}' for pubkey '{data.public_key}'",
|
memo=f"Order '{data.id}' for pubkey '{data.public_key}'",
|
||||||
extra={
|
extra={
|
||||||
"tag": "nostrmarket",
|
"tag": "nostrmarket",
|
||||||
|
|
@ -84,12 +89,16 @@ async def create_new_order(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
extra = await OrderExtra.from_products(products)
|
||||||
|
extra.shipping_cost_sat = shipping_cost_sat
|
||||||
|
extra.shipping_cost = shipping_zone.cost
|
||||||
|
|
||||||
order = Order(
|
order = Order(
|
||||||
**data.dict(),
|
**data.dict(),
|
||||||
stall_id=products[0].stall_id,
|
stall_id=products[0].stall_id,
|
||||||
invoice_id=payment_hash,
|
invoice_id=payment_hash,
|
||||||
total=total_amount,
|
total=product_cost_sat + shipping_cost_sat,
|
||||||
extra=await OrderExtra.from_products(products),
|
extra=extra,
|
||||||
)
|
)
|
||||||
await create_order(merchant.id, order)
|
await create_order(merchant.id, order)
|
||||||
await websocketUpdater(
|
await websocketUpdater(
|
||||||
|
|
@ -99,6 +108,7 @@ async def create_new_order(
|
||||||
"type": "new-order",
|
"type": "new-order",
|
||||||
"stallId": products[0].stall_id,
|
"stallId": products[0].stall_id,
|
||||||
"customerPubkey": data.public_key,
|
"customerPubkey": data.public_key,
|
||||||
|
"orderId": order.id,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -312,6 +322,11 @@ async def _handle_dirrect_message(
|
||||||
order["event_created_at"] = event_created_at
|
order["event_created_at"] = event_created_at
|
||||||
return await _handle_new_order(PartialOrder(**order))
|
return await _handle_new_order(PartialOrder(**order))
|
||||||
|
|
||||||
|
await websocketUpdater(
|
||||||
|
merchant_id,
|
||||||
|
json.dumps({"type": "new-direct-message", "customerPubkey": from_pubkey}),
|
||||||
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
|
|
|
||||||
|
|
@ -375,8 +375,9 @@ async function customerStall(path) {
|
||||||
this.qrCodeDialog.data.message = json.message
|
this.qrCodeDialog.data.message = json.message
|
||||||
return cb()
|
return cb()
|
||||||
}
|
}
|
||||||
let payment_request = json.payment_options.find(o => o.type == 'ln')
|
let payment_request = json.payment_options.find(
|
||||||
.link
|
o => o.type == 'ln'
|
||||||
|
).link
|
||||||
if (!payment_request) return
|
if (!payment_request) return
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.qrCodeDialog.data.payment_request = payment_request
|
this.qrCodeDialog.data.payment_request = payment_request
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,13 @@ async function directMessages(path) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleNewMessage: async function (data) {
|
||||||
|
if (data.customerPubkey === this.activePublicKey) {
|
||||||
|
await this.getDirectMessages(this.activePublicKey)
|
||||||
|
} else {
|
||||||
|
await this.getCustomers()
|
||||||
|
}
|
||||||
|
},
|
||||||
showClientOrders: function () {
|
showClientOrders: function () {
|
||||||
this.$emit('customer-selected', this.activePublicKey)
|
this.$emit('customer-selected', this.activePublicKey)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1"></div>
|
<div class="col-1"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.row.extra.currency !== 'sat'" class="row items-center no-wrap q-mb-md q-mt-md">
|
<div
|
||||||
<div class="col-3 q-pr-lg">Exchange Rate:</div>
|
v-if="props.row.extra.currency !== 'sat'"
|
||||||
|
class="row items-center no-wrap q-mb-md q-mt-md"
|
||||||
|
>
|
||||||
|
<div class="col-3 q-pr-lg">Exchange Rate (1 BTC):</div>
|
||||||
<div class="col-6 col-sm-8 q-pr-lg">
|
<div class="col-6 col-sm-8 q-pr-lg">
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
|
|
|
||||||
|
|
@ -64,13 +64,13 @@ async function orderList(path) {
|
||||||
{
|
{
|
||||||
name: 'total',
|
name: 'total',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Total',
|
label: 'Total Sats',
|
||||||
field: 'total'
|
field: 'total'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'fiat',
|
name: 'fiat',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Fiat',
|
label: 'Total Fiat',
|
||||||
field: 'fiat'
|
field: 'fiat'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -136,7 +136,6 @@ async function orderList(path) {
|
||||||
},
|
},
|
||||||
orderTotal: function (order) {
|
orderTotal: function (order) {
|
||||||
return order.items.reduce((t, item) => {
|
return order.items.reduce((t, item) => {
|
||||||
console.log('### t, o', t, item)
|
|
||||||
product = order.extra.products.find(p => p.id === item.product_id)
|
product = order.extra.products.find(p => p.id === item.product_id)
|
||||||
return t + item.quantity * product.price
|
return t + item.quantity * product.price
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,7 @@ const merchant = async () => {
|
||||||
const port = location.port ? `:${location.port}` : ''
|
const port = location.port ? `:${location.port}` : ''
|
||||||
const wsUrl = `${scheme}://${document.domain}${port}/api/v1/ws/${this.merchant.id}`
|
const wsUrl = `${scheme}://${document.domain}${port}/api/v1/ws/${this.merchant.id}`
|
||||||
const wsConnection = new WebSocket(wsUrl)
|
const wsConnection = new WebSocket(wsUrl)
|
||||||
console.log('### waiting for events')
|
|
||||||
wsConnection.onmessage = async e => {
|
wsConnection.onmessage = async e => {
|
||||||
console.log('### e', e)
|
|
||||||
const data = JSON.parse(e.data)
|
const data = JSON.parse(e.data)
|
||||||
if (data.type === 'new-order') {
|
if (data.type === 'new-order') {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
|
|
@ -126,6 +124,7 @@ const merchant = async () => {
|
||||||
await this.$refs.orderListRef.addOrder(data)
|
await this.$refs.orderListRef.addOrder(data)
|
||||||
} else if (data.type === 'new-customer') {
|
} else if (data.type === 'new-customer') {
|
||||||
} else if (data.type === 'new-direct-message') {
|
} else if (data.type === 'new-direct-message') {
|
||||||
|
await this.$refs.directMessagesRef.handleNewMessage(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="merchant && merchant.id" class="col-12">
|
<div v-if="merchant && merchant.id" class="col-12">
|
||||||
<direct-messages
|
<direct-messages
|
||||||
|
ref="directMessagesRef"
|
||||||
:inkey="g.user.wallets[0].inkey"
|
:inkey="g.user.wallets[0].inkey"
|
||||||
:adminkey="g.user.wallets[0].adminkey"
|
:adminkey="g.user.wallets[0].adminkey"
|
||||||
:active-chat-customer="activeChatCustomer"
|
:active-chat-customer="activeChatCustomer"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue