@reblu/site-blocks 0.12.1 → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @reblu/site-blocks
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 614e826: New drop-in `FullInvoiceFields` (REB-73): the shop checkout's «¿Quieres
8
+ factura completa?» opt-in with the buyer's identifier (Spanish NIF/NIE/CIF,
9
+ EU VAT id, passport or ID document, with its issuing country), name and
10
+ billing address — required, without a toggle, when the cart total is over
11
+ `ShopConfig.simplifiedInvoiceCapCents`. Controlled and hook-free; with
12
+ `isOverSimplifiedInvoiceCap` and `toCheckoutInvoiceFields`, which builds the
13
+ `wantsFullInvoice`/`billing` checkout body fields.
14
+ - 614e826: Shop checkout: no full-invoice opt-in on a shop that issues no invoices (REB-73).
15
+ - `ShopErrorCode` gains `FULL_INVOICE_UNAVAILABLE` (HTTP 422,
16
+ `details.field: 'wantsFullInvoice'`): `wantsFullInvoice: true` sent to a shop
17
+ whose invoicing is off (`ShopConfig.simplifiedInvoiceCapCents` is `null`) is
18
+ refused before any payment instead of succeeding without an invoice. Clients
19
+ that never send the opt-in are unaffected. The union is additive: a SITE
20
+ with an exhaustive `switch` over `ShopErrorCode` (or a
21
+ `Record<ShopErrorCode, …>`) must add the new member when it upgrades.
22
+ - `@reblu/site-blocks`: new `shopIssuesInvoices(config)`; `FullInvoiceFields`
23
+ takes `issuesInvoices` (default `true`) and renders nothing when it is
24
+ `false`, and `toCheckoutInvoiceFields(value, overCap, issuesInvoices)`
25
+ returns no opt-in and no billing block then. Default Spanish copy for the
26
+ new code in `getShopErrorMessage`.
27
+
28
+ - 614e826: Shop: the order's invoice and the simplified-invoice cap (REB-73).
29
+ - `ShopOrder` gains the optional `invoice: { number, url } | null`
30
+ (`ShopOrderInvoice`): once the sale invoice is issued, its number and the
31
+ shop's authenticated client-portal route that serves it — never a signed
32
+ URL, so the link does not expire; the buyer signs in to open it.
33
+ - `ShopConfig` gains `simplifiedInvoiceCapCents` (cents, never above the legal
34
+ 300000; `null` while the shop issues no invoices): above it, checkout needs
35
+ the buyer's identifier, name and billing address.
36
+ - `@reblu/site-blocks`: new drop-in `OrderInvoiceLink`, which
37
+ `OrderConfirmation` now renders under a confirmed order (labels
38
+ `invoiceLinkLabel`/`invoiceSignInHint`), and default Spanish copy for the
39
+ `BILLING_DATA_REQUIRED`/`INVOICING_NOT_READY` checkout codes in
40
+ `getShopErrorMessage`.
41
+
42
+ ### Patch Changes
43
+
44
+ - Updated dependencies [614e826]
45
+ - Updated dependencies [614e826]
46
+ - Updated dependencies [614e826]
47
+ - @reblu/site-contracts@1.15.0
48
+
3
49
  ## 0.12.1
4
50
 
5
51
  ### Patch Changes
package/README.md CHANGED
@@ -204,6 +204,8 @@ for the wiring steps and a full example app).
204
204
  | `CheckoutLoginPrompt` | `.` | `open`, `email`, `onEmailChange`, `onClose`, `onSubmitEmail`, `onContinueAsGuest` | Yes (renders `null` when `open` is false) |
205
205
  | `CheckoutForm` | `.` | `contact`, `onContactChange`, `discountCode`, `onDiscountCodeChange`, `onApplyDiscount`, `onRemoveDiscount`, `onSubmit` | Yes |
206
206
  | `OrderConfirmation` | `.` | `status`, `order`, `cartHref?`, `onRecheck?` | Yes |
207
+ | `OrderInvoiceLink` | `.` | `invoice` (`order.invoice`), `labels?` | Yes (renders `null` without an invoice) |
208
+ | `FullInvoiceFields` | `.` | `value`, `onChange`, `overCap`, `errors?`, `countries?`, `labels?` | Yes (controlled, no hooks) |
207
209
 
208
210
  **REB-837 breaking move**: `AddToCartButton` and `CartIcon` now carry real
