diff options
Diffstat (limited to 'src/components/ProductPayPalButtons.astro')
| -rw-r--r-- | src/components/ProductPayPalButtons.astro | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/components/ProductPayPalButtons.astro b/src/components/ProductPayPalButtons.astro new file mode 100644 index 0000000..952e6a8 --- /dev/null +++ b/src/components/ProductPayPalButtons.astro @@ -0,0 +1,150 @@ +--- +import env from '$lib/env'; +import type Price from '$types/Price'; + +interface Props { + successPageUrl: string; + productDescription: string; + shortDescription: string; + totalPrice: Price; +} + +const { successPageUrl, productDescription, shortDescription, totalPrice } = Astro.props; + +const PAYPAL_CLIENT_ID = + env.ENVIRONMENT === 'prod' ? env.PAYPAL_LIVE_CLIENT_ID : env.PAYPAL_SANDBOX_CLIENT_ID; +--- + +<product-paypal-buttons + data-paypal-client-id={PAYPAL_CLIENT_ID} + data-success-page-url={successPageUrl} + data-product-description={productDescription} + data-short-description={shortDescription} + data-total-price={totalPrice} +> + <div id="paypal-button-container"></div> +</product-paypal-buttons> + +<script> + import { PayPalNamespace, loadScript } from '@paypal/paypal-js'; + + const backendUrl = '/api'; + + class ProductPayPalButtons extends HTMLElement { + constructor() { + super(); + + const paypalClientId = this.getStringDataAttribute('paypalClientId'); + + const paypalOptions = { + clientId: paypalClientId, + locale: 'en_GB', + commit: false, + currency: 'GBP', + intent: 'capture', + }; + + loadScript(paypalOptions) + .catch((err) => { + throw new Error('Failed to load the PayPal JS SDK script. ', err); + }) + .then((paypal) => { + const constructButtons = paypal?.Buttons; + if (paypal && constructButtons) { + const successPageUrl = this.dataset['successPageUrl'] ?? '.'; + this.makePaypalButtons(constructButtons, successPageUrl).render( + '#paypal-button-container' + ); + } + }); + } + + makePaypalButtons( + constructButtons: Required<PayPalNamespace>['Buttons'], + successPageUrl: string + ) { + const productDescription = this.getStringDataAttribute('productDescription'); + const shortDescription = this.getStringDataAttribute('shortDescription'); + const totalPrice = this.getStringDataAttribute('totalPrice'); + + 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; + 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; + 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...'); + window.location.href = successPageUrl; + }, + }); + } + + getStringDataAttribute(key: string): string { + const value = this.dataset[key]; + if (!value || typeof value !== 'string') { + throw new Error(`data-${key} attribute not provided to ProductPayPalButtons component.`); + } + return value; + } + } + + customElements.define('product-paypal-buttons', ProductPayPalButtons); +</script> |
