1 | |
2 | type Paypal = { |
3 | clientId: string; |
4 | clientSecret: string; |
5 | }; |
6 |
|
7 | async function getToken(auth: Paypal): Promise<string> { |
8 | const url = new URL(`https://api-m.paypal.com/v1/oauth2/token`); |
9 | const response = await fetch(url, { |
10 | method: "POST", |
11 | headers: { |
12 | Authorization: `Basic ${btoa(`${auth.clientId}:${auth.clientSecret}`)}`, |
13 | }, |
14 | body: new URLSearchParams({ |
15 | grant_type: "client_credentials", |
16 | }), |
17 | }); |
18 | if (!response.ok) { |
19 | const text = await response.text(); |
20 | throw new Error(`Could not get token: ${response.status} ${text}`); |
21 | } |
22 | const json = await response.json(); |
23 | return json.access_token; |
24 | } |
25 | |
26 | * Fully update invoice |
27 | * Fully updates an invoice, by ID. In the JSON request body, include a complete `invoice` object. This call does not support partial updates. |
28 | */ |
29 | export async function main( |
30 | auth: Paypal, |
31 | invoice_id: string, |
32 | send_to_recipient: string | undefined, |
33 | send_to_invoicer: string | undefined, |
34 | body: { |
35 | id?: string; |
36 | parent_id?: string; |
37 | status?: |
38 | | "DRAFT" |
39 | | "SENT" |
40 | | "SCHEDULED" |
41 | | "PAID" |
42 | | "MARKED_AS_PAID" |
43 | | "CANCELLED" |
44 | | "REFUNDED" |
45 | | "PARTIALLY_PAID" |
46 | | "PARTIALLY_REFUNDED" |
47 | | "MARKED_AS_REFUNDED" |
48 | | "UNPAID" |
49 | | "PAYMENT_PENDING"; |
50 | detail: { |
51 | reference?: string; |
52 | currency_code: string; |
53 | note?: string; |
54 | terms_and_conditions?: string; |
55 | memo?: string; |
56 | attachments?: { |
57 | id?: string; |
58 | reference_url?: string; |
59 | content_type?: string; |
60 | create_time?: string; |
61 | size?: string; |
62 | }[]; |
63 | } & { |
64 | invoice_number?: string; |
65 | invoice_date?: string; |
66 | payment_term?: { |
67 | term_type?: |
68 | | "DUE_ON_RECEIPT" |
69 | | "DUE_ON_DATE_SPECIFIED" |
70 | | "NET_10" |
71 | | "NET_15" |
72 | | "NET_30" |
73 | | "NET_45" |
74 | | "NET_60" |
75 | | "NET_90" |
76 | | "NO_DUE_DATE"; |
77 | } & { due_date?: string }; |
78 | metadata?: { |
79 | create_time?: string; |
80 | created_by?: string; |
81 | last_update_time?: string; |
82 | last_updated_by?: string; |
83 | } & { |
84 | cancel_time?: string; |
85 | cancelled_by?: string; |
86 | first_sent_time?: string; |
87 | last_sent_time?: string; |
88 | last_sent_by?: string; |
89 | created_by_flow?: |
90 | | "MULTIPLE_RECIPIENTS_GROUP" |
91 | | "BATCH" |
92 | | "REGULAR_SINGLE"; |
93 | recipient_view_url?: string; |
94 | invoicer_view_url?: string; |
95 | }; |
96 | }; |
97 | invoicer?: { business_name?: string } & { |
98 | name?: { |
99 | prefix?: string; |
100 | given_name?: string; |
101 | surname?: string; |
102 | middle_name?: string; |
103 | suffix?: string; |
104 | alternate_full_name?: string; |
105 | full_name?: string; |
106 | }; |
107 | address?: { |
108 | address_line_1?: string; |
109 | address_line_2?: string; |
110 | address_line_3?: string; |
111 | admin_area_4?: string; |
112 | admin_area_3?: string; |
113 | admin_area_2?: string; |
114 | admin_area_1?: string; |
115 | postal_code?: string; |
116 | country_code: string; |
117 | address_details?: { |
118 | street_number?: string; |
119 | street_name?: string; |
120 | street_type?: string; |
121 | delivery_service?: string; |
122 | building_name?: string; |
123 | sub_building?: string; |
124 | }; |
125 | }; |
126 | } & { |
127 | email_address?: string; |
128 | phones?: { |
129 | country_code: string; |
130 | national_number: string; |
131 | extension_number?: string; |
132 | } & { phone_type?: "FAX" | "HOME" | "MOBILE" | "OTHER" | "PAGER" }[]; |
133 | website?: string; |
134 | tax_id?: string; |
135 | additional_notes?: string; |
136 | logo_url?: string; |
137 | }; |
138 | primary_recipients?: { |
139 | billing_info?: { business_name?: string } & { |
140 | name?: { |
141 | prefix?: string; |
142 | given_name?: string; |
143 | surname?: string; |
144 | middle_name?: string; |
145 | suffix?: string; |
146 | alternate_full_name?: string; |
147 | full_name?: string; |
148 | }; |
149 | address?: { |
150 | address_line_1?: string; |
151 | address_line_2?: string; |
152 | address_line_3?: string; |
153 | admin_area_4?: string; |
154 | admin_area_3?: string; |
155 | admin_area_2?: string; |
156 | admin_area_1?: string; |
157 | postal_code?: string; |
158 | country_code: string; |
159 | address_details?: { |
160 | street_number?: string; |
161 | street_name?: string; |
162 | street_type?: string; |
163 | delivery_service?: string; |
164 | building_name?: string; |
165 | sub_building?: string; |
166 | }; |
167 | }; |
168 | } & { |
169 | email_address?: string; |
170 | phones?: { |
171 | country_code: string; |
172 | national_number: string; |
173 | extension_number?: string; |
174 | } & { phone_type?: "FAX" | "HOME" | "MOBILE" | "OTHER" | "PAGER" }[]; |
175 | additional_info?: string; |
176 | language?: string; |
177 | }; |
178 | shipping_info?: { business_name?: string } & { |
179 | name?: { |
180 | prefix?: string; |
181 | given_name?: string; |
182 | surname?: string; |
183 | middle_name?: string; |
184 | suffix?: string; |
185 | alternate_full_name?: string; |
186 | full_name?: string; |
187 | }; |
188 | address?: { |
189 | address_line_1?: string; |
190 | address_line_2?: string; |
191 | address_line_3?: string; |
192 | admin_area_4?: string; |
193 | admin_area_3?: string; |
194 | admin_area_2?: string; |
195 | admin_area_1?: string; |
196 | postal_code?: string; |
197 | country_code: string; |
198 | address_details?: { |
199 | street_number?: string; |
200 | street_name?: string; |
201 | street_type?: string; |
202 | delivery_service?: string; |
203 | building_name?: string; |
204 | sub_building?: string; |
205 | }; |
206 | }; |
207 | }; |
208 | }[]; |
209 | additional_recipients?: string[]; |
210 | items?: { |
211 | id?: string; |
212 | name: string; |
213 | description?: string; |
214 | quantity: string; |
215 | unit_amount: { currency_code: string; value: string }; |
216 | tax?: { |
217 | name: string; |
218 | percent: string; |
219 | amount?: { currency_code: string; value: string }; |
220 | }; |
221 | item_date?: string; |
222 | discount?: { |
223 | percent?: string; |
224 | amount?: { currency_code: string; value: string }; |
225 | }; |
226 | unit_of_measure?: "QUANTITY" | "HOURS" | "AMOUNT"; |
227 | }[]; |
228 | configuration?: { |
229 | tax_calculated_after_discount?: false | true; |
230 | tax_inclusive?: false | true; |
231 | allow_tip?: false | true; |
232 | partial_payment?: { |
233 | allow_partial_payment?: false | true; |
234 | minimum_amount_due?: { currency_code: string; value: string }; |
235 | }; |
236 | } & { template_id?: string }; |
237 | amount?: { |
238 | currency_code?: string; |
239 | value?: string; |
240 | breakdown?: { |
241 | item_total?: { currency_code: string; value: string }; |
242 | discount?: { |
243 | invoice_discount?: { |
244 | percent?: string; |
245 | amount?: { currency_code: string; value: string }; |
246 | }; |
247 | item_discount?: { currency_code: string; value: string }; |
248 | }; |
249 | tax_total?: { currency_code: string; value: string }; |
250 | shipping?: { |
251 | amount?: { currency_code: string; value: string }; |
252 | tax?: { |
253 | name: string; |
254 | percent: string; |
255 | amount?: { currency_code: string; value: string }; |
256 | }; |
257 | }; |
258 | custom?: { |
259 | label: string; |
260 | amount?: { currency_code: string; value: string }; |
261 | }; |
262 | }; |
263 | }; |
264 | due_amount?: { currency_code: string; value: string }; |
265 | gratuity?: { currency_code: string; value: string }; |
266 | payments?: { |
267 | paid_amount?: { currency_code: string; value: string }; |
268 | transactions?: { |
269 | type?: "PAYPAL" | "EXTERNAL"; |
270 | payment_id?: string; |
271 | payment_date?: string; |
272 | method: |
273 | | "OTHER" |
274 | | "PAYPAL" |
275 | | "BANK_TRANSFER" |
276 | | "CASH" |
277 | | "CHECK" |
278 | | "CREDIT_CARD" |
279 | | "DEBIT_CARD" |
280 | | "WIRE_TRANSFER"; |
281 | note?: string; |
282 | amount?: { currency_code: string; value: string }; |
283 | shipping_info?: { business_name?: string } & { |
284 | name?: { |
285 | prefix?: string; |
286 | given_name?: string; |
287 | surname?: string; |
288 | middle_name?: string; |
289 | suffix?: string; |
290 | alternate_full_name?: string; |
291 | full_name?: string; |
292 | }; |
293 | address?: { |
294 | address_line_1?: string; |
295 | address_line_2?: string; |
296 | address_line_3?: string; |
297 | admin_area_4?: string; |
298 | admin_area_3?: string; |
299 | admin_area_2?: string; |
300 | admin_area_1?: string; |
301 | postal_code?: string; |
302 | country_code: string; |
303 | address_details?: { |
304 | street_number?: string; |
305 | street_name?: string; |
306 | street_type?: string; |
307 | delivery_service?: string; |
308 | building_name?: string; |
309 | sub_building?: string; |
310 | }; |
311 | }; |
312 | }; |
313 | }[]; |
314 | }; |
315 | refunds?: { |
316 | refund_amount?: { currency_code: string; value: string }; |
317 | transactions?: { |
318 | type?: "PAYPAL" | "EXTERNAL"; |
319 | refund_id?: string; |
320 | refund_date?: string; |
321 | amount?: { currency_code: string; value: string }; |
322 | method: |
323 | | "OTHER" |
324 | | "PAYPAL" |
325 | | "BANK_TRANSFER" |
326 | | "CASH" |
327 | | "CHECK" |
328 | | "CREDIT_CARD" |
329 | | "DEBIT_CARD" |
330 | | "WIRE_TRANSFER"; |
331 | }[]; |
332 | }; |
333 | links?: { |
334 | href: string; |
335 | rel: string; |
336 | method?: |
337 | | "GET" |
338 | | "POST" |
339 | | "PUT" |
340 | | "DELETE" |
341 | | "HEAD" |
342 | | "CONNECT" |
343 | | "OPTIONS" |
344 | | "PATCH"; |
345 | }[]; |
346 | }, |
347 | ) { |
348 | const token = await getToken(auth); |
349 | const url = new URL( |
350 | `https://api-m.paypal.com/v2/invoicing/invoices/${invoice_id}`, |
351 | ); |
352 | for (const [k, v] of [ |
353 | ["send_to_recipient", send_to_recipient], |
354 | ["send_to_invoicer", send_to_invoicer], |
355 | ]) { |
356 | if (v !== undefined && v !== "" && k !== undefined) { |
357 | url.searchParams.append(k, v); |
358 | } |
359 | } |
360 | const response = await fetch(url, { |
361 | method: "PUT", |
362 | headers: { |
363 | "Content-Type": "application/json", |
364 | Authorization: "Bearer " + token, |
365 | }, |
366 | body: JSON.stringify(body), |
367 | }); |
368 | if (!response.ok) { |
369 | const text = await response.text(); |
370 | throw new Error(`${response.status} ${text}`); |
371 | } |
372 | return await response.json(); |
373 | } |
374 |
|