mirror of
https://github.com/fergalmoran/supanextail.git
synced 2025-12-22 09:17:54 +00:00
First stripe integration.
User can subscribe and it will be effective in the database. We now need to reflect this on the front end. Issue : Need to check how to upgrade a current subscription
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,6 +29,7 @@ yarn-error.log*
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
@@ -7,8 +7,8 @@ function with your new elements.
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import Avatar from "./Avatar";
|
||||
import MyModal from "./MyModal";
|
||||
import { supabase } from "../utils/supabaseClient";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export default function Account({ session }) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -71,6 +71,7 @@ export default function Account({ session }) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
toast.success("Your profile has been updated")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const Layout = (props) => {
|
||||
|
||||
const toastStyle = {
|
||||
//Style your toast elements here
|
||||
success: "bg-secondary",
|
||||
success: "bg-accent",
|
||||
error: "bg-red-600",
|
||||
info: "bg-gray-600",
|
||||
warning: "bg-orange-400",
|
||||
|
||||
@@ -3,12 +3,50 @@ This is the pricing component.
|
||||
You can switch between flat payment or subscription by setting the flat variable.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Auth } from "@supabase/ui";
|
||||
import { Switch } from "@headlessui/react";
|
||||
import { useState } from "react";
|
||||
import axios from "axios";
|
||||
import getStripe from "utils/stripe";
|
||||
import { supabase } from "utils/supabaseClient";
|
||||
|
||||
const Pricing = () => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const flat = false;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { user, session } = Auth.useUser();
|
||||
const [customerId, setCustomerId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
supabase
|
||||
.from("profiles")
|
||||
.select(`customerId`)
|
||||
.eq("id", user.id)
|
||||
.single()
|
||||
.then(
|
||||
(result) =>
|
||||
// Even if null, it returns something so we check if the customerId is > 1 to be sure it exist
|
||||
result.data.customerId.length > 1 &&
|
||||
setCustomerId(result.data.customerId)
|
||||
);
|
||||
}
|
||||
}, [user]);
|
||||
const prices = {
|
||||
personal: {
|
||||
monthly: "price_1J5q2yDMjD0UnVmMXzEWYDnl",
|
||||
anually: "price_1J5q45DMjD0UnVmMQxXHKGAv",
|
||||
},
|
||||
team: {
|
||||
monthly: "price_1J5q3GDMjD0UnVmMlHc5Eedq",
|
||||
anually: "price_1J5q8zDMjD0UnVmMqsngM91X",
|
||||
},
|
||||
pro: {
|
||||
monthly: "price_1J5q3TDMjD0UnVmMJKX3nkDq",
|
||||
anually: "price_1J5q9VDMjD0UnVmMIQtVDSZ9",
|
||||
},
|
||||
};
|
||||
const flat = false; // Switch between subscription system or flat prices
|
||||
const pricing = {
|
||||
monthly: {
|
||||
personal: "$5/mo",
|
||||
@@ -26,6 +64,40 @@ const Pricing = () => {
|
||||
pro: "€149",
|
||||
},
|
||||
};
|
||||
|
||||
const handleSubmit = async (e, priceId) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
// Create a Checkout Session. This will redirect the user to the Stripe website for the payment.
|
||||
|
||||
axios
|
||||
.post("/api/stripe/create-checkout-session", {
|
||||
priceId: priceId,
|
||||
email: user.email,
|
||||
customerId: customerId,
|
||||
userId: user.id,
|
||||
tokenId: session.access_token,
|
||||
})
|
||||
.then((result) => {
|
||||
stripe
|
||||
.redirectToCheckout({
|
||||
sessionId: result.data.sessionId,
|
||||
})
|
||||
.then((result) => {
|
||||
console.log(result);
|
||||
});
|
||||
});
|
||||
|
||||
// Redirect to Checkout.
|
||||
const stripe = await getStripe();
|
||||
|
||||
// If `redirectToCheckout` fails due to a browser or network
|
||||
// error, display the localized error message to your customer
|
||||
// using `error.message`.
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
return (
|
||||
<div className='w-full mx-auto px-5 py-10 mb-10'>
|
||||
<div className='text-center max-w-xl mx-auto'>
|
||||
@@ -88,7 +160,16 @@ const Pricing = () => {
|
||||
</ul>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<button className='btn btn-primary w-full'>Buy Now</button>
|
||||
<button
|
||||
className='btn btn-primary w-full'
|
||||
onClick={(e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled ? prices.personal.anually : prices.personal.monthly
|
||||
)
|
||||
}>
|
||||
Buy Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full md:w-1/3 md:max-w-none bg-base-200 px-8 md:px-10 py-8 md:py-10 mb-3 mx-auto md:my-6 rounded-md shadow-lg shadow-gray-600 md:flex md:flex-col'>
|
||||
@@ -120,7 +201,16 @@ const Pricing = () => {
|
||||
</ul>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<button className='btn btn-primary w-full'>Buy Now</button>
|
||||
<button
|
||||
className='btn btn-primary w-full'
|
||||
onClick={(e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled ? prices.team.anually : prices.team.monthly
|
||||
)
|
||||
}>
|
||||
Buy Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full md:w-1/3 md:max-w-none bg-base-200 px-8 md:px-10 py-8 md:py-10 mb-3 mx-auto md:my-6 rounded-md shadow-lg shadow-gray-600 md:flex md:flex-col'>
|
||||
@@ -152,7 +242,16 @@ const Pricing = () => {
|
||||
</ul>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<button className='btn btn-primary w-full'>Buy Now</button>
|
||||
<button
|
||||
className='btn btn-primary w-full'
|
||||
onClick={(e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled ? prices.pro.anually : prices.pro.monthly
|
||||
)
|
||||
}>
|
||||
Buy Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
SUPABASE_ADMIN_KEY=YOUR_SUPABASE_ADMIN_KEY
|
||||
NEXT_PUBLIC_TITLE="SupaNexTail"
|
||||
SENDGRID_SECRET=YOUR_SENGRID_SECRET
|
||||
SENDGRID_MAILTO=YOUR_RECIPIENT
|
||||
SENDGRID_MAILFROM=YOUR_VERIFIED_SENDER
|
||||
STRIPE_SECRET=YOUR_SECRET_STRIPE_KEY
|
||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=YOUR_PUBLIC_STRIPE_KEY
|
||||
STRIPE_WEBHOOK=YOUR_STRIPE_WEBHOOK
|
||||
309
package-lock.json
generated
309
package-lock.json
generated
@@ -1,26 +1,30 @@
|
||||
{
|
||||
"name": "with-tailwindcss",
|
||||
"version": "0.1.0",
|
||||
"name": "supanextail",
|
||||
"version": "0.4.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "with-tailwindcss",
|
||||
"version": "0.1.0",
|
||||
"version": "0.4.0",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.2.0",
|
||||
"@sendgrid/mail": "^7.4.4",
|
||||
"@stripe/stripe-js": "^1.15.1",
|
||||
"@supabase/supabase-js": "^1.15.0",
|
||||
"@supabase/ui": "^0.27.3",
|
||||
"axios": "^0.21.1",
|
||||
"cors": "^2.8.5",
|
||||
"daisyui": "^1.3.4",
|
||||
"express-rate-limit": "^5.2.6",
|
||||
"micro": "^9.3.4",
|
||||
"next": "^11.0.0",
|
||||
"next-seo": "^4.24.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-toastify": "^7.0.4"
|
||||
"react-toastify": "^7.0.4",
|
||||
"stripe": "^8.156.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.0.4",
|
||||
@@ -300,6 +304,11 @@
|
||||
"node": "6.* || 8.* || >=10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@stripe/stripe-js": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.15.1.tgz",
|
||||
"integrity": "sha512-yJiDGutlwu25iajCy51VRJeoH3UMs+s5qVIDGfmPUuFpZ+F6AJ9g9EFrsBNvHxAGBahQFMLlBdzlCVydhGp6tg=="
|
||||
},
|
||||
"node_modules/@supabase/gotrue-js": {
|
||||
"version": "1.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-1.16.5.tgz",
|
||||
@@ -895,6 +904,14 @@
|
||||
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
|
||||
"integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U="
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
|
||||
@@ -913,6 +930,18 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
|
||||
@@ -1346,6 +1375,11 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.2.6.tgz",
|
||||
"integrity": "sha512-nE96xaxGfxiS5jP3tD3kIW1Jg9yQgX0rXCs3rCkZtmbWHEGyotwaezkLj7bnB41Z0uaOLM8W4AX6qHao4IZ2YA=="
|
||||
},
|
||||
"node_modules/ext": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
|
||||
@@ -1953,6 +1987,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-string": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz",
|
||||
@@ -2185,6 +2227,90 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/micro": {
|
||||
"version": "9.3.4",
|
||||
"resolved": "https://registry.npmjs.org/micro/-/micro-9.3.4.tgz",
|
||||
"integrity": "sha512-smz9naZwTG7qaFnEZ2vn248YZq9XR+XoOH3auieZbkhDL4xLOxiE+KqG8qqnBeKfXA9c1uEFGCxPN1D+nT6N7w==",
|
||||
"dependencies": {
|
||||
"arg": "4.1.0",
|
||||
"content-type": "1.0.4",
|
||||
"is-stream": "1.1.0",
|
||||
"raw-body": "2.3.2"
|
||||
},
|
||||
"bin": {
|
||||
"micro": "bin/micro.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micro/node_modules/arg": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz",
|
||||
"integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="
|
||||
},
|
||||
"node_modules/micro/node_modules/bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/micro/node_modules/depd": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
|
||||
"integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/micro/node_modules/http-errors": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
|
||||
"integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
|
||||
"dependencies": {
|
||||
"depd": "1.1.1",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.0.3",
|
||||
"statuses": ">= 1.3.1 < 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/micro/node_modules/iconv-lite": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
|
||||
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micro/node_modules/inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"node_modules/micro/node_modules/raw-body": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
|
||||
"integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
|
||||
"dependencies": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.2",
|
||||
"iconv-lite": "0.4.19",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/micro/node_modules/setprototypeof": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
|
||||
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
@@ -3034,6 +3160,20 @@
|
||||
"purgecss": "bin/purgecss.js"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
|
||||
"integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/querystring": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
|
||||
@@ -3393,6 +3533,19 @@
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
|
||||
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
@@ -3522,6 +3675,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/stripe": {
|
||||
"version": "8.156.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-8.156.0.tgz",
|
||||
"integrity": "sha512-q+bixlhaxnSI/Htk/iB1i5LhuZ557hL0pFgECBxQNhso1elxIsOsPOIXEuo3tSLJEb8CJSB7t/+Fyq6KP69tAQ==",
|
||||
"dependencies": {
|
||||
"@types/node": ">=8.1.0",
|
||||
"qs": "^6.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.1 || >=10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-3.3.2.tgz",
|
||||
@@ -3905,6 +4070,14 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
@@ -4241,6 +4414,11 @@
|
||||
"@sendgrid/helpers": "^7.4.3"
|
||||
}
|
||||
},
|
||||
"@stripe/stripe-js": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.15.1.tgz",
|
||||
"integrity": "sha512-yJiDGutlwu25iajCy51VRJeoH3UMs+s5qVIDGfmPUuFpZ+F6AJ9g9EFrsBNvHxAGBahQFMLlBdzlCVydhGp6tg=="
|
||||
},
|
||||
"@supabase/gotrue-js": {
|
||||
"version": "1.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-1.16.5.tgz",
|
||||
@@ -4730,6 +4908,11 @@
|
||||
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
|
||||
"integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"convert-source-map": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
|
||||
@@ -4750,6 +4933,15 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"requires": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
|
||||
@@ -5117,6 +5309,11 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"express-rate-limit": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.2.6.tgz",
|
||||
"integrity": "sha512-nE96xaxGfxiS5jP3tD3kIW1Jg9yQgX0rXCs3rCkZtmbWHEGyotwaezkLj7bnB41Z0uaOLM8W4AX6qHao4IZ2YA=="
|
||||
},
|
||||
"ext": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
|
||||
@@ -5533,6 +5730,11 @@
|
||||
"has-symbols": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"is-string": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz",
|
||||
@@ -5711,6 +5913,71 @@
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true
|
||||
},
|
||||
"micro": {
|
||||
"version": "9.3.4",
|
||||
"resolved": "https://registry.npmjs.org/micro/-/micro-9.3.4.tgz",
|
||||
"integrity": "sha512-smz9naZwTG7qaFnEZ2vn248YZq9XR+XoOH3auieZbkhDL4xLOxiE+KqG8qqnBeKfXA9c1uEFGCxPN1D+nT6N7w==",
|
||||
"requires": {
|
||||
"arg": "4.1.0",
|
||||
"content-type": "1.0.4",
|
||||
"is-stream": "1.1.0",
|
||||
"raw-body": "2.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"arg": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz",
|
||||
"integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
|
||||
"integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
|
||||
"integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
|
||||
"requires": {
|
||||
"depd": "1.1.1",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.0.3",
|
||||
"statuses": ">= 1.3.1 < 2"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
|
||||
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
|
||||
"integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.2",
|
||||
"iconv-lite": "0.4.19",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
|
||||
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
@@ -6371,6 +6638,14 @@
|
||||
"postcss-selector-parser": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
|
||||
"integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
|
||||
@@ -6628,6 +6903,16 @@
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
|
||||
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
@@ -6738,6 +7023,15 @@
|
||||
"ansi-regex": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"stripe": {
|
||||
"version": "8.156.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-8.156.0.tgz",
|
||||
"integrity": "sha512-q+bixlhaxnSI/Htk/iB1i5LhuZ557hL0pFgECBxQNhso1elxIsOsPOIXEuo3tSLJEb8CJSB7t/+Fyq6KP69tAQ==",
|
||||
"requires": {
|
||||
"@types/node": ">=8.1.0",
|
||||
"qs": "^6.6.0"
|
||||
}
|
||||
},
|
||||
"styled-jsx": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-3.3.2.tgz",
|
||||
@@ -7038,6 +7332,11 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
|
||||
@@ -10,17 +10,22 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.2.0",
|
||||
"@sendgrid/mail": "^7.4.4",
|
||||
"@stripe/stripe-js": "^1.15.1",
|
||||
"@supabase/supabase-js": "^1.15.0",
|
||||
"@supabase/ui": "^0.27.3",
|
||||
"axios": "^0.21.1",
|
||||
"cors": "^2.8.5",
|
||||
"daisyui": "^1.3.4",
|
||||
"express-rate-limit": "^5.2.6",
|
||||
"micro": "^9.3.4",
|
||||
"next": "^11.0.0",
|
||||
"next-seo": "^4.24.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-toastify": "^7.0.4"
|
||||
"react-toastify": "^7.0.4",
|
||||
"stripe": "^8.156.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.0.4",
|
||||
|
||||
83
pages/api/stripe/create-checkout-session.js
Normal file
83
pages/api/stripe/create-checkout-session.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import Cors from "cors";
|
||||
import initMiddleware from "utils/init-middleware";
|
||||
|
||||
const rateLimit = require("express-rate-limit");
|
||||
|
||||
const cors = initMiddleware(
|
||||
Cors({
|
||||
methods: ["POST"],
|
||||
})
|
||||
);
|
||||
|
||||
const limiter = initMiddleware(
|
||||
rateLimit({
|
||||
windowMs: 30000, // 30sec
|
||||
max: 4, // Max 4 request per 30 sec
|
||||
})
|
||||
);
|
||||
// Set your secret key. Remember to switch to your live secret key in production.
|
||||
// See your keys here: https://dashboard.stripe.com/apikeys
|
||||
const stripe = require("stripe")(process.env.STRIPE_SECRET);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
await cors(req, res);
|
||||
await limiter(req, res);
|
||||
if (req.method === "POST") {
|
||||
const priceId = req.body.priceId;
|
||||
|
||||
// See https://stripe.com/docs/api/checkout/sessions/create
|
||||
// for additional parameters to pass.
|
||||
try {
|
||||
const session = req.body.customerId
|
||||
? await stripe.checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
payment_method_types: ["card"],
|
||||
client_reference_id: req.body.userId,
|
||||
metadata: { token: req.body.tokenId, priceId: req.body.priceId },
|
||||
customer: req.body.customerId,
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
// For metered billing, do not pass quantity
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
// {CHECKOUT_SESSION_ID} is a string literal; do not change it!
|
||||
// the actual Session ID is returned in the query parameter when your customer
|
||||
// is redirected to the success page.
|
||||
success_url: `${req.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${req.headers.origin}/dashboard?session_id=canceled`,
|
||||
})
|
||||
: await stripe.checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
payment_method_types: ["card"],
|
||||
customer_email: req.body.email,
|
||||
client_reference_id: req.body.userId,
|
||||
metadata: { token: req.body.tokenId, priceId: req.body.priceId },
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
// For metered billing, do not pass quantity
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
// {CHECKOUT_SESSION_ID} is a string literal; do not change it!
|
||||
// the actual Session ID is returned in the query parameter when your customer
|
||||
// is redirected to the success page.
|
||||
success_url: `${req.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${req.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
|
||||
});
|
||||
|
||||
res.send({
|
||||
sessionId: session.id,
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(400);
|
||||
return res.send({
|
||||
error: {
|
||||
message: e.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
33
pages/api/stripe/customer-portal.js
Normal file
33
pages/api/stripe/customer-portal.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import Cors from "cors";
|
||||
import initMiddleware from "utils/init-middleware";
|
||||
const rateLimit = require("express-rate-limit");
|
||||
|
||||
const cors = initMiddleware(
|
||||
Cors({
|
||||
methods: ["POST", "PUT"],
|
||||
})
|
||||
);
|
||||
|
||||
const limiter = initMiddleware(
|
||||
rateLimit({
|
||||
windowMs: 30000, // 30sec
|
||||
max: 150, // Max 4 request per 30 sec
|
||||
})
|
||||
);
|
||||
// Set your secret key. Remember to switch to your live secret key in production.
|
||||
// See your keys here: https://dashboard.stripe.com/apikeys
|
||||
const stripe = require("stripe")(process.env.STRIPE_SECRET);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
await cors(req, res);
|
||||
await limiter(req, res);
|
||||
if (req.method === "POST") {
|
||||
const returnUrl = `${req.headers.origin}/dashboard`;
|
||||
|
||||
const portalsession = await stripe.billingPortal.sessions.create({
|
||||
customer: req.query.customerId,
|
||||
return_url: returnUrl,
|
||||
});
|
||||
res.status(200).send({ url: portalsession.url });
|
||||
}
|
||||
}
|
||||
135
pages/api/stripe/stripe-webhook.js
Normal file
135
pages/api/stripe/stripe-webhook.js
Normal file
@@ -0,0 +1,135 @@
|
||||
import Cors from "cors";
|
||||
import { buffer } from "micro";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import initMiddleware from "utils/init-middleware";
|
||||
const rateLimit = require("express-rate-limit");
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize the cors middleware -> Allow the browser extension to create lists
|
||||
const cors = initMiddleware(
|
||||
Cors({
|
||||
methods: ["POST", "HEAD"],
|
||||
})
|
||||
);
|
||||
|
||||
// Init Supabase Admin
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.SUPABASE_ADMIN_KEY
|
||||
);
|
||||
|
||||
// Rate limiter : The user can only create one list every 20 seconds (avoid spam)
|
||||
|
||||
const limiter = initMiddleware(
|
||||
rateLimit({
|
||||
windowMs: 30000, // 30sec
|
||||
max: 150, // Max 150 request per 30 sec
|
||||
})
|
||||
);
|
||||
// Set your secret key. Remember to switch to your live secret key in production.
|
||||
// See your keys here: https://dashboard.stripe.com/apikeys
|
||||
const stripe = require("stripe")(process.env.STRIPE_SECRET);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
await cors(req, res);
|
||||
await limiter(req, res);
|
||||
stripe.setMaxNetworkRetries(2);
|
||||
|
||||
if (req.method === "POST") {
|
||||
// Retrieve the event by verifying the signature using the raw body and secret.
|
||||
let event;
|
||||
const buf = await buffer(req);
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
buf,
|
||||
req.headers["stripe-signature"],
|
||||
process.env.STRIPE_WEBHOOK
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log(`⚠️ Webhook signature verification failed.`);
|
||||
console.log(
|
||||
`⚠️ Check the env file and enter the correct webhook secret.`
|
||||
);
|
||||
return res.send(400);
|
||||
}
|
||||
// Extract the object from the event.
|
||||
const dataObject = event.data.object;
|
||||
|
||||
// Handle the event
|
||||
// Review important events for Billing webhooks
|
||||
// https://stripe.com/docs/billing/webhooks
|
||||
// Remove comment to see the various objects sent for this sample
|
||||
switch (event.type) {
|
||||
case "checkout.session.completed":
|
||||
let { data: subscriptions, error } = await supabase
|
||||
.from("subscriptions")
|
||||
.select("*")
|
||||
.eq("id", dataObject.client_reference_id);
|
||||
|
||||
if (subscriptions.length == 0) {
|
||||
const { data, error } = await supabase
|
||||
.from("profiles")
|
||||
.update({ customerId: dataObject.customer })
|
||||
.eq("id", dataObject.client_reference_id);
|
||||
if (error) console.log(error);
|
||||
|
||||
await supabase
|
||||
.from("subscriptions")
|
||||
.insert([
|
||||
{
|
||||
id: dataObject.client_reference_id,
|
||||
customer_id: dataObject.customer,
|
||||
paid_user: true,
|
||||
plan: dataObject.metadata.priceId,
|
||||
},
|
||||
])
|
||||
.then()
|
||||
.catch((err) => console.log(err));
|
||||
} else if (subscriptions.length > 0) {
|
||||
await supabase
|
||||
.from("subscriptions")
|
||||
.update({
|
||||
customer_id: dataObject.customer,
|
||||
paid_user: true,
|
||||
plan: dataObject.metadata.priceId,
|
||||
})
|
||||
.eq("id", dataObject.client_reference_id)
|
||||
.then()
|
||||
.catch((err) => console.log(err));
|
||||
}
|
||||
break;
|
||||
case "customer.subscription.deleted":
|
||||
await supabase
|
||||
.from("subscriptions")
|
||||
.update({ paid_user: false })
|
||||
.eq("customer_id", dataObject.customer)
|
||||
.then()
|
||||
.catch((err) => console.log(err));
|
||||
break;
|
||||
case "invoice.payment_failed":
|
||||
// If the payment fails or the customer does not have a valid payment method,
|
||||
// an invoice.payment_failed event is sent, the subscription becomes past_due.
|
||||
// Use this webhook to notify your user that their payment has
|
||||
// failed and to retrieve new card details.
|
||||
break;
|
||||
|
||||
case "invoice.paid":
|
||||
// Used to provision services after the trial has ended.
|
||||
// The status of the invoice will show up as paid. Store the status in your
|
||||
// database to reference when a user accesses your service to avoid hitting rate limits.
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unexpected event type
|
||||
}
|
||||
res.send(200);
|
||||
}
|
||||
}
|
||||
13
utils/init-middleware.js
Normal file
13
utils/init-middleware.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// Helper method to wait for a middleware to execute before continuing
|
||||
// And to throw an error when an error happens in a middleware
|
||||
export default function initMiddleware(middleware) {
|
||||
return (req, res) =>
|
||||
new Promise((resolve, reject) => {
|
||||
middleware(req, res, (result) => {
|
||||
if (result instanceof Error) {
|
||||
return reject(result);
|
||||
}
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
14
utils/stripe.js
Normal file
14
utils/stripe.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* This is a singleton to ensure we only instantiate Stripe once.
|
||||
*/
|
||||
import { loadStripe } from "@stripe/stripe-js";
|
||||
|
||||
let stripePromise = null;
|
||||
const getStripe = () => {
|
||||
if (!stripePromise) {
|
||||
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY);
|
||||
}
|
||||
return stripePromise;
|
||||
};
|
||||
|
||||
export default getStripe;
|
||||
Reference in New Issue
Block a user