using LiteCharms.Features.Api.Configuration; using LiteCharms.Features.Hasher; using LiteCharms.Features.MidrandBooks.AuthorBooks; using LiteCharms.Features.MidrandBooks.Customers; using LiteCharms.Features.MidrandBooks.Customers.Models; using LiteCharms.Features.MidrandBooks.Orders; using LiteCharms.Features.MidrandBooks.Orders.Models; using LiteCharms.Features.MidrandBooks.Payments; using LiteCharms.Features.MidrandBooks.Payments.Models; using LiteCharms.Features.MidrandBooks.Products; namespace MidrandBookshop.Components.Pages; public partial class Checkout() { [Inject] public HashService HashService { get; set; } = default!; [Inject] public PaymentService PaymentService { get; set; } = default!; [Inject] public OrderService OrderService { get; set; } = default!; [Inject] public BooksService BooksService { get; set; } = default!; [Inject] public CartService CartService { get; set; } = default!; [Inject] public PayfastService PayfastService { get; set; } = default!; [Inject] public CustomerService CustomerService { get; set; } = default!; [Inject] public ProductService ProductService { get; set; } = default!; [Inject] public IOptions PayfastOptions { get; set; } = default!; [Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!; [Inject] public IJSRuntime JSRuntime { get; set; } = default!; private Cart ShoppingCart => CartService.ShoppingCart; private AuthenticationState? AuthState { get; set; } private ClaimsPrincipal? User { get; set; } private bool IsProcessing { get; set; } private decimal ShippingCost = 0; private bool IsSameAddress = true; private Dictionary CheckoutPayload { get; set; } = []; protected override async Task OnInitializedAsync() { AuthState = await AuthStateProvider.GetAuthenticationStateAsync(); User = AuthState!.User; Navigation.LocationChanged += OnLocationChanged; CartService.OnCartChanged += CartService_OnCartChanged; } private async void CartService_OnCartChanged() => await InvokeAsync(StateHasChanged); private void OnLocationChanged(object? sender, LocationChangedEventArgs e) => StateHasChanged(); private async void ChangeQuantity(CartItem item, int delta) { var peekQuantity = item.Quantity + delta; if (peekQuantity < 1) return; CartService.UpdateQuantity(item.Price!.Id, delta); await CartService.SaveCartToStorageAsync(); } private async void RemoveFromCart(CartItem item) { CartService.RemoveOneItem(item.Price!.Id); await CartService.SaveCartToStorageAsync(); } private async Task PayNow(MouseEventArgs args) { if (IsProcessing) return; try { // 1. Instantly disable the button to prevent duplicate click submissions IsProcessing = true; StateHasChanged(); // Force Blazor Server to push the disabled state over SignalR immediately var customerEmail = User?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)!.Value!; // 2. Create customer if ShoppingCart.CustomerId is null if (ShoppingCart.CustomerId == null) { var existingCustomer = await CustomerService.GetCustomerAsync(customerEmail); if (existingCustomer.IsSuccess) ShoppingCart.CustomerId = existingCustomer.Value.Id; if (existingCustomer.IsFailed) { var customerCreate = await CustomerService.CreateCustomerAsync(new CreateCustomer { Email = customerEmail }); if (customerCreate.IsSuccess) ShoppingCart.CustomerId = customerCreate.Value; } } // 3. Create order using shopping cart and assign the ShoppingCart.OrderId var order = await OrderService.CreateOrderAsync(ShoppingCart.CustomerId!.Value, new CreateOrder(ShoppingCart.TotalAmount, null)); List orderItems = []; foreach (var item in ShoppingCart.Items) { var bookRequest = await BooksService.GetBookByProductIdAsync(item.Price!.Id); if (bookRequest.IsSuccess) { var orderItem = new CreateOrderItem(bookRequest.Value.Id, item.Price.Id, item.Quantity); orderItems.Add(orderItem); } } var paymentGen = await PaymentService.CreatePaymentAsync(ShoppingCart.TotalAmount, order.Value, HashService.HashEncodeLongId(order.Value).Value); var merchantPaymentId = HashService.HashEncodeLongId(order.Value).Value; await PaymentService.WriteLedgerEntryAsync(new CreateLedgerEntry { OrderId = order.Value, CustomerId = ShoppingCart.CustomerId.Value, PaymentGatewayId = 1, PaymentGatewayReference = merchantPaymentId, PaymentId = paymentGen.Value, Status = LiteCharms.Features.LedgerStatuses.Sent, }); var addItemsResult = await OrderService.AddItemsToOrderAsync(order.Value, [.. orderItems]); // 4. Generate the signed Payfast form payload using your backend service var hostAddress = "https://localhost:7021"; CheckoutPayload = new Dictionary { { "merchant_id", PayfastOptions.Value.MerchantId! }, { "merchant_key", PayfastOptions.Value.MerchantKey! }, { "return_url", $"{hostAddress}/payment-success" }, { "cancel_url", $"{hostAddress}/payment-failed" }, { "notify_url", "https://api.uat.midrandbooks.co.za/v1/payments/payfast/confirm" }, { "email_address", customerEmail }, { "m_payment_id", merchantPaymentId }, { "amount", ShoppingCart.TotalAmount.ToString("F2", CultureInfo.InvariantCulture) }, { "item_name", "MidrandBooks Sale" }, }; var signature = PayfastService.GenerateSignature(CheckoutPayload!, PayfastOptions.Value.Passphrase).Value; CheckoutPayload.Add("signature", signature); StateHasChanged(); // 6. Execute programmatic submit directly into the sandbox await JSRuntime.InvokeVoidAsync("eval", "document.getElementById('payfastForm').submit();"); } catch { IsProcessing = false; StateHasChanged(); } } }