Part 15: Online Payment Gateway, Cash on Delivery & Order Checkout
Step 1: Configuring External App Secret Providers
The appsettings.json file handles your environmental configuration keys. This block initializes the three major external integration points your commercial pipeline requires: secure payments, OAuth identification routing, and background messaging engines.
Core Configuration Sections Breakdown
The Stripe Payment Portal Block (
Stripe): This section holds your payment gateway access tokens.The
PublishableKey(pk_test_...) is completely safe to expose to the public browser view. It is used on the client side by Stripe's JavaScript library to tokenize card details directly on Stripe's servers.The
SecretKey(sk_test_...) must remain strictly hidden on the server side. Your C# controllers use it to securely call Stripe's APIs and capture funds from a transaction.
The Background Notification SMTP Engine (
EmailSettings): This section establishes a connection to a secure Mail Transfer Protocol (SMTP) server. By mapping out variables likeSmtpServerandSmtpPort(using port 587 for standard secure TLS encryption), your application can send automated notifications—such as order confirmations or invoice receipts—directly to your buyers.
Step 2: Exposing the Public Stripe Key to the Frontend
In this step, you are using the ViewBag dictionary inside your CheckoutController's Index action to read the public configuration value and send it straight to your user interface.
Core Mechanism & Architectural Breakdown
Direct Key Index Resolution (
_configuration["Stripe:PublishableKey"]): The_configurationobject (which you injected into your controller constructor back in Part 14, Step 2) reads your JSON application files. By passing the colon-separated path string"Stripe:PublishableKey", the configuration framework instantly traverses into yourappsettings.jsonfile, looks up theStripeparent section, and extracts the test key literal string.Dynamic Data Transfer Wrapper (
ViewBag):ViewBagis a dynamic property wrapper that allows you to pass temporary data from a controller to a view without needing to modify your main strongly typed object structure (CheckoutViewModel). Creating the custom property.StripePublishableKeyallows the value to be carried over seamlessly during page compilation.Strict Architectural Separation of Concerns (SoC): Notice what is happening here from a security engineering standpoint: you are only exposing the
PublishableKey. Your sensitiveSecretKeyremains locked on the server side where the frontend browser can never see it. This is a crucial security pattern that satisfies standard payment card industry security regulations.
Step 3: Implementing the Client-Side Payment Gateway Scripts
By wrapping this code inside the @section Scripts directive, you ensure that the checkout logic is loaded at the bottom of the rendered page body. This preserves optimal page-loading performance while preventing conflicts with your master layout dependencies.
Core JavaScript & Integration Logic Breakdown
Defensive Gateway Script Loading:
@if (!string.IsNullOrEmpty(ViewBag.StripePublishableKey))This server-side Razor conditional protects your page stability. The external Stripe script (
js.stripe.com/v3/) and setup configurations only embed into the document if your controller successfully loads a valid key string fromappsettings.json, preventing runtime browser reference errors.Secure Element Embedding (
card.mount('#cardElement')): This single line provides industry-standard security. Instead of rendering custom, unsecure input text boxes for credit card numbers, expirations, and CVC codes inside your application, Stripe creates an isolated iframe component inside your#cardElementdiv wrapper. The user's sensitive digits are written directly onto secure remote cloud servers, keeping your local servers out of compliance risks.Conditional Form Visual Router: The
querySelectorAll('input[name="PaymentMethod"]')block sets up real-time layout event listeners. Whenever a customer changes their payment choice radio button, the callback script checks the active selection value. If it matches'Stripe', it flips the target interface layer todisplay: 'block'; otherwise, it hides it immediately via'none'.Form Hijacking and Secure Token Interception: When a customer clicks "Place Order," the submit handler evaluates the active payment choice. If Stripe is active,
event.preventDefault()halts standard form processing. The script callsstripe.createToken(card)to convert raw card details into a single-use token signature identifier (result.token.id).Once successfully created, a hidden input field named
StripeTokenis created on the fly and appended to the document object model, and the form is securely sent along to your server backend controller.
Step 4: Implementing the Email Service Blueprint and Infrastructure
This codebase handles building custom, clean HTML transactional notification layouts dynamically and dispatching them securely through an external mail transport layer.
Core Interface & Implementation Breakdown
The Dependency Abstraction Contract (
IEmailService): The interface maps out clear, domain-specific operations (SendOrderConfirmationAsyncandSendOrderStatusUpdateAsync) alongside a raw base processor (SendEmailAsync). This hides complex low-level SMTP routing properties from your business workflows.Dynamic Configuration Mining:
var smtpServer = _configuration["EmailSettings:SmtpServer"];var smtpPort = int.Parse(_configuration["EmailSettings:SmtpPort"] ?? "587");The code safely fetches network routing targets directly from the
appsettings.jsonkeys configured back in Step 1. Using a null-coalescing default operator (?? "587") prevents runtime exceptions if a port setting is missing, falling back to standard secure TLS lines automatically.Secure Networking with SMTP Lifecycle Scoping: By instantiating
SmtpClientwith the C#using vardeclaration syntax, you ensure that the active TCP networking socket is instantly torn down and disposed of cleanly from memory once the transmission completes. SettingEnableSsl = trueforces strong cryptographic encryption across the transmission wire, safely protecting your sender credentials.Asynchronous Execution Block Isolation (
try-catch): E-commerce order completions should never crash or freeze just because an external mail server experiences a brief timeout delay. Wrapping the logic in an asynchronoustry-catchcontainer with targeted diagnostics (_logger.LogError) ensures that network execution failures are recorded silently while the checkout flow finishes safely.Polished HTML String Interpolation Templates: Using multi-line C# verbatim string blocks (
$@"...") allows you to write clean, maintainable HTML design structures straight inside your method logic. The code passes raw pricing data types directly through standard numeric text formatters ({total:N2}) to guarantee a clean appearance across all client mail applications.
Step 5: Wiring Services and Middleware pipeline inside Program.cs
This code block modifies both the Dependency Injection Container (before builder.Build()) and the HTTP Request Pipeline (after builder.Build()).
Core Service Registrations & Middleware Breakdown
Strongly Typed Options Mapping:
builder.Services.Configure<StripeSettings>(builder.Configuration.GetSection("Stripe"));Instead of manually reading configuration strings inside every controller or class, this pattern binds your
"Stripe"JSON section fromappsettings.jsondirectly into a strongly typedStripeSettingsclass object. This configuration can now be cleanly injected into any constructor using the standardIOptions<StripeSettings>interface pattern.Scoped Mail Service Lifetime Registration:
builder.Services.AddScoped<IEmailService, EmailService>();Registering your
EmailServiceas Scoped means a new, isolated instance of the mail service will be created for each incoming web request (e.g., when a user submits an order) and destroyed cleanly once that request completes. This ensures proper memory management and protects data isolation between simultaneous shoppers.Global SDK Initialization & Secret Authorization:
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];This is a critical initialization pattern for the Stripe C# SDK. By setting the static
StripeConfiguration.ApiKeyproperty directly during application startup, you establish a universal, secure validation token. Every backend API call your application makes from this point forward will be automatically signed with your secret key, allowing your store to create charges safely.Middleware Ordering Pipeline (
app.UseSession()): Placing your middleware components in the correct sequence is highly important.app.UseSession()must be executed before authentication, authorization, and route processing blocks. This ensures that session-state memory pools are fully loaded into the request context before any user verification or cart deduction checks run.
Step 6: Explaining the Concrete IOrderService Implementation
This service coordinates interactions with your ApplicationDbContext to ensure that data transitions smoothly from temporary cart vectors to permanent database schemas.
1. CreateOrderAsync (Order Persistence & Inventory Control)
This method takes the submitted web form data and converts it into a permanent database transaction ledger.
Relational Mapping Pattern: It creates a new
Orderentity and populates its properties using the values passed from theCheckoutViewModel.Freezing Purchase Costs (
UnitPrice = item.UnitPrice): Inside theforeachloop, the script explicitly maps the current product cost down into historical individualOrderItemrows. This ensures that even if you alter a mobile phone's pricing in your catalog next month, the original purchase ledger amount remains unchanged.Inline Inventory Subtraction:
product.StockQuantity -= item.Quantity;Every time an item is successfully processed, Entity Framework tracks down the product record and automatically subtracts the requested quantity from your database stock. This prevents overselling out-of-stock items.
2. UpdateOrderStatusAsync (Fulfillment & Payment Synchronizer)
This state-machine engine manages the order fulfillment lifecycle (Pending, Shipped, Delivered, etc.) while applying logical state validation fixes:
Dynamic Timestamp Logging: Moving an order status to
ShippedorDeliveredautomatically attaches a live server date stamp (DateTime.Now) onto your records for fulfillment metrics tracking.Cash on Delivery (COD) Resolution Rules: If an order status is marked as
Delivered, the system checks if the payment method was Cash on Delivery or physical banking notes. If it matches, it converts thePaymentStatusfromPendingstraight toPaid.Refund Guard Rails: If an administrator triggers a
RefundedorCancelledaction, the state machine intercepts the tracking loop to accurately update the payment status column, keeping your financial statements accurate.
3. ProcessPaymentAsync (Digital Gateway Settlement Verification)
This method acts as the callback landing strip for digital transactions (such as successful Stripe webhooks or client-side captures).
Transaction Signature Binding: Once an online payment provider authorizes a credit card charge, this method updates the target record's
PaymentStatustoPaidand securely binds the external provider's unique payment tracking string (transactionId) straight to the order row. This ensures you can easily trace the transaction inside your Stripe Dashboard later if a dispute arises.
Step 7: Registering the Order Service in Program.cs
By adding this configuration to your application startup engine, you instruct ASP.NET Core on how to manage the lifecycle of your database order tracking logic.
Core Mechanism & Lifetime Breakdown
The Scoped Dependency Lifecycle Pattern:
builder.Services.AddScoped<IOrderService, OrderService>();Registering this service as Scoped is an architectural best practice for e-commerce transactional workloads. It ensures that a single instance of
OrderServiceis created per individual HTTP request context (when a customer clicks "Place Order") and shared across any components handling that request.Clean Context Alignment: Because your
OrderServicedepends directly on Entity Framework Core'sApplicationDbContext(which is registered as Scoped by default), registering your order manager as Scoped prevents severe architectural dependency mismatch issues (such as scoping errors where a transient service attempts to hold onto a disposed database pipeline context).Complete Middleware Pipeline Synergy: Once this registration is dropped in, the runtime can automatically instantiate and resolve your
OrderServicewhen you pass it as a constructor dependency into yourCheckoutController.
Step 8: Implementing the Stripe Payment Processing Engine
This method handles converting currency formats, requesting secure network charges via the Stripe SDK, clearing active sessions, and triggering post-purchase emails.
Core Logic & Payment Engineering Breakdown
The Zero-Decimal Currency Format Requirement:
Amount = (long)(order.TotalAmount * 100),This is a critical rule when integrating Stripe. Stripe processes all global transactions in smallest currency units (cents for USD, paisa for PKR) to prevent floating-point calculation rounding issues. If your order total is
RS 150,000, multiplying by100converts it to15000000paisas so Stripe reads and captures the exact monetary value.SDK Integration Parameters (
ChargeCreateOptions):Currency = "PKR"maps the transaction payload directly to your local currency.Source = model.StripeTokenaccepts the single-use frontend security token generated by JavaScript back in Part 15, Step 3. This ensures that sensitive credit card details never touch your server logs.
Gateway Charge Validation Check:
if (charge.Status == "succeeded")Once
ChargeService.CreateAsync()communicates with the server networks, you evaluate the response string payload. If it matches"succeeded", the transaction is officially captured, and the code launches your post-purchase cleanup chain.Post-Payment Cleanup and Notification Sequence:
ProcessPaymentAsync: Updates the database order payment column status toPaidand maps Stripe's unique tracking receipt key (charge.Id) to the order row.ClearCartAsync: Disposes of all items inside the active shopping cart layout so the customer returns to an empty basket status.SendOrderConfirmationAsync: Dispatches your newly compiled HTML transactional receipt email built back in Step 4.
Targeted Exception Filters (
StripeException): Wrapping the network charge routine in a distinctcatch (StripeException ex)block separates gateway API errors (like expired cards or insufficient funds) from general server application errors. This enables your system to safely record the failure details via_loggerwhile cleanly displaying the explicit user-facing error message usingTempData.
Step 9: Implementing the Order Submission and Payment Branching Engine
This method orchestrates the absolute lifecycle of an order from submission to database lock down, handling defensive checks, input failures, and multi-channel fulfillment models.
Core Logic & Architectural Breakdown
Defensive Boundary Security Guards:
[HttpPost]limits the endpoint to handle only network form submissions.[ValidateAntiForgeryToken]intercepts the request to verify that the form data originates securely from an authentic session generated by your app, preventing malicious Cross-Site Request Forgery (CSRF) attacks.The method runs another empty cart guard check just in case a user attempts a double-submit or uses duplicate browser tabs.
Re-hydrating UI Fallbacks on Invalid State (
!ModelState.IsValid): If a required field (like a missing shipping phone number) fails verification, the engine drops out of execution. Crucially, because you are returning the user back to the"Index"view container, you must re-hydrate the layout dependencies by re-assigningViewBag.CartItemCountandViewBag.StripePublishableKey. Failing to do this will cause your layout headers or JavaScript elements to throw null-pointer errors upon page reload.Unified Record Generation Layer (
CreateOrderAsync): Before checking how the customer wants to pay, the system proactively generates the core database rows via_orderService.CreateOrderAsync(). This ensures that even if an online card processor experiences network dropouts, a tracked order entity has already been compiled and stored in your backend database system.The Multi-Channel Payment Branching Architecture: The system evaluates the user's payment selection using a conditional control flow branch:
Payment Selection Route Core Architectural Behavior Stripe Hands over entire thread execution directly to your private ProcessStripePaymenthelper method to run a real-time card capture check.Cash on Delivery (COD) Sets the invoice tracking state straight to PaymentStatus.Pendingand registers the fulfillment workflow status toOrderStatus.Pending.Demo Gateways / Others Automatically marks the purchase invoice as immediately paid by appending a randomized tracking signature ( DEMO-GUID), bypassing bank calls for development purposes.
Step 10: Creating the OrderConfirmationViewModel
By placing this file inside your ViewModels folder, you ensure that your Razor view receives exactly the data it needs to build the receipt page—nothing more, nothing less.
Core C# Mechanics & Architectural Breakdown
The Safe Null Forgiving Operator (
= null!;): By declaringpublic Order Order { get; set; } = null!;, you are using C# Nullable Reference Types (NRT) formatting. This safely tells the compiler: "I guarantee that this property will be explicitly populated by the controller before it is ever sent to the view." This suppresses annoying compiler nullability warnings while keeping your code clean.Eager Collection Initialization (
= new List<OrderItem>();): Instantiating theOrderItemsproperty with an empty list by default is a defensive programming best practice. It completely eliminates the risk of throwing a frustratingNullReferenceExceptionin your view if an order somehow goes through without any items attached.Optimizing Data Flow & Preventing Lazy Loading Errors: In ASP.NET Core with Entity Framework, passing a raw database
Orderobject straight to a frontend view can cause severe "Lazy Loading" crashes when the HTML loop tries to read related data (likeProduct.Name) after the database context has already been disposed of. This ViewModel acts as a secure memory container, holding all fully evaluated data ready for display.
Step 11: Implementing the Order Confirmation Landing Action
This method handles reading the freshly created order using its public order string, validating its existence, and setting up a dedicated confirmation model context.
Core Logic & Architectural Breakdown
Defensive Route Validation Check:
if (order == null) { return NotFound(); }This is a critical security and user-experience guard rail. If a user manually alters the URL query string or typing errors occur when tracking an order number, the system gracefully handles the empty result by returning a standard HTTP
404 NotFoundstatus page, instead of throwing a null-pointer error on the screen.Strict View Model Encapsulation: Instead of passing the raw domain database entity directly into the frontend layout, the code wraps the data inside an
OrderConfirmationViewModel. Explicitly pulling down the related list collections using.ToList()ensures that all order snapshot information is fully loaded in memory before the view engine attempts compilation, completely bypassing any lazy-loading entity framework exceptions.Explicit Header Interface Reset:
ViewBag.CartItemCount = 0;Since the shopping cart state was completely cleared inside the previous
Processmethod, this assignment guarantees that your shared website layout header instantly updates to display a count of0items in the navigation badge. This provides the shopper with clear visual confirmation that their purchase has been processed and their basket is empty.
Step 12: Breaking Down the Razor Order Confirmation View Layout
This view binds tightly to your strongly typed model data and converts financial parameters into a visual invoice block.
Core UI and Data Layer Breakdown
Strongly Typed Data Context Definition:
@model OrderConfirmationViewModelBy explicitly defining the view's model mapping token at the first line, Razor gains access to compile-time checking. This prevents text interpolation typos across your nested order properties during server rendering.
Split Receipt Matrix Grid (
row,col-md-6): The template uses a 50/50 block grid split for medium screens and above. The left side handles core transaction metadata (like the unique generated order sequence and payment routing definitions). The right side isolates shipping coordinates, adapting cleanly when shrinking down onto mobile screen contexts.Defensive Optional Rendering Block:
@if (!string.IsNullOrEmpty(Model.Order.ShippingPhone)) { ... }Not all users will supply secondary delivery properties or custom purchase notes. By enclosing these fields in quick Razor logic checking blocks, you prevent empty text labels or broken spacing layers from appearing on the generated layout sheet.
Itemized Table Mapping Loop (
@foreach): The view loops over the structuralOrderItemsproperty array list, generating standard rows dynamically. Notice how it calls.ToString("N0")alongside your localRScurrency tag prefix. This handles standard grouping separators (e.g., displaying RS 150,000 instead of a raw unformatted150000literal string), providing a polished appearance.The Clean UI State Recovery Router: At the very base of the component container, you provide two distinct navigation link components:
A clear direct route link button mapping back to the
HomeControllerIndexview so they can continue loading item lists.An automated tracking loop route link passing the dynamic string code token (
asp-route-orderNumber="@Model.Order.OrderNumber") ahead to prepare for your upcoming package tracking feature build.

Comments
Post a Comment