Skip to content

E-commerce Template

Digital products e-commerce store with Stripe products catalog, shopping cart, checkout, order management, and Vue 3 frontend.

Terminal window
# Create project
stacksolo init --template ecommerce
# Install dependencies
cd my-store
npm install
# Set up Stripe secrets
echo "sk_test_xxx" | gcloud secrets create stripe-secret-key --data-file=-
echo "whsec_xxx" | gcloud secrets create stripe-webhook-secret --data-file=-
# Start development
stacksolo dev
  • Firebase SDK for authentication (email/password, Google)
  • Pinia stores for auth, cart, and products
  • Vue Router with protected routes
  • Tailwind CSS styling
  • Homepage with featured products
  • Product catalog and detail pages
  • Persistent shopping cart with drawer
  • Checkout flow with Stripe
  • Order history and detail pages
  • Express API on Cloud Functions
  • Stripe integration for products and checkout
  • PostgreSQL with Drizzle ORM
  • Cart, Orders, Webhooks routes
  • Webhook handler for order completion
├── apps/web/ # Vue 3 frontend
│ └── src/
│ ├── components/
│ │ ├── Navbar.vue
│ │ ├── ProductCard.vue
│ │ └── CartDrawer.vue
│ ├── stores/
│ │ ├── auth.ts
│ │ ├── cart.ts
│ │ └── products.ts
│ ├── pages/
│ │ ├── Home.vue
│ │ ├── Products.vue
│ │ ├── Product.vue
│ │ ├── Cart.vue
│ │ ├── Orders.vue
│ │ ├── Order.vue
│ │ ├── Login.vue
│ │ └── CheckoutSuccess.vue
│ ├── router/index.ts
│ └── lib/
│ ├── firebase.ts
│ └── api.ts
├── functions/api/ # Express API
│ └── src/
│ ├── db/
│ │ ├── index.ts # Drizzle connection
│ │ └── schema.ts # User, Cart, Order tables
│ ├── services/
│ │ └── stripe.service.ts
│ ├── routes/
│ │ ├── products.ts # Stripe products
│ │ ├── cart.ts # Cart CRUD
│ │ ├── checkout.ts # Stripe checkout
│ │ ├── orders.ts # Order history
│ │ └── webhooks.ts # Stripe webhooks
│ └── index.ts
└── stacksolo.config.json
// users - synced from Firebase Auth
export const users = pgTable('users', {
id: varchar('id', { length: 128 }).primaryKey(),
email: varchar('email', { length: 255 }).notNull(),
stripeCustomerId: varchar('stripe_customer_id', { length: 255 }),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
// cart_items - persistent shopping cart
export const cartItems = pgTable('cart_items', {
id: serial('id').primaryKey(),
userId: varchar('user_id', { length: 128 }).notNull(),
priceId: varchar('price_id', { length: 255 }).notNull(),
productId: varchar('product_id', { length: 255 }).notNull(),
quantity: integer('quantity').notNull().default(1),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
// orders - completed orders from Stripe
export const orders = pgTable('orders', {
id: varchar('id', { length: 255 }).primaryKey(),
userId: varchar('user_id', { length: 128 }).notNull(),
status: varchar('status', { length: 50 }).notNull(),
totalAmount: integer('total_amount').notNull(),
currency: varchar('currency', { length: 3 }).notNull(),
stripeCheckoutId: varchar('stripe_checkout_id', { length: 255 }),
createdAt: timestamp('created_at').defaultNow().notNull(),
completedAt: timestamp('completed_at'),
});
// order_items - line items in orders
export const orderItems = pgTable('order_items', {
id: serial('id').primaryKey(),
orderId: varchar('order_id', { length: 255 }).notNull(),
productId: varchar('product_id', { length: 255 }).notNull(),
productName: varchar('product_name', { length: 255 }).notNull(),
productDescription: text('product_description'),
priceId: varchar('price_id', { length: 255 }).notNull(),
quantity: integer('quantity').notNull(),
unitAmount: integer('unit_amount').notNull(),
downloadUrl: varchar('download_url', { length: 500 }),
});
MethodPathAuthDescription
GET/api/healthNoHealth check
GET/api/productsNoList Stripe products
GET/api/products/:idNoGet product details
GET/api/cartYesGet user’s cart
POST/api/cartYesAdd item to cart
PUT/api/cart/:idYesUpdate cart item quantity
DELETE/api/cart/:idYesRemove item from cart
DELETE/api/cartYesClear entire cart
POST/api/checkoutYesCreate Stripe checkout session
GET/api/ordersYesList user’s orders
GET/api/orders/:idYesGet order details
POST/api/webhooks/stripeNo*Handle Stripe webhooks

*Verified by Stripe signature

  1. Create Stripe account at https://stripe.com
  2. Create products in Stripe Dashboard:
    • Add product name, description, images
    • Add price (one-time payment)
    • Products appear automatically in the store
  3. Create secrets:
Terminal window
echo "sk_test_xxx" | gcloud secrets create stripe-secret-key --data-file=-
echo "whsec_xxx" | gcloud secrets create stripe-webhook-secret --data-file=-

The webhook handler processes:

  • checkout.session.completed - Creates order, clears cart
  • checkout.session.expired - Marks order as expired
RoutePageAuth Required
/Homepage with featured productsNo
/productsProduct catalogNo
/products/:idProduct detailNo
/cartShopping cartYes
/ordersOrder historyYes
/orders/:idOrder detailYes
/loginLogin pageNo
/checkout/successPost-checkout confirmationYes

Update webhook handler to include download URLs:

// In routes/webhooks.ts
const downloadUrl = await generateSignedUrl(item.productId);
await db.insert(orderItems).values({
...itemData,
downloadUrl,
});
  1. Add shipping address fields to checkout
  2. Update Stripe checkout to collect shipping
  3. Add fulfillment status to orders table
  1. Add quantity tracking to products (Stripe metadata)
  2. Check availability before checkout
  3. Decrement on order completion

For local development, create .env.local:

VITE_FIREBASE_API_KEY=your-api-key
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-project-id
Terminal window
stacksolo deploy
# After deployment, set up Stripe webhook in Dashboard:
# URL: https://your-domain.com/api/webhooks/stripe
# Events: checkout.session.completed, checkout.session.expired

This creates:

  • Cloud Functions API
  • Cloud SQL PostgreSQL
  • Cloud Storage for frontend
  • Load balancer with SSL