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);