From 7ab203340493db8d870a60f857dd27a2c1a4651a Mon Sep 17 00:00:00 2001 From: Joaquim d'Souza Date: Thu, 10 Oct 2024 13:22:37 +0200 Subject: [PATCH 1/5] feat: add Stripe to main join flow block --- .../join-block/src/Services/JoinService.php | 26 ++++ packages/join-block/src/Settings.php | 2 +- packages/join-flow/package-lock.json | 16 +++ packages/join-flow/package.json | 1 + packages/join-flow/src/app.tsx | 16 ++- packages/join-flow/src/components/atoms.tsx | 4 +- packages/join-flow/src/env.ts | 4 +- packages/join-flow/src/images/stripe.png | Bin 0 -> 12712 bytes .../src/pages/payment-details.page.tsx | 121 +++++++++++++++--- .../src/pages/payment-method.page.tsx | 1 - packages/join-flow/src/pages/plan.page.tsx | 2 +- packages/join-flow/yarn.lock | 5 + 12 files changed, 169 insertions(+), 29 deletions(-) create mode 100644 packages/join-flow/src/images/stripe.png diff --git a/packages/join-block/src/Services/JoinService.php b/packages/join-block/src/Services/JoinService.php index 10b95ae..774d24a 100644 --- a/packages/join-block/src/Services/JoinService.php +++ b/packages/join-block/src/Services/JoinService.php @@ -175,6 +175,32 @@ private static function tryHandleJoin($data) $data['gocardlessMandate'] = $subscription ? $subscription->links->mandate : null; $data['gocardlessCustomer'] = $subscription ? $subscription->links->customer : null; + if (Settings::get("USE_STRIPE")) { + $joinBlockLog->info('Subscribing with Stripe', [ + "email" => $data['email'], + "membershipPlan" => $data['membershipPlan'], + "paymentToken" => $data['paymentToken'] + ]); + + StripeService::initialise(); + [$customer, $newCustomer] = StripeService::upsertCustomer($data['email']); + $subscription = StripeService::createSubscription($customer, $data['membershipPlan']); + $confirmedPaymentIntent = StripeService::confirmSubscriptionPaymentIntent($subscription, $data['paymentToken']); + StripeService::updateCustomerDefaultPaymentMethod($customer->id, $subscription->latest_invoice->payment_intent->payment_method); + + $data['stripeStatus'] = $confirmedPaymentIntent->status; + $data['stripeNewCustomer'] = $newCustomer; + $data['stripeCustomer'] = $customer->toArray(); + $data['stripeSubscription'] = $subscription->toArray(); + + $joinBlockLog->info("Subscribed with Stripe", [ + "email" => $data['email'], + "customer" => $data['stripeCustomer'], + "newCustomer" => $data['stripeNewCustomer'], + "subscription" => $data['stripeSubscription'] + ]); + } + $subscriptionPlanId = ''; if ($useChargebee && $customerResult) { $customer = $customerResult->customer(); diff --git a/packages/join-block/src/Settings.php b/packages/join-block/src/Settings.php index 2625113..248631a 100644 --- a/packages/join-block/src/Settings.php +++ b/packages/join-block/src/Settings.php @@ -215,7 +215,7 @@ public static function createMembershipPlansField($name = 'membership_plans') Field::make('text', 'amount', "Price")->set_required(true)->set_attribute('type', 'number') ->set_help_text("Price without currency, e.g. 10"), Field::make('checkbox', 'allow_custom_amount', 'Allow users to change the amount') - ->set_help_text('This makes the above price a minimum.'), + ->set_help_text('This makes the above price a minimum. Not compatible with Stripe.'), $payment_frequency_select, $payment_currency_select, Field::make('text', 'description'), diff --git a/packages/join-flow/package-lock.json b/packages/join-flow/package-lock.json index bf4091f..6f8dd9c 100644 --- a/packages/join-flow/package-lock.json +++ b/packages/join-flow/package-lock.json @@ -52,6 +52,7 @@ "@types/yup": "^0.29.7", "env-cmd": "^10.1.0", "gifsicle": "^5.0.0", + "prettier": "^3.3.3", "resolve-url-loader": "^3.1.1", "typescript": "^4.0.3" } @@ -10172,6 +10173,21 @@ "node": ">=4" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", diff --git a/packages/join-flow/package.json b/packages/join-flow/package.json index 652979a..61ff668 100644 --- a/packages/join-flow/package.json +++ b/packages/join-flow/package.json @@ -68,6 +68,7 @@ "@types/yup": "^0.29.7", "env-cmd": "^10.1.0", "gifsicle": "^5.0.0", + "prettier": "^3.3.3", "resolve-url-loader": "^3.1.1", "typescript": "^4.0.3" } diff --git a/packages/join-flow/src/app.tsx b/packages/join-flow/src/app.tsx index 1c892f5..7191c3f 100644 --- a/packages/join-flow/src/app.tsx +++ b/packages/join-flow/src/app.tsx @@ -25,6 +25,7 @@ import { get as getEnv, getPaymentMethods } from "./env"; import { usePostResource } from "./services/rest-resource.service"; import gocardless from "./images/gocardless.svg"; import chargebee from "./images/chargebee.png"; +import stripe from "./images/stripe.png"; import { Elements } from '@stripe/react-stripe-js'; import MinimalJoinForm from "./components/minimal-join-flow"; @@ -160,12 +161,15 @@ const App = () => { const paymentProviderLogos = getPaymentMethods().map((method) => { return method === "directDebit" ? - - GoCardless - : - - Chargebee - + + GoCardless + : getEnv("USE_CHARGEBEE") ? + + Chargebee + : getEnv("USE_STRIPE") ? + + Stripe + : null }) const options = { diff --git a/packages/join-flow/src/components/atoms.tsx b/packages/join-flow/src/components/atoms.tsx index 5adb192..1ed275d 100644 --- a/packages/join-flow/src/components/atoms.tsx +++ b/packages/join-flow/src/components/atoms.tsx @@ -4,15 +4,17 @@ import { Controller, UseFormMethods } from "react-hook-form"; import { PageState, useCurrentRouter } from "../services/router.service"; interface ContinueButtonProps { + disabled?: boolean; text?: string; onClick?(event: React.MouseEvent): void; } -export const ContinueButton: FC = ({ text, onClick }) => ( +export const ContinueButton: FC = ({ disabled, text, onClick }) => ( diff --git a/packages/join-flow/src/env.ts b/packages/join-flow/src/env.ts index 8a28906..f852a8c 100644 --- a/packages/join-flow/src/env.ts +++ b/packages/join-flow/src/env.ts @@ -25,6 +25,7 @@ interface StaticEnv { USE_GOCARDLESS_API: boolean; USE_MAILCHIMP: boolean; USE_POSTCODE_LOOKUP: boolean; + USE_STRIPE: boolean; USE_TEST_DATA: boolean; WEBHOOK_UUID: string; // Connected to a URL in the wp_options table: `SELECT option_name FROM wp_options where option_value = :uuid` WP_REST_API: string; @@ -66,6 +67,7 @@ const staticEnv: StaticEnv = { USE_GOCARDLESS_API: parseBooleanEnvVar("REACT_APP_USE_GOCARDLESS_API"), USE_MAILCHIMP: parseBooleanEnvVar("REACT_APP_USE_MAILCHIMP"), USE_POSTCODE_LOOKUP: parseBooleanEnvVar("REACT_APP_USE_POSTCODE_LOOKUP"), + USE_STRIPE: parseBooleanEnvVar("REACT_APP_USE_STRIPE"), USE_TEST_DATA: parseBooleanEnvVar("REACT_APP_USE_TEST_DATA"), WEBHOOK_UUID: process.env.WEBHOOK_UUID || '', WP_REST_API: '', @@ -88,7 +90,7 @@ export const getPaymentMethods = () => { if (get("USE_GOCARDLESS")) { paymentMethods.push("directDebit") } - if (get("USE_CHARGEBEE")) { + if (get("USE_CHARGEBEE") || get("USE_STRIPE")) { paymentMethods.push("creditCard") } return paymentMethods diff --git a/packages/join-flow/src/images/stripe.png b/packages/join-flow/src/images/stripe.png new file mode 100644 index 0000000000000000000000000000000000000000..3588235fb84ef19774ca849c8e778565ed93ca6f GIT binary patch literal 12712 zcmb_@W0WV&^5(B?+qP}nwr$(CZQHip)3$BQboaD-T08H(ckh4q^KRCOiYFr@p2)~A zbxu`Ol%l)@95gmG004lKk`z_`t0(`86D0WGdt%;ClD`VnLP$;s0BA~p`7i?eJ0~=e zRF(q(yvYH8z)%3-&tFsE1pwg22mo9e0s!2Z004$#ZnqNe--S>!O(}CZIRN!v3<&@O zLI!~RML>Tq01!6dAGW^;APt1`U#tv7`5zx(03gB&0Qw&v?Z5h;A^BJSiTSq%DFpf- z#zNr#dQTRD{1-d@6Xw>={qR>oIZA4|001y3{|FEuI~VgWo0gTTrmLo$EVqe+9lepM zgRvRCr=8^FtF767o8mnsY0QivK74?~ISw($&?Gn}NZ@!-L*~h2Fv0f`NΠIDg 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 = ({ data, @@ -25,7 +32,13 @@ export const PaymentDetailsPage: StagerComponent = ({ return ; } if (data.paymentMethod === "creditCard") { - return ; + if (getEnv("USE_CHARGEBEE")) { + return ; + } + if (getEnv("USE_STRIPE")) { + return ; + } + return

