<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Samuel_Thomas</title>
    <description>The latest articles on DEV Community by Samuel_Thomas (@sammex45).</description>
    <link>https://dev.to/sammex45</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1100598%2Fc716b9c1-235c-4817-80bf-b992809791db.jpeg</url>
      <title>DEV Community: Samuel_Thomas</title>
      <link>https://dev.to/sammex45</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sammex45"/>
    <language>en</language>
    <item>
      <title># Creating a Custom Payment Gateway Integration with Next.js and Permit.io</title>
      <dc:creator>Samuel_Thomas</dc:creator>
      <pubDate>Thu, 05 Feb 2026 13:06:20 +0000</pubDate>
      <link>https://dev.to/sammex45/-creating-a-custom-payment-gateway-integration-with-nextjs-and-permitio-3j4b</link>
      <guid>https://dev.to/sammex45/-creating-a-custom-payment-gateway-integration-with-nextjs-and-permitio-3j4b</guid>
      <description>&lt;h2&gt;
  
  
  Why This Project?
&lt;/h2&gt;

&lt;p&gt;You've probably seen dozens of payment tutorials that stop at "add Stripe, hit submit." Cool, but what if you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Lock down the payment route so only authorized users can access it?&lt;/li&gt;
&lt;li&gt;Dynamically show different UI based on user roles (e.g., customer, admin)?&lt;/li&gt;
&lt;li&gt; Actually make it secure with real authentication and authorization?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's what this guide is about.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're going to:&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://nextjs.org/docs/app" rel="noopener noreferrer"&gt;Next.js App Router&lt;/a&gt; for UI + routing&lt;/li&gt;
&lt;li&gt;Plug in &lt;a href="https://firebase.google.com/" rel="noopener noreferrer"&gt;Firebase&lt;/a&gt; for auth (sign up, log in)&lt;/li&gt;
&lt;li&gt;Layer in Permit.io to enforce access based on user roles&lt;/li&gt;
&lt;li&gt;Deploy it on &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, ready for the real world.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;You can play around with the live application &lt;a href="https://custom-payment-gateway-tau.vercel.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and access the code in this &lt;a href="https://github.com/Bannieugbede/custom-payment-gateway/" rel="noopener noreferrer"&gt;github repo&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yfuvaypj3cnll45696y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yfuvaypj3cnll45696y.png" alt="image" width="800" height="438"&gt;&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Step 1: Create the &lt;a href="https://www.permit.io/blog/how-to-protect-a-url-inside-a-nestjs-app-using-rbac-authorization" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; App&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest custom-payment-gateway
&lt;span class="nb"&gt;cd &lt;/span&gt;custom-payment-gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Install Dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;firebase firebase-admin axios permitio tailwindcss postcss autoprefixer
npx tailwindcss init &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Setup Tailwind CSS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailwind.config.js
content: &lt;span class="o"&gt;[&lt;/span&gt;
 &lt;span class="s2"&gt;"./app/**/*.{js,ts,jsx,tsx}"&lt;/span&gt;,
 &lt;span class="s2"&gt;"./components/**/*.{js,ts,jsx,tsx}"&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;,
Create app/globals.css with base styles:
body &lt;span class="o"&gt;{&lt;/span&gt;
  font-family: sans-serif&lt;span class="p"&gt;;&lt;/span&gt;
  background-color: &lt;span class="c"&gt;#f9f9f9;&lt;/span&gt;
  color: &lt;span class="c"&gt;#111;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Firebase Auth Setup
&lt;/h2&gt;

&lt;p&gt;You'll use &lt;a href="https://www.permit.io/blog/authentication-and-authorization-with-firebase" rel="noopener noreferrer"&gt;Firebase authentication&lt;/a&gt; for signing users up and generating ID tokens.&lt;br&gt;
In your Firebase project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable Email/Password Auth&lt;/li&gt;
&lt;li&gt;Generate a service account key (used by server-side Firebase Admin SDK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then set these as environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;FIREBASE_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_project_id&lt;/span&gt;
&lt;span class="nx"&gt;FIREBASE_CLIENT_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_email&lt;/span&gt;
&lt;span class="nx"&gt;FIREBASE_PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your_key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use these to initialize Firebase Admin SDK in &lt;code&gt;lib/firebase-admin.ts:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { cert, getApps, initializeApp } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';

if (!getApps().length) {
  initializeApp({
    credential: cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n')
    })
  });
}

export const adminAuth = getAuth();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code initializes the &lt;a href="https://firebase.google.com/docs/admin/setup" rel="noopener noreferrer"&gt;Firebase Admin SDK&lt;/a&gt; using credentials from environment variables, ensuring it only runs once even if the file is imported multiple times. It then exports the &lt;code&gt;adminAuth&lt;/code&gt; instance, which provides access to Firebase Authentication functions for server-side use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Permit.io - Access Control, Sorted
&lt;/h2&gt;

&lt;p&gt;Think of &lt;a href="https://www.permit.io/" rel="noopener noreferrer"&gt;Permit.io&lt;/a&gt; as your app's bouncer. It decides who can enter VIP (your protected pages).&lt;/p&gt;

&lt;p&gt;Step 1: Create a Free Permit.io Project&lt;/p&gt;

&lt;p&gt;Go to permit.io → Sign up → Create project → Add environment → Add resource payment&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps2tbdgbpy25jo3505de.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps2tbdgbpy25jo3505de.png" alt="image" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Permit.io provides a clear separation between development and production environments, helping teams manage access control safely. In the Development environment, access enforcement is active, with 5 users, 3 defined roles, and 1 connected resource. The Production environment, however, is still in the setup phase, awaiting user connections and role configurations. This structure ensures that changes can be tested thoroughly before being pushed live, reducing the risk of security issues or disruptions.&lt;/p&gt;

&lt;p&gt;Step 2: Define Actions + Roles&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource: payment
  actions:
    - pay
    - cancel
    - process
roles:
  - customer
  - admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndgf6csdeu80ag5bdst8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndgf6csdeu80ag5bdst8.png" alt="image" width="800" height="395"&gt;&lt;/a&gt;&lt;br&gt;
Above here shows the key roles and the access they have. The Permit.io dashboard provides a detailed view of the roles and permissions assigned across environments. In the Development environment, three roles are actively managing access control among five users, with enforced access already enabled. Each role defines specific access policies and governs how users interact with the system resources. Meanwhile, the Production environment is configured but not yet enforcing access, ensuring that permissions can be carefully tested and reviewed before deployment to live users.&lt;/p&gt;

&lt;p&gt;Step 3: Connect SDK to Your App&lt;br&gt;
&lt;code&gt;Create lib/permit.ts:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Permit } from '@permit.io/sdk';

