diff --git a/static/components/shopping-cart-checkout/shopping-cart-checkout.html b/static/components/shopping-cart-checkout/shopping-cart-checkout.html
index b23cbdd..7c0b324 100644
--- a/static/components/shopping-cart-checkout/shopping-cart-checkout.html
+++ b/static/components/shopping-cart-checkout/shopping-cart-checkout.html
@@ -117,12 +117,8 @@
-
-
-
-
-
+
Place Order
diff --git a/static/components/shopping-cart-checkout/shopping-cart-checkout.js b/static/components/shopping-cart-checkout/shopping-cart-checkout.js
index 82a9692..2e0d911 100644
--- a/static/components/shopping-cart-checkout/shopping-cart-checkout.js
+++ b/static/components/shopping-cart-checkout/shopping-cart-checkout.js
@@ -5,7 +5,7 @@ async function shoppingCartCheckout(path) {
name: 'shopping-cart-checkout',
template,
- props: ['cart', 'stall'],
+ props: ['cart', 'stall', 'customer-pubkey'],
data: function () {
return {
paymentMethod: 'ln',
@@ -58,9 +58,53 @@ async function shoppingCartCheckout(path) {
selectShippingZone: function (zone) {
this.shippingZone = zone
},
- requestInvoice: function () {
- }
+ async placeOrder() {
+ console.log('### placeOrder cart', this.cart)
+ console.log('### placeOrder stal', this.stall)
+ if (!this.shippingZone) {
+ this.$q.notify({
+ timeout: 5000,
+ type: 'warning',
+ message: 'Please select a shipping zone!',
+ })
+ return
+ }
+ if (!this.customerPubkey) {
+ this.$emit('login-required')
+ return
+ }
+ const order = {
+ address: this.contactData.address,
+ message: this.contactData.message,
+ contact: {
+ nostr: this.customerPubkey,
+ email: this.contactData.email
+ },
+ items: Array.from(this.cart.products, p => {
+ return { product_id: p.id, quantity: p.orderedQuantity }
+ }),
+ shipping_id: this.shippingZone.id,
+ type: 0
+ }
+ const created_at = Math.floor(Date.now() / 1000)
+ order.id = await hash(
+ [this.customerPubkey, created_at, JSON.stringify(order)].join(':')
+ )
+
+ const event = {
+ ...(await NostrTools.getBlankEvent()),
+ kind: 4,
+ created_at,
+ tags: [['p', this.stall.pubkey]],
+ pubkey: this.customerPubkey
+ }
+
+ this.$emit('place-order', { event, order })
+
+
+
+ },
},
created() {
console.log('### shoppingCartCheckout', this.stall)
diff --git a/static/js/market.js b/static/js/market.js
index c474fce..065cc89 100644
--- a/static/js/market.js
+++ b/static/js/market.js
@@ -57,6 +57,15 @@ const market = async () => {
checkoutStall: null,
activePage: 'market',
+ activeOrderId: null,
+
+ qrCodeDialog: {
+ data: {
+ payment_request: null
+ },
+ dismissMsg: null,
+ show: false
+ },
searchNostr: false,
drawer: true,
@@ -87,6 +96,7 @@ const market = async () => {
}
},
naddr: null,
+ loading: false
}
},
computed: {
@@ -137,8 +147,10 @@ const market = async () => {
this.merchants = this.$q.localStorage.getItem('nostrmarket.merchants') || []
this.shoppingCarts = this.$q.localStorage.getItem('nostrmarket.shoppingCarts') || []
- // Check for user stored
this.account = this.$q.localStorage.getItem('nostrmarket.account') || null
+
+
+
//console.log("UUID", crypto.randomUUID())
// Check for stored merchants and relays on localStorage
@@ -204,6 +216,9 @@ const market = async () => {
await this.initNostr()
this.$q.loading.hide()
+
+
+ await this.listenForIncommingDms(this.merchants.map(m => m.publicKey))
},
methods: {
async deleteAccount() {
@@ -616,8 +631,138 @@ const market = async () => {
this.checkoutCart = cart
this.checkoutStall = this.stalls.find(s => s.id === cart.id)
this.setActivePage('shopping-cart-checkout')
+ },
+
+ async placeOrder({ event, order }) {
+ if (!this.account.privkey) {
+ this.openAccountDialog()
+ return
+ }
+ this.activeOrderId = order.id
+ event.content = await NostrTools.nip04.encrypt(
+ this.account.privkey,
+ this.checkoutStall.pubkey,
+ JSON.stringify(order)
+ )
+
+ event.id = NostrTools.getEventHash(event)
+ event.sig = await NostrTools.signEvent(event, this.account.privkey)
+
+ this.sendOrderEvent(event)
+ },
+
+ sendOrderEvent(event) {
+ const pub = this.pool.publish(Array.from(this.relays), event)
+ pub.on('ok', () => {
+ this.qrCodeDialog.show = true
+ this.$q.notify({
+ type: 'positive',
+ message: 'The order has been placed!'
+ })
+ })
+ pub.on('failed', (error) => {
+ // do not show to user. It is possible that only one relay has failed
+ console.error(error)
+ })
+ },
+
+ async listenForIncommingDms(publicKeys) {
+ if (!this.account?.privkey) {
+ this.$q.notify({
+ message: 'Cannot listen for direct messages. You need to login first!',
+ icon: 'warning'
+ })
+ return
+ }
+ try {
+ const filters = [
+ {
+ kinds: [4],
+ authors: publicKeys,
+ '#p': [this.account.pubkey]
+ }
+ ]
+
+ const subs = this.pool.sub(Array.from(this.relays), filters)
+ subs.on('event', async event => {
+ const receiverPubkey = event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1]
+ if (receiverPubkey !== this.account.pubkey) {
+ console.log('Unexpected DM. Dropped!')
+ return
+ }
+ await this.handleIncommingDm(event)
+ })
+ } catch (err) {
+ console.error(`Error: ${err}`)
+ }
+ },
+ async handleIncommingDm(event) {
+ try {
+ const plainText = await NostrTools.nip04.decrypt(
+ this.account.privkey,
+ event.pubkey,
+ event.content
+ )
+ console.log('### plainText', plainText)
+ if (!isJson(plainText)) return
+
+ const jsonData = JSON.parse(plainText)
+ if (jsonData.type === 1) {
+ this.handlePaymentRequest(jsonData)
+ } else if (jsonData.type === 2) {
+ console.log('### this.qrCodeDialog.dismissMsg', this.qrCodeDialog.dismissMs)
+ console.log('### jsonData', jsonData)
+ if (this.qrCodeDialog.dismissMsg) {
+ this.qrCodeDialog.dismissMsg()
+ this.qrCodeDialog.show = false
+ }
+ const message = jsonData.shipped ? 'Order shipped' : jsonData.paid ? 'Order paid' : 'Order updated'
+ this.$q.notify({
+ type: 'positive',
+ message: message
+ })
+ }
+ } catch (e){
+ console.warn('Unable to handle incomming DM', e)
+ }
+ },
+
+ handlePaymentRequest(json) {
+ console.log('### handlePaymentRequest', json, this.activeOrderId)
+
+ if (!json.payment_options?.length) {
+ this.qrCodeDialog.data.message = json.message || 'Unexpected error'
+ return
+ }
+ console.log('### ')
+ if (json.id && (json.id !== this.activeOrderId)) {
+ // not for active order, store somewehre else
+ return
+ }
+ let paymentRequest = json.payment_options.find(o => o.type == 'ln')
+ .link
+ if (!paymentRequest) return
+ this.qrCodeDialog.data.payment_request = paymentRequest
+ 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
+ // }
+
}
})
}
diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html
index 347d75d..f80be49 100644
--- a/templates/nostrmarket/market.html
+++ b/templates/nostrmarket/market.html
@@ -213,8 +213,9 @@
-
+
+
+
+
+
+
+
+ Copy invoice
+ Close
+
+
+
{% endblock %} {% block scripts %}