mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-20 18:51:05 +01:00
add currency to stalls show products in fiat
This commit is contained in:
parent
b1473fc1a4
commit
11b015e516
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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>
|
||||||
|
@ -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},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user