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:
Jepp9350
2025-04-22 12:04:23 +02:00
parent 4fb994a4a6
commit 9149cb9933
5 changed files with 298 additions and 1 deletions
@@ -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',
@@ -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"
@@ -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>
@@ -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>