const permit = new Permit({
  token: process.env.PERMIT_API_KEY,
  pdp: process.env.PERMIT_IO_PDP_URL,
});

export default permit;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4: Assign Roles via API&lt;/p&gt;

&lt;p&gt;The below happens automatically when a user signs up&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. You've got policy-based access without writing middleware logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signup + Login Flows (Firebase + Permit.io)
&lt;/h2&gt;

&lt;p&gt;Let's wire up the user flows. Both use Firebase Auth on the frontend, with Permit.io logic handled via API routes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signup Page
&lt;/h3&gt;

&lt;p&gt;The inclusion of a password visibility toggle enhances usability, allowing users to verify their input without compromising security. Additionally, the role selector is a thoughtful touch that enables customized user experiences based on their function within the platform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/signup/page.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSignup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userCredential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUserWithEmailAndPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userCredential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIdToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;idToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code defines an asynchronous handleSignup function that manages user registration using Firebase Authentication. It creates a new user with an email and password, updates their profile with a display name, and retrieves an ID token for authentication. The function then sends the user's details, including role and token, to a backend API for further processing. Finally, it redirects the user to the payment page, completing the signup workflow.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94p370u07hduojgng4pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94p370u07hduojgng4pk.png" alt="image" width="800" height="395"&gt;&lt;/a&gt;&lt;br&gt;
The above shows a user signup page, this page shown above exemplifies a clean, intuitive, and user-centric approach to onboarding users onto a digital payment platform. With a minimalist layout and clear input fields, new users can easily create an account by entering their full name, email, password, and selecting their role (e.g., Customer, Admin, etc.) from a dropdown menu.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/api/signup&lt;/code&gt; Route&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;idToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyIdToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login Page&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/login/page.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleLogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userCredential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signInWithEmailAndPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userCredential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIdToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/set-token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;idToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/api/set-token&lt;/code&gt; Route&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idToken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Absolutely! Here's your &lt;strong&gt;full Dev.to-ready Markdown&lt;/strong&gt; version—all in one piece so you can copy and paste directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Protected Payment Page (Only If You're Allowed)&lt;/span&gt;

