1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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;
---
<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>
</paypal-buttons>
<script>
import { PayPalNamespace, loadScript } from '@paypal/paypal-js';
const backendUrl = '/api';
class PayPalButtons 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 PayPalButtons component.`);
}
return value;
}
}
customElements.define('paypal-buttons', PayPalButtons);
</script>
|