PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / resources / js / lib / invoice-download.ts

invoice-download.ts in Yatra – Travel Booking & Tour Operator Software 3.0.16, at resources/js/lib/invoice-download.ts

152 lines 4.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Admin invoice download helper.
3 *
4 * Calls the public invoice REST endpoint with the WordPress admin nonce + cookie,
5 * so administrators can trigger an invoice PDF download from the admin UI without
6 * any extra permission/login round-trip. The same endpoint is used on the
7 * customer account page (with the same auth pattern), and supports an HMAC
8 * `invoice_token` for guests on the booking confirmation page.
9 */
10
11 interface AdminRestConfig {
12 base: string;
13 nonce: string;
14 }
15
16 function getAdminRestConfig(): AdminRestConfig {
17 const w = window as unknown as {
18 yatraAdmin?: { restUrl?: string; nonce?: string };
19 };
20 const raw = w?.yatraAdmin?.restUrl || "/wp-json";
21 const base = raw.endsWith("/") ? raw.slice(0, -1) : raw;
22 const nonce = w?.yatraAdmin?.nonce || "";
23 return { base, nonce };
24 }
25
26 function buildInvoiceUrl(base: string, suffix: string): string {
27 const origin =
28 typeof window !== "undefined" ? window.location.origin : "http://localhost";
29
30 let u: URL;
31 try {
32 u = new URL(base, origin);
33 } catch {
34 return `${base.replace(/\/$/, "")}${suffix}?download=1`;
35 }
36
37 // Plain permalinks: /wp-json is exposed as ?rest_route=
38 if (u.searchParams.has("rest_route")) {
39 const route = (u.searchParams.get("rest_route") || "").replace(/\/$/, "");
40 u.searchParams.set("rest_route", `${route}${suffix}`);
41 u.searchParams.set("download", "1");
42 return u.toString();
43 }
44
45 u.pathname = (u.pathname || "").replace(/\/$/, "") + suffix;
46 u.searchParams.set("download", "1");
47 return u.toString();
48 }
49
50 async function readError(res: Response): Promise<string> {
51 let msg = res.statusText || "Request failed";
52 try {
53 const j = await res.json();
54 if (j?.message && typeof j.message === "string") {
55 msg = j.message;
56 }
57 } catch {
58 const t = await res.text().catch(() => "");
59 if (t && t.length < 300) {
60 msg = t;
61 }
62 }
63 return msg;
64 }
65
66 /**
67 * Fetch an invoice PDF from `suffix` and save it, falling back to
68 * `fallbackName` when the response carries no Content-Disposition.
69 *
70 * Authentication is handled server-side via the admin REST nonce + cookie
71 * (administrators bypass the ownership check inside the endpoint).
72 */
73 async function downloadInvoicePdf(
74 suffix: string,
75 fallbackName: string,
76 ): Promise<void> {
77 const { base, nonce } = getAdminRestConfig();
78 if (!nonce) {
79 throw new Error("Missing REST nonce; please reload the page.");
80 }
81
82 const url = buildInvoiceUrl(base, suffix);
83 const res = await fetch(url, {
84 method: "GET",
85 credentials: "include",
86 headers: {
87 "X-WP-Nonce": nonce,
88 Accept: "application/pdf, application/octet-stream, */*",
89 },
90 });
91
92 if (!res.ok) {
93 throw new Error(await readError(res));
94 }
95
96 const blob = await res.blob();
97 const dispo = res.headers.get("Content-Disposition");
98 let filename = fallbackName;
99 if (dispo) {
100 const m = /filename\*?=(?:UTF-8'')?["']?([^";\n]+)/i.exec(dispo);
101 if (m?.[1]) {
102 try {
103 filename = decodeURIComponent(m[1].replace(/['"]/g, "").trim());
104 } catch {
105 filename = m[1].replace(/['"]/g, "").trim();
106 }
107 }
108 }
109
110 const objectUrl = URL.createObjectURL(blob);
111 const link = document.createElement("a");
112 link.href = objectUrl;
113 link.download = filename;
114 document.body.appendChild(link);
115 link.click();
116 document.body.removeChild(link);
117 URL.revokeObjectURL(objectUrl);
118 }
119
120 /**
121 * Download a payment invoice (receipt for one recorded payment).
122 */
123 export async function downloadAdminInvoice(paymentId: number): Promise<void> {
124 if (!paymentId || paymentId <= 0) {
125 throw new Error("Invalid payment ID");
126 }
127
128 await downloadInvoicePdf(
129 `/yatra/v1/payment/${paymentId}/invoice`,
130 `invoice-${paymentId}.pdf`,
131 );
132 }
133
134 /**
135 * Download a booking invoice — the whole booking rather than a single
136 * payment, so it also works for bookings with no payment recorded yet
137 * (bank transfer, pay later), where it renders as a pro-forma invoice with
138 * the gateway's payment instructions.
139 */
140 export async function downloadAdminBookingInvoice(
141 bookingId: number,
142 ): Promise<void> {
143 if (!bookingId || bookingId <= 0) {
144 throw new Error("Invalid booking ID");
145 }
146
147 await downloadInvoicePdf(
148 `/yatra/v1/booking/${bookingId}/invoice`,
149 `invoice-booking-${bookingId}.pdf`,
150 );
151 }
152