eny.space Landingpage
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(app): restructure checkout and payment elements pages

Sam Sauer cb6585be 489a7934

+100 -45
+3 -2
README.md
··· 1 1 # Easy PDS 2 2 3 - A full-stack TypeScript application using Next.js for processing donations. 3 + A full-stack TypeScript application using Next.js for processing hosting service purchases. 4 4 5 5 ## Features 6 6 7 - - **Checkout** - Custom amount donations with hosted and embedded checkout 7 + - **Checkout** - Custom amount hosting service purchases with hosted checkout 8 8 - **Payment Elements** - Custom payment form with Payment Element 9 9 - **Webhook handling** - Server-side webhook processing for payment events 10 10 ··· 82 82 ## Testing 83 83 84 84 Use test cards for testing payments. Common test cards: 85 + 85 86 - `4242 4242 4242 4242` - Successful payment 86 87 - `4000 0027 6000 3184` - 3D Secure authentication required 87 88
+4 -4
app/actions/stripe.ts
··· 31 31 const checkoutSession: Stripe.Checkout.Session = 32 32 await stripe.checkout.sessions.create({ 33 33 mode: "payment", 34 - submit_type: "donate", 34 + submit_type: "pay", 35 35 line_items: [ 36 36 { 37 37 quantity: 1, 38 38 price_data: { 39 39 currency: CURRENCY, 40 40 product_data: { 41 - name: "Custom amount donation", 41 + name: "Hosting Service", 42 42 }, 43 43 unit_amount: formatAmountForStripe( 44 44 Number(data.get("customDonation") as string), ··· 48 48 }, 49 49 ], 50 50 ...(ui_mode === "hosted" && { 51 - success_url: `${origin}/donate-with-checkout/result?session_id={CHECKOUT_SESSION_ID}`, 52 - cancel_url: `${origin}/donate-with-checkout`, 51 + success_url: `${origin}/checkout/result?session_id={CHECKOUT_SESSION_ID}`, 52 + cancel_url: `${origin}/checkout`, 53 53 }), 54 54 ...(ui_mode === "embedded" && { 55 55 return_url: `${origin}/donate-with-embedded-checkout/result?session_id={CHECKOUT_SESSION_ID}`,
+82
app/checkout/result/page.tsx
··· 1 + import type { Stripe } from "stripe"; 2 + 3 + import PrintObject from "@/components/PrintObject"; 4 + import { stripe } from "@/lib/stripe"; 5 + 6 + import Link from "next/link"; 7 + 8 + export default async function ResultPage({ 9 + searchParams, 10 + }: { 11 + searchParams: Promise<{ session_id?: string | string[] }>; 12 + }): Promise<JSX.Element> { 13 + const params = await searchParams; 14 + const sessionId = Array.isArray(params.session_id) 15 + ? params.session_id[0] 16 + : params.session_id; 17 + 18 + if (!sessionId) { 19 + return ( 20 + <div className="page-container"> 21 + <h2>No session found</h2> 22 + <p> 23 + It looks like you didn't complete a checkout session, or the session 24 + information is missing. 25 + </p> 26 + <Link 27 + href="/checkout" 28 + style={{ 29 + display: "inline-block", 30 + padding: "12px 24px", 31 + borderRadius: "6px", 32 + marginTop: "16px", 33 + backgroundColor: "#8f6ed5", 34 + color: "#fff", 35 + textDecoration: "none", 36 + }} 37 + > 38 + Return to purchase page 39 + </Link> 40 + </div> 41 + ); 42 + } 43 + 44 + try { 45 + const checkoutSession: Stripe.Checkout.Session = 46 + await stripe.checkout.sessions.retrieve(sessionId, { 47 + expand: ["line_items", "payment_intent"], 48 + }); 49 + 50 + const paymentIntent = 51 + checkoutSession.payment_intent as Stripe.PaymentIntent; 52 + 53 + return ( 54 + <> 55 + <h2>Status: {paymentIntent.status}</h2> 56 + <h3>Checkout Session response:</h3> 57 + <PrintObject content={checkoutSession} /> 58 + </> 59 + ); 60 + } catch (error) { 61 + return ( 62 + <div className="page-container"> 63 + <h2>Error retrieving session</h2> 64 + <p>The checkout session could not be retrieved. Please try again.</p> 65 + <Link 66 + href="/checkout" 67 + style={{ 68 + display: "inline-block", 69 + padding: "12px 24px", 70 + borderRadius: "6px", 71 + marginTop: "16px", 72 + backgroundColor: "#8f6ed5", 73 + color: "#fff", 74 + textDecoration: "none", 75 + }} 76 + > 77 + Return to purchase page 78 + </Link> 79 + </div> 80 + ); 81 + } 82 + }
+1 -1
app/components/CheckoutForm.tsx
··· 66 66 type="submit" 67 67 disabled={loading} 68 68 > 69 - Donate {formatAmountForDisplay(input.customDonation, config.CURRENCY)} 69 + Purchase {formatAmountForDisplay(input.customDonation, config.CURRENCY)} 70 70 </button> 71 71 </form> 72 72 {clientSecret ? (
+1 -1
app/components/CustomDonationInput.tsx
··· 21 21 }): JSX.Element { 22 22 return ( 23 23 <label> 24 - Custom donation amount ({formatAmountForDisplay(min, currency)}- 24 + Service amount ({formatAmountForDisplay(min, currency)}- 25 25 {formatAmountForDisplay(max, currency)}): 26 26 <input 27 27 type="range"
+1 -1
app/components/ElementsForm.tsx
··· 161 161 !stripe 162 162 } 163 163 > 164 - Donate {formatAmountForDisplay(input.customDonation, config.CURRENCY)} 164 + Purchase {formatAmountForDisplay(input.customDonation, config.CURRENCY)} 165 165 </button> 166 166 </form> 167 167 <PaymentStatus status={payment.status} />
+3 -3
app/donate-with-checkout/page.tsx app/checkout/page.tsx
··· 3 3 import CheckoutForm from "@/components/CheckoutForm"; 4 4 5 5 export const metadata: Metadata = { 6 - title: "Donate", 6 + title: "Purchase Hosting", 7 7 }; 8 8 9 9 export default function DonatePage(): JSX.Element { 10 10 return ( 11 11 <div className="page-container"> 12 - <h1>Donate</h1> 13 - <p>Donate to our project 💖</p> 12 + <h1>Purchase Hosting Service</h1> 13 + <p>Select your hosting plan and complete your purchase</p> 14 14 <CheckoutForm uiMode="hosted" /> 15 15 </div> 16 16 );
app/donate-with-checkout/result/error.tsx app/checkout/result/error.tsx
app/donate-with-checkout/result/layout.tsx app/checkout/result/layout.tsx
-28
app/donate-with-checkout/result/page.tsx
··· 1 - import type { Stripe } from "stripe"; 2 - 3 - import PrintObject from "@/components/PrintObject"; 4 - import { stripe } from "@/lib/stripe"; 5 - 6 - export default async function ResultPage({ 7 - searchParams, 8 - }: { 9 - searchParams: { session_id: string }; 10 - }): Promise<JSX.Element> { 11 - if (!searchParams.session_id) 12 - throw new Error("Please provide a valid session_id (`cs_test_...`)"); 13 - 14 - const checkoutSession: Stripe.Checkout.Session = 15 - await stripe.checkout.sessions.retrieve(searchParams.session_id, { 16 - expand: ["line_items", "payment_intent"], 17 - }); 18 - 19 - const paymentIntent = checkoutSession.payment_intent as Stripe.PaymentIntent; 20 - 21 - return ( 22 - <> 23 - <h2>Status: {paymentIntent.status}</h2> 24 - <h3>Checkout Session response:</h3> 25 - <PrintObject content={checkoutSession} /> 26 - </> 27 - ); 28 - }
+1 -1
app/donate-with-elements/page.tsx
··· 1 1 import { redirect } from "next/navigation"; 2 2 3 3 export default function PaymentElementPage() { 4 - redirect("/donate-with-checkout"); 4 + redirect("/checkout"); 5 5 }
+1 -1
app/donate-with-embedded-checkout/page.tsx
··· 1 1 import { redirect } from "next/navigation"; 2 2 3 3 export default function DonatePage() { 4 - redirect("/donate-with-checkout"); 4 + redirect("/checkout"); 5 5 }
+1 -1
app/layout.tsx
··· 13 13 }, 14 14 twitter: { 15 15 card: "summary_large_image", 16 - description: "Easy PDS - Secure donation platform.", 16 + description: "Easy PDS - Hosting service platform.", 17 17 }, 18 18 }; 19 19
+2 -2
app/page.tsx
··· 11 11 <ul className="card-list"> 12 12 <li> 13 13 <Link 14 - href="/donate-with-checkout" 14 + href="/checkout" 15 15 className="card checkout-style-background" 16 16 > 17 - <h2 className="bottom">Donate</h2> 17 + <h2 className="bottom">Purchase Hosting</h2> 18 18 <img src="/checkout-one-time-payments.svg" /> 19 19 </Link> 20 20 </li>