Error: no payment providers available. Please contact us.

; } return ( @@ -46,7 +59,7 @@ const DirectDebitPaymentPage: StagerComponent = ({ }, resolver: validate(PaymentMethodDDSchema) }); - const organisation = getEnv('ORGANISATION_NAME'); + const organisation = getEnv("ORGANISATION_NAME"); return (
= ({ - {getEnv('IS_UPDATE_FLOW') ? ( + {getEnv("IS_UPDATE_FLOW") ? ( - - {sortedCountries.map((c) => ( - - ))} - - + + {sortedCountries.map((c) => ( + + ))} + + ) : null} @@ -149,7 +162,7 @@ const CreditCardPaymentPage: StagerComponent = ({ onCompleted, data }) => { - const organisationName = getEnv('ORGANISATION_NAME'); + const organisationName = getEnv("ORGANISATION_NAME"); const cardRef = useRef(); const form = useForm(); @@ -242,3 +255,75 @@ const CreditCardPaymentPage: StagerComponent = ({ ); }; + +const StripePaymentPage: StagerComponent = ({ + onCompleted, + data +}) => { + const stripePromise = loadStripe(getEnv('STRIPE_PUBLISHABLE_KEY') as string); + const plan = (getEnv("MEMBERSHIP_PLANS") as any[]).find(plan => plan.value === data.membership) + const amount = plan.amount ? plan.amount * 100 : 100 + const currency = plan.currency.toLowerCase() || "gbp" + return ( + + + + ); +}; + +const StripeForm = ({ + onCompleted +}: { + onCompleted: (data: FormSchema) => void; +}) => { + const stripe = useStripe(); + const elements = useElements(); + + const [errorMessage, setErrorMessage] = useState(); + const [loading, setLoading] = useState(false); + + const handleError = (error: { message?: string }) => { + setLoading(false); + setErrorMessage(error.message); + }; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + if (!stripe || !elements) { + return; + } + + setLoading(true); + + const { error: submitError } = await elements.submit(); + + if (submitError) { + handleError(submitError); + return; + } + + const { error, confirmationToken } = await stripe.createConfirmationToken({ + elements + }); + + // This point is only reached if there's an immediate error when + // creating the ConfirmationToken. Show the error to your customer (for example, payment details incomplete) + if (error) { + handleError(error); + return; + } + + onCompleted({ paymentToken: confirmationToken.id }); + }; + + return ( +
+
+ + + {errorMessage &&
{errorMessage}
} +
+
+ ); +}; diff --git a/packages/join-flow/src/pages/payment-method.page.tsx b/packages/join-flow/src/pages/payment-method.page.tsx index bd3c54a..7745e00 100644 --- a/packages/join-flow/src/pages/payment-method.page.tsx +++ b/packages/join-flow/src/pages/payment-method.page.tsx @@ -27,7 +27,6 @@ export const PaymentPage: StagerComponent = ({ data, onCompleted }) => { name="paymentMethod" value="directDebit" label="Direct Debit" - description="Best for the party!" form={form} /> = ({ name="membership" value={plan.value} label={plan.label} - allowCustomAmount={plan.allowCustomAmount} + allowCustomAmount={plan.allowCustomAmount && !getEnv("USE_STRIPE")} currencySymbol={currencyCodeToSymbol(plan.currency)} amount={plan.amount} frequency={plan.frequency} diff --git a/packages/join-flow/yarn.lock b/packages/join-flow/yarn.lock index 3762d0f..4326630 100644 --- a/packages/join-flow/yarn.lock +++ b/packages/join-flow/yarn.lock @@ -5674,6 +5674,11 @@ prepend-http@^2.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz" From 69393138216cf33c41e28bf65dbac59b7fb7c5e5 Mon Sep 17 00:00:00 2001 From: Joaquim d'Souza Date: Thu, 10 Oct 2024 14:02:02 +0200 Subject: [PATCH 2/5] fix: minor style tweaks to improve look in base wordpress --- packages/join-block/src/Settings.php | 1 + packages/join-flow/scss/theme.scss | 2 +- packages/join-flow/src/app.tsx | 2 +- .../src/pages/payment-details.page.tsx | 35 +++++++++---------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/join-block/src/Settings.php b/packages/join-block/src/Settings.php index 248631a..5a14867 100644 --- a/packages/join-block/src/Settings.php +++ b/packages/join-block/src/Settings.php @@ -56,6 +56,7 @@ public static function init() $theme_fields = [ Field::make('color', 'theme_primary_color', 'Primary Color') + ->set_default_value('#007bff') ->set_help_text("The color of interactive elements, e.g. buttons"), Field::make('color', 'theme_gray_color', 'Gray Color') ->set_default_value('#dfdcda') diff --git a/packages/join-flow/scss/theme.scss b/packages/join-flow/scss/theme.scss index de00b30..2bc29ef 100644 --- a/packages/join-flow/scss/theme.scss +++ b/packages/join-flow/scss/theme.scss @@ -11,7 +11,7 @@ $black: var(--wp--preset--color--black, #212529); $gray: var(--ck-join-form-gray-color, --wp--preset--color--cyan-bluish-gray, #dfdcda); $background: var(--ck-join-form-background-color, --wp--preset--color--base, #f4f1ee); -$primary: var(--ck-join-form-primary-color, --wp--preset--color--accent, #00643b); +$primary: var(--ck-join-form-primary-color, --wp--preset--color--accent, #007bff); $primary-focus: native-rgb(from $primary r g b, 0.6); $danger: var(--wp--preset--color--accent-5, #e3220c); diff --git a/packages/join-flow/src/app.tsx b/packages/join-flow/src/app.tsx index 7191c3f..7fac24c 100644 --- a/packages/join-flow/src/app.tsx +++ b/packages/join-flow/src/app.tsx @@ -246,7 +246,7 @@ const getInitialState = (): FormSchema => { const paymentMethods = getPaymentMethods(); const getDefaultState = () => ({ membership: membershipPlans.length ? membershipPlans[0].value : "standard", - paymentMethod: paymentMethods.length ? paymentMethods[0] : "directDebit", + paymentMethod: paymentMethods.length ? paymentMethods[0] : null, // Default contact flags to true if not collecting consent, otherwise false contactByEmail: !getEnv('COLLECT_PHONE_AND_EMAIL_CONTACT_CONSENT'), contactByPhone: !getEnv('COLLECT_PHONE_AND_EMAIL_CONTACT_CONSENT'), diff --git a/packages/join-flow/src/pages/payment-details.page.tsx b/packages/join-flow/src/pages/payment-details.page.tsx index 136c0b3..1c8c0ec 100644 --- a/packages/join-flow/src/pages/payment-details.page.tsx +++ b/packages/join-flow/src/pages/payment-details.page.tsx @@ -28,22 +28,27 @@ export const PaymentDetailsPage: StagerComponent = ({ data, onCompleted }) => { - if (data.paymentMethod === "directDebit") { - return ; - } - if (data.paymentMethod === "creditCard") { - if (getEnv("USE_CHARGEBEE")) { - return ; + const renderForm = () => { + if (data.paymentMethod === "directDebit") { + return ; } - if (getEnv("USE_STRIPE")) { - return ; + if (data.paymentMethod === "creditCard") { + if (getEnv("USE_CHARGEBEE")) { + return ; + } + if (getEnv("USE_STRIPE")) { + return ; + } + return

Error: no payment providers available. Please contact us.

; } - return

Error: no payment providers available. Please contact us.

; } return ( -
- +
+
+ +
+ {renderForm()}
); }; @@ -67,10 +72,6 @@ const DirectDebitPaymentPage: StagerComponent = ({ noValidate onSubmit={form.handleSubmit(onCompleted)} > -
- -
-

Your bank details

@@ -203,10 +204,6 @@ const CreditCardPaymentPage: StagerComponent = ({ noValidate onSubmit={form.handleSubmit(handleCompleted)} > -
- -
- Date: Thu, 10 Oct 2024 14:30:24 +0200 Subject: [PATCH 3/5] feat: set stripe payment methods to card and bacs_debit --- .../src/pages/payment-details.page.tsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/join-flow/src/pages/payment-details.page.tsx b/packages/join-flow/src/pages/payment-details.page.tsx index 1c8c0ec..8deafe4 100644 --- a/packages/join-flow/src/pages/payment-details.page.tsx +++ b/packages/join-flow/src/pages/payment-details.page.tsx @@ -41,7 +41,7 @@ export const PaymentDetailsPage: StagerComponent = ({ } return

Error: no payment providers available. Please contact us.

; } - } + }; return (
@@ -257,12 +257,27 @@ const StripePaymentPage: StagerComponent = ({ onCompleted, data }) => { - const stripePromise = loadStripe(getEnv('STRIPE_PUBLISHABLE_KEY') as string); - const plan = (getEnv("MEMBERSHIP_PLANS") as any[]).find(plan => plan.value === data.membership) - const amount = plan.amount ? plan.amount * 100 : 100 - const currency = plan.currency.toLowerCase() || "gbp" + const stripePromise = loadStripe(getEnv("STRIPE_PUBLISHABLE_KEY") as string); + const plan = (getEnv("MEMBERSHIP_PLANS") as any[]).find( + (plan) => plan.value === data.membership + ); + const amount = plan.amount ? plan.amount * 100 : 100; + const currency = plan.currency.toLowerCase() || "gbp"; + const paymentMethodTypes = ['card'] + if (currency === 'gbp') { + paymentMethodTypes.push('bacs_debit') + } return ( - + ); From 39a3b50d056950cf332e606d6b373b94c372b821 Mon Sep 17 00:00:00 2001 From: Joaquim d'Souza Date: Thu, 10 Oct 2024 14:52:59 +0200 Subject: [PATCH 4/5] fix: autocomplete and build tweak --- packages/join-flow/src/pages/details.page.tsx | 6 +++--- scripts/package.sh | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/join-flow/src/pages/details.page.tsx b/packages/join-flow/src/pages/details.page.tsx index 70c930c..9b65092 100644 --- a/packages/join-flow/src/pages/details.page.tsx +++ b/packages/join-flow/src/pages/details.page.tsx @@ -205,19 +205,19 @@ export const DetailsPage: StagerComponent = ({
diff --git a/scripts/package.sh b/scripts/package.sh index fd9387b..a63bde7 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -7,12 +7,22 @@ echo -n "Clearing up..." rm -rf dist echo "done" -echo -n "Creating plugin zip file..." +echo "Creating plugin zip file" mkdir -p dist + +echo "Building front end" +cd packages/join-flow +npm i && npm run build +cd ../.. + +echo "Copying WordPress plugin files" cp -r packages/join-block/ dist/join-block cd dist/join-block rm .env rm -rf node_modules -zip -r -q ck-join-plugin ./ -mv ck-join-plugin.zip ../.. -echo "done" + +echo "Zipping" +cd .. +zip -r -q ck-join-plugin join-block +mv ck-join-plugin.zip .. +echo "Done" From 9825b030c66d194a07e5cdf4bb36d79372d84af2 Mon Sep 17 00:00:00 2001 From: Joaquim d'Souza Date: Mon, 21 Oct 2024 12:05:06 +0200 Subject: [PATCH 5/5] docs: improve stripe integration comments --- packages/join-flow/src/env.ts | 3 +++ packages/join-flow/src/pages/payment-details.page.tsx | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/join-flow/src/env.ts b/packages/join-flow/src/env.ts index f852a8c..61a3b16 100644 --- a/packages/join-flow/src/env.ts +++ b/packages/join-flow/src/env.ts @@ -87,6 +87,9 @@ export const getStr = (envVar: keyof StaticEnv): string => { export const getPaymentMethods = () => { const paymentMethods = [] + // TODO: refactor paymentMethods to be ["gocardless", "chargebee", "stripe"] + // Originally gocardless => directDebit, and chargebee => creditCard, but + // stripe does direct debit and credit card, so this distinction is wrong. if (get("USE_GOCARDLESS")) { paymentMethods.push("directDebit") } diff --git a/packages/join-flow/src/pages/payment-details.page.tsx b/packages/join-flow/src/pages/payment-details.page.tsx index 8deafe4..fa3e729 100644 --- a/packages/join-flow/src/pages/payment-details.page.tsx +++ b/packages/join-flow/src/pages/payment-details.page.tsx @@ -253,6 +253,8 @@ const CreditCardPaymentPage: StagerComponent = ({ ); }; +const convertCurrencyFromMajorToMinorUnits = (amount: number) => amount * 100 + const StripePaymentPage: StagerComponent = ({ onCompleted, data @@ -261,9 +263,10 @@ const StripePaymentPage: StagerComponent = ({ const plan = (getEnv("MEMBERSHIP_PLANS") as any[]).find( (plan) => plan.value === data.membership ); - const amount = plan.amount ? plan.amount * 100 : 100; + const amount = plan.amount ? convertCurrencyFromMajorToMinorUnits(plan.amount) : 100; const currency = plan.currency.toLowerCase() || "gbp"; const paymentMethodTypes = ['card'] + // Add direct debit payment method for GBP only, as it is a UK only feature if (currency === 'gbp') { paymentMethodTypes.push('bacs_debit') } @@ -319,8 +322,7 @@ const StripeForm = ({ elements }); - // This point is only reached if there's an immediate error when - // creating the ConfirmationToken. Show the error to your customer (for example, payment details incomplete) + // Display error if the confirmation token cannot be obtained if (error) { handleError(error); return;