fix: shipping cost; update UI on new order

This commit is contained in:
Vlad Stan 2023-04-03 11:57:55 +03:00
parent f80a75e90c
commit 547c477ce6
11 changed files with 57 additions and 23 deletions

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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):

View file

@ -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)

View file

@ -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

View file

@ -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)
}, },

View file

@ -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

View file

@ -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)

View file

@ -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) {

View file

@ -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"