summaryrefslogtreecommitdiff
path: root/src/scripts/wc/paypal-product-buttons.ts
diff options
context:
space:
mode:
authorJoe Carstairs <118172583+jcarstairs-scottlogic@users.noreply.github.com>2024-01-25 16:59:42 +0000
committerGitHub <noreply@github.com>2024-01-25 16:59:42 +0000
commit1a6eb7e2ff0bf551e1560c67170e90fa8ec2a8ea (patch)
tree53f73dcca5693e656d50fac965955560a513a233 /src/scripts/wc/paypal-product-buttons.ts
parent83c3460191a2089b56b091a7f4e09f39ac636e1e (diff)
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 <address> * 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
Diffstat (limited to 'src/scripts/wc/paypal-product-buttons.ts')
-rw-r--r--src/scripts/wc/paypal-product-buttons.ts146
1 files changed, 146 insertions, 0 deletions
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 = `
+ <details>
+ <summary>Failed to load PayPal buttons</summary>
+ <p>${err}</p>
+ </details>
+ `;
+ 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 = `
+ <details>
+ <summary>Failed to load PayPal buttons</summary>
+ <p>${err}</p>
+ </details>
+ `;
+ console.error(`Failed to construct the PayPal buttons. ${err}`);
+ });
+ } catch (err) {
+ this.innerHTML = `
+ <details>
+ <summary>Failed to load PayPal buttons</summary>
+ <p>${err}</p>
+ </details>
+ `;
+ }
+ }
+
+ makePaypalButtons(
+ constructButtons: Required<PayPalNamespace>['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);