Now for the fun part—creating the payment form and locking it behind &lt;span class="gs"&gt;**role-based access control**&lt;/span&gt;.

&lt;span class="gu"&gt;## `/payment/page.tsx`&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;
typescript
const idToken = cookies().get("idToken")?.value;
const decoded = await adminAuth.verifyIdToken(idToken);
const email = decoded.email;

const hasAccess = await permit.check(email, "pay", "payment");
if (!hasAccess) {
  return &amp;lt;p&amp;gt;You do not have permission to view this page.&amp;lt;/p&amp;gt;;
}
return &amp;lt;PaymentForm /&amp;gt;;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;/components/PaymentForm.tsx&lt;/code&gt;
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
typescript
const handleSubmit = async (e: FormEvent) =&amp;gt; {
  e.preventDefault();
  const res = await axios.post("/api/payment", {
    name, email, amount
  });
  setResponse(res.data);
};


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;/payment/page.tsx&lt;/code&gt; code verifies the user's identity by decoding their &lt;strong&gt;Firebase ID token&lt;/strong&gt; using &lt;code&gt;adminAuth&lt;/code&gt;. It extracts the user's email and checks their permission to access the payment page through a &lt;strong&gt;role-based access control system&lt;/strong&gt; (&lt;code&gt;permit.check&lt;/code&gt;). If unauthorized, it displays a permission error; otherwise, it renders the &lt;code&gt;PaymentForm&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;PaymentForm.tsx&lt;/code&gt;, the &lt;code&gt;handleSubmit&lt;/code&gt; function handles form submissions by sending a POST request to &lt;code&gt;/api/payment&lt;/code&gt; with the user's &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;amount&lt;/code&gt;, then stores the server response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqoweqjcz0tzsj4ri4es.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqoweqjcz0tzsj4ri4es.png" alt="Payment Form Example" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The payment form provides a &lt;strong&gt;clean, user-friendly interface&lt;/strong&gt; for completing transactions. Users provide essential details such as cardholder name, role, and the desired payment amount. The system calculates fees and total amounts dynamically, ensuring a &lt;strong&gt;transparent, secure, and smooth payment experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxcjknrsw021pq5t71to.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxcjknrsw021pq5t71to.png" alt="Login Page" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The login page is &lt;strong&gt;simple and functional&lt;/strong&gt;. Users enter their registered email and password to access accounts, with a &lt;strong&gt;password visibility toggle&lt;/strong&gt; and a direct link to the signup page. This ensures &lt;strong&gt;seamless and secure authentication&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enforce HTTPS and TLS in Production
&lt;/h2&gt;

