mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-11 13:16:50 +00:00
Some checks are pending
CI / checks (push) Waiting to run
Introduce backend and frontend support for generating Stripe payment links for outstanding booking balances. Adds a new POST endpoint to create payment links, updates booking view to include a Stripe button, and integrates error handling and feedback for payment link creation. Refactors view models and templates to support the new feature.
215 lines
6.2 KiB
Text
215 lines
6.2 KiB
Text
package view
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/rjNemo/rentease/internal/view/layout"
|
|
)
|
|
|
|
templ BookingById(booking *BookingViewModel) {
|
|
@layout.BaseLayout() {
|
|
<section class="flex justify-between items-center mb-6">
|
|
<hgroup>
|
|
<h1 class="text-3xl font-bold mb-1">{ booking.Name }</h1>
|
|
<p class="text-lg text-base-content/80">{ booking.Id }</p>
|
|
</hgroup>
|
|
<div class="flex space-x-2">
|
|
<a class="btn btn-outline btn-secondary" href={ booking.PdfUrl } target="_blank">Create PDF</a>
|
|
<a
|
|
href="https://web.whatsapp.com/"
|
|
target="_blank"
|
|
rel="noreferrer noopener"
|
|
class="btn btn-ghost btn-sm btn-square"
|
|
>
|
|
<img src="/static/icons/whatsapp.png" class="w-6 h-6"/>
|
|
</a>
|
|
if booking.Canceled {
|
|
<span class="badge badge-error">Canceled</span>
|
|
} else {
|
|
<button class="btn btn-outline" hx-patch={ booking.CancelUrl } hx-swap="outerHTML">
|
|
Cancel
|
|
</button>
|
|
}
|
|
</div>
|
|
</section>
|
|
<section>
|
|
@BookingForm(*booking)
|
|
</section>
|
|
<section class="card bg-base-100 shadow-md p-6 mb-8">
|
|
<hgroup class="flex justify-between items-center">
|
|
<h2
|
|
class="text-xl font-semibold mb-4 border-b pb-2 border-base-content/10"
|
|
>
|
|
Line Items
|
|
</h2>
|
|
<div class="flex items-center gap-2">
|
|
<button class="btn btn-secondary btn-sm " onclick="document.getElementById('payment_modal').showModal()">Add Payment</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm btn-square"
|
|
data-payment-link-url={ booking.StripePaymentLinkUrl }
|
|
aria-label="Create Stripe payment link"
|
|
onclick="createStripePaymentLink(event)"
|
|
>
|
|
<img src="/static/icons/stripe.png" class="w-6 h-6"/>
|
|
</button>
|
|
</div>
|
|
</hgroup>
|
|
<div class="overflow-x-auto mb-6">
|
|
<table class="table w-full">
|
|
<thead>
|
|
<tr>
|
|
<th>Item</th>
|
|
<th>Quantity</th>
|
|
<th>Price (€)</th>
|
|
<th>Payment Method</th>
|
|
<th>Sub-total (€)</th>
|
|
<th class="text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
@ItemList(booking.Items)
|
|
<tfoot>
|
|
<tr>
|
|
<td colspan="4" class="text-right font-bold">Total:</td>
|
|
<td colspan="2" class="font-bold">{ booking.Total }</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
<div class="border-t pt-4 border-base-content/10">
|
|
<h3 class="text-lg font-semibold mb-3">Add New Line Item</h3>
|
|
<form
|
|
hx-post={ fmt.Sprintf("%s/items", booking.Url) }
|
|
hx-target="#line-items"
|
|
hx-swap="afterend"
|
|
hx-on::after-request="if(event.detail.successful) this.reset()"
|
|
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 items-end"
|
|
>
|
|
<div class="form-control w-full">
|
|
<label class="label" for="new-line-item">
|
|
<span class="label-text">Item</span>
|
|
</label>
|
|
<select class="select select-bordered w-full" name="item" id="new-line-item">
|
|
for _, item := range booking.ItemList {
|
|
<option value={ item }>{ item }</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="form-control w-full">
|
|
<label class="label" for="new-line-quantity">
|
|
<span class="label-text">Quantity</span>
|
|
</label>
|
|
<input
|
|
type="number"
|
|
name="quantity"
|
|
id="new-line-quantity"
|
|
required
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</div>
|
|
<div class="form-control w-full">
|
|
<label class="label" for="new-line-price">
|
|
<span class="label-text">Price (€)</span>
|
|
</label>
|
|
<input
|
|
type="number"
|
|
name="price"
|
|
inputmode="decimal"
|
|
step="0.01"
|
|
id="new-line-price"
|
|
required
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</div>
|
|
<div class="flex justify-end md:justify-start">
|
|
<button type="submit" class="btn btn-secondary">Add Line Item</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<script>
|
|
async function createStripePaymentLink(event) {
|
|
const button = event.currentTarget;
|
|
if (!button) {
|
|
return;
|
|
}
|
|
const endpoint = button.dataset.paymentLinkUrl;
|
|
if (!endpoint) {
|
|
return;
|
|
}
|
|
button.disabled = true;
|
|
button.classList.add("loading");
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"Accept": "application/json",
|
|
},
|
|
});
|
|
if (!response.ok) {
|
|
const message = await extractStripePaymentLinkError(response);
|
|
alert(message);
|
|
return;
|
|
}
|
|
const data = await response.json();
|
|
if (data && data.url) {
|
|
window.open(data.url, "_blank", "noopener");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to create Stripe payment link", error);
|
|
alert("Unable to create the Stripe payment link. Please try again.");
|
|
} finally {
|
|
button.disabled = false;
|
|
button.classList.remove("loading");
|
|
}
|
|
}
|
|
|
|
async function extractStripePaymentLinkError(response) {
|
|
try {
|
|
const payload = await response.json();
|
|
if (payload && payload.message) {
|
|
return payload.message;
|
|
}
|
|
} catch (_error) {
|
|
// ignore parsing errors
|
|
}
|
|
return response.statusText || "Unexpected error while creating payment link.";
|
|
}
|
|
</script>
|
|
@PaymentModal(booking.PaymentUrl)
|
|
}
|
|
}
|
|
|
|
templ PaymentModal(paymentUrl string) {
|
|
<dialog id="payment_modal" class="modal">
|
|
<div class="modal-box">
|
|
<h3 class="text-lg font-bold">Add Payment</h3>
|
|
<form
|
|
class="py-4 space-y-4"
|
|
hx-post={ paymentUrl }
|
|
hx-target="#payment-lines"
|
|
hx-swap="outerHTML"
|
|
hx-on::after-request="if(event.detail.successful) payment_modal.close()"
|
|
>
|
|
<div class="form-control w-full">
|
|
<label class="label">
|
|
<span class="label-text">Amount</span>
|
|
</label>
|
|
<input type="number" step="0.01" name="amount" class="input input-bordered w-full" required autofocus/>
|
|
</div>
|
|
<div class="form-control w-full">
|
|
<label class="label">
|
|
<span class="label-text">Payment Method</span>
|
|
</label>
|
|
<select name="paymentMethod" class="select select-bordered w-full" required>
|
|
<option value="">Select payment method</option>
|
|
<option value="Cash">Cash</option>
|
|
<option value="Card">Card</option>
|
|
<option value="Cheque">Cheque</option>
|
|
<option value="Transfer">Bank Transfer</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Add Payment </button>
|
|
</form>
|
|
</div>
|
|
</dialog>
|
|
}
|