Use Stripe Address and email admin on membership creation
This commit is contained in:
parent
896bb5242a
commit
9287b4da1a
142
package-lock.json
generated
142
package-lock.json
generated
@ -29,6 +29,7 @@
|
||||
"feed": "^4.2.2",
|
||||
"next": "13.4.16",
|
||||
"next-themes": "^0.2.1",
|
||||
"postmark": "4.0.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
@ -1300,6 +1301,11 @@
|
||||
"astring": "bin/astring"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.14",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
|
||||
@ -1353,6 +1359,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
|
||||
@ -2085,6 +2101,17 @@
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||
@ -2351,6 +2378,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@ -3446,6 +3481,25 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
@ -3455,6 +3509,19 @@
|
||||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@ -6706,7 +6773,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@ -6715,7 +6781,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
@ -7721,6 +7786,14 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/postmark": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postmark/-/postmark-4.0.2.tgz",
|
||||
"integrity": "sha512-2zlCv+KVVQ0KoamXZHE7d+gXzLlr8tPE+PxQmtUaIZhbHzZAq4D6yH2b+ykhA8wYCc5ISodcx8U1aNLenXBs9g==",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
@ -7914,6 +7987,11 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
@ -11351,6 +11429,11 @@
|
||||
"resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz",
|
||||
"integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"autoprefixer": {
|
||||
"version": "10.4.14",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
|
||||
@ -11376,6 +11459,16 @@
|
||||
"integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"axobject-query": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
|
||||
@ -11883,6 +11976,14 @@
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"comma-separated-tokens": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||
@ -12067,6 +12168,11 @@
|
||||
"object-keys": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@ -12896,6 +13002,11 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q=="
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
@ -12905,6 +13016,16 @@
|
||||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@ -15147,14 +15268,12 @@
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"peer": true
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
@ -15835,6 +15954,14 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"postmark": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postmark/-/postmark-4.0.2.tgz",
|
||||
"integrity": "sha512-2zlCv+KVVQ0KoamXZHE7d+gXzLlr8tPE+PxQmtUaIZhbHzZAq4D6yH2b+ykhA8wYCc5ISodcx8U1aNLenXBs9g==",
|
||||
"requires": {
|
||||
"axios": "^1.6.2"
|
||||
}
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
@ -15934,6 +16061,11 @@
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz",
|
||||
"integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg=="
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
|
@ -31,6 +31,7 @@
|
||||
"feed": "^4.2.2",
|
||||
"next": "13.4.16",
|
||||
"next-themes": "^0.2.1",
|
||||
"postmark": "4.0.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
|
@ -4,7 +4,7 @@ import Stripe from 'stripe';
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '');
|
||||
|
||||
export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||
const { amount, payment_intent_id } = await request.json();
|
||||
const { amount, payment_intent_id, metadata } = await request.json();
|
||||
|
||||
if (payment_intent_id) {
|
||||
try {
|
||||
@ -18,6 +18,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||
payment_intent_id,
|
||||
{
|
||||
amount: amount,
|
||||
metadata: metadata || {},
|
||||
},
|
||||
);
|
||||
|
||||
@ -38,13 +39,14 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||
|
||||
try {
|
||||
// Create PaymentIntent
|
||||
const params = {
|
||||
const params: Stripe.PaymentIntentCreateParams = {
|
||||
amount: amount,
|
||||
currency: 'usd',
|
||||
payment_method_types: ['card'],
|
||||
automatic_payment_methods: {
|
||||
enabled: false,
|
||||
},
|
||||
metadata: metadata || {},
|
||||
};
|
||||
const payment_intent = await stripe.paymentIntents.create(params);
|
||||
//Return the payment_intent object
|
||||
|
29
src/app/api/stripe_webhook/route.ts
Normal file
29
src/app/api/stripe_webhook/route.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { emailNotification } from '@/lib/email';
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest): Promise<Response> {
|
||||
const body = await request.json();
|
||||
|
||||
if (body.type === 'charge.succeeded') {
|
||||
const data = body.data.object;
|
||||
const billing = data.billing_details;
|
||||
const cityStateZip = `${billing.address.city}, ${billing.address.state} ${billing.address.postal_code}`;
|
||||
const address = billing.address.line2
|
||||
? `${billing.address.line1}\n${cityStateZip}`
|
||||
: `${billing.address.line1}\n${billing.address.line2}\n${cityStateZip}`;
|
||||
|
||||
emailNotification({
|
||||
name: billing.name,
|
||||
type: data.metadata.type,
|
||||
amount: data.amount,
|
||||
email: billing.email,
|
||||
phone: billing.phone,
|
||||
address,
|
||||
});
|
||||
} else {
|
||||
console.log('Another type of event');
|
||||
console.log(body);
|
||||
}
|
||||
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import React, { useEffect, useState, FormEvent } from 'react';
|
||||
import { Elements } from '@stripe/react-stripe-js';
|
||||
import { Appearance, StripeElementsOptions, loadStripe } from '@stripe/stripe-js';
|
||||
import { AddressElement, Elements } from '@stripe/react-stripe-js';
|
||||
import { Appearance, StripeAddressElementOptions, StripeElementsOptions, loadStripe } from '@stripe/stripe-js';
|
||||
import {
|
||||
PaymentElement,
|
||||
useStripe,
|
||||
@ -12,6 +12,25 @@ import { RadioGroup } from '@headlessui/react'
|
||||
|
||||
const stripe = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || '');
|
||||
|
||||
const addressOptions: StripeAddressElementOptions = {
|
||||
mode: 'billing',
|
||||
allowedCountries: ['US'],
|
||||
fields: {
|
||||
phone: 'always',
|
||||
},
|
||||
validation: {
|
||||
phone: {
|
||||
required: 'always'
|
||||
}
|
||||
},
|
||||
defaultValues: {
|
||||
address: {
|
||||
state: 'WA',
|
||||
country: 'US',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const membershipLevels = [
|
||||
{ id: 1, title: 'Individual', description: 'One vote', price: 20 },
|
||||
{ id: 2, title: 'Household', description: 'Two votes', price: 30 },
|
||||
@ -40,9 +59,7 @@ function CheckoutForm({
|
||||
const [selectedAdditionalDonation, setSelectedAdditionalDonation] = useState<number | null>(null);
|
||||
const [customAmount, setCustomAmount] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [street, setStreet] = useState('');
|
||||
const [zip, setZip] = useState('');
|
||||
const [offsetFees, setOffsetFees] = useState(true);
|
||||
const [totalAmount, setTotalAmount] = useState(300);
|
||||
const [message, setMessage] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -94,6 +111,10 @@ function CheckoutForm({
|
||||
}
|
||||
}
|
||||
|
||||
if (offsetFees) {
|
||||
subtotal = Math.ceil(subtotal * 1.03)
|
||||
}
|
||||
|
||||
setTotalAmount(subtotal);
|
||||
|
||||
fetch('api/stripe_intent', {
|
||||
@ -102,9 +123,12 @@ function CheckoutForm({
|
||||
body: JSON.stringify({
|
||||
amount: subtotal * 100,
|
||||
payment_intent_id: paymentIntentID,
|
||||
metadata: {
|
||||
'type': selectedMembershipLevel.title,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}, [paymentIntentID, selectedMembershipLevel, selectedAdditionalDonation, customAmount])
|
||||
}, [paymentIntentID, selectedMembershipLevel, selectedAdditionalDonation, customAmount, offsetFees, email])
|
||||
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
@ -123,7 +147,8 @@ function CheckoutForm({
|
||||
receipt_email: email,
|
||||
payment_method_data: {
|
||||
billing_details: {
|
||||
name: 'Billing user',
|
||||
// Other details are filled automatically by address form.
|
||||
email,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -270,54 +295,7 @@ function CheckoutForm({
|
||||
placeholder="you@example.com"
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-md mb-3 px-3 pb-1.5 pt-2.5 shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600">
|
||||
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||
Phone
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
name="phone"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="block w-full border-0 p-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||
placeholder="360-376-XXXX"
|
||||
/>
|
||||
</div>
|
||||
<fieldset>
|
||||
<div className="mb-3 -space-y-px rounded-md bg-white shadow-sm">
|
||||
<div className="flex -space-x-px">
|
||||
<div className="w-2/3 min-w-0 flex-1">
|
||||
<label htmlFor="street-address" className="sr-only">
|
||||
Street Address
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="street-address"
|
||||
id="street-address"
|
||||
value={street}
|
||||
onChange={(e) => setStreet(e.target.value)}
|
||||
className="relative block w-full rounded-none rounded-l-md border-0 bg-transparent py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
placeholder="Street"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<label htmlFor="zipcode" className="sr-only">
|
||||
Zipcode
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="zipcode"
|
||||
id="zipcode"
|
||||
value={zip}
|
||||
onChange={(e) => setZip(e.target.value)}
|
||||
className="relative block w-full rounded-none rounded-r-md border-0 bg-transparent py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
placeholder="98XXX"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<AddressElement options={addressOptions} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
@ -334,6 +312,24 @@ function CheckoutForm({
|
||||
|
||||
{/* TODO: Automatically renew toggle? */}
|
||||
|
||||
<div className="relative flex gap-x-3">
|
||||
<div className="flex h-6 items-center">
|
||||
<input
|
||||
id="offsetFees"
|
||||
name="offsetFees"
|
||||
type="checkbox"
|
||||
checked={offsetFees}
|
||||
onChange={(e) => setOffsetFees(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm leading-6">
|
||||
<label htmlFor="offsetFees" className="font-medium text-gray-900">
|
||||
Help offset credit card fees
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="mt-6 w-full rounded-md flex justify-center border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
disabled={isLoading || !stripe || !elements}
|
||||
@ -347,7 +343,7 @@ function CheckoutForm({
|
||||
</button>
|
||||
|
||||
{/* Show any error or success messages */}
|
||||
{message && <div id="payment-message">{message}</div>}
|
||||
{message && <div className="text-red-500" id="payment-message">{message}</div>}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
@ -362,7 +358,7 @@ export function ClubPayment() {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
amount: 30000,
|
||||
amount: 2000,
|
||||
payment_intent_id: '',
|
||||
}),
|
||||
})
|
||||
|
@ -7,7 +7,17 @@ export const metadata: Metadata = {
|
||||
description: 'Thanks for becoming a member.',
|
||||
}
|
||||
|
||||
export default function ThankYou() {
|
||||
export default async function ThankYou({
|
||||
searchParams
|
||||
}: {
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) {
|
||||
|
||||
const { redirect_status } = searchParams;
|
||||
if (redirect_status !== 'succeeded') {
|
||||
// TODO: Display error
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleLayout
|
||||
title="Thanks for becoming a member."
|
||||
|
38
src/lib/email.ts
Normal file
38
src/lib/email.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { ServerClient } from 'postmark';
|
||||
|
||||
const fromAddr = process.env.FROM_ADDRESS || '';
|
||||
const toAddr = process.env.ADMIN_ADDRESS || '';
|
||||
const serverToken = process.env.POSTMARK_SERVER_TOKEN || '';
|
||||
const client = new ServerClient(serverToken);
|
||||
|
||||
export function emailNotification({
|
||||
name,
|
||||
type,
|
||||
amount,
|
||||
email,
|
||||
phone,
|
||||
address,
|
||||
}: {
|
||||
name: string;
|
||||
type: string;
|
||||
amount: number;
|
||||
email: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
}) {
|
||||
client.sendEmail({
|
||||
From: fromAddr,
|
||||
To: toAddr,
|
||||
Subject: 'New WSCC Membership',
|
||||
TextBody: [
|
||||
'New WSCC member:',
|
||||
'',
|
||||
'Name: ' + name,
|
||||
'Type: ' + type,
|
||||
'Amount: ' + amount,
|
||||
'Email: ' + email,
|
||||
'Phone: ' + phone,
|
||||
'Address: ' + address,
|
||||
].join('\n'),
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user