add currency to stalls show products in fiat

This commit is contained in:
Tiago Vasconcelos 2022-12-27 11:12:29 +00:00
parent b1473fc1a4
commit 11b015e516
8 changed files with 141 additions and 31 deletions

View File

@ -148,16 +148,18 @@ async def create_shop_stall(data: createStalls) -> Stalls:
id, id,
wallet, wallet,
name, name,
currency,
publickey, publickey,
relays, relays,
shippingzones shippingzones
) )
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", """,
( (
stall_id, stall_id,
data.wallet, data.wallet,
data.name, data.name,
data.currency,
data.publickey, data.publickey,
data.relays, data.relays,
data.shippingzones, data.shippingzones,
@ -447,17 +449,31 @@ async def get_shop_chat_by_merchant(ids: List[str]) -> List[ChatMessage]:
async def get_shop_settings(user) -> Optional[ShopSettings]: async def get_shop_settings(user) -> Optional[ShopSettings]:
row = await db.fetchone("SELECT * FROM shop.settings WHERE 'user = ?", (user,)) row = await db.fetchone("""SELECT * FROM shop.settings WHERE "user" = ?""", (user,))
return ShopSettings(**row) if row else None return ShopSettings(**row) if row else None
async def set_shop_settings(user: str, data) -> Optional[ShopSettings]: async def create_shop_settings(user: str, data):
await db.execute( await db.execute(
f""" """
INSERT INTO shop.settings ("user", currency, fiat_base_multiplier)
VALUES (?, ?, ?)
""",
(
user,
data.currency,
data.fiat_base_multiplier,
),
)
async def set_shop_settings(user: str, data):
await db.execute(
"""
UPDATE shop.settings UPDATE shop.settings
SET currency = ?, fiat_base_multiplier = ? SET currency = ?, fiat_base_multiplier = ?
WHERE 'user' = ?; WHERE "user" = ?;
""", """,
( (
data.currency, data.currency,

View File

@ -21,6 +21,7 @@ async def m001_initial(db):
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
wallet TEXT NOT NULL, wallet TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
currency TEXT,
publickey TEXT, publickey TEXT,
relays TEXT, relays TEXT,
shippingzones TEXT NOT NULL, shippingzones TEXT NOT NULL,

View File

@ -19,6 +19,7 @@ class Stalls(BaseModel):
id: str id: str
wallet: str wallet: str
name: str name: str
currency: str
publickey: Optional[str] publickey: Optional[str]
relays: Optional[str] relays: Optional[str]
shippingzones: str shippingzones: str
@ -27,6 +28,7 @@ class Stalls(BaseModel):
class createStalls(BaseModel): class createStalls(BaseModel):
wallet: str = Query(...) wallet: str = Query(...)
name: str = Query(...) name: str = Query(...)
currency: str = Query("sat")
publickey: str = Query(None) publickey: str = Query(None)
relays: str = Query(None) relays: str = Query(None)
shippingzones: str = Query(...) shippingzones: str = Query(...)
@ -38,7 +40,7 @@ class createProduct(BaseModel):
categories: str = Query(None) categories: str = Query(None)
description: str = Query(None) description: str = Query(None)
image: str = Query(None) image: str = Query(None)
price: int = Query(0, ge=0) price: float = Query(0, ge=0)
quantity: int = Query(0, ge=0) quantity: int = Query(0, ge=0)
@ -49,19 +51,19 @@ class Products(BaseModel):
categories: Optional[str] categories: Optional[str]
description: Optional[str] description: Optional[str]
image: Optional[str] image: Optional[str]
price: int price: float
quantity: int quantity: int
class createZones(BaseModel): class createZones(BaseModel):
cost: int = Query(0, ge=0) cost: float = Query(0, ge=0)
countries: str = Query(...) countries: str = Query(...)
class Zones(BaseModel): class Zones(BaseModel):
id: str id: str
user: str user: str
cost: int cost: float
countries: str countries: str

View File

@ -84,7 +84,11 @@
dense dense
v-model.number="productDialog.data.price" v-model.number="productDialog.data.price"
type="number" type="number"
label="Price" :label="'Price (' + currencies.unit + ') *'"
:mask="currencies.unit != 'sat' ? '#.##' : '#'"
fill-mask="0"
reverse-fill-mask
:step="currencies.unit != 'sat' ? '0.01' : '1'"
></q-input> ></q-input>
<q-input <q-input
filled filled
@ -142,9 +146,13 @@
<q-input <q-input
filled filled
dense dense
:label="'Amount (' + currencies.unit + ') *'"
:mask="currencies.unit != 'sat' ? '#.##' : '#'"
fill-mask="0"
reverse-fill-mask
:step="currencies.unit != 'sat' ? '0.01' : '1'"
type="number" type="number"
v-model.trim="zoneDialog.data.cost" v-model.trim="zoneDialog.data.cost"
label="Cost (sats)"
></q-input> ></q-input>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn <q-btn

View File

@ -188,6 +188,9 @@
} }
const mapProducts = obj => { const mapProducts = obj => {
obj._data = _.clone(obj) obj._data = _.clone(obj)
if ('{{ currency }}' != 'sat') {
obj.price /= 100
}
return obj return obj
} }
const mapZone = obj => { const mapZone = obj => {
@ -545,7 +548,7 @@
LNbits.api LNbits.api
.request( .request(
'PUT', 'PUT',
'/shop/api/v1/settings', '/shop/api/v1/settings/' + this.g.user.id,
this.g.user.wallets[0].adminkey, this.g.user.wallets[0].adminkey,
data data
) )
@ -680,6 +683,7 @@
name: this.stallDialog.data.name, name: this.stallDialog.data.name,
wallet: this.stallDialog.data.wallet, wallet: this.stallDialog.data.wallet,
publickey: this.stallDialog.data.publickey || this.keys.pubkey, publickey: this.stallDialog.data.publickey || this.keys.pubkey,
currency: this.currencies.unit,
relays: this.stallDialog.data.relays, relays: this.stallDialog.data.relays,
shippingzones: this.stallDialog.data.shippingzones shippingzones: this.stallDialog.data.shippingzones
.map(z => z.split('-')[0].trim()) .map(z => z.split('-')[0].trim())
@ -1376,6 +1380,8 @@
this.onboarding.showAgain = showOnboard || false this.onboarding.showAgain = showOnboard || false
this.diagonAlley = this.diagonAlley =
this.$q.localStorage.getItem('lnbits.DAmode') || false this.$q.localStorage.getItem('lnbits.DAmode') || false
this.currencies.unit = '{{ currency }}'
console.log(this.currencies.unit, '{{currency}}')
await this.getCurrencies() await this.getCurrencies()
this.getStalls() this.getStalls()
this.getProducts() this.getProducts()

View File

@ -48,7 +48,10 @@
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side>
<span> {{p.price}} sats</span> <span>
{{unit != 'sat' ? getAmountFormated(p.price) : p.price +
'sats'}}</span
>
</q-item-section> </q-item-section>
</q-item> </q-item>
{% endraw %} {% endraw %}
@ -112,13 +115,18 @@
<q-card-section class="q-py-sm"> <q-card-section class="q-py-sm">
<div> <div>
<!-- <div class="text-caption text-green-8 text-weight-bolder"> <span v-if="unit == 'sat'">
{{ stall.name }} <span class="text-h6">{{ item.price }} sats</span
</div> --> ><span class="q-ml-sm text-grey-6"
<span class="text-h6">{{ item.price }} sats</span >BTC {{ (item.price / 1e8).toFixed(8) }}</span
><span class="q-ml-sm text-grey-6" >
>BTC {{ (item.price / 1e8).toFixed(8) }}</span </span>
> <span v-else>
<span class="text-h6">{{ getAmountFormated(item.price) }}</span>
<span v-if="exchangeRate" class="q-ml-sm text-grey-6"
>({{ getValueInSats(item.price) }} sats)</span
>
</span>
<span <span
class="q-ml-md text-caption text-green-8 text-weight-bolder q-mt-md" class="q-ml-md text-caption text-green-8 text-weight-bolder q-mt-md"
>{{item.quantity}} left</span >{{item.quantity}} left</span
@ -203,7 +211,12 @@
/> />
</div> </div>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
{% raw %} Total: {{ finalCost }} {% endraw %} {% raw %} Total: {{ unit != 'sat' ? getAmountFormated(finalCost) :
finalCost + 'sats' }}
<span v-if="unit != 'sat'" class="q-ml-sm text-grey-6"
>({{ getValueInSats(finalCost) }} sats)</span
>
{% endraw %}
</div> </div>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn <q-btn
@ -281,6 +294,8 @@
products: [], products: [],
searchText: null, searchText: null,
diagonalley: false, diagonalley: false,
unit: 'sat',
exchangeRate: 0,
cart: { cart: {
total: 0, total: 0,
size: 0, size: 0,
@ -334,6 +349,28 @@
products: new Map() products: new Map()
} }
}, },
getAmountFormated(amount) {
return LNbits.utils.formatCurrency(amount.toFixed(2), this.unit)
},
async getRates() {
if (this.unit == 'sat') return
try {
let rate = (
await LNbits.api.request('POST', '/api/v1/conversion', null, {
amount: 1e8,
to: this.unit
})
).data
this.exchangeRate = rate[this.unit]
console.log(this.exchangeRate)
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
getValueInSats(amount) {
if (!this.exchangeRate) return 0
return Math.ceil((amount / this.exchangeRate) * 1e8)
},
addToCart(item) { addToCart(item) {
let prod = this.cart.products let prod = this.cart.products
if (prod.has(item.id)) { if (prod.has(item.id)) {
@ -382,7 +419,10 @@
let data = { let data = {
...this.checkoutDialog.data, ...this.checkoutDialog.data,
wallet: this.stall.wallet, wallet: this.stall.wallet,
total: this.finalCost, total:
this.unit != 'sat'
? this.getValueInSats(this.finalCost)
: this.finalCost, // maybe this is better made in Python to allow API ordering?!
products: Array.from(this.cart.products, p => { products: Array.from(this.cart.products, p => {
return {product_id: p[0], quantity: p[1].quantity} return {product_id: p[0], quantity: p[1].quantity}
}) })
@ -448,9 +488,12 @@
}) })
} }
}, },
created() { async created() {
this.stall = JSON.parse('{{ stall | tojson }}') this.stall = JSON.parse('{{ stall | tojson }}')
this.products = JSON.parse('{{ products | tojson }}') this.products = JSON.parse('{{ products | tojson }}')
this.unit = this.stall.currency
await this.getRates()
setInterval(this.getRates, 300000)
} }
}) })
</script> </script>

