Skip to content

Commit

Permalink
feat(payment): stripe checkout (#25)
Browse files Browse the repository at this point in the history
* feat(payment): stripe checkout

* build(changelog): update to 1.2.0
  • Loading branch information
sammaji authored Jul 27, 2023
1 parent 041d798 commit 0b51c09
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 10 deletions.
4 changes: 4 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This project adheres to [Semantic Versioning](https://semver.org/).

## 1.2.0

- Added stripe checkouts

## 1.1.0

- Automated releases
Expand Down
52 changes: 52 additions & 0 deletions app/api/checkout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { NextResponse } from "next/server";
import { Stripe } from "stripe";

export async function GET() {
return NextResponse.json({ message: "success" }, { status: 200 });
}
export async function POST(request: Request) {
const stripe = new Stripe(process.env.STRIPE_API_SECRET as string, {
apiVersion: "2022-11-15",
});

const data = await request.json();
const price_id = data.price_id;

if (!price_id) {
return NextResponse.json(
"create-checkout: please provide a valid price id",
{ status: 422 },
);
}

let event;
try {
event = await stripe.checkout.sessions.create({
success_url: process.env.STRIPE_SUCCESS_URL as string,
cancel_url: process.env.STRIPE_CANCEL_URL as string,
mode: "subscription",
line_items: [
{
price: price_id,
quantity: 1,
},
],
});
return NextResponse.json(
{ message: "success", checkout_url: event.url },
{ status: 200 },
);
} catch (error: any) {
return NextResponse.json(
{
message: `create-checkout: ${
error?.message || "unexpected error"
}`,
},
{
status: error?.status || 512,
statusText: error || "unexpected error",
},
);
}
}
42 changes: 42 additions & 0 deletions components/pricing-card/checkout-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import React, { useState } from "react";
import { Button, ButtonProps } from "../ui/button";
import LoadingDots from "../ui/loading-dots";

export default function CheckoutButton({ children, ...rest }: ButtonProps) {
const [isLoading, setLoadingState] = useState<boolean>(false);

const handleCheckout = async () => {
setLoadingState(true);
const response = await fetch("/api/checkout", {
method: "POST",
body: JSON.stringify({
price_id:
process.env.NEXT_PUBLIC_STRIPE_PRICE_ID_SUBSCRIPTION_HOBBY,
}),
});

if (!response.ok) {
throw new Error(
`client(${response.status}): ${
response.statusText || "unexpected error"
}`,
);
}

const data = await response.json();
const checkout_url = data?.checkout_url;
if (!checkout_url)
throw new Error("client: no checkout_url found, please try again");

setLoadingState(false);
window.location = checkout_url;
};

return (
<Button {...rest} disabled={isLoading} onClick={handleCheckout}>
{isLoading ? <LoadingDots /> : children}
</Button>
);
}
7 changes: 3 additions & 4 deletions components/pricing-card/pricing-card.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Check } from "lucide-react";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
Expand All @@ -11,6 +9,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { TypographyP } from "../ui/typography";
import CheckoutButton from "./checkout-button";

export enum PricingTiers {
FREE,
Expand Down Expand Up @@ -71,12 +70,12 @@ export default function PricingCard({
*No credit cards required
</TypographyP>
)}
<Button
<CheckoutButton
variant={tier === PricingTiers.FREE ? "outline" : "default"}
className="w-full"
>
{callToAction}
</Button>
</CheckoutButton>
</CardFooter>
</Card>
);
Expand Down
3 changes: 2 additions & 1 deletion middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { authMiddleware } from "@clerk/nextjs";
// See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware
export default authMiddleware({
publicRoutes: ["/", "/(github|twitter|linkedin)"],
ignoredRoutes: ["/(api|trpc)(.*)"],
clockSkewInMs: 100_000,
clockSkewInSeconds: 100,
});

export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/(dashboard)(.*)", "/(api|trpc)(.*)"],
matcher: ["/((?!.*\\..*|_next).*)", "/(dashboard)(.*)"],
};
48 changes: 46 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next-starter",
"version": "1.1.0",
"version": "1.2.0",
"private": true,
"scripts": {
"dev": "cross-env NODE_ENV=development next dev -p 3000",
Expand Down Expand Up @@ -38,14 +38,15 @@
"postcss": "8.4.27",
"react": "18.2.0",
"react-dom": "18.2.0",
"stripe": "^12.14.0",
"tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.6",
"typescript": "5.1.6"
},
"devDependencies": {
"cross-env": "^7.0.3",
"prisma": "^5.0.0",
"husky": "^8.0.0"
"husky": "^8.0.0",
"prisma": "^5.0.0"
}
}

0 comments on commit 0b51c09

Please sign in to comment.