209
211
  internal state (the confirmed/error timer, the bump-on-add guard) and ship
@@ -392,6 +394,78 @@ Every title/description is overridable via `labels` (see
392
394
  "`useShopOrderStatus` — the order-confirmation flow" for the full wiring
393
395
  recipe.
394
396
 
397
+ ### The order's invoice — `OrderInvoiceLink` (REB-73)
398
+
399
+ Once the sale invoice is issued, the order lookup (`client.shop.order(sessionId)`,
400
+ or `useShopOrderStatus`) carries `order.invoice: { number, url }` — `null`
401
+ while there is none. `url` is the shop's **authenticated** client-portal route,
402
+ never a signed URL: the buyer signs in to open the PDF, so the link never
403
+ expires and is safe to show anywhere. `OrderConfirmation` already renders it
404
+ under a confirmed order (`paid`/`found`/`timeout`), with its copy overridable
405
+ through `labels.invoiceLinkLabel` (`{number}` is replaced) and
406
+ `labels.invoiceSignInHint`. To place it elsewhere — an account page, a custom
407
+ confirmation — drop it in:
408
+
409
+ ```tsx
410
+ import { OrderInvoiceLink } from '@reblu/site-blocks'
411
+
412
+ // order: ShopOrder from client.shop.order(sessionId) / useShopOrderStatus
413
+ ;<OrderInvoiceLink invoice={order.invoice} />
414
+ // «Descargar la factura FAC-2026-0010» + «Te pediremos que inicies sesión para abrirla.»
415
+ ```
416
+
417
+ ### The full-invoice request — `FullInvoiceFields` (REB-73)
418
+
419
+ A full invoice needs the buyer's identifier — a Spanish NIF/NIE/CIF, or for a
420
+ foreign buyer an EU VAT id, a passport or an ID document of their country,
421
+ with the country that issued it — plus their name and billing address;
422
+ without it the invoice is simplified. Over `ShopConfig.simplifiedInvoiceCapCents`
423
+ a full invoice is the only legal one, so checkout refuses the sale without
424
+ them (`BILLING_DATA_REQUIRED`). Drop the fields into your checkout form and
425
+ send what they produce:
426
+
427
+ ```tsx
428
+ 'use client'
429
+ import { useState } from 'react'
430
+ import {
431
+ EMPTY_FULL_INVOICE_VALUE,
432
+ FullInvoiceFields,
433
+ isOverSimplifiedInvoiceCap,
434
+ shopIssuesInvoices,
435
+ toCheckoutInvoiceFields,
436
+ } from '@reblu/site-blocks'
437
+
438
+ const [invoice, setInvoice] = useState(EMPTY_FULL_INVOICE_VALUE)
439
+ const issuesInvoices = shopIssuesInvoices(shopConfig)
440
+ const overCap = isOverSimplifiedInvoiceCap(cartTotalCents, shopConfig.simplifiedInvoiceCapCents)
441
+
442
+ <FullInvoiceFields
443
+ value={invoice}
444
+ onChange={setInvoice}
445
+ overCap={overCap}
446
+ issuesInvoices={issuesInvoices}
447
+ />
448
+
449
+ // in onSubmit:
450
+ await client.shop.checkout({
451
+ ...checkoutBody,
452
+ ...toCheckoutInvoiceFields(invoice, overCap, issuesInvoices),
453
+ })
454
+ ```
455
+
456
+ A shop that issues no invoices (`ShopConfig.simplifiedInvoiceCapCents` is
457
+ `null`) offers no full invoice: pass `issuesInvoices={shopIssuesInvoices(config)}`
458
+ and the block renders nothing, and give the same flag to
459
+ `toCheckoutInvoiceFields(invoice, overCap, issuesInvoices)` so the body never
460
+ carries the opt-in.
461
+
462
+ Checkout refusals tied to invoicing come with their own default copy in
463
+ `getShopErrorMessage`: `BILLING_DATA_REQUIRED` (422 — a full invoice, or a
464
+ total over `ShopConfig.simplifiedInvoiceCapCents`, needs the buyer's
465
+ NIF/NIE or ID document, name and billing address), `INVOICING_NOT_READY`
466
+ (503) and `FULL_INVOICE_UNAVAILABLE` (422 — `wantsFullInvoice: true` sent to a
467
+ shop that issues no invoices).
468
+
395
469
  ## Product trust items
396
470
 
397
471
  `ProductTrustItems` renders the store-wide trust messages (shipping, returns,
package/dist/index.cjs CHANGED
@@ -26,13 +26,17 @@ __export(src_exports, {
26
26
  CartSummary: () => CartSummary,
27
27
  CheckoutForm: () => CheckoutForm,
28
28
  CheckoutLoginPrompt: () => CheckoutLoginPrompt,
29
+ DEFAULT_INVOICE_COUNTRIES: () => DEFAULT_INVOICE_COUNTRIES,
29
30
  DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE: () => DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE,
30
31
  DEFAULT_SHOP_ERROR_MESSAGES: () => DEFAULT_SHOP_ERROR_MESSAGES,
31
32
  DEFAULT_SHOP_PRICE_BANDS: () => DEFAULT_SHOP_PRICE_BANDS,
32
33
  DEFAULT_TRUST_ICONS: () => DEFAULT_TRUST_ICONS,
34
+ EMPTY_FULL_INVOICE_VALUE: () => EMPTY_FULL_INVOICE_VALUE,
33
35
  FaqAccordion: () => FaqAccordion,
36
+ FullInvoiceFields: () => FullInvoiceFields,
34
37
  MarkdownRenderer: () => MarkdownRenderer,
35
38
  OrderConfirmation: () => OrderConfirmation,
39
+ OrderInvoiceLink: () => OrderInvoiceLink,
36
40
  PageBlocks: () => PageBlocks,
37
41
  PageCardGrid: () => PageCardGrid,
38
42
  ParentPage: () => ParentPage,
@@ -49,10 +53,13 @@ __export(src_exports, {
49
53
  formatPrice: () => formatPrice,
50
54
  getShopErrorMessage: () => getShopErrorMessage,
51
55
  isAllowedYouTubeHost: () => isAllowedYouTubeHost,
56
+ isOverSimplifiedInvoiceCap: () => isOverSimplifiedInvoiceCap,
52
57
  markdownToSafeHtml: () => markdownToSafeHtml,
53
58
  readingTime: () => readingTime,
54
59
  sanitizeHtml: () => sanitizeHtml,
55
- shouldRenderShopFilters: () => shouldRenderShopFilters
60
+ shopIssuesInvoices: () => shopIssuesInvoices,
61
+ shouldRenderShopFilters: () => shouldRenderShopFilters,
62
+ toCheckoutInvoiceFields: () => toCheckoutInvoiceFields
56
63
  });
57
64
  module.exports = __toCommonJS(src_exports);
58
65
 
@@ -98,7 +105,11 @@ var DEFAULT_SHOP_ERROR_MESSAGES = {
98
105
  INVALID_INPUT: "Revisa los datos introducidos.",
99
106
  STORE_DISABLED: "La tienda no est\xE1 disponible en este momento.",
100
107
  PAYMENT_PROVIDER_ERROR: "No hemos podido conectar con la pasarela de pago. Int\xE9ntalo de nuevo.",
101
- INTERNAL_ERROR: "Ha ocurrido un error inesperado. Int\xE9ntalo de nuevo en unos minutos."
108
+ INTERNAL_ERROR: "Ha ocurrido un error inesperado. Int\xE9ntalo de nuevo en unos minutos.",
109
+ // REB-73 — checkout refusals before any payment.
110
+ BILLING_DATA_REQUIRED: "Para emitir tu factura necesitamos tu NIF/NIE o un documento de identificaci\xF3n, tu nombre y tu direcci\xF3n de facturaci\xF3n.",
111
+ INVOICING_NOT_READY: "No se puede completar la compra en este momento. Int\xE9ntalo m\xE1s tarde.",
112
+ FULL_INVOICE_UNAVAILABLE: "Esta tienda no emite facturas completas: contin\xFAa sin solicitarla."
102
113
  };
103
114
  var DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE = "No se ha podido completar la operaci\xF3n. Int\xE9ntalo de nuevo.";
104
115
  function getShopErrorMessage(code, overrides, fallback = DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE) {
@@ -14777,9 +14788,34 @@ function CheckoutForm({
14777
14788
  }
14778
14789
 
14779
14790
  // src/components/order-confirmation.tsx
14791
+ var import_utils20 = require("@reblu/site-ui/utils");
14792
+
14793
+ // src/components/order-invoice-link.tsx
14780
14794
  var import_utils19 = require("@reblu/site-ui/utils");
14781
14795
  var import_jsx_runtime18 = require("react/jsx-runtime");
14782
14796
  var DEFAULT_LABELS7 = {
14797
+ linkLabel: "Descargar la factura {number}",
14798
+ signInHint: "Te pediremos que inicies sesi\xF3n para abrirla."
14799
+ };
14800
+ function OrderInvoiceLink({ invoice, labels, className }) {
14801
+ if (!invoice) return null;
14802
+ const t = { ...DEFAULT_LABELS7, ...labels };
14803
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: (0, import_utils19.cn)("space-y-1 text-sm", className), "data-slot": "order-invoice-link", children: [
14804
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
14805
+ "a",
14806
+ {
14807
+ href: invoice.url,
14808
+ className: "font-semibold text-primary underline-offset-2 hover:underline",
14809
+ children: t.linkLabel.replace("{number}", invoice.number)
14810
+ }
14811
+ ),
14812
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-muted-foreground", children: t.signInHint })
14813
+ ] });
14814
+ }
14815
+
14816
+ // src/components/order-confirmation.tsx
14817
+ var import_jsx_runtime19 = require("react/jsx-runtime");
14818
+ var DEFAULT_LABELS8 = {
14783
14819
  pollingTitle: "Confirmando tu pedido\u2026",
14784
14820
  pollingDescription: "Estamos procesando el pago, un momento\u2026",
14785
14821
  foundTitle: "\xA1Pedido confirmado!",
@@ -14805,7 +14841,9 @@ var DEFAULT_LABELS7 = {
14805
14841
  notFoundDescription: "Comprueba el enlace o contacta con nosotros si el problema persiste.",
14806
14842
  errorTitle: "Ha ocurrido un error",
14807
14843
  errorDescription: "No hemos podido comprobar el estado de tu pedido.",
14808
- orderReferenceLabel: "Referencia del pedido"
14844
+ orderReferenceLabel: "Referencia del pedido",
14845
+ invoiceLinkLabel: "Descargar la factura {number}",
14846
+ invoiceSignInHint: "Te pediremos que inicies sesi\xF3n para abrirla."
14809
14847
  };
14810
14848
  function OrderConfirmation({
14811
14849
  status,
@@ -14818,7 +14856,7 @@ function OrderConfirmation({
14818
14856
  locale,
14819
14857
  className
14820
14858
  }) {
14821
- const t = { ...DEFAULT_LABELS7, ...labels };
14859
+ const t = { ...DEFAULT_LABELS8, ...labels };
14822
14860
  const isPolling = status === "polling" || status === "loading";
14823
14861
  const isFound = status === "found" || status === "paid";
14824
14862
  const { title, description } = (() => {
@@ -14840,48 +14878,55 @@ function OrderConfirmation({
14840
14878
  const showOrderReference = Boolean(order) && !showFullOrderSummary && (status === "refunded" || status === "partially_refunded" || status === "pending_timeout");
14841
14879
  const showSessionReference = !order && sessionId && (status === "timeout" || status === "not_found" || status === "pending_timeout");
14842
14880
  const showRecheck = (status === "pending_timeout" || status === "error") && onRecheck;
14843
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: (0, import_utils19.cn)("space-y-6 text-center", className), "data-slot": "order-confirmation", children: [
14844
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h1", { className: "text-3xl font-bold", children: title }),
14845
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-lg text-muted-foreground", children: description }),
14846
- showFullOrderSummary && order && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "space-y-6", children: [
14847
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "inline-block rounded-xl bg-muted px-6 py-4", children: [
14848
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "mb-1 text-xs uppercase tracking-widest text-muted-foreground", children: t.orderNumberLabel }),
14849
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "font-mono text-lg font-semibold", children: order.orderNumber })
14881
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: (0, import_utils20.cn)("space-y-6 text-center", className), "data-slot": "order-confirmation", children: [
14882
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h1", { className: "text-3xl font-bold", children: title }),
14883
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-lg text-muted-foreground", children: description }),
14884
+ showFullOrderSummary && order && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-6", children: [
14885
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "inline-block rounded-xl bg-muted px-6 py-4", children: [
14886
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mb-1 text-xs uppercase tracking-widest text-muted-foreground", children: t.orderNumberLabel }),
14887
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "font-mono text-lg font-semibold", children: order.orderNumber })
14850
14888
  ] }),
14851
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mx-auto max-w-sm space-y-3 rounded-xl border border-border bg-card p-5 text-left", children: [
14852
- order.items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
14889
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "mx-auto max-w-sm space-y-3 rounded-xl border border-border bg-card p-5 text-left", children: [
14890
+ order.items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
14853
14891
  "div",
14854
14892
  {
14855
14893
  className: "flex justify-between gap-4 text-sm",
14856
14894
  children: [
14857
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "max-w-[60%] truncate text-muted-foreground", children: [
14895
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "max-w-[60%] truncate text-muted-foreground", children: [
14858
14896
  item.product.name,
14859
14897
  item.variant ? ` (${item.variant.name})` : "",
14860
14898
  " x",
14861
14899
  item.quantity
14862
14900
  ] }),
14863
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: formatPrice(item.total, currencyCode, locale) })
14901
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: formatPrice(item.total, currencyCode, locale) })
14864
14902
  ]
14865
14903
  },
14866
14904
  idx
14867
14905
  )),
14868
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "space-y-1 border-t border-border pt-3 text-sm", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex justify-between pt-1 text-base font-bold", children: [
14869
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: t.totalLabel }),
14870
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: formatPrice(order.total, currencyCode, locale) })
14906
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "space-y-1 border-t border-border pt-3 text-sm", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex justify-between pt-1 text-base font-bold", children: [
14907
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: t.totalLabel }),
14908
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: formatPrice(order.total, currencyCode, locale) })
14871
14909
  ] }) })