View File

@ -12,7 +12,7 @@ from starlette.responses import HTMLResponse
from lnbits.core.models import User from lnbits.core.models import User
from lnbits.decorators import check_user_exists # type: ignore from lnbits.decorators import check_user_exists # type: ignore
from lnbits.extensions.shop import shop_ext, shop_renderer from lnbits.extensions.shop import shop_ext, shop_renderer
from lnbits.extensions.shop.models import CreateChatMessage from lnbits.extensions.shop.models import CreateChatMessage, SetSettings
from lnbits.extensions.shop.notifier import Notifier from lnbits.extensions.shop.notifier import Notifier
from .crud import ( from .crud import (
@ -26,6 +26,8 @@ from .crud import (
get_shop_zone, get_shop_zone,
get_shop_zones, get_shop_zones,
update_shop_product_stock, update_shop_product_stock,
get_shop_settings,
create_shop_settings,
) )
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@ -33,8 +35,16 @@ templates = Jinja2Templates(directory="templates")
@shop_ext.get("/", response_class=HTMLResponse) @shop_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
settings = await get_shop_settings(user=user.id)
if not settings:
await create_shop_settings(
user=user.id, data=SetSettings(currency="sat", fiat_base_multiplier=1)
)
settings = await get_shop_settings(user.id)
return shop_renderer().TemplateResponse( return shop_renderer().TemplateResponse(
"shop/index.html", {"request": request, "user": user.dict()} "shop/index.html",
{"request": request, "user": user.dict(), "currency": settings.currency},
) )

View File

@ -33,6 +33,7 @@ from .crud import (
create_shop_product, create_shop_product,
create_shop_stall, create_shop_stall,
create_shop_zone, create_shop_zone,
create_shop_settings,
delete_shop_order, delete_shop_order,
delete_shop_product, delete_shop_product,
delete_shop_stall, delete_shop_stall,
@ -102,15 +103,21 @@ async def api_shop_product_create(
product_id=None, product_id=None,
wallet: WalletTypeInfo = Depends(require_invoice_key), wallet: WalletTypeInfo = Depends(require_invoice_key),
): ):
# For fiat currencies,
# we multiply by data.fiat_base_multiplier (usually 100) to save the value in cents.
settings = await get_shop_settings(user=wallet.wallet.user)
stall = await get_shop_stall(stall_id=data.stall)
if stall.currency != "sat":
data.price *= settings.fiat_base_multiplier
if product_id: if product_id:
product = await get_shop_product(product_id) product = await get_shop_product(product_id)
if not product: if not product:
return {"message": "Withdraw product does not exist."} return {"message": "Product does not exist."}
stall = await get_shop_stall(stall_id=product.stall) # stall = await get_shop_stall(stall_id=product.stall)
if stall.wallet != wallet.wallet.id: if stall.wallet != wallet.wallet.id:
return {"message": "Not your withdraw product."} return {"message": "Not your product."}
product = await update_shop_product(product_id, **data.dict()) product = await update_shop_product(product_id, **data.dict())
else: else:
@ -250,6 +257,8 @@ async def api_shop_orders(
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
orders = await get_shop_orders(wallet_ids) orders = await get_shop_orders(wallet_ids)
if not orders:
return
orders_with_details = [] orders_with_details = []
for order in orders: for order in orders:
order = order.dict() order = order.dict()
@ -472,9 +481,24 @@ async def api_get_settings(wallet: WalletTypeInfo = Depends(require_admin_key)):
return settings return settings
@shop_ext.put("/api/v1/settings") @shop_ext.post("/api/v1/settings")
@shop_ext.put("/api/v1/settings/{usr}")
async def api_set_settings( async def api_set_settings(
data: SetSettings, wallet: WalletTypeInfo = Depends(require_admin_key) data: SetSettings,
usr: str = None,
wallet: WalletTypeInfo = Depends(require_admin_key),
): ):
if usr:
if usr != wallet.wallet.user:
return {"message": "Not your Shop."}
settings = await get_shop_settings(user=usr)
if settings.user != wallet.wallet.user:
return {"message": "Not your Shop."}
return await set_shop_settings(usr, data)
user = wallet.wallet.user user = wallet.wallet.user
return await set_shop_settings(user, data)
return await create_shop_settings(user, data)