tailprint 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/dist/config/env.d.ts +77 -0
  4. package/dist/config/env.js +38 -0
  5. package/dist/config/env.js.map +1 -0
  6. package/dist/config/plans.d.ts +15 -0
  7. package/dist/config/plans.js +104 -0
  8. package/dist/config/plans.js.map +1 -0
  9. package/dist/config/templates.d.ts +10 -0
  10. package/dist/config/templates.js +746 -0
  11. package/dist/config/templates.js.map +1 -0
  12. package/dist/mcp/server.d.ts +10 -0
  13. package/dist/mcp/server.js +897 -0
  14. package/dist/mcp/server.js.map +1 -0
  15. package/dist/mcp/stdio.d.ts +2 -0
  16. package/dist/mcp/stdio.js +27 -0
  17. package/dist/mcp/stdio.js.map +1 -0
  18. package/dist/routes/dashboard.d.ts +2 -0
  19. package/dist/routes/dashboard.js +454 -0
  20. package/dist/routes/dashboard.js.map +1 -0
  21. package/dist/routes/mcp.d.ts +2 -0
  22. package/dist/routes/mcp.js +329 -0
  23. package/dist/routes/mcp.js.map +1 -0
  24. package/dist/routes/render.d.ts +2 -0
  25. package/dist/routes/render.js +444 -0
  26. package/dist/routes/render.js.map +1 -0
  27. package/dist/routes/scrape.d.ts +2 -0
  28. package/dist/routes/scrape.js +132 -0
  29. package/dist/routes/scrape.js.map +1 -0
  30. package/dist/routes/templates.d.ts +2 -0
  31. package/dist/routes/templates.js +2449 -0
  32. package/dist/routes/templates.js.map +1 -0
  33. package/dist/routes/usage.d.ts +31 -0
  34. package/dist/routes/usage.js +73 -0
  35. package/dist/routes/usage.js.map +1 -0
  36. package/dist/routes/webhooks.d.ts +2 -0
  37. package/dist/routes/webhooks.js +227 -0
  38. package/dist/routes/webhooks.js.map +1 -0
  39. package/dist/schemas/render.d.ts +422 -0
  40. package/dist/schemas/render.js +91 -0
  41. package/dist/schemas/render.js.map +1 -0
  42. package/dist/schemas/scrape.d.ts +65 -0
  43. package/dist/schemas/scrape.js +24 -0
  44. package/dist/schemas/scrape.js.map +1 -0
  45. package/dist/server.d.ts +2 -0
  46. package/dist/server.js +206 -0
  47. package/dist/server.js.map +1 -0
  48. package/dist/services/browser.d.ts +21 -0
  49. package/dist/services/browser.js +299 -0
  50. package/dist/services/browser.js.map +1 -0
  51. package/dist/services/carousel.d.ts +43 -0
  52. package/dist/services/carousel.js +300 -0
  53. package/dist/services/carousel.js.map +1 -0
  54. package/dist/services/chart.d.ts +29 -0
  55. package/dist/services/chart.js +129 -0
  56. package/dist/services/chart.js.map +1 -0
  57. package/dist/services/eInvoice.d.ts +62 -0
  58. package/dist/services/eInvoice.js +413 -0
  59. package/dist/services/eInvoice.js.map +1 -0
  60. package/dist/services/email.d.ts +24 -0
  61. package/dist/services/email.js +177 -0
  62. package/dist/services/email.js.map +1 -0
  63. package/dist/services/mockup.d.ts +14 -0
  64. package/dist/services/mockup.js +130 -0
  65. package/dist/services/mockup.js.map +1 -0
  66. package/dist/services/pdfStamper.d.ts +16 -0
  67. package/dist/services/pdfStamper.js +47 -0
  68. package/dist/services/pdfStamper.js.map +1 -0
  69. package/dist/services/qr.d.ts +20 -0
  70. package/dist/services/qr.js +44 -0
  71. package/dist/services/qr.js.map +1 -0
  72. package/dist/services/redis.d.ts +65 -0
  73. package/dist/services/redis.js +318 -0
  74. package/dist/services/redis.js.map +1 -0
  75. package/dist/services/scraper.d.ts +16 -0
  76. package/dist/services/scraper.js +138 -0
  77. package/dist/services/scraper.js.map +1 -0
  78. package/dist/services/storage.d.ts +38 -0
  79. package/dist/services/storage.js +123 -0
  80. package/dist/services/storage.js.map +1 -0
  81. package/dist/services/stripe.d.ts +32 -0
  82. package/dist/services/stripe.js +229 -0
  83. package/dist/services/stripe.js.map +1 -0
  84. package/dist/services/visualDiff.d.ts +33 -0
  85. package/dist/services/visualDiff.js +218 -0
  86. package/dist/services/visualDiff.js.map +1 -0
  87. package/package.json +74 -4
