<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[dashforge-ui]]></title><description><![CDATA[dashforge-ui]]></description><link>https://dashforge-ui.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a3109d3f6e6a179a4d35935/921280d5-f4d2-4514-8720-08a92b1a7531.jpg</url><title>dashforge-ui</title><link>https://dashforge-ui.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 18 Jun 2026 15:44:52 GMT</lastBuildDate><atom:link href="https://dashforge-ui.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I've built Stripe checkout from scratch 3 times. Here's what no one tells you.]]></title><description><![CDATA[Every time a client needs an e-commerce feature, the conversation starts the same way.
"We need Stripe checkout. How long?" And every time I say two months, they look at me like I'm padding the estima]]></description><link>https://dashforge-ui.hashnode.dev/i-ve-built-stripe-checkout-from-scratch-3-times-here-s-what-no-one-tells-you</link><guid isPermaLink="true">https://dashforge-ui.hashnode.dev/i-ve-built-stripe-checkout-from-scratch-3-times-here-s-what-no-one-tells-you</guid><category><![CDATA[webdev]]></category><category><![CDATA[React]]></category><category><![CDATA[stripe]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Ken Saadi]]></dc:creator><pubDate>Tue, 16 Jun 2026 08:36:26 GMT</pubDate><content:encoded><![CDATA[<p>Every time a client needs an e-commerce feature, the conversation starts the same way.</p>
<p><em>"We need Stripe checkout. How long?"</em> And every time I say two months, they look at me like I'm padding the estimate.</p>
<p>I'm not.</p>
<p>Here's what's actually hiding inside "just add Stripe checkout."</p>
<hr />
<h2>1. The cart is not a frontend problem</h2>
<p>The instinct is to keep cart state in React. <code>useState</code>, maybe Zustand, done in an afternoon.</p>
<p>Then your user adds items on desktop, opens their phone, and the cart is empty.</p>
<p>A production cart lives on the server. Every add/remove/update hits an API. The frontend is a view, not the source of truth. That's a full Mongoose model, a set of CRUD endpoints, and session-aware middleware — before you've touched Stripe at all.</p>
<hr />
<h2>2. Stripe SCA is not Stripe Checkout</h2>
<p>SCA (Strong Customer Authentication) is mandatory in Europe and increasingly common everywhere. It means the payment flow isn't a single redirect — it's a multi-step confirmation that your frontend has to handle gracefully.</p>
<pre><code class="language-typescript">const paymentIntent = await stripe.paymentIntents.create({
  amount: orderTotal,
  currency: 'usd',
  automatic_payment_methods: { enabled: true },
});
</code></pre>
<p>Then on the client, you're handling <code>requires_action</code>, <code>requires_payment_method</code>, and <code>succeeded</code> states separately. Each one needs a different UI path.</p>
<hr />
<h2>3. Webhooks are where things break in production</h2>
<p>The payment succeeded on the client. Great. But your server doesn't know yet.</p>
<p>Webhooks are how Stripe tells your backend what actually happened — and they arrive asynchronously, out of order, and sometimes more than once.</p>
<pre><code class="language-typescript">const event = stripe.webhooks.constructEvent(
  req.body,
  req.headers['stripe-signature'],
  process.env.STRIPE_WEBHOOK_SECRET
);

switch (event.type) {
  case 'payment_intent.succeeded':
    await fulfillOrder(event.data.object);
    break;
  case 'charge.refunded':
    await handleRefund(event.data.object);
    break;
}
</code></pre>
<p>You need idempotency handling, signature verification, and a clear fulfillment model. Miss any of these and you'll have orders that paid but never confirmed — or confirmed twice.</p>
<hr />
<h2>4. Order management is a separate product</h2>
<p>Once the payment goes through, you need:</p>
<ul>
<li><p>A customer view (what did I order, what's the status)</p>
</li>
<li><p>An admin view (filter all orders, update status, process refunds)</p>
</li>
<li><p>Status transitions that make sense (pending → paid → shipped → delivered)</p>
</li>
</ul>
<p>That's two full UIs and a state machine. Not complex — but a lot of screens.</p>
<hr />
<h2>5. The admin dashboard needs RBAC from day one</h2>
<p>Without role-based access control, your admin routes are either wide open or you're scattering <code>if (user.isAdmin)</code> checks across the codebase. Neither scales.</p>
<p>A proper RBAC middleware layer means routes declare what they need and middleware enforces it:</p>
<pre><code class="language-typescript">router.get('/admin/orders',
  authenticate,
  requireRole('admin', 'sales'),
  OrderController.getAll
);
</code></pre>
<p>Three roles minimum: <code>public</code>, <code>customer</code>, <code>admin</code>. Adding more later is trivial when the pattern is in place from the start.</p>
<hr />
<h2>6. S3 image upload is a full integration on its own</h2>
<p>Product images need to live somewhere. That means presigned URLs, upload from the admin form, and references stored in MongoDB. Works with AWS S3 or DigitalOcean Spaces — but either way it's a separate surface to wire correctly.</p>
<hr />
<h2>What this actually adds up to</h2>
<p>Every time I've built this stack from scratch, it's taken 6–8 weeks for a senior developer. Not because any single piece is hard — because there are multiple pieces and they all have to work together correctly in production.</p>
<p>After doing it for the third time, I packaged everything into a kit.</p>
<p><strong>Checkout Kit</strong> ships with:</p>
<ul>
<li><p>React 19 frontend — MUI and Tailwind flavors, both included</p>
</li>
<li><p>Go or Node backend — same API contracts, your choice of runtime</p>
</li>
<li><p>MongoDB, Mongoose, JWT + bcrypt</p>
</li>
<li><p>All 6 surfaces above, already wired together</p>
</li>
<li><p>Mock mode for full frontend dev without a running server</p>
</li>
<li><p>17+ production screens, 35+ API endpoints</p>
</li>
</ul>
<p>Setup to first live checkout: under 15 minutes.</p>
<p>Live demo (no signup): <a href="https://checkout-kit.dashforge-ui.com">https://checkout-kit.dashforge-ui.com</a> Full details: <a href="https://dashforge-ui.com/starter-kits/checkout-kit">https://dashforge-ui.com/starter-kits/checkout-kit</a></p>
<hr />
<h2>Free for the first 5 developers who want to try it</h2>
<p>I'm giving the Developer tier away free to the first 5 developers who want to try it.</p>
<p>Try it. Then leave an honest comment with your impressions — what worked, what didn't. No star ratings, no forms. Just real feedback from a real developer.</p>
<p><strong>Comment "interested" below and I'll DM you the link.</strong></p>
]]></content:encoded></item></channel></rss>