From f567710c7b71eaf5e10ed6fb4a4ffb077490aa6b Mon Sep 17 00:00:00 2001 From: Jumpy Squirrel Date: Sun, 11 Jan 2026 18:21:16 +0100 Subject: [PATCH 1/2] feat(#300): semi-working draft for card widget --- src/pages/payment.tsx | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/pages/payment.tsx diff --git a/src/pages/payment.tsx b/src/pages/payment.tsx new file mode 100644 index 0000000..ff5906b --- /dev/null +++ b/src/pages/payment.tsx @@ -0,0 +1,101 @@ +import Layout from '~/components/layout' +import SEO from '~/components/seo' +import type { ReadonlyRouteComponentProps } from '~/util/readonly-types' +import { useEffect, useState } from 'react' + +export const Head = () => + +// documentation for SumUpCard: https://developer.sumup.com/online-payments/checkouts/card-widget + +const PaymentPage = (_: ReadonlyRouteComponentProps) => { + const checkoutParam = new URLSearchParams(window.location.search).get( + 'checkout' + ) + // ensure safe + const sanityRegex = /[^a-z0-9-]/g + const checkoutParamSanitized = checkoutParam + ? checkoutParam.replaceAll(sanityRegex, '') + : '' + + const [isScriptLoaded, setIsScriptLoaded] = useState(false) + const [scriptError, setScriptError] = useState(null) + + useEffect(() => { + // Create script element to load library + const script = document.createElement('script') + script.src = 'https://gateway.sumup.com/gateway/ecom/card/v2/sdk.js' + script.type = 'text/javascript' + script.async = true + + // Handle script load success + const handleLoad = () => { + console.log('External script loaded!') + setIsScriptLoaded(true) + + // now we can mount the payment widget + if (checkoutParamSanitized) { + // @ts-expect-error + SumUpCard.mount({ + id: 'sumup-card', + checkoutId: checkoutParamSanitized, + onResponse: function (type: any, body: any) { + console.log('Type', type) + console.log('Body', body) + }, + }) + } + } + + // Handle script load error + const handleError = () => { + setScriptError('Failed to load external script.') + } + + // Attach event listeners + script.addEventListener('load', handleLoad) + script.addEventListener('error', handleError) + + // Inject script into the DOM + document.body.appendChild(script) + + // Cleanup: Remove script and event listeners on unmount + return () => { + script.removeEventListener('load', handleLoad) + script.removeEventListener('error', handleError) + document.body.removeChild(script) + } + }, []) // Empty dependency array: runs once on mount + + if (scriptError) { + return ( + +

Error: {scriptError}

+
+ ) + } else if (!isScriptLoaded) { + return ( + +

loading...

+
+ ) + } else if (!checkoutParamSanitized) { + return ( + +

no checkout id provided

+
+ ) + } else { + return ( + +

Please make your payment here:

+
+

+ Note that processing payments may sometimes take a bit. Do not pay + multiple times. +

+
+ ) + } +} + +export default PaymentPage From 17e7f0f2781afc0e1bca067c4eba65d3283561a0 Mon Sep 17 00:00:00 2001 From: Faye Date: Sun, 11 Jan 2026 23:36:52 +0100 Subject: [PATCH 2/2] fix: widget timing --- gatsby-browser.js | 1 + src/pages/payment.tsx | 40 +++++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index 3830f57..ec7544a 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,4 @@ +import React from 'react' import { LocalizationProvider, ReactLocalization } from '@fluent/react' import { useObservable, useObservableState } from 'observable-hooks' import { from } from 'rxjs' diff --git a/src/pages/payment.tsx b/src/pages/payment.tsx index ff5906b..25b3668 100644 --- a/src/pages/payment.tsx +++ b/src/pages/payment.tsx @@ -1,7 +1,7 @@ +import { useEffect, useState } from 'react' import Layout from '~/components/layout' import SEO from '~/components/seo' import type { ReadonlyRouteComponentProps } from '~/util/readonly-types' -import { useEffect, useState } from 'react' export const Head = () => @@ -18,10 +18,10 @@ const PaymentPage = (_: ReadonlyRouteComponentProps) => { : '' const [isScriptLoaded, setIsScriptLoaded] = useState(false) - const [scriptError, setScriptError] = useState(null) + const [scriptError, setScriptError] = useState(null) + // Effect 1: Load the SumUp SDK script useEffect(() => { - // Create script element to load library const script = document.createElement('script') script.src = 'https://gateway.sumup.com/gateway/ecom/card/v2/sdk.js' script.type = 'text/javascript' @@ -29,26 +29,13 @@ const PaymentPage = (_: ReadonlyRouteComponentProps) => { // Handle script load success const handleLoad = () => { - console.log('External script loaded!') + console.log('SumUp script loaded!') setIsScriptLoaded(true) - - // now we can mount the payment widget - if (checkoutParamSanitized) { - // @ts-expect-error - SumUpCard.mount({ - id: 'sumup-card', - checkoutId: checkoutParamSanitized, - onResponse: function (type: any, body: any) { - console.log('Type', type) - console.log('Body', body) - }, - }) - } } // Handle script load error const handleError = () => { - setScriptError('Failed to load external script.') + setScriptError('Failed to load SumUp script.') } // Attach event listeners @@ -64,7 +51,22 @@ const PaymentPage = (_: ReadonlyRouteComponentProps) => { script.removeEventListener('error', handleError) document.body.removeChild(script) } - }, []) // Empty dependency array: runs once on mount + }, []) + + // Effect 2: Mount the widget AFTER the div is rendered + useEffect(() => { + if (!isScriptLoaded || !checkoutParamSanitized) return + + // @ts-expect-error - SumUpCard is loaded via external script + SumUpCard.mount({ + id: 'sumup-card', + checkoutId: checkoutParamSanitized, + onResponse: (type: any, body: any) => { + console.log('Type', type) + console.log('Body', body) + }, + }) + }, [isScriptLoaded, checkoutParamSanitized]) if (scriptError) { return (