diff --git a/.env.production b/.env.production index 9c1b3341..f9b510c1 100644 --- a/.env.production +++ b/.env.production @@ -20,4 +20,7 @@ SENTRY_AUTH_TOKEN= SOURCE_VERSION= RECURRENCY=true -TRACKING_KEY= \ No newline at end of file +TRACKING_KEY= + +ENABLE_APPLE_PAY= +ENABLE_GOOGLE_PAY= \ No newline at end of file diff --git a/next.config.js b/next.config.js index 29ea5bb9..5f730dc6 100644 --- a/next.config.js +++ b/next.config.js @@ -37,7 +37,10 @@ const scheme = : "https"; let APPUrl; -if (process.env.NEXT_PUBLIC_VERCEL_ENV === "preview") { +if ( + process.env.NEXT_PUBLIC_VERCEL_ENV === "preview" && + process.env.DISABLE_VERCEL_REDIRECT !== "true" +) { APPUrl = `${scheme}://${process.env.VERCEL_URL}`; } else { APPUrl = process.env.APP_URL; @@ -115,6 +118,9 @@ const nextConfig = { ESRI_CLIENT_SECRET: process.env.ESRI_CLIENT_SECRET, RECURRENCY: process.env.RECURRENCY, TRACKING_KEY: process.env.TRACKING_KEY, + ENABLE_GOOGLE_PAY: process.env.ENABLE_GOOGLE_PAY, + ENABLE_APPLE_PAY: process.env.ENABLE_APPLE_PAY, + DISABLE_VERCEL_REDIRECT: process.env.DISABLE_VERCEL_REDIRECT, }, trailingSlash: false, reactStrictMode: true, diff --git a/package-lock.json b/package-lock.json index e9254966..822811d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@sparticuz/chromium": "^106.0.2", "@stripe/react-stripe-js": "^1.4.0", "@stripe/stripe-js": "^1.13.2", - "axios": "^0.21.1", + "axios": "^1.6.0", "dom-to-image": "^2.6.0", "geocoder-arcgis": "^2.0.5", "i18next": "^22.4.10", @@ -2712,11 +2712,26 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/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/axobject-query": { @@ -4930,9 +4945,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -11088,11 +11103,25 @@ "dev": true }, "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "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" + } + } } }, "axobject-query": { @@ -12770,9 +12799,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" }, "for-each": { "version": "0.3.3", diff --git a/package.json b/package.json index af29d68b..c9650888 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@sparticuz/chromium": "^106.0.2", "@stripe/react-stripe-js": "^1.4.0", "@stripe/stripe-js": "^1.13.2", - "axios": "^0.21.1", + "axios": "^1.6.0", "dom-to-image": "^2.6.0", "geocoder-arcgis": "^2.0.5", "i18next": "^22.4.10", diff --git a/pages/_app.tsx b/pages/_app.tsx index 39c3fa57..dd5b986a 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -87,7 +87,10 @@ function MyApp({ process.env.VERCEL_URL && typeof window !== "undefined" ) { - if (process.env.VERCEL_URL !== window.location.hostname) { + if ( + process.env.VERCEL_URL !== window.location.hostname && + process.env.DISABLE_VERCEL_REDIRECT !== "true" + ) { router.replace(`https://${process.env.VERCEL_URL}`); } } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 3cf51bfd..3d87e1d9 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -134,6 +134,7 @@ "billingAddress": "Contact Details", "treesInCountry": "{{treeCount}} trees, Plant-for-the-Planet", "fundingPaymentLabel": "Donate {{amount}}, Plant-for-the-Planet", + "pcashPaymentLabel": "Load {{amount}} PlanetCash, Plant-for-the-Planet", "bouquetPaymentLabel": "Donate {{amount}}, Plant-for-the-Planet", "errorOccurred": "Something went wrong. Please feel free to take a screenshot and email us at support@plant-for-the-planet.org.", "dedicatedTo": "Dedicated to", diff --git a/src/Common/Types/QueryParamContextInterface.ts b/src/Common/Types/QueryParamContextInterface.ts index 1648409d..e93aa8bf 100644 --- a/src/Common/Types/QueryParamContextInterface.ts +++ b/src/Common/Types/QueryParamContextInterface.ts @@ -109,8 +109,8 @@ export default interface QueryParamContextInterface { paymentSetupCountry: string; shouldSetPaymentDetails?: boolean; }) => Promise; - isPlanetCashActive: boolean; - setIsPlanetCashActive: Dispatch>; + isPlanetCashActive: boolean | null; + setIsPlanetCashActive: Dispatch>; onBehalf: boolean; setOnBehalf: Dispatch>; onBehalfDonor: OnBehalfDonor; diff --git a/src/Donations/Components/DonationsForm.tsx b/src/Donations/Components/DonationsForm.tsx index be22af4c..b26266f7 100644 --- a/src/Donations/Components/DonationsForm.tsx +++ b/src/Donations/Components/DonationsForm.tsx @@ -68,6 +68,7 @@ function DonationsForm(): ReactElement { utmMedium, utmSource, isPackageWanted, + setPaymentRequest, } = React.useContext(QueryParamContext); const { t, i18n } = useTranslation(["common", "country", "donate"]); @@ -78,6 +79,16 @@ function DonationsForm(): ReactElement { React.useState(false); const router = useRouter(); + React.useEffect(() => { + setPaymentRequest(null); + }, []); + + React.useEffect(() => { + if (isPlanetCashActive) { + setPaymentRequest(null); + } + }, [isPlanetCashActive]); + React.useEffect(() => { setMinAmt(getMinimumAmountForCurrency(currency)); }, [currency]); @@ -241,6 +252,15 @@ function DonationsForm(): ReactElement { ), }); break; + case "planet-cash": + paymentLabel = t("pcashPaymentLabel", { + amount: getFormatedCurrency( + i18n.language, + currency, + paymentSetup.unitCost * quantity + ), + }); + break; case "bouquet": case "conservation": paymentLabel = t("bouquetPaymentLabel", { @@ -400,8 +420,6 @@ function DonationsForm(): ReactElement { paymentSetup?.gateways?.stripe?.account && currency ? ( { setPaymentType("CARD"); + setPaymentRequest(null); }, []); const sofortCountries = ["AT", "BE", "DE", "IT", "NL", "ES"]; @@ -111,7 +113,7 @@ function PaymentsForm(): ReactElement { | string | PaymentMethod | PaypalApproveData - | PaypalErrorData, + | PaypalErrorData ) => { if (!paymentSetup || !donationID) { console.log("Missing payment options"); //TODOO - better error handling @@ -144,7 +146,7 @@ function PaymentsForm(): ReactElement { // Seems to work only for native pay. Should this be removed? const onPaymentFunction = async ( paymentMethod: PaymentMethod, - paymentRequest: PaymentRequest, + paymentRequest: PaymentRequest ) => { setPaymentType(paymentRequest._activeBackingLibraryName); //TODOO --_activeBackingLibraryName is a private variable? const gateway = "stripe"; @@ -289,7 +291,7 @@ function PaymentsForm(): ReactElement { query: { ...router.query, step: CONTACT }, }, undefined, - { shallow: true }, + { shallow: true } ); }} className="d-flex" @@ -420,7 +422,7 @@ function PaymentsForm(): ReactElement { totalCost={getFormatedCurrency( i18n.language, currency, - paymentSetup?.unitCost * quantity, + paymentSetup?.unitCost * quantity )} onPaymentFunction={(providerObject: PaymentMethod) => onSubmitPayment("stripe", "card", providerObject) diff --git a/src/Donations/Micros/PlanetCashSelector.tsx b/src/Donations/Micros/PlanetCashSelector.tsx index eadba14a..15cdeacb 100644 --- a/src/Donations/Micros/PlanetCashSelector.tsx +++ b/src/Donations/Micros/PlanetCashSelector.tsx @@ -19,7 +19,6 @@ const PlanetCashSelector: FC = () => { country, setcountry, frequency, - paymentRequest, } = useContext(QueryParamContext); const router = useRouter(); @@ -37,20 +36,28 @@ const PlanetCashSelector: FC = () => { }, [paymentSetup?.unitCost, quantity, setIsPlanetCashActive]); useEffect(() => { - // On Load If selected country is planetCash Country and balance is sufficient activate planetCash. - - if ( - country === profile?.planetCash?.country && - paymentSetup && - paymentSetup.unitCost * quantity <= - profile.planetCash.balance / 100 + profile.planetCash.creditLimit / 100 - ) { - setIsPlanetCashActive(true); - } - if (frequency !== "once") { + if (frequency !== "once" && isPlanetCashActive !== null) { setIsPlanetCashActive(false); + } else { + if ( + isPlanetCashActive === null && + country === profile?.planetCash?.country && + paymentSetup && + paymentSetup.unitCost * quantity <= + (profile.planetCash.balance + profile.planetCash.creditLimit) / 100 + ) { + setIsPlanetCashActive(true); + } } - }, [paymentRequest, frequency]); + }, [ + country, + profile, + paymentSetup, + quantity, + frequency, + isPlanetCashActive, + frequency, + ]); useEffect(() => { // This is done to lock the transaction with PlanetCash in a single currency. @@ -208,11 +215,13 @@ const PlanetCashSelector: FC = () => {
- setIsPlanetCashActive((isPlanetCashActive) => !isPlanetCashActive) - } + onChange={() => { + setIsPlanetCashActive( + (isPlanetCashActive) => !isPlanetCashActive + ); + }} />
diff --git a/src/Donations/PaymentMethods/PaymentMethodTabs.tsx b/src/Donations/PaymentMethods/PaymentMethodTabs.tsx index 636fb456..f60a22fd 100644 --- a/src/Donations/PaymentMethods/PaymentMethodTabs.tsx +++ b/src/Donations/PaymentMethods/PaymentMethodTabs.tsx @@ -111,6 +111,15 @@ export default function PaymentMethodTabs({ ), }); break; + case "planet-cash": + paymentLabel = t("pcashPaymentLabel", { + amount: getFormatedCurrency( + i18n.language, + currency, + paymentSetup.unitCost * quantity + ), + }); + break; case "bouquet": case "conservation": paymentLabel = t("bouquetPaymentLabel", { @@ -218,8 +227,6 @@ export default function PaymentMethodTabs({ {/*9 May 2023 - Apple Pay / Google Pay is disabled currently as it is not working correctly*/} {showNativePay && ( { + const isApplePayEnabled = process.env.ENABLE_APPLE_PAY === "true" || false; + const isGooglePayEnabled = process.env.ENABLE_GOOGLE_PAY === "true" || false; const { t, ready } = useTranslation(["common"]); const { paymentRequest, setPaymentRequest } = useContext(QueryParamContext); const stripe = useStripe(); const [canMakePayment, setCanMakePayment] = useState(false); const [paymentLoading, setPaymentLoading] = useState(false); + // Tracks if native pay buttons were shown at least once to prevent layout jerks + const [wasNativePayInit, setWasNativePayInit] = useState(false); useEffect(() => { if ( @@ -73,17 +74,16 @@ export const PaymentRequestCustomButton = ({ pr.canMakePayment().then((result) => { if (result) { setPaymentRequest(pr); + setWasNativePayInit(true); } }); } }, [stripe, paymentRequest, country, currency, amount]); useEffect(() => { - if (stripe && paymentRequest) { - setPaymentRequest(null); - setCanMakePayment(false); - setPaymentLoading(false); - } + setPaymentRequest(null); + setCanMakePayment(false); + setPaymentLoading(false); }, [country, currency, amount]); useEffect(() => { @@ -108,6 +108,7 @@ export const PaymentRequestCustomButton = ({ useEffect(() => { if (paymentRequest && !paymentLoading) { setPaymentLoading(true); + paymentRequest.off("paymentmethod"); paymentRequest.on( "paymentmethod", ({ complete, paymentMethod }: PaymentRequestPaymentMethodEvent) => { @@ -118,15 +119,13 @@ export const PaymentRequestCustomButton = ({ ); } return () => { - if (paymentRequest && !paymentLoading) { - paymentRequest.off( - "paymentmethod", - ({ complete, paymentMethod }: PaymentRequestPaymentMethodEvent) => { - onPaymentFunction(paymentMethod, paymentRequest); - complete("success"); - setPaymentLoading(false); - } - ); + if ( + paymentRequest && + paymentRequest.hasRegisteredListener("paymentmethod") + ) { + paymentRequest.off("paymentmethod", () => { + setPaymentLoading(false); + }); } }; }, [paymentRequest, onPaymentFunction]); @@ -196,8 +195,25 @@ export const PaymentRequestCustomButton = ({
{t("or")}
)} - ) : null - ) : null} + ) : ( + <> + ) + ) : wasNativePayInit ? ( + //Loader shown if native pay was initiated at least once to avoid a jerky effect when payment details change +
+ + {!isPaymentPage && ( +
{t("or")}
+ )} +
+ ) : ( + <> + )} {!isPaymentPage && (