&lt;p&gt;In production, it's crucial to enforce &lt;a href="https://www.goanywhere.com/blog/what-is-ssl-tls-and-https" rel="noopener noreferrer"&gt;HTTPS and TLS&lt;/a&gt; to protect data in transit. All communication between client and server is encrypted, preventing sensitive information exposure. Always redirect HTTP to HTTPS and maintain updated TLS certificates.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: "/:path*",
        headers: [
          {
            key: "Strict-Transport-Security",
            value: "max-age=31536000; includeSubDomains; preload",
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS enforced via &lt;code&gt;next.config.js&lt;/code&gt; and Vercel&lt;/li&gt;
&lt;li&gt;Local development HTTPS supported via &lt;a href="https://mkcert.org/" rel="noopener noreferrer"&gt;mkcert&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Validating User Input and Preventing Vulnerabilities
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Client-side validation in &lt;code&gt;PaymentForm&lt;/code&gt; for all fields&lt;/li&gt;
&lt;li&gt;Server-side validation and sanitization in &lt;code&gt;/api/payment&lt;/code&gt; to prevent &lt;strong&gt;XSS and injections&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Secure headers to mitigate &lt;a href="https://www.imperva.com/learn/application-security/clickjacking/#" rel="noopener noreferrer"&gt;clickjacking&lt;/a&gt;, &lt;strong&gt;XSS&lt;/strong&gt;, and MIME-type sniffing&lt;/li&gt;
&lt;li&gt;Custom rate limiting to prevent abuse&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;/api/payment/route.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
const { amount, email, name } = await req.json();
console.log(`Processing $${amount} for ${name}`);

return new Response(JSON.stringify({
  success: true,
  message: "Payment successful",
  name, email, amount
}), { status: 200 });


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This snippet reads JSON data from the request, logs the transaction, and responds with a &lt;strong&gt;success message&lt;/strong&gt; including the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;amount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj5nsp441rz9ip3heddj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj5nsp441rz9ip3heddj.png" alt="Payment Success" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After completing payment, users see a &lt;strong&gt;confirmation screen&lt;/strong&gt; showing cardholder info, payment amount, and transaction reference. Users can &lt;strong&gt;print the receipt&lt;/strong&gt; or return to login.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auth + Access Flow Diagram
&lt;/h2&gt;

&lt;p&gt;When a user signs up or logs in:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

User → Signup/Login → Get Firebase Token
     → Set Cookie → Visit /payment → Server Verifies Token
     → Permit.io: Can this user "pay" on "payment"?
         ├── Yes → Show Payment Form
         └── No  → Block with Unauthorized


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Deploying to Production (Vercel FTW)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Push Code to GitHub
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
git init
git remote add origin https://github.com/your/repo.git
git push -u origin main


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 2: Connect GitHub to Vercel
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;a href="https://vercel.com" rel="noopener noreferrer"&gt;vercel.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Connect your GitHub repo&lt;/li&gt;
&lt;li&gt;Add environment variables in the Vercel dashboard:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
# Permit.io (server-side)
PERMIT_API_KEY=&amp;lt;your-permit-api-key&amp;gt;
PERMIT_IO_PDP_URL=&amp;lt;permit-io-PDP-URL&amp;gt;

# Firebase Admin SDK (server-side)
FIREBASE_PROJECT_ID=&amp;lt;your-firebase-project-id&amp;gt;
FIREBASE_CLIENT_EMAIL=&amp;lt;your-firebase-client-email&amp;gt;
FIREBASE_PRIVATE_KEY="your-firebase-private-key"

# Custom Payment Gateway (server-side)
PAYMENT_GATEWAY_SECRET=&amp;lt;your-custom-payment-secret&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace placeholders with your actual credentials. &lt;strong&gt;Server-side only&lt;/strong&gt; for security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Deploy
&lt;/h3&gt;

&lt;p&gt;Hit &lt;strong&gt;Deploy&lt;/strong&gt; and watch the magic happen. Test and explore the live application &lt;a href="https://custom-payment-gateway-tau.vercel.app" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitor &amp;amp; Debug Like a Pro
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;console.log()&lt;/code&gt; in frontend &amp;amp; backend to trace payment flow&lt;/li&gt;
&lt;li&gt;Monitor Firebase logs in the Firebase console&lt;/li&gt;
&lt;li&gt;Check Permit.io audit trail for denied actions&lt;/li&gt;
&lt;li&gt;Set breakpoints in Vercel deployment logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Final Thoughts: You Did It.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvdt565ikpdyhj1plcee.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvdt565ikpdyhj1plcee.gif" alt="Payment Gateway Preview" width="500" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We just built a &lt;strong&gt;secure, role-aware, seamless payment gateway&lt;/strong&gt; using Next.js, Firebase, and Permit.io.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we accomplished:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full-stack app with real authentication&lt;/li&gt;
&lt;li&gt;Real-time &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;authorization&lt;/a&gt; using roles&lt;/li&gt;
&lt;li&gt;Secure payment gateway (mocked)&lt;/li&gt;
&lt;li&gt;Production-ready deployment viewable &lt;a href="https://custom-payment-gateway-tau.vercel.app" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>programming</category>
      <category>nextjs</category>
      <category>firebase</category>
    </item>
  </channel>
</rss>
