From 64c5ee53a0d158b38caff3285dac008826d7ae88 Mon Sep 17 00:00:00 2001 From: Joe Carstairs <65492573+joeacarstairs@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:02:50 +0000 Subject: Update from staging (#70) * 33 / Pay annual subscription by PayPal (#68) * Refactors paypal-buttons -> paypal-product-buttons WebComponent * Pulls out address to data file * Defines annual subscription prices data hash * Styles paypal-product-buttons * Styles
* Details have cursor:pointer on the summary * Moves Jyne section to separate page * Rewrites payment options using details components * Reworks translation typing * Defines MembershipType enum * Adds translations for MembershipType enum * Can pay annual subs by PayPal * Styles tables better * Replaces a few straggler --margins with --spacing (#67) --------- Co-authored-by: Joe Carstairs <118172583+jcarstairs-scottlogic@users.noreply.github.com> --- src/scripts/wc/paypal-product-buttons.ts | 146 +++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/scripts/wc/paypal-product-buttons.ts (limited to 'src/scripts') diff --git a/src/scripts/wc/paypal-product-buttons.ts b/src/scripts/wc/paypal-product-buttons.ts new file mode 100644 index 0000000..ca06524 --- /dev/null +++ b/src/scripts/wc/paypal-product-buttons.ts @@ -0,0 +1,146 @@ +import { type PayPalNamespace, loadScript } from '@paypal/paypal-js'; + +const backendUrl = '/api'; + +class PaypalProductButtons extends HTMLElement { + constructor() { + super(); + + try { + const paypalClientId = this.getAttribute('paypal-client-id'); + + const paypalOptions = { + clientId: paypalClientId, + locale: 'en_GB', + commit: false, + currency: 'GBP', + intent: 'capture', + }; + + loadScript(paypalOptions) + .catch((err) => { + this.innerHTML = ` +
+ Failed to load PayPal buttons +

${err}

+
+ `; + console.error(`Failed to load the PayPal JS SDK script. ${err}`); + }) + .then((paypal) => { + const constructButtons = paypal?.Buttons; + if (paypal && constructButtons) { + const successPageUrl = this.getAttribute('success-page-url'); + this.innerHTML = ''; + const div = document.createElement('div'); + this.appendChild(div); + this.makePaypalButtons(constructButtons, successPageUrl).render(div); + } + }) + .catch((err) => { + this.innerHTML = ` +
+ Failed to load PayPal buttons +

${err}

+
+ `; + console.error(`Failed to construct the PayPal buttons. ${err}`); + }); + } catch (err) { + this.innerHTML = ` +
+ Failed to load PayPal buttons +

${err}

+
+ `; + } + } + + makePaypalButtons( + constructButtons: Required['Buttons'], + successPageUrl: string + ) { + const productDescription = this.getAttribute('product-description'); + const shortDescription = this.getAttribute('short-description'); + const totalPrice = this.getAttribute('total-price'); + + return constructButtons({ + // This function is called to set up the transaction details without actually carrying it out + async createOrder() { + const body = JSON.stringify({ + productDescription, + shortDescription, + totalPrice, + }); + const headers = { + 'Content-Type': 'application/json', + }; + + let response: Response; + try { + response = await fetch(`${backendUrl}/order`, { method: 'POST', body, headers }); + } catch (err) { + throw new Error(`Failed to create order. ${err}`); + } + + if (!response.ok) { + throw new Error(` + Failed to create order. Backend response was + ${response.status} ${response.statusText}. ${await response.text()} + `); + } + + const { orderId, approvalLink } = await response.json(); + console.info(` + Successfully created order with ID + ${orderId}. Approval link: ${approvalLink} + `); + return orderId; + }, + + // This function is called after payment is confirmed + async onApprove(data) { + const body = JSON.stringify({ + id: data.orderID, + isApproved: true, + }); + const headers = { + 'Content-Type': 'application/json', + }; + + let response: Response; + try { + response = await fetch(`${backendUrl}/order`, { method: 'PATCH', body, headers }); + } catch (err) { + throw new Error(`Failed to capture order. ${err}`); + } + + if (!response.ok) { + throw new Error(` + Failed to capture order. Backend response was ${response.status} + ${response.statusText}. ${await response.text()} + `); + } + + const { orderId, requestStatus } = await response.json(); + console.info(` + Successfully captured order with ID ${orderId}, + request status is ${requestStatus}. + `); + + console.info(`Redirecting to confirmation page, ${successPageUrl}...`); + window.location.href = successPageUrl; + }, + }); + } + + getAttribute(key: string): string { + const value = super.getAttribute(key); + if (!value || typeof value !== 'string') { + throw new Error(`${key} attribute not provided to PaypalProductButtons component.`); + } + return value; + } +} + +customElements.define('paypal-product-buttons', PaypalProductButtons); -- cgit v1.2.3