Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 6, 2025

Adds Stripe Checkout, webhook processing, and refund capabilities for multi-tenant e-commerce orders.

Schema Changes

  • PaymentAttempt: Tracks payment lifecycle (PENDING/SUCCESS/FAILED) with Stripe payment intent ID, error codes, and metadata
  • Refund: Manages refund transactions with status tracking and Stripe refund ID linkage
  • Order: Added paidAt timestamp for payment completion tracking
  • Store: Added Stripe Connect fields (stripeAccountId, stripeSecretKey, stripePublishableKey) for multi-tenant payment routing

Payment Service

PaymentService singleton with:

  • createCheckoutSession() - Generates Stripe Checkout with line items, multi-currency support, and Stripe Connect routing
  • processRefund() - Handles full/partial refunds with inventory restoration on full refunds
  • getPaymentIntent() - Retrieves payment intent details

Multi-currency: Amounts converted to smallest unit (cents for USD, paisa for BDT). Currency code normalized to lowercase for Stripe API.

Stripe Connect: When Store.stripeAccountId exists, passes stripeAccount parameter to route payments to connected account.

API Routes

POST /api/payments/create-session

Creates Stripe Checkout session. Validates user store access via Membership relation and order status (PENDING or PAYMENT_FAILED only).

POST /api/payments/refund

Processes refunds with idempotency key generation. Validates refund amount ≤ order total and order status (PAID/PROCESSING/DELIVERED). Restores inventory on full refunds, updates refundedAmount on partial refunds.

POST /api/webhooks/stripe

Signature-verified event handlers:

  • checkout.session.completed → Order to PAID, PaymentAttempt to SUCCESS, audit log created
  • payment_intent.succeeded → PaymentAttempt to SUCCESS
  • payment_intent.payment_failed → Order to PAYMENT_FAILED with error details
  • charge.refunded → Refund to COMPLETED

Returns 400 for invalid signatures, 500 on processing errors (triggers Stripe retry).

Security

  • Webhook signature verification via stripe.webhooks.constructEvent()
  • All payment APIs require NextAuth session
  • Multi-tenant isolation: all queries filter by storeId, user access validated via Membership
  • API keys encrypted in Store model, never exposed to client

Example Usage

// In checkout flow
import { CheckoutButton } from "@/components/checkout-button";

<CheckoutButton orderId={order.id} />

// Process refund programmatically
const refund = await paymentService.processRefund({
  orderId: "clxxx...",
  amount: 50.00,
  reason: "REQUESTED_BY_CUSTOMER",
  idempotencyKey: `refund_${orderId}_${randomBytes(8).toString("hex")}`
});

Environment Variables

STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
Original prompt

This section details on the original issue you should resolve

<issue_title>[Phase 1] Stripe Payment Integration</issue_title>
<issue_description>## Priority: P0
Phase: 1
Epic: #26 Payment Integration
Estimate: 2 days
Type: Story

Context

Stripe Payment Integration provides the payment processing infrastructure for StormCom, enabling secure credit/debit card payments, handling asynchronous webhooks for payment confirmation, and supporting refund processing. This implementation must handle idempotency, webhook signature verification, and seamless integration with the Order Processing API.

According to the research documentation, payment processing requires atomic transaction handling to prevent order creation without payment confirmation, proper webhook security to prevent spoofing attacks, and comprehensive error handling for declined payments.

