This commit is contained in:
2026-08-28 17:31:02 +02:00
commit 2b30e8bd39
694 changed files with 49243 additions and 0 deletions
@@ -0,0 +1,188 @@
<template>
<el-card v-if="hasManualLines" shadow="never">
<template #header>
<span>Manual shipping payment</span>
</template>
<template v-if="canSetDeliveryCost">
<div class="mb-16">
<el-alert
type="warning"
title="Shipping quote required"
description="Publish a shipping quote to create the delivery payment session for the buyer. Until then, they cannot pay for shipping and the order cannot be fulfilled."
:closable="false"
show-icon
/>
</div>
<el-form
ref="deliveryCostFormRef"
label-position="top"
:model="deliveryCostForm"
:rules="deliveryCostRules"
>
<el-form-item :label="`Shipping cost (${order.fiatCurrency})`" prop="deliveryCost">
<div class="w-full">
<el-input-number
v-model="deliveryCostForm.deliveryCost"
:min="0"
:precision="2"
:step="1"
class="w-full"
@update:model-value="deliveryCostFormRef?.clearValidate('deliveryCost')"
/>
<p class="secondary-text m-0">Set 0 for free shipping.</p>
</div>
</el-form-item>
<el-button type="primary" :loading="deliveryCostSaving" @click="submitDeliveryCost">
Publish shipping quote
</el-button>
</el-form>
</template>
<template v-else-if="quoted">
<el-descriptions :column="1" class="mb-16 descriptions-row-labels">
<el-descriptions-item label="Shipping cost">
{{ formatFiatPrice(order.totals.shippingCostFiat ?? 0, order.fiatCurrency) }}
</el-descriptions-item>
<el-descriptions-item v-if="order.shippingInvoice" label="Payment expires">
{{ formatDate(order.shippingInvoice.expiresAt) }}
</el-descriptions-item>
</el-descriptions>
<template v-if="order.shippingInvoice">
<order-monero-payment-panel
:invoice="order.shippingInvoice"
rate-label="quote"
empty-text="Shipping payment session not created yet."
/>
</template>
<p v-else class="secondary-text m-0">Free shipping no payment required.</p>
</template>
<el-alert
v-else-if="order.failureReason"
type="error"
title="Shipping quote unavailable"
:description="failureReasonDescription"
:closable="false"
show-icon
/>
</el-card>
</template>
<script setup lang="ts">
import { computed, reactive, ref, type PropType } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useOrdersStore } from '@/stores/orders';
import type { OrderExtended } from '@/types/order/OrderExtended';
import { DeliveryMode } from '@/types/product/DeliveryMode';
import { formatDate } from '@/utils/formatDate';
import { formatFiatPrice } from '@/utils/formatFiatPrice';
import { formatOrderFailureReason } from '@/utils/order/formatOrderFailureReason';
import { isSet } from '@/utils/isSet';
import { resolveAxiosErrorMessage } from '@/utils/resolveAxiosErrorMessage';
const props = defineProps({
order: {
type: Object as PropType<OrderExtended>,
required: true
}
});
const { setDeliveryCost } = useOrdersStore();
const deliveryCostSaving = ref(false);
const deliveryCostFormRef = ref<FormInstance>();
const deliveryCostForm = reactive({
deliveryCost: 0
});
const quoted = computed(() => isSet(props.order.quotedAt));
const hasManualLines = computed(() =>
(props.order.lines ?? []).some(line => line.deliveryMode === DeliveryMode.Manual)
);
const canSetDeliveryCost = computed(() => !props.order.failureReason && !quoted.value && hasManualLines.value);
const failureReasonDescription = computed(() => {
if (!props.order.failureReason) {
return '';
}
const reason = formatOrderFailureReason(props.order.failureReason);
return `This order cannot be fulfilled (${reason}). A shipping quote cannot be published.`;
});
const deliveryCostRules = computed<FormRules>(() => ({
deliveryCost: [
{
required: true,
message: 'Shipping cost is required',
trigger: 'change'
}
]
}));
const submitDeliveryCost = async () => {
const formEl = deliveryCostFormRef.value;
if (!formEl) {
return;
}
try {
await formEl.validate();
} catch {
return;
}
const deliveryCost = deliveryCostForm.deliveryCost;
const formattedCost = formatFiatPrice(deliveryCost, props.order.fiatCurrency);
const buyerImpactNote =
deliveryCost > 0
? 'This will create a payment session for the buyer.'
: 'This will publish the quote for the buyer; no payment is required for free shipping.';
try {
await ElMessageBox.confirm(
`You are about to set the shipping quote to ${formattedCost}. ${buyerImpactNote} This cannot be undone. Ensure the quote is correct.`,
'Publish shipping quote',
{
type: 'warning',
confirmButtonText: 'Publish',
cancelButtonText: 'Cancel'
}
);
} catch {
return;
}
deliveryCostSaving.value = true;
try {
await setDeliveryCost(props.order.id, {
deliveryCost
});
ElMessage.success('Shipping quote published.');
} catch (error) {
const fallback = 'Could not publish shipping quote.';
const message = resolveAxiosErrorMessage(error, fallback);
ElMessage.error(message);
} finally {
deliveryCostSaving.value = false;
}
};
</script>