14872
14910
  ] }),
14873
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "mx-auto max-w-sm text-sm text-muted-foreground", children: [
14911
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "mx-auto max-w-sm text-sm text-muted-foreground", children: [
14874
14912
  t.confirmationEmailPrefix,
14875
14913
  " ",
14876
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "font-medium text-foreground", children: order.customerEmail }),
14914
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-medium text-foreground", children: order.customerEmail }),
14877
14915
  "."
14878
- ] })
14916
+ ] }),
14917
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
14918
+ OrderInvoiceLink,
14919
+ {
14920
+ invoice: order.invoice,
14921
+ labels: { linkLabel: t.invoiceLinkLabel, signInHint: t.invoiceSignInHint }
14922
+ }
14923
+ )
14879
14924
  ] }),
14880
- showOrderReference && order && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "inline-block rounded-xl bg-muted px-6 py-4", children: [
14881
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "mb-1 text-xs uppercase tracking-widest text-muted-foreground", children: t.orderReferenceLabel }),
14882
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "font-mono text-lg font-semibold", children: order.orderNumber })
14925
+ showOrderReference && order && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "inline-block rounded-xl bg-muted px-6 py-4", children: [
14926
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mb-1 text-xs uppercase tracking-widest text-muted-foreground", children: t.orderReferenceLabel }),
14927
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "font-mono text-lg font-semibold", children: order.orderNumber })
14883
14928
  ] }),
