Add Stripe integration for managing collected order invoices
Introduced a new Vue component for handling Stripe invoices and payment processing. Enhanced existing components and logic to distinguish between Stripe and E-conomic processors, allowing seamless management of both payment platforms. Added support for viewing Stripe payment details, booking invoices, and handling read-only notes in invoice management.
This commit is contained in:
@@ -169,6 +169,20 @@ export const CollectedOrderInvoices = {
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
stripe: {
|
||||
book_invoice: async (id) => {
|
||||
// Send the request
|
||||
return authenticatedRequest('/collected-invoices/stripe/book', 'POST', {
|
||||
id: parseInt(id)
|
||||
}).then((response) => {
|
||||
console.log(response);
|
||||
return response;
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
},
|
||||
economic: {
|
||||
runCheckDrafts: async () => {
|
||||
// Send the request
|
||||
|
||||
@@ -47,6 +47,7 @@ export const ObjectsGlobal = {
|
||||
this_year: 'I år',
|
||||
all: 'Altid',
|
||||
},
|
||||
in: 'i',
|
||||
nothing_left_to_show: 'Der er ikke mere at vise',
|
||||
pending: 'Afventer',
|
||||
cancelled: 'Annulleret',
|
||||
|
||||
+12
@@ -7,6 +7,10 @@ import PosNotes from "@/components/displays/department/pos/PosNotes.vue";
|
||||
|
||||
const props = defineProps({
|
||||
user_id: Number,
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
});
|
||||
|
||||
const uniqueId = () => {
|
||||
@@ -60,6 +64,14 @@ getNotes();
|
||||
<template v-else-if="isLoading">
|
||||
<progress class="progress is-small is-link" max="100">Loading...</progress>
|
||||
</template>
|
||||
<template v-else-if="props.readOnly">
|
||||
<pos-notes
|
||||
:notes="notes"
|
||||
:isAddFormVisible="false"
|
||||
:isOldNotesVisible="true"
|
||||
:isLabelVisible="false"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<pos-notes
|
||||
:notes="notes"
|
||||
|
||||
+10
-1
@@ -3,6 +3,8 @@ import {ref, defineProps} from 'vue';
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
import CollectedOrderInvoiceManageEconomic
|
||||
from "@/views/dashboards/superUserDashboard/collectedOrderInvoice/displays/manage/collectedOrderInvoiceManageEconomic.vue";
|
||||
import CollectedOrderInvoiceManageStripe
|
||||
from "@/views/dashboards/superUserDashboard/collectedOrderInvoice/displays/manage/collectedOrderInvoiceManageStripe.vue";
|
||||
|
||||
const props = defineProps({
|
||||
invoiceId: {
|
||||
@@ -46,7 +48,14 @@ const columnClasses = ref({
|
||||
:invoiceId="invoiceId"
|
||||
:collectedOrderInvoice="collectedOrderInvoice"
|
||||
:class="columnClasses"
|
||||
v-if="props.collectedOrderInvoice.processor === 1 || props.collectedOrderInvoice.processor === 0"
|
||||
v-if="(props.collectedOrderInvoice.processor === 1 || props.collectedOrderInvoice.processor === 0) && props.collectedOrderInvoice.stripe === null"
|
||||
/>
|
||||
<!-- Stripe -->
|
||||
<CollectedOrderInvoiceManageStripe
|
||||
:invoiceId="invoiceId"
|
||||
:collectedOrderInvoice="collectedOrderInvoice"
|
||||
:class="columnClasses"
|
||||
v-else-if="(props.collectedOrderInvoice.processor === 2 || props.collectedOrderInvoice.processor === 0) || props.collectedOrderInvoice.stripe !== null"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
<script setup>
|
||||
import { defineProps, ref } from 'vue';
|
||||
import { Colors } from "@/ThemeConfig.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { StripeModule } from "@/components/stripe/StripeModule.vue";
|
||||
import GetOrderInvoicePDFButton from "@/components/search/economic/getOrderInvoicePDFButton.vue";
|
||||
import Swal from "sweetalert2";
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
|
||||
|
||||
const props = defineProps({
|
||||
collectedOrderInvoice: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
/** Define the variables */
|
||||
const isCollapsed = ref(true);
|
||||
const isPaid = ref((props.collectedOrderInvoice?.stripe.status === 'succeeded'));
|
||||
const isDraft = ref((props.collectedOrderInvoice?.economic_invoice_draft_id !== null));
|
||||
const isBooked = ref((props.collectedOrderInvoice?.economic_invoice_booked_id !== null));
|
||||
|
||||
|
||||
/** Create the invoice */
|
||||
const onBookStripeInvoice = async () => {
|
||||
// Book the invoice
|
||||
console.log('Book invoice');
|
||||
await SessionUser.objects.collectedOrderInvoices.functions.stripe.book_invoice(parseInt(props.collectedOrderInvoice.id)).then((response) => {
|
||||
console.log('Invoice booked successfully', response);
|
||||
Swal.fire({
|
||||
title: 'Fakturaen er bogført',
|
||||
text: 'Fakturaen er bogført i Stripe',
|
||||
icon: 'success',
|
||||
showConfirmButton: false,
|
||||
timer: 2000
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.log('Error booking invoice', error);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const stripePayment = {
|
||||
id: props.collectedOrderInvoice.stripe.id || null,
|
||||
currency: props.collectedOrderInvoice.stripe.currency || 'DKK',
|
||||
amount: props.collectedOrderInvoice.stripe.amount || 0,
|
||||
amount_received: props.collectedOrderInvoice.stripe.amount_received || 0,
|
||||
amount_capturable: props.collectedOrderInvoice.stripe.amount_capturable || 0,
|
||||
status: props.collectedOrderInvoice.stripe.status || 'pending',
|
||||
receipt_url: props.collectedOrderInvoice.stripe?.latest_charge?.receipt_url || null,
|
||||
metadata: {
|
||||
tax_percentage: props.collectedOrderInvoice.stripe?.latest_charge?.metadata?.tax_percentage || 0,
|
||||
},
|
||||
fees: {
|
||||
stripe_fee: props.collectedOrderInvoice.stripe?.latest_charge?.balance_transaction?.fee || 0,
|
||||
stripe_fee_details: props.collectedOrderInvoice.stripe?.latest_charge?.balance_transaction?.fee_details || [],
|
||||
stripe_fee_amount: props.collectedOrderInvoice.stripe?.latest_charge?.balance_transaction?.fee || 0,
|
||||
stripe_net_after_fees: props.collectedOrderInvoice.stripe?.latest_charge?.balance_transaction?.net || 0,
|
||||
currency: props.collectedOrderInvoice.stripe?.latest_charge?.balance_transaction?.currency || 'DKK',
|
||||
},
|
||||
getFeePercentage: (fee, amount) => {
|
||||
if (amount > 0) {
|
||||
return ((fee / amount) * 100).toFixed(2);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- If the invoice is paid -->
|
||||
<div
|
||||
v-if="isPaid"
|
||||
class="message"
|
||||
:class="{
|
||||
'is-success': isBooked,
|
||||
'is-warning': isDraft,
|
||||
'is-grey': !isDraft && !isBooked,
|
||||
}"
|
||||
>
|
||||
<div class="message-body">
|
||||
<div class="columns is-vcentered is-multiline">
|
||||
<div class="column is-narrow">
|
||||
<!-- Check-mark -->
|
||||
<span
|
||||
class="icon is-large"
|
||||
:class="
|
||||
isBooked
|
||||
? 'has-text-success'
|
||||
: 'has-text-warning'
|
||||
"
|
||||
>
|
||||
<i class="fas fa-check-circle fa-3x"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="column is-fullwidth">
|
||||
<p class="title is-4">Fakturaen er {{ isDraft ? 'gemt som kladde i' : isBooked ? 'bogført med' : 'ikke bogført' }} E-conomic</p>
|
||||
<p class="subtitle is-6">Fakturaen er betalt <strong>{{ isBooked ? 'og bogført' : isDraft ? 'og gemt som kladde' : 'men ikke bogført' }}</strong> i E-conomic</p>
|
||||
</div>
|
||||
<!-- Charges or refunds overview -->
|
||||
<div class="column is-12">
|
||||
<p class="title is-4">Betalingsoversigt</p>
|
||||
<!-- Stripe payment -->
|
||||
<template v-if="collectedOrderInvoice.stripe.latest_charge">
|
||||
<div class="card">
|
||||
<!-- Card header -->
|
||||
<div class="card-header">
|
||||
<div class="card-header-icon">
|
||||
<span class="icon">
|
||||
<i
|
||||
class="fas fa-circle"
|
||||
:class="{
|
||||
'has-text-success': stripePayment.status === 'succeeded',
|
||||
'has-text-danger': stripePayment.status === 'failed',
|
||||
'has-text-warning': stripePayment.status === 'pending',
|
||||
}"
|
||||
></i>
|
||||
</span>
|
||||
<div class="tag is-success is-light">
|
||||
{{stripePayment.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-header-title">
|
||||
<span>{{stripePayment.id}}</span>
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
<span class="icon">
|
||||
<i class="fas fa-money-bill-wave"></i>
|
||||
</span>
|
||||
<div class="tag is-success is-light">
|
||||
{{stripePayment.amount / 100}} {{stripePayment.currency.toUpperCase()}} ( {{stripePayment.metadata?.tax_percentage}}% moms )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card content -->
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<p class="subtitle is-6">Betalings-id: {{stripePayment.id}}</p>
|
||||
<p class="subtitle is-6">Betalingsbeløb: {{stripePayment.amount / 100}} {{stripePayment.currency.toUpperCase()}}</p>
|
||||
<p class="subtitle is-6">Betalingsstatus: {{stripePayment.status}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card footer -->
|
||||
<div class="card-footer">
|
||||
<!-- Button to view the payment in Stripe -->
|
||||
<a
|
||||
class="card-footer-item"
|
||||
:href="'https://dashboard.stripe.com/payments/' + stripePayment.id"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</span>
|
||||
<span>{{SessionUser.objects.global.language.show + ' ' + SessionUser.objects.global.language.in + ' Stripe'}}</span>
|
||||
</a>
|
||||
<!-- Button to process the payment, shown if the payment is ready to capture -->
|
||||
<a
|
||||
v-if="stripePayment.amount_capturable > 0"
|
||||
class="card-footer-item"
|
||||
@click="StripeModule.paymentIntents.capturePaymentIntent(collectedOrderInvoice.stripe)"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
<span>{{SessionUser.objects.global.language.capture}}</span>
|
||||
</a>
|
||||
<!-- Button to view the receipt -->
|
||||
<a
|
||||
v-if="stripePayment.receipt_url"
|
||||
class="card-footer-item"
|
||||
:href="stripePayment.receipt_url"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-receipt"></i>
|
||||
</span>
|
||||
<span>{{SessionUser.objects.global.language.show + ' ' + SessionUser.objects.global.language.payment.receipt}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Fees overview -->
|
||||
<p class="title is-4" v-if="stripePayment.fees.stripe_fee > 0">Stripe gebyrer</p>
|
||||
<template v-for="( fee, index ) in stripePayment.fees.stripe_fee_details" :key="index">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-header-icon">
|
||||
<span class="icon">
|
||||
<i class="fas fa-circle has-text-grey"></i>
|
||||
</span>
|
||||
<div class="tag is-grey is-light">
|
||||
{{fee.type}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-header-title">
|
||||
<span>{{fee.description}}</span>
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
<span class="icon">
|
||||
<i class="fas fa-money-bill-wave"></i>
|
||||
</span>
|
||||
<div class="tag is-grey is-light">
|
||||
- {{fee.amount / 100}} {{stripePayment.currency.toUpperCase()}} ( {{stripePayment.getFeePercentage(fee.amount, stripePayment.amount)}}% )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- If the invoice is paid, but not booked -->
|
||||
<div v-else-if="!isBooked && isPaid"
|
||||
class="card"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left is-flex p-2">
|
||||
<figure class="image is-48x48">
|
||||
<span class="icon" :style="{ color: Colors.global.primaryColor }">
|
||||
<i class="fas fa-file-invoice-dollar fa-3x"></i>
|
||||
</span>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">E-conomic</p>
|
||||
<p class="subtitle is-6">Brug E-conomic til at oprette fakturaer</p>
|
||||
</div>
|
||||
<!-- Confirm button -->
|
||||
<div class="media-right">
|
||||
<div class="buttons">
|
||||
<button
|
||||
class="button is-small is-light"
|
||||
@click="onBookStripeInvoice"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-file-invoice-dollar"></i>
|
||||
</span>
|
||||
<span>Bogfør faktura</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- If the invoice is not paid -->
|
||||
<div v-else>
|
||||
<p>TEST 1234</p>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user