diff --git a/components/TabbedCheckout.vue b/components/TabbedCheckout.vue index 0ef44c4..2166b1b 100644 --- a/components/TabbedCheckout.vue +++ b/components/TabbedCheckout.vue @@ -54,6 +54,8 @@ {{ expiringSoon ? "Invoice expiring soon..." + : underpaid + ? "Invoice underpaid..." : "Awaiting payment..." }}

@@ -475,20 +477,13 @@
-

Amount

-

- {{ itemv.amount }} - {{ itemv.symbol.toUpperCase() }} -

+
+ +
- {{ texts[status].icon }} + {{ texts[displayStatus].icon }}
- {{ texts[status].text }} + {{ texts[displayStatus].text }}
@@ -79,7 +77,7 @@ export default { showSnackbar: false, showPartial: false, showDialog: true, - status: "pending", + redirected: false, invoice: {}, store: {}, loading: true, @@ -93,13 +91,21 @@ export default { icon: "mdi-close", text: "This invoice has been marked as invalid", }, - paid: { - icon: "mdi-check", - text: "This invoice has been paid", + waiting_confirmation: { + icon: "mdi-clock-outline", + text: "Payment received, awaiting confirmation...", + }, + underpaid: { + icon: "mdi-alert-circle-outline", + text: "Invoice underpaid. Please send the remaining amount.", + }, + pending: { + icon: "", + text: "Awaiting payment...", }, confirmed: { icon: "mdi-check", - text: "This invoice has been paid", + text: "Payment received, awaiting confirmations...", }, complete: { icon: "mdi-check", @@ -121,12 +127,29 @@ export default { hasEthPaymentMethod() { return ( this.selectedPaymentMethod?.payment_url?.startsWith("ethereum:") && - this.status === "pending" + this.displayStatus === "pending" ) }, poweredByStyle() { return this.$route.query.modal ? { "white--text": true } : {} }, + displayStatus() { + if (!this.invoice || !this.invoice.status) return 'pending' + if (this.invoice.status === 'pending' && this.invoice.exception_status === 'paid_partial') return 'underpaid' + if (this.invoice.status === 'paid') return 'waiting_confirmation' + return this.invoice.status + }, + isFinalStatus() { + return ['complete', 'expired', 'invalid', 'refunded'].includes(this.displayStatus) + } + }, + watch: { + displayStatus(newStatus) { + if (!this.redirected && this.isFinalStatus && this.invoice.redirect_url) { + this.redirected = true + this.$utils.redirectTo(this.invoice.redirect_url) + } + } }, beforeCreate() { this.$vuetify.theme.dark = false // dark theme unsupported here @@ -136,7 +159,6 @@ export default { .get(`/invoices/${this.$route.params.id}`) .then((resp) => { this.invoice = resp.data - this.status = resp.data.status window.parent.postMessage("loaded", "*") this.$axios .get(`/stores/${resp.data.store_id}`) @@ -177,24 +199,30 @@ export default { this.websocket = new WebSocket(url) this.websocket.onmessage = (event) => { const data = JSON.parse(event.data) - const status = data.status - if ( - status === "pending" && - this.invoice.sent_amount !== data.sent_amount - ) { - // received partial payment - this.invoice.exception_status = data.exception_status - this.invoice.sent_amount = data.sent_amount - this.invoice.paid_currency = data.paid_currency - this.invoice.payment_id = data.payment_id + const oldStatus = this.invoice.status + const oldSentAmount = this.invoice.sent_amount + + // Update invoice data + this.invoice.status = data.status + this.invoice.exception_status = data.exception_status + this.invoice.sent_amount = data.sent_amount + this.invoice.paid_currency = data.paid_currency + this.invoice.payment_id = data.payment_id + + // Show partial payment notification if needed + if (data.status === 'pending' && + data.exception_status === 'paid_partial' && + oldSentAmount !== data.sent_amount) { this.$bus.$emit("showDetails") this.showPartial = true } - if (this.invoice.status !== status) { + + // Notify parent window of status change + if (oldStatus !== data.status) { window.parent.postMessage( { invoice_id: this.invoice.id, - status, + status: data.status, exception_status: data.exception_status, sent_amount: data.sent_amount, paid_currency: data.paid_currency, @@ -203,13 +231,6 @@ export default { "*" ) } - if ( - ["paid", "confirmed", "complete"].includes(status) && - this.invoice.redirect_url - ) { - this.$utils.redirectTo(this.invoice.redirect_url) - } - this.status = status } }, colorClass(icon) { @@ -226,6 +247,10 @@ export default { ? baseURL.replace(/\/+$/, "") + "/" + relativeURL.replace(/^\/+/, "") : baseURL }, + getIconColor(icon) { + if (["mdi-check", "mdi-cash-refund"].includes(icon)) return "green" + return "red" + }, }, } @@ -292,6 +317,16 @@ $dialog-max-height: 100%; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } } + +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + .success-circle { border-radius: 50%; height: 154px; @@ -299,7 +334,37 @@ $dialog-max-height: 100%; margin: 0 auto; border-width: 2px; border-style: solid; + border-color: currentColor; + position: relative; + display: flex; + align-items: center; + justify-content: center; +} + +.icon-static { + position: relative; + z-index: 1; } + +.success-circle.confirming { + border-style: dashed; + animation: rotate 2s linear infinite; +} + +.success-circle.confirming::before { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border-radius: 50%; + border: 2px solid transparent; + border-top-color: currentColor; + border-right-color: currentColor; + animation: rotate 2s linear infinite; +} + .green-color { border-color: #13e5b6; }