@@ -0,0 +1,413 @@
1
+ import { PDFDocument } from 'pdf-lib';
2
+ import { browserService } from './browser.js';
3
+ import { storageService } from './storage.js';
4
+ export class EInvoiceService {
5
+ static instance = null;
6
+ constructor() { }
7
+ static getInstance() {
8
+ if (!EInvoiceService.instance) {
9
+ EInvoiceService.instance = new EInvoiceService();
10
+ }
11
+ return EInvoiceService.instance;
12
+ }
13
+ escapeXml(unsafe) {
14
+ return unsafe
15
+ .replace(/&/g, '&')
16
+ .replace(/</g, '&lt;')
17
+ .replace(/>/g, '&gt;')
18
+ .replace(/"/g, '&quot;')
19
+ .replace(/'/g, '&apos;');
20
+ }
21
+ formatDate102(dateStr) {
22
+ if (!dateStr) {
23
+ const now = new Date();
24
+ const y = now.getFullYear();
25
+ const m = String(now.getMonth() + 1).padStart(2, '0');
26
+ const d = String(now.getDate()).padStart(2, '0');
27
+ return `${y}${m}${d}`;
28
+ }
29
+ const clean = dateStr.replace(/[^0-9]/g, '');
30
+ if (clean.length === 8)
31
+ return clean;
32
+ const parsed = new Date(dateStr);
33
+ if (!isNaN(parsed.getTime())) {
34
+ const y = parsed.getFullYear();
35
+ const m = String(parsed.getMonth() + 1).padStart(2, '0');
36
+ const d = String(parsed.getDate()).padStart(2, '0');
37
+ return `${y}${m}${d}`;
38
+ }
39
+ return new Date().toISOString().slice(0, 10).replace(/-/g, '');
40
+ }
41
+ /**
42
+ * Generates a fully compliant Factur-X / ZUGFeRD / EN 16931 electronic invoice XML (CII syntax).
43
+ */
44
+ generateFacturXXml(data) {
45
+ const currency = (data.currency || 'EUR').toUpperCase();
46
+ const issueDate102 = this.formatDate102(data.issueDate);
47
+ const dueDate102 = data.dueDate ? this.formatDate102(data.dueDate) : undefined;
48
+ const profileId = data.profile === 'EN16931'
49
+ ? 'urn:cen.eu:en16931:2017'
50
+ : 'urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic';
51
+ // Calculate line totals and taxes
52
+ let netTotal = 0;
53
+ const taxBuckets = new Map();
54
+ const lineItemsXml = data.lineItems
55
+ .map((item, idx) => {
56
+ const lineNum = idx + 1;
57
+ const qty = item.quantity;
58
+ const price = item.unitPrice;
59
+ const lineNet = Number((qty * price).toFixed(2));
60
+ const vatRate = item.vatRate !== undefined ? item.vatRate : 25; // default 25% in DK / typical EU
61
+ netTotal += lineNet;
62
+ // Bucket taxes
63
+ const currentBucket = taxBuckets.get(vatRate) || { taxableAmount: 0, taxAmount: 0 };
64
+ currentBucket.taxableAmount += lineNet;
65
+ currentBucket.taxAmount += Number(((lineNet * vatRate) / 100).toFixed(2));
66
+ taxBuckets.set(vatRate, currentBucket);
67
+ return `
68
+ <ram:IncludedSupplyChainTradeLineItem>
69
+ <ram:AssociatedDocumentLineDocument>
70
+ <ram:LineID>${lineNum}</ram:LineID>
71
+ </ram:AssociatedDocumentLineDocument>
72
+ <ram:SpecifiedTradeProduct>
73
+ <ram:Name>${this.escapeXml(item.name)}</ram:Name>
74
+ </ram:SpecifiedTradeProduct>
75
+ <ram:SpecifiedLineTradeAgreement>
76
+ <ram:NetPriceProductTradePrice>
77
+ <ram:ChargeAmount currencyID="${currency}">${price.toFixed(2)}</ram:ChargeAmount>
78
+ </ram:NetPriceProductTradePrice>
79
+ </ram:SpecifiedLineTradeAgreement>
80
+ <ram:SpecifiedLineTradeDelivery>
81
+ <ram:BilledQuantity unitCode="C62">${qty}</ram:BilledQuantity>
82
+ </ram:SpecifiedLineTradeDelivery>
83
+ <ram:SpecifiedLineTradeSettlement>
84
+ <ram:ApplicableTradeTax>
85
+ <ram:TypeCode>VAT</ram:TypeCode>
86
+ <ram:CategoryCode>${vatRate === 0 ? 'E' : 'S'}</ram:CategoryCode>
87
+ <ram:RateApplicablePercent>${vatRate.toFixed(2)}</ram:RateApplicablePercent>
88
+ </ram:ApplicableTradeTax>
89
+ <ram:SpecifiedTradeSettlementLineMonetarySummation>
90
+ <ram:LineTotalAmount currencyID="${currency}">${lineNet.toFixed(2)}</ram:LineTotalAmount>
91
+ </ram:SpecifiedTradeSettlementLineMonetarySummation>
92
+ </ram:SpecifiedLineTradeSettlement>
93
+ </ram:IncludedSupplyChainTradeLineItem>`;
94
+ })
95
+ .join('\n');
96
+ let totalTaxAmount = 0;
97
+ const taxSummaryXml = Array.from(taxBuckets.entries())
98
+ .map(([rate, bucket]) => {
99
+ totalTaxAmount += bucket.taxAmount;
100
+ return `
101
+ <ram:ApplicableTradeTax>
102
+ <ram:CalculatedAmount currencyID="${currency}">${bucket.taxAmount.toFixed(2)}</ram:CalculatedAmount>
103
+ <ram:TypeCode>VAT</ram:TypeCode>
104
+ <ram:BasisAmount currencyID="${currency}">${bucket.taxableAmount.toFixed(2)}</ram:BasisAmount>
105
+ <ram:CategoryCode>${rate === 0 ? 'E' : 'S'}</ram:CategoryCode>
106
+ <ram:RateApplicablePercent>${rate.toFixed(2)}</ram:RateApplicablePercent>
107
+ </ram:ApplicableTradeTax>`;
108
+ })
109
+ .join('\n');
110
+ const grossTotal = Number((netTotal + totalTaxAmount).toFixed(2));
111
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
112
+ <rsm:CrossIndustryInvoice
113
+ xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
114
+ xmlns:ccts="urn:un:unece:uncefact:documentation:standard:CoreComponentsTechnicalSpecification:2"
115
+ xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
116
+ xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
117
+ xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100">
118
+ <rsm:ExchangedDocumentContext>
119
+ <ram:GuidelineSpecifiedDocumentContextParameter>
120
+ <ram:ID>${profileId}</ram:ID>
121
+ </ram:GuidelineSpecifiedDocumentContextParameter>
122
+ </rsm:ExchangedDocumentContext>
123
+ <rsm:ExchangedDocument>
124
+ <ram:ID>${this.escapeXml(data.invoiceNumber)}</ram:ID>
125
+ <ram:TypeCode>380</ram:TypeCode>
126
+ <ram:IssueDateTime>
127
+ <udt:DateTimeString format="102">${issueDate102}</udt:DateTimeString>
128
+ </ram:IssueDateTime>
129
+ ${data.notes ? `<ram:IncludedNote><ram:Content>${this.escapeXml(data.notes)}</ram:Content></ram:IncludedNote>` : ''}
130
+ </rsm:ExchangedDocument>
131
+ <rsm:SupplyChainTradeTransaction>
132
+ ${lineItemsXml}
133
+ <ram:ApplicableHeaderTradeAgreement>
134
+ <ram:SellerTradeParty>
135
+ <ram:Name>${this.escapeXml(data.seller.name)}</ram:Name>
136
+ ${data.seller.street || data.seller.city
137
+ ? `<ram:PostalTradeAddress>
138
+ ${data.seller.postcode ? `<ram:PostcodeCode>${this.escapeXml(data.seller.postcode)}</ram:PostcodeCode>` : ''}
139
+ ${data.seller.street ? `<ram:LineOne>${this.escapeXml(data.seller.street)}</ram:LineOne>` : ''}
140
+ ${data.seller.city ? `<ram:CityName>${this.escapeXml(data.seller.city)}</ram:CityName>` : ''}
141
+ <ram:CountryID>${(data.seller.country || 'DK').toUpperCase()}</ram:CountryID>
142
+ </ram:PostalTradeAddress>`
143
+ : ''}
144
+ ${data.seller.vatId
145
+ ? `<ram:SpecifiedTaxRegistration>
146
+ <ram:ID schemeID="VA">${this.escapeXml(data.seller.vatId)}</ram:ID>
147
+ </ram:SpecifiedTaxRegistration>`
148
+ : ''}
149
+ </ram:SellerTradeParty>
150
+ <ram:BuyerTradeParty>
151
+ <ram:Name>${this.escapeXml(data.buyer.name)}</ram:Name>
152
+ ${data.buyer.street || data.buyer.city
153
+ ? `<ram:PostalTradeAddress>
154
+ ${data.buyer.postcode ? `<ram:PostcodeCode>${this.escapeXml(data.buyer.postcode)}</ram:PostcodeCode>` : ''}
155
+ ${data.buyer.street ? `<ram:LineOne>${this.escapeXml(data.buyer.street)}</ram:LineOne>` : ''}
156
+ ${data.buyer.city ? `<ram:CityName>${this.escapeXml(data.buyer.city)}</ram:CityName>` : ''}
157
+ <ram:CountryID>${(data.buyer.country || 'DK').toUpperCase()}</ram:CountryID>
158
+ </ram:PostalTradeAddress>`
159
+ : ''}
160
+ ${data.buyer.vatId
161
+ ? `<ram:SpecifiedTaxRegistration>
162
+ <ram:ID schemeID="VA">${this.escapeXml(data.buyer.vatId)}</ram:ID>
163
+ </ram:SpecifiedTaxRegistration>`
164
+ : ''}
165
+ </ram:BuyerTradeParty>
166
+ </ram:ApplicableHeaderTradeAgreement>
167
+ <ram:ApplicableHeaderTradeDelivery>
168
+ <ram:ActualDeliverySupplyChainEvent>
169
+ <ram:OccurrenceDateTime>
170
+ <udt:DateTimeString format="102">${issueDate102}</udt:DateTimeString>
171
+ </ram:OccurrenceDateTime>
172
+ </ram:ActualDeliverySupplyChainEvent>
173
+ </ram:ApplicableHeaderTradeDelivery>
174
+ <ram:ApplicableHeaderTradeSettlement>
175
+ <ram:InvoiceCurrencyCode>${currency}</ram:InvoiceCurrencyCode>
176
+ ${data.paymentIban
177
+ ? `<ram:SpecifiedTradeSettlementPaymentMeans>
178
+ <ram:TypeCode>58</ram:TypeCode>
179
+ <ram:PayeePartyCreditorFinancialAccount>
180
+ <ram:IBANID>${this.escapeXml(data.paymentIban.replace(/\s+/g, ''))}</ram:IBANID>
181
+ </ram:PayeePartyCreditorFinancialAccount>
182
+ ${data.paymentBic
183
+ ? `<ram:PayeeSpecifiedCreditorFinancialInstitution>
184
+ <ram:BICID>${this.escapeXml(data.paymentBic.replace(/\s+/g, ''))}</ram:BICID>
185
+ </ram:PayeeSpecifiedCreditorFinancialInstitution>`
186
+ : ''}
187
+ </ram:SpecifiedTradeSettlementPaymentMeans>`
188
+ : ''}
189
+ ${taxSummaryXml}
190
+ ${dueDate102
191
+ ? `<ram:SpecifiedTradePaymentTerms>
192
+ <ram:DueDateDateTime>
193
+ <udt:DateTimeString format="102">${dueDate102}</udt:DateTimeString>
194
+ </ram:DueDateDateTime>
195
+ </ram:SpecifiedTradePaymentTerms>`
196
+ : ''}
197
+ <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
198
+ <ram:LineTotalAmount currencyID="${currency}">${netTotal.toFixed(2)}</ram:LineTotalAmount>
199
+ <ram:TaxBasisTotalAmount currencyID="${currency}">${netTotal.toFixed(2)}</ram:TaxBasisTotalAmount>
200
+ <ram:TaxTotalAmount currencyID="${currency}">${totalTaxAmount.toFixed(2)}</ram:TaxTotalAmount>
201
+ <ram:GrandTotalAmount currencyID="${currency}">${grossTotal.toFixed(2)}</ram:GrandTotalAmount>
202
+ <ram:DuePayableAmount currencyID="${currency}">${grossTotal.toFixed(2)}</ram:DuePayableAmount>
203
+ </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
204
+ </ram:ApplicableHeaderTradeSettlement>
205
+ </rsm:SupplyChainTradeTransaction>
206
+ </rsm:CrossIndustryInvoice>`;
207
+ return xml;
208
+ }
209
+ /**
210
+ * Attaches the Factur-X / ZUGFeRD XML document directly into the PDF document catalog,
211
+ * producing a compliant Hybrid PDF/A-3 electronic invoice.
212
+ */
213
+ async embedFacturXXml(pdfBuffer, xmlString) {
214
+ const pdfDoc = await PDFDocument.load(pdfBuffer);
215
+ // Attach factur-x.xml into the PDF EmbeddedFiles tree
216
+ await pdfDoc.attach(Buffer.from(xmlString, 'utf-8'), 'factur-x.xml', {
217
+ mimeType: 'text/xml',
218
+ description: 'Factur-X / ZUGFeRD E-Invoice XML data (EN 16931)',
219
+ creationDate: new Date(),
220
+ modificationDate: new Date(),
221
+ });
222
+ // Set standard PDF document title and metadata
223
+ pdfDoc.setTitle('Electronic Invoice');
224
+ pdfDoc.setProducer('Tailprint E-Invoice Engine (https://tailprint.com)');
225
+ // Save with useObjectStreams: false for maximum compatibility with ERP systems and legacy XRef parsers
226
+ const modifiedBytes = await pdfDoc.save({ useObjectStreams: false });
227
+ return Buffer.from(modifiedBytes);
228
+ }
229
+ /**
230
+ * Generates both a pixel-perfect visual HTML invoice with Tailwind CSS
231
+ * and attaches the Factur-X XML into the resulting PDF.
232
+ */
233
+ async generateHybridInvoice(data) {
234
+ const currency = (data.currency || 'EUR').toUpperCase();
235
+ const xml = this.generateFacturXXml(data);
236
+ // Calculate totals
237
+ let netTotal = 0;
238
+ let taxTotal = 0;
239
+ for (const item of data.lineItems) {
240
+ const lineNet = Number((item.quantity * item.unitPrice).toFixed(2));
241
+ const vatRate = item.vatRate !== undefined ? item.vatRate : 25;
242
+ netTotal += lineNet;
243
+ taxTotal += Number(((lineNet * vatRate) / 100).toFixed(2));
244
+ }
245
+ const grossTotal = Number((netTotal + taxTotal).toFixed(2));
246
+ // Compile beautiful Tailwind HTML invoice
247
+ const html = `<!DOCTYPE html>
248
+ <html lang="en">
249
+ <head>
250
+ <meta charset="utf-8" />
251
+ <script src="https://cdn.tailwindcss.com"></script>
252
+ <style>
253
+ @page {
254
+ size: A4;
255
+ margin: 15mm;
256
+ }
257
+ body {
258
+ -webkit-print-color-adjust: exact;
259
+ }
260
+ </style>
261
+ </head>
262
+ <body class="bg-white text-slate-800 p-8 font-sans antialiased max-w-4xl mx-auto">
263
+
264
+ <!-- Header: Brand & Status -->
265
+ <div class="flex justify-between items-start border-b border-slate-200 pb-8">
266
+ <div>
267
+ <div class="flex items-center space-x-2">
268
+ <span class="text-2xl font-black text-slate-900">&lt;tail<span class="text-indigo-600">print</span>/&gt;</span>
269
+ </div>
270
+ <p class="text-xs text-slate-500 mt-1">Official Hybrid Electronic Invoice (EN 16931 / Factur-X)</p>
271
+ </div>
272
+ <div class="text-right">
273
+ <span class="text-xs font-mono font-bold px-2.5 py-1 rounded bg-indigo-50 text-indigo-700 border border-indigo-200">
274
+ 🇪🇺 FACTUR-X / ZUGFeRD COMPLIANT
275
+ </span>
276
+ <h1 class="text-2xl font-black text-slate-900 mt-2">INVOICE</h1>
277
+ <p class="text-xs font-mono text-slate-500 font-semibold"># ${data.invoiceNumber}</p>
278
+ </div>
279
+ </div>
280
+
281
+ <!-- Parties: Seller & Buyer Grid -->
282
+ <div class="grid grid-cols-2 gap-8 my-8 text-xs">
283
+ <div class="p-4 rounded-xl bg-slate-50 border border-slate-100">
284
+ <span class="font-bold text-slate-400 uppercase tracking-wider text-[10px] block mb-1">From (Seller)</span>
285
+ <h3 class="font-bold text-slate-900 text-sm">${data.seller.name}</h3>
286
+ ${data.seller.street ? `<p class="text-slate-600 mt-0.5">${data.seller.street}</p>` : ''}
287
+ ${data.seller.postcode || data.seller.city ? `<p class="text-slate-600">${data.seller.postcode || ''} ${data.seller.city || ''}, ${data.seller.country}</p>` : `<p class="text-slate-600">${data.seller.country}</p>`}
288
+ ${data.seller.vatId ? `<p class="text-slate-500 mt-2 font-mono">VAT ID / CVR: <strong class="text-slate-800">${data.seller.vatId}</strong></p>` : ''}
289
+ </div>
290
+
291
+ <div class="p-4 rounded-xl bg-slate-50 border border-slate-100">
292
+ <span class="font-bold text-slate-400 uppercase tracking-wider text-[10px] block mb-1">Bill To (Buyer)</span>
293
+ <h3 class="font-bold text-slate-900 text-sm">${data.buyer.name}</h3>
294
+ ${data.buyer.street ? `<p class="text-slate-600 mt-0.5">${data.buyer.street}</p>` : ''}
295
+ ${data.buyer.postcode || data.buyer.city ? `<p class="text-slate-600">${data.buyer.postcode || ''} ${data.buyer.city || ''}, ${data.buyer.country}</p>` : `<p class="text-slate-600">${data.buyer.country}</p>`}
296
+ ${data.buyer.vatId ? `<p class="text-slate-500 mt-2 font-mono">VAT ID / CVR: <strong class="text-slate-800">${data.buyer.vatId}</strong></p>` : ''}
297
+ </div>
298
+ </div>
299
+
300
+ <!-- Metadata Strip -->
301
+ <div class="grid grid-cols-4 gap-4 p-3.5 bg-slate-100/70 rounded-xl text-xs mb-8">
302
+ <div>
303
+ <span class="text-slate-400 text-[10px] uppercase font-bold block">Invoice Date</span>
304
+ <strong class="text-slate-900">${data.issueDate || new Date().toISOString().slice(0, 10)}</strong>
305
+ </div>
306
+ <div>
307
+ <span class="text-slate-400 text-[10px] uppercase font-bold block">Payment Due</span>
308
+ <strong class="text-slate-900">${data.dueDate || 'Upon Receipt'}</strong>
309
+ </div>
310
+ <div>
311
+ <span class="text-slate-400 text-[10px] uppercase font-bold block">Currency</span>
312
+ <strong class="text-slate-900">${currency}</strong>
313
+ </div>
314
+ <div>
315
+ <span class="text-slate-400 text-[10px] uppercase font-bold block">Profile</span>
316
+ <strong class="text-slate-900 font-mono text-[11px]">${data.profile || 'BASIC (EN 16931)'}</strong>
317
+ </div>
318
+ </div>
319
+
320
+ <!-- Line Items Table -->
321
+ <table class="w-full text-left text-xs mb-8">
322
+ <thead>
323
+ <tr class="border-b border-slate-200 text-slate-400 font-bold uppercase tracking-wider text-[10px]">
324
+ <th class="py-2.5">Description</th>
325
+ <th class="py-2.5 text-right">Qty</th>
326
+ <th class="py-2.5 text-right">Unit Price</th>
327
+ <th class="py-2.5 text-right">VAT</th>
328
+ <th class="py-2.5 text-right">Amount</th>
329
+ </tr>
330
+ </thead>
331
+ <tbody class="divide-y divide-slate-100">
332
+ ${data.lineItems
333
+ .map((item) => {
334
+ const lineTotal = item.quantity * item.unitPrice;
335
+ return `
336
+ <tr>
337
+ <td class="py-3 font-semibold text-slate-800">${item.name}</td>
338
+ <td class="py-3 text-right text-slate-600">${item.quantity}</td>
339
+ <td class="py-3 text-right font-mono text-slate-600">${item.unitPrice.toFixed(2)}</td>
340
+ <td class="py-3 text-right font-mono text-slate-500">${item.vatRate !== undefined ? item.vatRate : 25}%</td>
341
+ <td class="py-3 text-right font-mono font-bold text-slate-900">${lineTotal.toFixed(2)} ${currency}</td>
342
+ </tr>`;
343
+ })
344
+ .join('')}
345
+ </tbody>
346
+ </table>
347
+
348
+ <!-- Totals Summary & Payment Info -->
349
+ <div class="flex justify-between items-start pt-4 border-t border-slate-200 text-xs">
350
+ <div class="max-w-xs space-y-2">
351
+ ${data.paymentIban
352
+ ? `
353
+ <div class="p-3 bg-slate-50 rounded-xl border border-slate-100 text-[11px]">
354
+ <span class="font-bold text-slate-700 block mb-1">Direct Bank Transfer</span>
355
+ <p class="font-mono text-slate-600">IBAN: <strong class="text-slate-900">${data.paymentIban}</strong></p>
356
+ ${data.paymentBic ? `<p class="font-mono text-slate-600">BIC / SWIFT: ${data.paymentBic}</p>` : ''}
357
+ </div>`
358
+ : ''}
359
+ ${data.notes ? `<p class="text-slate-500 italic text-[11px]">${data.notes}</p>` : ''}
360
+ </div>
361
+
362
+ <div class="w-64 space-y-2 text-right">
363
+ <div class="flex justify-between text-slate-600">
364
+ <span>Subtotal (Net):</span>
365
+ <span class="font-mono font-semibold">${netTotal.toFixed(2)} ${currency}</span>
366
+ </div>
367
+ <div class="flex justify-between text-slate-600">
368
+ <span>Total VAT / Tax:</span>
369
+ <span class="font-mono font-semibold">${taxTotal.toFixed(2)} ${currency}</span>
370
+ </div>
371
+ <div class="flex justify-between border-t border-slate-200 pt-2 text-sm font-black text-slate-900">
372
+ <span>Total Due:</span>
373
+ <span class="font-mono text-indigo-600 text-base">${grossTotal.toFixed(2)} ${currency}</span>
374
+ </div>
375
+ </div>
376
+ </div>
377
+
378
+ <!-- Factur-X / PDF Attachment Banner -->
379
+ <div class="mt-12 p-3 rounded-lg bg-slate-50 border border-slate-200 text-center text-[10px] text-slate-400">
380
+ This document contains a valid, embedded <strong class="text-slate-600">factur-x.xml</strong> file conforming to the European standard EN 16931. It can be automatically processed by accounting software (SAP, DATEV, Dinero, Billy, Chorus Pro).
381
+ </div>
382
+
383
+ </body>
384
+ </html>`;
385
+ // 1. Render visual PDF invoice
386
+ const visualPdfBuffer = await browserService.renderPdf({
387
+ html,
388
+ format: 'A4',
389
+ print_background: true,
390
+ });
391
+ // 2. Embed the Factur-X XML directly into the PDF
392
+ const hybridPdfBuffer = await this.embedFacturXXml(visualPdfBuffer, xml);
393
+ // 3. Save to hosted CDN
394
+ const pdfStored = await storageService.saveFile(hybridPdfBuffer, {
395
+ mimeType: 'application/pdf',
396
+ extension: 'pdf',
397
+ filename: `factur-x-${data.invoiceNumber}.pdf`,
398
+ expiresIn: data.expiresIn,
399
+ });
400
+ return {
401
+ pdfBuffer: hybridPdfBuffer,
402
+ xml,
403
+ invoiceNumber: data.invoiceNumber,
404
+ currency,
405
+ netTotal,
406
+ taxTotal,
407
+ grossTotal,
408
+ pdfStored,
409
+ };
410
+ }
411
+ }
412
+ export const eInvoiceService = EInvoiceService.getInstance();
413
+ //# sourceMappingURL=eInvoice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eInvoice.js","sourceRoot":"","sources":["../../src/services/eInvoice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export declare class EmailService {
2
+ private static instance;
3
+ private transporter;
4
+ private constructor();
5
+ static getInstance(): EmailService;
6
+ /**
7
+ * Sends a styled welcome email containing the Developer Free Sandbox API key.
8
+ */
9
+ sendFreeApiKeyEmail(params: {
10
+ to: string;
11
+ apiKey: string;
12
+ monthlyLimit: number;
13
+ }): Promise<boolean>;
14
+ /**
15
+ * Sends an official receipt and API key email upon Stripe subscription purchase.
16
+ */
17
+ sendPaidApiKeyEmail(params: {
18
+ to: string;
19
+ apiKey: string;
20
+ planName: string;
21
+ monthlyLimit: number;
22
+ }): Promise<boolean>;
23
+ }
24
+ export declare const emailService: EmailService;
@@ -0,0 +1,177 @@
1
+ import nodemailer from 'nodemailer';
2
+ import { env } from '../config/env.js';
3
+ export class EmailService {
4
+ static instance;
5
+ transporter = null;
6
+ constructor() {
7
+ if (env.SMTP_USER && env.SMTP_PASS) {
8
+ this.transporter = nodemailer.createTransport({
9
+ host: env.SMTP_HOST,
10
+ port: env.SMTP_PORT,
11
+ secure: env.SMTP_PORT === 465,
12
+ auth: {
13
+ user: env.SMTP_USER,
14
+ pass: env.SMTP_PASS,
15
+ },
16
+ pool: true,
17
+ maxConnections: 3,
18
+ });
19
+ }
20
+ }
21
+ static getInstance() {
22
+ if (!EmailService.instance) {
23
+ EmailService.instance = new EmailService();
24
+ }
25
+ return EmailService.instance;
26
+ }
27
+ /**
28
+ * Sends a styled welcome email containing the Developer Free Sandbox API key.
29
+ */
30
+ async sendFreeApiKeyEmail(params) {
31
+ if (!this.transporter) {
32
+ console.warn('[EmailService] SMTP transporter not configured, skipping email.');
33
+ return false;
34
+ }
35
+ const { to, apiKey, monthlyLimit } = params;
36
+ const dashboardUrl = `${env.APP_URL}/dashboard`;
37
+ const docsUrl = `${env.APP_URL}/docs`;
38
+ const htmlContent = `
39
+ <!DOCTYPE html>
40
+ <html>
41
+ <head>
42
+ <meta charset="utf-8">
43
+ <title>Your Tailprint API Key</title>
44
+ <style>
45
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background-color: #020617; color: #f8fafc; margin: 0; padding: 0; }
46
+ .container { max-width: 600px; margin: 0 auto; padding: 40px 20px; }
47
+ .card { background-color: #0f172a; border: 1px solid #1e293b; border-radius: 16px; padding: 32px; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5); }
48
+ .logo { display: inline-flex; align-items: center; font-size: 20px; font-weight: 800; color: #ffffff; text-decoration: none; margin-bottom: 24px; }
49
+ .badge { display: inline-block; background-color: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.3); font-size: 11px; font-weight: 700; padding: 3px 8px; border-radius: 9999px; text-transform: uppercase; margin-bottom: 16px; }
50
+ h1 { font-size: 24px; font-weight: 900; color: #ffffff; margin-top: 0; margin-bottom: 8px; letter-spacing: -0.025em; }
51
+ p { font-size: 14px; line-height: 1.6; color: #94a3b8; margin: 12px 0; }
52
+ .key-box { background-color: #020617; border: 1px solid #10b981; border-radius: 10px; padding: 14px; margin: 20px 0; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #34d399; word-break: break-all; font-weight: 600; }
53
+ .code-block { background-color: #020617; border: 1px solid #1e293b; border-radius: 10px; padding: 14px; margin: 16px 0; font-family: monospace; font-size: 11px; color: #cbd5e1; overflow-x: auto; line-height: 1.5; }
54
+ .btn { display: inline-block; background-color: #4f46e5; color: #ffffff !important; font-weight: 700; font-size: 13px; text-decoration: none; padding: 12px 24px; border-radius: 10px; margin-top: 16px; text-align: center; }
55
+ .footer { margin-top: 32px; text-align: center; font-size: 11px; color: #64748b; line-height: 1.6; }
56
+ .footer a { color: #818cf8; text-decoration: none; }
57
+ </style>
58
+ </head>
59
+ <body>
60
+ <div class="container">
61
+ <div class="card">
62
+ <div class="logo">⚡ Tailprint</div>
63
+ <div><span class="badge">Developer Sandbox Active</span></div>
64
+ <h1>Your Free API Key is Ready</h1>
65
+ <p>Welcome to <strong>Tailprint</strong>. You have been provisioned <strong>${monthlyLimit.toLocaleString()} free renders per month</strong> for vector PDFs, Open Graph images, and clean Markdown scraping.</p>
66
+
67
+ <p style="font-size: 12px; font-weight: 600; color: #cbd5e1; margin-bottom: 6px;">Your Private API Key:</p>
68
+ <div class="key-box">${apiKey}</div>
69
+
70
+ <p style="font-size: 12px; font-weight: 600; color: #cbd5e1; margin-bottom: 6px;">Instant cURL Test (generates test.pdf):</p>
71
+ <div class="code-block">curl -X POST ${env.APP_URL}/v1/render/pdf \\<br>
72
+ -H "Authorization: Bearer ${apiKey}" \\<br>
73
+ -H "Content-Type: application/json" \\<br>
74
+ -d '{"html":"&lt;div class=\\"p-10 bg-slate-900 text-white font-bold\\"&gt;Hello Tailprint&lt;/div&gt;"}' \\<br>
75
+ --output test.pdf</div>
76
+
77
+ <a href="${dashboardUrl}" class="btn">Open Customer Dashboard →</a>
78
+
79
+ <p style="font-size: 12px; color: #64748b; margin-top: 24px;">
80
+ Need interactive documentation? Explore our <a href="${docsUrl}" style="color: #818cf8;">Scalar API Reference</a>.<br>
81
+ Using Claude Desktop or Cursor? Connect directly with our <a href="${env.APP_URL}/#mcp" style="color: #818cf8;">Model Context Protocol (MCP) server</a>.
82
+ </p>
83
+ </div>
84
+
85
+ <div class="footer">
86
+ <p>© 2026 Tailprint (tailprint.com). Fastify & Chromium Cloud Rendering.<br>
87
+ You received this because you claimed a free developer key at tailprint.com.</p>
88
+ </div>
89
+ </div>
90
+ </body>
91
+ </html>
92
+ `;
93
+ try {
94
+ const info = await this.transporter.sendMail({
95
+ from: env.SMTP_FROM,
96
+ to,
97
+ subject: '⚡ Your Tailprint API Key (100 Free Renders / mo)',
98
+ text: `Welcome to Tailprint!\n\nYour Developer Free API Key is:\n${apiKey}\n\nMonthly limit: ${monthlyLimit} renders.\n\nOpen your dashboard: ${dashboardUrl}\nDocumentation: ${docsUrl}`,
99
+ html: htmlContent,
100
+ });
101
+ console.log(`[EmailService] Free API key email dispatched to ${to} (MessageId: ${info.messageId})`);
102
+ return true;
103
+ }
104
+ catch (err) {
105
+ console.error(`[EmailService] Failed to send email to ${to}:`, err.message);
106
+ return false;
107
+ }
108
+ }
109
+ /**
110
+ * Sends an official receipt and API key email upon Stripe subscription purchase.
111
+ */
112
+ async sendPaidApiKeyEmail(params) {
113
+ if (!this.transporter) {
114
+ return false;
115
+ }
116
+ const { to, apiKey, planName, monthlyLimit } = params;
117
+ const dashboardUrl = `${env.APP_URL}/dashboard`;
118
+ const htmlContent = `
119
+ <!DOCTYPE html>
120
+ <html>
121
+ <head>
122
+ <meta charset="utf-8">
123
+ <title>Welcome to Tailprint ${planName}</title>
124
+ <style>
125
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background-color: #020617; color: #f8fafc; margin: 0; padding: 0; }
126
+ .container { max-width: 600px; margin: 0 auto; padding: 40px 20px; }
127
+ .card { background-color: #0f172a; border: 1px solid #1e293b; border-radius: 16px; padding: 32px; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5); }
128
+ .logo { display: inline-flex; align-items: center; font-size: 20px; font-weight: 800; color: #ffffff; text-decoration: none; margin-bottom: 24px; }
129
+ .badge { display: inline-block; background-color: rgba(99, 102, 241, 0.15); color: #818cf8; border: 1px solid rgba(99, 102, 241, 0.3); font-size: 11px; font-weight: 700; padding: 3px 8px; border-radius: 9999px; text-transform: uppercase; margin-bottom: 16px; }
130
+ h1 { font-size: 24px; font-weight: 900; color: #ffffff; margin-top: 0; margin-bottom: 8px; letter-spacing: -0.025em; }
131
+ p { font-size: 14px; line-height: 1.6; color: #94a3b8; margin: 12px 0; }
132
+ .key-box { background-color: #020617; border: 1px solid #6366f1; border-radius: 10px; padding: 14px; margin: 20px 0; font-family: monospace; font-size: 13px; color: #a5b4fc; word-break: break-all; font-weight: 600; }
133
+ .btn { display: inline-block; background-color: #4f46e5; color: #ffffff !important; font-weight: 700; font-size: 13px; text-decoration: none; padding: 12px 24px; border-radius: 10px; margin-top: 16px; text-align: center; }
134
+ .footer { margin-top: 32px; text-align: center; font-size: 11px; color: #64748b; line-height: 1.6; }
135
+ .footer a { color: #818cf8; text-decoration: none; }
136
+ </style>
137
+ </head>
138
+ <body>
139
+ <div class="container">
140
+ <div class="card">
141
+ <div class="logo">⚡ Tailprint</div>
142
+ <div><span class="badge">${planName} Tier Subscribed</span></div>
143
+ <h1>Thank you for your purchase!</h1>
144
+ <p>Your subscription is active with <strong>${monthlyLimit.toLocaleString()} renders per month</strong>.</p>
145
+
146
+ <p style="font-size: 12px; font-weight: 600; color: #cbd5e1; margin-bottom: 6px;">Your Production API Key:</p>
147
+ <div class="key-box">${apiKey}</div>
148
+
149
+ <a href="${dashboardUrl}" class="btn">Manage Subscription & View Usage →</a>
150
+ </div>
151
+
152
+ <div class="footer">
153
+ <p>© 2026 Tailprint (tailprint.com).<br>Manage subscription, update VAT/tax number, or cancel anytime from the dashboard.</p>
154
+ </div>
155
+ </div>
156
+ </body>
157
+ </html>
158
+ `;
159
+ try {
160
+ const info = await this.transporter.sendMail({
161
+ from: env.SMTP_FROM,
162
+ to,
163
+ subject: `⚡ Welcome to Tailprint ${planName} (Your Production API Key)`,
164
+ text: `Thank you for choosing Tailprint ${planName}!\n\nYour API key is:\n${apiKey}\n\nMonthly limit: ${monthlyLimit} renders.\n\nDashboard: ${dashboardUrl}`,
165
+ html: htmlContent,
166
+ });
167
+ console.log(`[EmailService] Paid API key email dispatched to ${to} (MessageId: ${info.messageId})`);
168
+ return true;
169
+ }
170
+ catch (err) {
171
+ console.error(`[EmailService] Failed to send paid key email to ${to}:`, err.message);
172
+ return false;
173
+ }
174
+ }
175
+ }
176
+ export const emailService = EmailService.getInstance();
177
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/services/email.ts"],"names":[],"mappings":"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"}