382 lines
11 KiB
JavaScript
382 lines
11 KiB
JavaScript
async function customerStall(path) {
|
|
const template = await loadTemplateAsync(path)
|
|
|
|
Vue.component('customer-stall', {
|
|
name: 'customer-stall',
|
|
template,
|
|
|
|
props: [
|
|
'account',
|
|
'login-dialog',
|
|
'stall',
|
|
'products',
|
|
'stall-products',
|
|
'product-detail',
|
|
'change-page',
|
|
'relays',
|
|
'pool',
|
|
'styles'
|
|
],
|
|
data: function () {
|
|
return {
|
|
loading: false,
|
|
isPwd: true,
|
|
cart: {
|
|
total: 0,
|
|
size: 0,
|
|
products: new Map()
|
|
},
|
|
cartMenu: [],
|
|
hasNip07: false,
|
|
customerPubkey: null,
|
|
customerPrivkey: null,
|
|
customerUseExtension: null,
|
|
activeOrder: null,
|
|
banner: null,
|
|
checkoutDialog: {
|
|
show: false,
|
|
data: {
|
|
pubkey: null
|
|
}
|
|
},
|
|
qrCodeDialog: {
|
|
data: {
|
|
payment_request: null
|
|
},
|
|
dismissMsg: null,
|
|
show: false
|
|
},
|
|
downloadOrderDialog: {
|
|
show: false,
|
|
data: {}
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
product() {
|
|
if (this.productDetail) {
|
|
return this.products.find(p => p.id == this.productDetail)
|
|
}
|
|
},
|
|
finalCost() {
|
|
if (!this.checkoutDialog.data.shippingzone) return this.cart.total
|
|
|
|
let zoneCost = this.stall.shipping.find(
|
|
z => z.id == this.checkoutDialog.data.shippingzone
|
|
)
|
|
return +this.cart.total + zoneCost.cost
|
|
},
|
|
dropIn() {
|
|
return {
|
|
privkey: this.customerPrivkey,
|
|
pubkey: this.customerPubkey,
|
|
useExtension: this.customerUseExtension
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
changePageS(page, opts) {
|
|
this.$emit('change-page', page, opts)
|
|
},
|
|
makeLogin() {
|
|
this.resetCheckout()
|
|
this.$emit('login-dialog')
|
|
},
|
|
copyText: function (text) {
|
|
var notify = this.$q.notify
|
|
Quasar.utils.copyToClipboard(text).then(function () {
|
|
notify({
|
|
message: 'Copied to clipboard!',
|
|
position: 'bottom'
|
|
})
|
|
})
|
|
},
|
|
getAmountFormated(amount, unit = 'USD') {
|
|
return LNbits.utils.formatCurrency(amount, unit)
|
|
},
|
|
updateQty(id, qty) {
|
|
let prod = this.cart.products
|
|
let product = prod.get(id)
|
|
prod.set(id, {
|
|
...product,
|
|
quantity: qty
|
|
})
|
|
this.updateCart()
|
|
},
|
|
addToCart(item) {
|
|
this.$emit('add-to-cart', item)
|
|
},
|
|
updateCart() {
|
|
this.cart.total = 0
|
|
this.cart.products.forEach(p => {
|
|
this.cart.total += p.quantity * p.price
|
|
})
|
|
|
|
this.cart.size = this.cart.products.size
|
|
this.cartMenu = Array.from(this.cart.products, item => {
|
|
return {id: item[0], ...item[1]}
|
|
})
|
|
this.$q.localStorage.set(`diagonAlley.carts.${this.stall.id}`, {
|
|
...this.cart,
|
|
products: Object.fromEntries(this.cart.products)
|
|
})
|
|
},
|
|
resetCart() {
|
|
this.cart = {
|
|
total: 0,
|
|
size: 0,
|
|
products: new Map()
|
|
}
|
|
this.$q.localStorage.remove(`diagonAlley.carts.${this.stall.id}`)
|
|
},
|
|
async downloadOrder() {
|
|
let created_at = Math.floor(Date.now() / 1000)
|
|
let orderData = this.checkoutDialog.data
|
|
let orderObj = {
|
|
name: orderData?.username,
|
|
address: orderData.address,
|
|
message: orderData?.message,
|
|
contact: {
|
|
nostr: orderData.pubkey,
|
|
phone: null,
|
|
email: orderData?.email
|
|
},
|
|
items: Array.from(this.cart.products, p => {
|
|
return {product_id: p[0], quantity: p[1].quantity}
|
|
}),
|
|
shipping_id: orderData.shippingzone
|
|
}
|
|
orderObj.id = await hash(
|
|
[orderData.pubkey, created_at, JSON.stringify(orderObj)].join(':')
|
|
)
|
|
this.downloadOrderDialog.data = orderObj
|
|
this.downloadOrderDialog.show = true
|
|
this.resetCheckout()
|
|
this.resetCart()
|
|
},
|
|
async getFromExtension() {
|
|
this.customerPubkey = await window.nostr.getPublicKey()
|
|
this.customerUseExtension = true
|
|
this.checkoutDialog.data.pubkey = this.customerPubkey
|
|
},
|
|
async generateKeyPair() {
|
|
this.customerPrivkey = NostrTools.generatePrivateKey()
|
|
this.customerPubkey = NostrTools.getPublicKey(this.customerPrivkey)
|
|
this.customerUseExtension = false
|
|
this.checkoutDialog.data.pubkey = this.customerPubkey
|
|
this.checkoutDialog.data.privkey = this.customerPrivkey
|
|
},
|
|
checkLogIn() {
|
|
this.customerPubkey = this.account?.pubkey
|
|
this.customerPrivkey = this.account?.privkey
|
|
this.customerUseExtension = this.account?.useExtension
|
|
},
|
|
openCheckout() {
|
|
// Check if user is logged in
|
|
this.checkLogIn()
|
|
if (this.customerPubkey) {
|
|
this.checkoutDialog.data.pubkey = this.customerPubkey
|
|
if (this.customerPrivkey && !this.useExtension) {
|
|
this.checkoutDialog.data.privkey = this.customerPrivkey
|
|
}
|
|
}
|
|
this.checkoutDialog.show = true
|
|
},
|
|
resetCheckout() {
|
|
this.loading = false
|
|
this.checkoutDialog = {
|
|
show: false,
|
|
data: {
|
|
pubkey: null
|
|
}
|
|
}
|
|
},
|
|
openQrCodeDialog() {
|
|
this.qrCodeDialog = {
|
|
data: {
|
|
payment_request: null,
|
|
message: null
|
|
},
|
|
dismissMsg: this.$q.notify({
|
|
message: 'Waiting for invoice from merchant...'
|
|
}),
|
|
show: true
|
|
}
|
|
},
|
|
closeQrCodeDialog() {
|
|
this.qrCodeDialog.show = false
|
|
this.qrCodeDialog.data = {
|
|
payment_request: null,
|
|
message: null
|
|
}
|
|
setTimeout(() => {
|
|
this.qrCodeDialog.dismissMsg()
|
|
}, 1000)
|
|
},
|
|
async placeOrder() {
|
|
this.loading = true
|
|
let orderData = this.checkoutDialog.data
|
|
let orderObj = {
|
|
name: orderData?.username,
|
|
address: orderData.address,
|
|
message: orderData?.message,
|
|
contact: {
|
|
nostr: this.customerPubkey,
|
|
phone: null,
|
|
email: orderData?.email
|
|
},
|
|
items: Array.from(this.cart.products, p => {
|
|
return {product_id: p[0], quantity: p[1].quantity}
|
|
}),
|
|
shipping_id: orderData.shippingzone,
|
|
type: 0
|
|
}
|
|
let created_at = Math.floor(Date.now() / 1000)
|
|
orderObj.id = await hash(
|
|
[this.customerPubkey, created_at, JSON.stringify(orderObj)].join(':')
|
|
)
|
|
this.activeOrder = orderObj.id
|
|
let event = {
|
|
...(await NostrTools.getBlankEvent()),
|
|
kind: 4,
|
|
created_at,
|
|
tags: [['p', this.stall.pubkey]],
|
|
pubkey: this.customerPubkey
|
|
}
|
|
if (this.customerPrivkey) {
|
|
event.content = await NostrTools.nip04.encrypt(
|
|
this.customerPrivkey,
|
|
this.stall.pubkey,
|
|
JSON.stringify(orderObj)
|
|
)
|
|
} else if (this.customerUseExtension && this.hasNip07) {
|
|
event.content = await window.nostr.nip04.encrypt(
|
|
this.stall.pubkey,
|
|
JSON.stringify(orderObj)
|
|
)
|
|
let userRelays = Object.keys(
|
|
(await window.nostr?.getRelays?.()) || []
|
|
)
|
|
if (userRelays.length != 0) {
|
|
userRelays.map(r => this.relays.add(r))
|
|
}
|
|
}
|
|
event.id = NostrTools.getEventHash(event)
|
|
if (this.customerPrivkey) {
|
|
event.sig = await NostrTools.signEvent(event, this.customerPrivkey)
|
|
} else if (this.customerUseExtension && this.hasNip07) {
|
|
event = await window.nostr.signEvent(event)
|
|
}
|
|
this.resetCheckout()
|
|
await this.sendOrder(event)
|
|
},
|
|
async sendOrder(order) {
|
|
let pub = this.pool.publish(Array.from(this.relays), order)
|
|
pub.on('ok', () => console.debug(`Order event was sent`))
|
|
pub.on('failed', error => console.error(error))
|
|
|
|
this.loading = false
|
|
this.resetCart()
|
|
this.openQrCodeDialog()
|
|
this.listenMessages()
|
|
},
|
|
async listenMessages() {
|
|
this.loading = true
|
|
try {
|
|
const filters = [
|
|
{
|
|
kinds: [4],
|
|
authors: [this.stall.pubkey]
|
|
},
|
|
{
|
|
kinds: [4],
|
|
'#p': [this.customerPubkey]
|
|
}
|
|
]
|
|
let relays = Array.from(this.relays)
|
|
let subs = this.pool.sub(relays, filters)
|
|
subs.on('event', async event => {
|
|
let mine = event.pubkey == this.customerPubkey
|
|
let sender = mine
|
|
? event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1]
|
|
: event.pubkey
|
|
try {
|
|
let plaintext
|
|
if (this.customerPrivkey) {
|
|
plaintext = await NostrTools.nip04.decrypt(
|
|
this.customerPrivkey,
|
|
sender,
|
|
event.content
|
|
)
|
|
} else if (this.customerUseExtension && this.hasNip07) {
|
|
plaintext = await window.nostr.nip04.decrypt(
|
|
sender,
|
|
event.content
|
|
)
|
|
}
|
|
|
|
this.messageFilter(plaintext, cb => subs.unsub())
|
|
} catch {
|
|
console.debug('Unable to decrypt message! Probably not for us!')
|
|
}
|
|
})
|
|
} catch (err) {
|
|
console.error(`Error: ${err}`)
|
|
}
|
|
},
|
|
messageFilter(text, cb = () => {}) {
|
|
if (!isJson(text)) return
|
|
let json = JSON.parse(text)
|
|
if (json.id != this.activeOrder) return
|
|
|
|
if (json.payment_options) {
|
|
if (json.payment_options.length == 0 && json.message) {
|
|
this.loading = false
|
|
this.qrCodeDialog.data.message = json.message
|
|
return cb()
|
|
}
|
|
let payment_request = json.payment_options.find(o => o.type == 'ln')
|
|
.link
|
|
if (!payment_request) return
|
|
this.loading = false
|
|
this.qrCodeDialog.data.payment_request = payment_request
|
|
this.qrCodeDialog.dismissMsg = this.$q.notify({
|
|
timeout: 0,
|
|
message: 'Waiting for payment...'
|
|
})
|
|
} else if (json.paid) {
|
|
this.closeQrCodeDialog()
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: 'Sats received, thanks!',
|
|
icon: 'thumb_up'
|
|
})
|
|
this.activeOrder = null
|
|
return cb()
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
this.checkLogIn()
|
|
let storedCart = this.$q.localStorage.getItem(
|
|
`diagonAlley.carts.${this.stall.id}`
|
|
)
|
|
if (storedCart) {
|
|
this.cart.total = storedCart.total
|
|
this.cart.size = storedCart.size
|
|
this.cart.products = new Map(Object.entries(storedCart.products))
|
|
|
|
this.cartMenu = Array.from(this.cart.products, item => {
|
|
return {id: item[0], ...item[1]}
|
|
})
|
|
}
|
|
setTimeout(() => {
|
|
if (window.nostr) {
|
|
this.hasNip07 = true
|
|
}
|
|
}, 1000)
|
|
}
|
|
})
|
|
}
|