14884
- status === "failed" && cartHref && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
14929
+ status === "failed" && cartHref && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
14885
14930
  "a",
14886
14931
  {
14887
14932
  href: cartHref,
@@ -14889,7 +14934,7 @@ function OrderConfirmation({
14889
14934
  children: t.backToCartLabel
14890
14935
  }
14891
14936
  ),
14892
- showRecheck && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
14937
+ showRecheck && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
14893
14938
  "button",
14894
14939
  {
14895
14940
  type: "button",
@@ -14898,9 +14943,270 @@ function OrderConfirmation({
14898
14943
  children: t.recheckLabel
14899
14944
  }
14900
14945
  ),
14901
- showSessionReference && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "inline-block rounded-xl bg-muted px-6 py-4", children: [
14902
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "mb-1 text-xs uppercase tracking-widest text-muted-foreground", children: t.sessionReferenceLabel }),
14903
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "break-all font-mono text-sm font-semibold", children: sessionId })
14946
+ showSessionReference && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "inline-block rounded-xl bg-muted px-6 py-4", children: [
14947
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mb-1 text-xs uppercase tracking-widest text-muted-foreground", children: t.sessionReferenceLabel }),
14948
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "break-all font-mono text-sm font-semibold", children: sessionId })
14949
+ ] })
14950
+ ] });
14951
+ }
14952
+
14953
+ // src/components/full-invoice-fields.tsx
14954
+ var import_utils21 = require("@reblu/site-ui/utils");
14955
+ var import_jsx_runtime20 = require("react/jsx-runtime");
14956
+ var EMPTY_FULL_INVOICE_VALUE = {
14957
+ wantsFullInvoice: false,
14958
+ billingIdType: "NIF",
14959
+ billingIdCountry: "",
14960
+ billingNif: "",
14961
+ billingName: "",
14962
+ street: "",
14963
+ postalCode: "",
14964
+ city: "",
14965
+ country: "ES"
14966
+ };
14967
+ var DEFAULT_LABELS9 = {
14968
+ toggle: "\xBFQuieres factura completa?",
14969
+ underCapNotice: "Para una factura completa necesitas tu NIF/NIE o un documento de identificaci\xF3n. Si no, recibir\xE1s una factura simplificada.",
14970
+ overCapNotice: "El importe del pedido supera el m\xE1ximo de la factura simplificada: para completar la compra, indica tu NIF/NIE o un documento de identificaci\xF3n, tu nombre y tu direcci\xF3n de facturaci\xF3n.",
14971
+ idType: "Tipo de documento",
14972
+ idCountry: "Pa\xEDs que expidi\xF3 el documento",
14973
+ chooseCountry: "Elige un pa\xEDs",
14974
+ spanishNif: "NIF / NIE / CIF",
14975
+ documentNumber: "N\xFAmero de documento",
14976
+ name: "Nombre o raz\xF3n social",
14977
+ street: "Direcci\xF3n",
14978
+ postalCode: "C\xF3digo postal",
14979
+ city: "Ciudad",
14980
+ country: "Pa\xEDs",
14981
+ idTypeOptions: {
14982
+ NIF: "NIF / NIE / CIF (Espa\xF1a)",
14983
+ "02": "NIF-IVA intracomunitario (otro pa\xEDs de la UE)",
14984
+ "03": "Pasaporte",
14985
+ "04": "Documento oficial de identidad de tu pa\xEDs de residencia",
14986
+ "05": "Certificado de residencia",
14987
+ "06": "Otro documento acreditativo"
14988
+ }
14989
+ };
14990
+ var ID_TYPES = ["NIF", "02", "03", "04", "05", "06"];
14991
+ var DEFAULT_INVOICE_COUNTRIES = [
14992
+ { code: "ES", name: "Espa\xF1a" },
14993
+ { code: "DE", name: "Alemania" },
14994
+ { code: "AD", name: "Andorra" },
14995
+ { code: "AR", name: "Argentina" },
14996
+ { code: "AT", name: "Austria" },
14997
+ { code: "BE", name: "B\xE9lgica" },
14998
+ { code: "BG", name: "Bulgaria" },
14999
+ { code: "CY", name: "Chipre" },
15000
+ { code: "CO", name: "Colombia" },
15001
+ { code: "HR", name: "Croacia" },
15002
+ { code: "DK", name: "Dinamarca" },
15003
+ { code: "SK", name: "Eslovaquia" },
15004
+ { code: "SI", name: "Eslovenia" },
15005
+ { code: "US", name: "Estados Unidos" },
15006
+ { code: "EE", name: "Estonia" },
15007
+ { code: "FI", name: "Finlandia" },
15008
+ { code: "FR", name: "Francia" },
15009
+ { code: "GR", name: "Grecia" },
15010
+ { code: "HU", name: "Hungr\xEDa" },
15011
+ { code: "IE", name: "Irlanda" },
15012
+ { code: "IT", name: "Italia" },
15013
+ { code: "LV", name: "Letonia" },
15014
+ { code: "LT", name: "Lituania" },
15015
+ { code: "LU", name: "Luxemburgo" },
15016
+ { code: "MT", name: "Malta" },
15017
+ { code: "MA", name: "Marruecos" },
15018
+ { code: "MX", name: "M\xE9xico" },
15019
+ { code: "NL", name: "Pa\xEDses Bajos" },
15020
+ { code: "PL", name: "Polonia" },
15021
+ { code: "PT", name: "Portugal" },
15022
+ { code: "GB", name: "Reino Unido" },
15023
+ { code: "CZ", name: "Rep\xFAblica Checa" },
15024
+ { code: "RO", name: "Ruman\xEDa" },
15025
+ { code: "SE", name: "Suecia" },
15026
+ { code: "CH", name: "Suiza" },
15027
+ { code: "VE", name: "Venezuela" }
15028
+ ];
15029
+ function shopIssuesInvoices(config) {
15030
+ return config.simplifiedInvoiceCapCents != null;
15031
+ }
15032
+ function isOverSimplifiedInvoiceCap(totalCents, capCents) {
15033
+ return capCents != null && totalCents > capCents;
15034
+ }
15035
+ function toCheckoutInvoiceFields(value, overCap, issuesInvoices = true) {
15036
+ if (!issuesInvoices) return {};
15037
+ if (!value.wantsFullInvoice && !overCap) return {};
15038
+ const name = value.billingName.trim();
15039
+ const [first = "", ...rest] = name.split(/\s+/);
15040
+ const billing = {
15041
+ billingName: name,
15042
+ billingNif: value.billingNif.trim(),
15043
+ billingIdType: value.billingIdType,
15044
+ ...value.billingIdType === "NIF" ? {} : { billingIdCountry: value.billingIdCountry },
15045
+ billingAddress: {
15046
+ firstName: first,
15047
+ lastName: rest.join(" ") || first,
15048
+ street: value.street.trim(),
15049
+ city: value.city.trim(),
15050
+ postalCode: value.postalCode.trim(),
15051
+ country: value.country
15052
+ }
15053
+ };
15054
+ return { ...value.wantsFullInvoice ? { wantsFullInvoice: true } : {}, billing };
15055
+ }
15056
+ var FIELD_CLASS = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20";
15057
+ function FullInvoiceFields({
15058
+ value,
15059
+ onChange,
15060
+ overCap,
15061
+ issuesInvoices = true,
15062
+ errors = {},
15063
+ countries = DEFAULT_INVOICE_COUNTRIES,
15064
+ labels,
15065
+ className
15066
+ }) {
15067
+ if (!issuesInvoices) return null;
15068
+ const t = { ...DEFAULT_LABELS9, ...labels };
15069
+ const showFields = value.wantsFullInvoice || overCap;
15070
+ const isSpanishNif = value.billingIdType === "NIF";
15071
+ const set = (key2, next2) => onChange({ ...value, [key2]: next2 });
15072
+ const error = (key2) => errors[key2] ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { id: `full-invoice-${key2}-error`, className: "text-sm text-destructive", children: errors[key2] }) : null;
15073
+ const invalid2 = (key2) => errors[key2] ? { "aria-invalid": true, "aria-describedby": `full-invoice-${key2}-error` } : {};
15074
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: (0, import_utils21.cn)("space-y-4", className), "data-slot": "full-invoice-fields", children: [
15075
+ overCap ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { role: "note", className: "rounded-lg border border-border p-4 text-sm", children: t.overCapNotice }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15076
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: "flex items-center gap-2 text-sm font-medium", children: [
15077
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15078
+ "input",
15079
+ {
15080
+ type: "checkbox",
15081
+ checked: value.wantsFullInvoice,
15082
+ onChange: (e) => set("wantsFullInvoice", e.target.checked)
15083
+ }
15084
+ ),
15085
+ t.toggle
15086
+ ] }),
15087
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-sm text-muted-foreground", children: t.underCapNotice })
15088
+ ] }),
15089
+ showFields && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
15090
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15091
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-id-type", className: "text-sm font-medium", children: t.idType }),
15092
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15093
+ "select",
15094
+ {
15095
+ id: "full-invoice-id-type",
15096
+ className: FIELD_CLASS,
15097
+ required: true,
15098
+ value: value.billingIdType,
15099
+ onChange: (e) => set("billingIdType", e.target.value),
15100
+ ...invalid2("billingIdType"),
15101
+ children: ID_TYPES.map((type) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: type, children: t.idTypeOptions[type] }, type))
15102
+ }
15103
+ ),
15104
+ error("billingIdType")
15105
+ ] }),
15106
+ !isSpanishNif && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15107
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-id-country", className: "text-sm font-medium", children: t.idCountry }),
15108
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
15109
+ "select",
15110
+ {
15111
+ id: "full-invoice-id-country",
15112
+ className: FIELD_CLASS,
15113
+ required: true,
15114
+ value: value.billingIdCountry,
15115
+ onChange: (e) => set("billingIdCountry", e.target.value),
15116
+ ...invalid2("billingIdCountry"),
15117
+ children: [
15118
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: "", disabled: true, children: t.chooseCountry }),
15119
+ countries.map((c) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: c.code, children: c.name }, c.code))
15120
+ ]
15121
+ }
15122
+ ),
15123
+ error("billingIdCountry")
15124
+ ] }),
15125
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15126
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-nif", className: "text-sm font-medium", children: isSpanishNif ? t.spanishNif : t.documentNumber }),
15127
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15128
+ "input",
15129
+ {
15130
+ id: "full-invoice-nif",
15131
+ className: FIELD_CLASS,
15132
+ required: true,
15133
+ value: value.billingNif,
15134
+ onChange: (e) => set("billingNif", e.target.value),
15135
+ ...invalid2("billingNif")
15136
+ }
15137
+ ),
15138
+ error("billingNif")
15139
+ ] }),
15140
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15141
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-name", className: "text-sm font-medium", children: t.name }),
15142
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15143
+ "input",
15144
+ {
15145
+ id: "full-invoice-name",
15146
+ className: FIELD_CLASS,
15147
+ required: true,
15148
+ value: value.billingName,
15149
+ onChange: (e) => set("billingName", e.target.value),
15150
+ ...invalid2("billingName")
15151
+ }
15152
+ ),
15153
+ error("billingName")
15154
+ ] }),
15155
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1 sm:col-span-2", children: [
15156
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-street", className: "text-sm font-medium", children: t.street }),
15157
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15158
+ "input",
15159
+ {
15160
+ id: "full-invoice-street",
15161
+ className: FIELD_CLASS,
15162
+ required: true,
15163
+ value: value.street,
15164
+ onChange: (e) => set("street", e.target.value),
15165
+ ...invalid2("address")
15166
+ }
15167
+ )
15168
+ ] }),
15169
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15170
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-postal-code", className: "text-sm font-medium", children: t.postalCode }),
15171
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15172
+ "input",
15173
+ {
15174
+ id: "full-invoice-postal-code",
15175
+ className: FIELD_CLASS,
15176
+ required: true,
15177
+ value: value.postalCode,
15178
+ onChange: (e) => set("postalCode", e.target.value)
15179
+ }
15180
+ )
15181
+ ] }),
15182
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15183
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-city", className: "text-sm font-medium", children: t.city }),
15184
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15185
+ "input",
15186
+ {
15187
+ id: "full-invoice-city",
15188
+ className: FIELD_CLASS,
15189
+ required: true,
15190
+ value: value.city,
15191
+ onChange: (e) => set("city", e.target.value)
15192
+ }
15193
+ )
15194
+ ] }),
15195
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
15196
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { htmlFor: "full-invoice-country", className: "text-sm font-medium", children: t.country }),
15197
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
15198
+ "select",
15199
+ {
15200
+ id: "full-invoice-country",
15201
+ className: FIELD_CLASS,
15202
+ required: true,
15203
+ value: value.country,
15204
+ onChange: (e) => set("country", e.target.value),
15205
+ children: countries.map((c) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: c.code, children: c.name }, c.code))
15206
+ }
15207
+ )
15208
+ ] }),
15209
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "sm:col-span-2", children: error("address") })
14904
15210
  ] })
