-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create checkout page and stripe systems (#85)
not actually tho, since i have to refactor the repo a lot
- Loading branch information
Showing
16 changed files
with
2,348 additions
and
1,204 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import path from "node:path"; | ||
import { fileURLToPath } from "node:url"; | ||
import js from "@eslint/js"; | ||
import { FlatCompat } from "@eslint/eslintrc"; | ||
|
||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
const compat = new FlatCompat({ | ||
baseDirectory: __dirname, | ||
recommendedConfig: js.configs.recommended, | ||
allConfig: js.configs.all | ||
}); | ||
export default [...compat.extends("next/core-web-vitals")]; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { NextResponse } from "next/server"; | ||
import Stripe from "stripe"; | ||
|
||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); | ||
|
||
type Item = { | ||
id: string; | ||
quantity: number; | ||
// this will change | ||
}; | ||
|
||
const calculateOrderAmount = (items: Item[]): number => { | ||
// wip, prisma soon | ||
const amount = /* placeholder currently */ (10000.00 * 100); // it is in cents (14€) | ||
|
||
return amount; | ||
}; | ||
|
||
export async function POST(req: Request) { | ||
try { | ||
const { items }: { items: Item[] } = await req.json(); | ||
|
||
const paymentIntent = await stripe.paymentIntents.create({ | ||
amount: calculateOrderAmount(items), | ||
currency: "eur", | ||
automatic_payment_methods: { | ||
enabled: false, | ||
}, | ||
payment_method_types: ["card", "alipay", "wechat_pay", "paypal", "mobilepay", "klarna"] | ||
}); | ||
|
||
return NextResponse.json({ | ||
clientSecret: paymentIntent.client_secret, | ||
amount: paymentIntent.amount | ||
}); | ||
|
||
} catch (error) { | ||
console.error("Error creating a payment intent:", error); | ||
return NextResponse.json({ | ||
error: "An error occurred while creating the payment intent using Stripe, please try again soon. This is not your problem, but rather a problem with us or Stripe.", | ||
}, { | ||
status: 500, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
"use client" | ||
|
||
import { useState, useEffect } from "react"; | ||
import { loadStripe, StripeElementLocale } from "@stripe/stripe-js"; | ||
import { Elements } from "@stripe/react-stripe-js"; | ||
import { StripeElementsOptions, Appearance } from "@stripe/stripe-js"; | ||
|
||
import CheckoutForm from "@/components/blocks/checkout/Form"; | ||
import CompletePage from "@/components/blocks/checkout/Complete"; | ||
import { useLocale } from "next-intl"; | ||
|
||
export default function Cart() { | ||
const locale = useLocale() as StripeElementLocale; | ||
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, { | ||
locale: locale, | ||
}); | ||
const [clientSecret, setClientSecret] = useState<string | null>(null); | ||
const [amount, setAmount] = useState<string | null>(null); | ||
const [confirmed, setConfirmed] = useState<boolean>(false); | ||
|
||
useEffect(() => { | ||
const paymentIntentClientSecret = new URLSearchParams(window.location.search).get("payment_intent_client_secret"); | ||
if (paymentIntentClientSecret) { | ||
setConfirmed(true); | ||
} | ||
}, []); | ||
|
||
useEffect(() => { | ||
fetch("/api/stripe/genPaymentIntent", { | ||
method: "POST", | ||
headers: { "Content-Type": "application/json" }, | ||
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }), | ||
}) | ||
.then((res) => { | ||
if (!res.ok) { | ||
throw new Error("Failed to create payment intent"); | ||
} | ||
return res.json(); | ||
}) | ||
.then((data) => { | ||
setClientSecret(data.clientSecret); | ||
setAmount(data.amount) | ||
}) | ||
.catch((error) => { | ||
console.error("Error fetching payment intent:", error); | ||
}); | ||
}, []); | ||
|
||
const appearance: Appearance = { | ||
theme: 'flat', | ||
variables: { | ||
colorPrimary: '#393cb9', | ||
colorText: '#ffffff', | ||
colorBackground: '#475569', | ||
borderRadius: '6px', | ||
}, | ||
}; | ||
|
||
// Options for the Elements component | ||
const options: StripeElementsOptions | undefined = clientSecret ? { | ||
clientSecret, | ||
appearance, | ||
} : undefined; | ||
|
||
return ( | ||
<main> | ||
{clientSecret && ( | ||
<> | ||
<Elements options={options} stripe={stripePromise}> | ||
{confirmed ? <CompletePage /> : <CheckoutForm amount={amount} />} | ||
</Elements> | ||
</> | ||
)} | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { useState, useEffect } from "react"; | ||
import { useStripe } from "@stripe/react-stripe-js"; | ||
|
||
const STATUS_CONTENT_MAP: { | ||
[key: string]: { | ||
text: string; | ||
iconColor: string; | ||
}; | ||
} = { | ||
succeeded: { | ||
text: "Payment succeeded", | ||
iconColor: "#30B130", | ||
}, | ||
processing: { | ||
text: "Your payment is processing.", | ||
iconColor: "#6D6E78", | ||
}, | ||
requires_payment_method: { | ||
text: "Your payment was not successful, please try again.", | ||
iconColor: "#DF1B41", | ||
}, | ||
default: { | ||
text: "Something went wrong, please try again.", | ||
iconColor: "#DF1B41", | ||
}, | ||
}; | ||
|
||
|
||
// WIP, COPIED FROM STRIPE DOCS | ||
export default function CompletePage() { | ||
const stripe = useStripe(); | ||
const [status, setStatus] = useState<string>("default"); | ||
const [intentId, setIntentId] = useState<string | null>(null); | ||
|
||
useEffect(() => { | ||
if (!stripe) return; | ||
|
||
const clientSecret = new URLSearchParams(window.location.search).get( | ||
"payment_intent_client_secret" | ||
); | ||
if (!clientSecret) return; | ||
|
||
const retrievePaymentIntent = async () => { | ||
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret); | ||
if (!paymentIntent) return; | ||
|
||
setStatus(paymentIntent.status || "default"); | ||
setIntentId(paymentIntent.id || null); | ||
}; | ||
|
||
retrievePaymentIntent(); | ||
}, [stripe]); | ||
|
||
const currentStatus = STATUS_CONTENT_MAP[status] || STATUS_CONTENT_MAP["default"]; | ||
|
||
return ( | ||
<div id="payment-status"> | ||
<h2 id="status-text">{currentStatus.text}</h2> | ||
{intentId && ( | ||
<div id="details-table"> | ||
<table> | ||
<tbody> | ||
<tr> | ||
<td className="TableLabel">id</td> | ||
<td id="intent-id" className="TableContent">{intentId}</td> | ||
</tr> | ||
<tr> | ||
<td className="TableLabel">status</td> | ||
<td id="intent-status" className="TableContent">{status}</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
</div> | ||
)} | ||
{intentId && ( | ||
<a | ||
href={`https://dashboard.stripe.com/payments/${intentId}`} | ||
id="view-details" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
Stripe link | ||
</a> | ||
)} | ||
<a id="retry-button" href="/billing/cart">Test another</a> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.