Skip to content
27 changes: 13 additions & 14 deletions components/TabbedCheckout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
{{
expiringSoon
? "Invoice expiring soon..."
: underpaid
? "Invoice underpaid..."
: "Awaiting payment..."
}}
</p>
Expand Down Expand Up @@ -475,20 +477,13 @@
<v-card flat class="pa-0 ma-0">
<v-card-text>
<div v-if="!zeroAmountInvoice">
<p class="d-flex justify-center">Amount</p>
<p
class="d-flex justify-center"
:class="
getAmountClass(
itemv.amount.length +
itemv.symbol.length +
1
)
"
>
{{ itemv.amount }}
{{ itemv.symbol.toUpperCase() }}
</p>
<div class="d-flex align-center justify-center">
<display-field
:title="`Amount in ${itemv.symbol.toUpperCase()}`"
:value="itemv.amount"
class="flex-grow-1"
/>
</div>
<v-divider />
</div>
<UIExtensionSlot
Expand Down Expand Up @@ -699,6 +694,10 @@ export default {
type: Boolean,
default: false,
},
underpaid: {
type: Boolean,
default: false,
},
invoice: {
type: Object,
default() {
Expand Down
137 changes: 101 additions & 36 deletions pages/i/_id.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,32 @@
</div>
<div v-else>
<TabbedCheckout
v-if="status === 'pending'"
v-if="displayStatus === 'pending' || displayStatus === 'underpaid'"
:checkout-page="true"
:invoice.sync="invoice"
:store="store"
:underpaid="displayStatus === 'underpaid'"
class="px-0 pb-0"
@closedialog="closeDialog"
@update:itemv="selectedPaymentMethod = $event"
/>
<v-card-text v-else class="no-overflow py-12">
<close-button class="mt-n8" @closedialog="closeDialog" />
<div
:class="colorClass(texts[status].icon)"
:class="[colorClass(texts[displayStatus].icon), displayStatus === 'waiting_confirmation' ? 'confirming' : '']"
class="d-flex justify-center success-circle success-icon"
>
<v-icon
:color="
['mdi-check', 'mdi-cash-refund'].includes(texts[status].icon)
? 'green'
: 'red'
"
class="d-flex justify-center"
v-if="displayStatus !== 'waiting_confirmation'"
:color="getIconColor(texts[displayStatus].icon)"
class="d-flex justify-center icon-static"
>
{{ texts[status].icon }}
{{ texts[displayStatus].icon }}
</v-icon>
</div>
<div class="d-flex justify-center pt-4">
<div class="text-subtitle-1 font-weight-bold">
{{ texts[status].text }}
{{ texts[displayStatus].text }}
</div>
</div>
</v-card-text>
Expand All @@ -79,7 +77,7 @@ export default {
showSnackbar: false,
showPartial: false,
showDialog: true,
status: "pending",
redirected: false,
invoice: {},
store: {},
loading: true,
Expand All @@ -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",
Expand All @@ -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
Expand All @@ -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}`)
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -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"
},
},
}
</script>
Expand Down Expand Up @@ -292,14 +317,54 @@ $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;
width: 154px;
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;
}
Expand Down