14905
15211
  ] });
14906
15212
  }
@@ -14912,13 +15218,17 @@ function OrderConfirmation({
14912
15218
  CartSummary,
14913
15219
  CheckoutForm,
14914
15220
  CheckoutLoginPrompt,
15221
+ DEFAULT_INVOICE_COUNTRIES,
14915
15222
  DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE,
14916
15223
  DEFAULT_SHOP_ERROR_MESSAGES,
14917
15224
  DEFAULT_SHOP_PRICE_BANDS,
14918
15225
  DEFAULT_TRUST_ICONS,
15226
+ EMPTY_FULL_INVOICE_VALUE,
14919
15227
  FaqAccordion,
15228
+ FullInvoiceFields,
14920
15229
  MarkdownRenderer,
14921
15230
  OrderConfirmation,
15231
+ OrderInvoiceLink,
14922
15232
  PageBlocks,
14923
15233
  PageCardGrid,
14924
15234
  ParentPage,
@@ -14935,10 +15245,13 @@ function OrderConfirmation({
14935
15245
  formatPrice,
14936
15246
  getShopErrorMessage,
14937
15247
  isAllowedYouTubeHost,
15248
+ isOverSimplifiedInvoiceCap,
14938
15249
  markdownToSafeHtml,
14939
15250
  readingTime,
14940
15251
  sanitizeHtml,
14941
- shouldRenderShopFilters
15252
+ shopIssuesInvoices,
15253
+ shouldRenderShopFilters,
15254
+ toCheckoutInvoiceFields
14942
15255
  });
14943
15256
  /*! Bundled license information:
14944
15257