Acceptance Criteria

  • Stripe Checkout Session: Create Stripe Checkout sessions with order metadata, line items, success/cancel URLs, support for 10+ currencies (USD, BDT, EUR, GBP)
  • Webhook Handler: Process checkout.session.completed, payment_intent.succeeded, charge.refunded events with signature verification, idempotency key validation
  • Payment Attempts Tracking: Log all payment attempts (PENDING → SUCCESS/FAILED) in PaymentAttempt table with Stripe payment intent ID, amount, currency, error codes
  • Refund Processing: Issue full/partial refunds via Stripe API with idempotency keys, update order refundable balance, restore inventory on refund
  • Error Handling: Handle declined cards (insufficient funds, expired, invalid), network failures with exponential backoff retry (3 attempts max), webhook delivery failures
  • Security: Verify webhook signatures using Stripe secret, use HTTPS for all webhook endpoints, sanitize webhook payload before processing
  • Multi-Currency Support: Handle BDT (Bangladeshi Taka) via Stripe, convert display prices to customer currency, store amounts in smallest currency unit (paisa for BDT)
  • Performance: Process webhook in <2 seconds, create checkout session in <500ms, refund processing <3 seconds
  • Multi-Tenancy: Each store has unique Stripe Connect account ID, API keys stored encrypted in Store model, all payments filtered by storeId
  • Testing: Stripe test mode integration, test card numbers for success/decline scenarios, webhook replay testing via Stripe CLI

Technical Implementation

1. Stripe Checkout Session Creation

// src/lib/services/payment.service.ts
import Stripe from "stripe";
import { prisma } from "@/lib/prisma";

if (!process.env.STRIPE_SECRET_KEY) {
  throw new Error("STRIPE_SECRET_KEY is not defined");
}

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: "2024-11-20.acacia",
  typescript: true,
});

export class PaymentService {
  async createCheckoutSession(params: {
    orderId: string;
    storeId: string;
    successUrl: string;
    cancelUrl: string;
  }) {
    const { orderId, storeId, successUrl, cancelUrl } = params;

    // Fetch order with items
    const order = await prisma.order.findUnique({
      where: { id: orderId, storeId },
      include: {
        items: {
          include: {
            product: { select: { name: true, images: true } },
            variant: { select: { name: true } },
          },
        },
        store: { select: { name: true, currency: true, stripeAccountId: true } },
        customer: { select: { email: true } },
      },
    });

    if (!order) {
      throw new Error("Order not found");
    }

    // Create line items for Stripe
    const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = order.items.map((item) => ({
      price_data: {
        currency: order.store.currency || "usd",
        product_data: {
          name: item.product.name,
          description: item.variant?.name,
          images: item.product.images.slice(0, 1), // First image only
        },
        unit_amount: Math.round(item.price * 100), // Convert to cents
      },
      quantity: item.quantity,
    }));

    // Create checkout session
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      line_items: lineItems,
      mode: "payment",
      success_url: successUrl,
      cancel_url: cancelUrl,
      client_reference_id: orderId,
      customer_email: order.customer.email,
      metadata: {
        orderId,
        storeId,
        orderNumber: order.orderNumber,
      },
      ...(order.store.stripeAccountId && {
        stripe_account: order.store.stripeAccountId, // Stripe Connect
      }),
    });

    // Create pending payment attempt
    await prisma.paymentAttempt.create({
      data: {
        orderId,
        storeId,
        provider: "STRIPE",
        amount: order.totalAmount,
        currency: order.store.currency || "USD",
        st...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes CodeStorm-Hub/stormcomui#27

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

@vercel
Copy link

vercel bot commented Dec 6, 2025

Unable to deploy a commit from a private repository on your GitHub organization to the CodeStorm Hub team on Vercel, which is currently on the Hobby plan. In order to deploy, you can:

  • Make your repository public or
  • Upgrade to Pro. A Pro subscription is required to deploy from a private organization repository.

To read more about collaboration on Vercel, click here.

Co-authored-by: rafiqul4 <124497017+rafiqul4@users.noreply.github.com>
@vercel
Copy link

vercel bot commented Dec 6, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stormcomui Error Error Dec 6, 2025 6:50pm

Co-authored-by: rafiqul4 <124497017+rafiqul4@users.noreply.github.com>
Co-authored-by: rafiqul4 <124497017+rafiqul4@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement Stripe payment integration for phase 1 Implement Stripe payment integration with webhook handling and refund processing Dec 6, 2025
Copilot AI requested a review from rafiqul4 December 6, 2025 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants