@propeller-commerce/propeller-v2-react-ui 0.4.12 → 0.4.14

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/dist/index.d.cts CHANGED
@@ -673,14 +673,32 @@ interface UseCheckoutOptions {
673
673
  interface PlaceOrderOptions {
674
674
  /** Id of the cart to convert. */
675
675
  cartId: string;
676
- /** Order status to set — `'NEW'` for an order, `'REQUEST'` for a quote request. */
677
- orderStatus: 'NEW' | 'REQUEST';
676
+ /**
677
+ * Order status to set. `'NEW'` for a completed order, `'REQUEST'` for a quote
678
+ * request, `'UNFINISHED'` for an order awaiting an external payment (PSP) — the
679
+ * payment webhook promotes it to its final status later. Any other backend
680
+ * status string is also accepted.
681
+ */
682
+ orderStatus: 'NEW' | 'REQUEST' | 'UNFINISHED' | (string & {});
678
683
  /** Customer reference printed on the order. */
679
684
  reference?: string;
680
685
  /** Free-text order notes. */
681
686
  notes?: string;
682
687
  /** When `true`, treats this as a quote: suppresses confirmation email/PDF and triggers a quote-send request. */
683
688
  isQuoteMode?: boolean;
689
+ /**
690
+ * Whether this placement *finalizes* the order — i.e. sends the order
691
+ * confirmation email, fires the confirm event, attaches the order PDF, and
692
+ * clears the cart. Defaults to `true` for a normal order and `false` for a
693
+ * quote.
694
+ *
695
+ * Set this to `false` for an order that still awaits an external payment
696
+ * (e.g. `'UNFINISHED'` orders handed off to a PSP). The confirmation/cart
697
+ * deletion should then happen when the payment is confirmed (the PSP webhook),
698
+ * not at placement — otherwise the shopper is emailed and the cart cleared
699
+ * before they've paid.
700
+ */
701
+ finalizeOrder?: boolean;
684
702
  }
685
703
  /** State and checkout actions returned by {@link useCheckout}. */
686
704
  interface UseCheckoutReturn {
@@ -3110,6 +3128,8 @@ interface CartPaymethodsProps {
3110
3128
  paymentsContainerClass?: string;
3111
3129
  /** Display the on account payment method for anonymous users */
3112
3130
  showOnAccountForGuests?: boolean;
3131
+ /** Display the payment method logo (default: true). Falls back to the name when no logo is set. */
3132
+ showPaymethodLogo?: boolean;
3113
3133
  /** Action when a payment method is selected */
3114
3134
  onPaymethodSelect?: (paymethod: CartPaymethod) => void;
3115
3135
  /** Custom price formatting function */
package/dist/index.d.ts CHANGED
@@ -673,14 +673,32 @@ interface UseCheckoutOptions {
673
673
  interface PlaceOrderOptions {
674
674
  /** Id of the cart to convert. */
675
675
  cartId: string;
676
- /** Order status to set — `'NEW'` for an order, `'REQUEST'` for a quote request. */
677
- orderStatus: 'NEW' | 'REQUEST';
676
+ /**
677
+ * Order status to set. `'NEW'` for a completed order, `'REQUEST'` for a quote
678
+ * request, `'UNFINISHED'` for an order awaiting an external payment (PSP) — the
679
+ * payment webhook promotes it to its final status later. Any other backend
680
+ * status string is also accepted.
681
+ */
682
+ orderStatus: 'NEW' | 'REQUEST' | 'UNFINISHED' | (string & {});
678
683
  /** Customer reference printed on the order. */
679
684
  reference?: string;
680
685
  /** Free-text order notes. */
681
686
  notes?: string;
682
687
  /** When `true`, treats this as a quote: suppresses confirmation email/PDF and triggers a quote-send request. */
683
688
  isQuoteMode?: boolean;
689
+ /**
690
+ * Whether this placement *finalizes* the order — i.e. sends the order
691
+ * confirmation email, fires the confirm event, attaches the order PDF, and
692
+ * clears the cart. Defaults to `true` for a normal order and `false` for a
693
+ * quote.
694
+ *
695
+ * Set this to `false` for an order that still awaits an external payment
696
+ * (e.g. `'UNFINISHED'` orders handed off to a PSP). The confirmation/cart
697
+ * deletion should then happen when the payment is confirmed (the PSP webhook),
698
+ * not at placement — otherwise the shopper is emailed and the cart cleared
699
+ * before they've paid.
700
+ */
701
+ finalizeOrder?: boolean;
684
702
  }
685
703
  /** State and checkout actions returned by {@link useCheckout}. */
686
704
  interface UseCheckoutReturn {
@@ -3110,6 +3128,8 @@ interface CartPaymethodsProps {
3110
3128
  paymentsContainerClass?: string;
3111
3129
  /** Display the on account payment method for anonymous users */
3112
3130
  showOnAccountForGuests?: boolean;
3131
+ /** Display the payment method logo (default: true). Falls back to the name when no logo is set. */
3132
+ showPaymethodLogo?: boolean;
3113
3133
  /** Action when a payment method is selected */
3114
3134
  onPaymethodSelect?: (paymethod: CartPaymethod) => void;
3115
3135
  /** Custom price formatting function */
package/dist/index.js CHANGED
@@ -995,6 +995,8 @@ function useCheckout(options) {
995
995
  const cartService = createServices(graphqlClient).cart;
996
996
  const orderService = createServices(graphqlClient).order;
997
997
  const { cartId, orderStatus, reference, notes, isQuoteMode } = opts;
998
+ const finalizeOrder = opts.finalizeOrder ?? true;
999
+ const sendConfirmation = finalizeOrder && !isQuoteMode;
998
1000
  if (reference || notes) {
999
1001
  await cartService.updateCart({
1000
1002
  id: cartId,
@@ -1016,10 +1018,13 @@ function useCheckout(options) {
1016
1018
  orderId,
1017
1019
  status: orderStatus,
1018
1020
  payStatus: PaymentStatuses.OPEN,
1019
- sendOrderConfirmationEmail: !isQuoteMode,
1020
- addPDFAttachment: !isQuoteMode,
1021
- triggerOrderSendConfirmEvent: !isQuoteMode,
1022
- deleteCart: true
1021
+ sendOrderConfirmationEmail: sendConfirmation,
1022
+ addPDFAttachment: sendConfirmation,
1023
+ triggerOrderSendConfirmEvent: sendConfirmation,
1024
+ // Keep the cart only for an unfinalized (PSP-pending) order so the
1025
+ // shopper can retry payment; clear it once the order is finalized
1026
+ // (a normal order or a quote).
1027
+ deleteCart: finalizeOrder
1023
1028
  });
1024
1029
  if (isQuoteMode) {
1025
1030
  await orderService.triggerQuoteSendRequest({ orderId, language });
@@ -5877,28 +5882,27 @@ function CartCarriers(props) {
5877
5882
  }
5878
5883
  }, [props.cart, selectedName, props.onCarrierSelect, carriers, props]);
5879
5884
  return /* @__PURE__ */ jsxs("div", { className: `propeller-cart-carriers ${containerClass}`, children: [
5880
- carriers.length > 0 ? /* @__PURE__ */ jsx("div", { className: "propeller-cart-carriers__grid grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3", children: carriers.map((carrier, index) => /* @__PURE__ */ jsxs(
5881
- "div",
5882
- {
5883
- onClick: () => handleSelect(carrier),
5884
- "data-selected": selectedName === carrier.name ? "true" : "false",
5885
- className: `propeller-cart-carriers__carrier cursor-pointer border border-border rounded-container p-4 flex flex-col gap-2 transition-all ${selectedName === carrier.name ? "border-secondary bg-secondary/5 shadow-sm" : "hover:border-secondary/30"}`,
5886
- children: [
5887
- /* @__PURE__ */ jsxs("div", { className: "propeller-cart-carriers__carrier-row flex justify-between items-center", children: [
5888
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5889
- showLogo && (carrier.logo || "") ? /* @__PURE__ */ jsx("img", { className: "propeller-cart-carriers__carrier-logo h-6 w-auto", src: carrier.logo || "", alt: carrier.name }) : null,
5890
- /* @__PURE__ */ jsx("span", { className: "propeller-cart-carriers__carrier-name font-medium", children: carrier.name })
5891
- ] }),
5892
- props.showPrice !== false ? /* @__PURE__ */ jsx("span", { className: "propeller-cart-carriers__carrier-price text-xs bg-surface-hover text-muted-foreground px-2 py-1 rounded-full", children: formatCarrierPrice(carrier.price) }) : null
5893
- ] }),
5894
- carrier.deliveryDeadline ? /* @__PURE__ */ jsxs("p", { className: "propeller-cart-carriers__carrier-deadline text-xs text-muted-foreground", children: [
5895
- getLabel(props.labels, "deliveryDeadline", "Delivery deadline:"),
5896
- carrier.deliveryDeadline
5897
- ] }) : null
5898
- ]
5899
- },
5900
- `${carrier.name}-${index}`
5901
- )) }) : null,
5885
+ carriers.length > 0 ? /* @__PURE__ */ jsx("div", { className: "propeller-cart-carriers__grid grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3", children: carriers.map((carrier, index) => {
5886
+ const logoUrl = showLogo ? carrier.logo || "" : "";
5887
+ return /* @__PURE__ */ jsxs(
5888
+ "div",
5889
+ {
5890
+ onClick: () => handleSelect(carrier),
5891
+ "data-selected": selectedName === carrier.name ? "true" : "false",
5892
+ className: `propeller-cart-carriers__carrier relative cursor-pointer border rounded-container p-3 flex flex-col items-center justify-center gap-2 text-center aspect-square transition-all ${selectedName === carrier.name ? "border-secondary bg-secondary/5 shadow-sm" : "border-border hover:border-secondary/30"}`,
5893
+ children: [
5894
+ props.showPrice !== false ? /* @__PURE__ */ jsx("span", { className: "propeller-cart-carriers__carrier-price absolute top-2 right-2 text-xs bg-surface-hover text-muted-foreground px-2 py-0.5 rounded-full", children: formatCarrierPrice(carrier.price) }) : null,
5895
+ logoUrl ? /* @__PURE__ */ jsx("span", { className: "propeller-cart-carriers__carrier-logo-wrap flex items-center justify-center h-10 w-full", children: /* @__PURE__ */ jsx("img", { className: "propeller-cart-carriers__carrier-logo max-h-10 max-w-[80%] w-auto object-contain", src: logoUrl, alt: carrier.name }) }) : null,
5896
+ /* @__PURE__ */ jsx("span", { className: "propeller-cart-carriers__carrier-name font-medium text-sm", children: carrier.name }),
5897
+ carrier.deliveryDeadline ? /* @__PURE__ */ jsxs("p", { className: "propeller-cart-carriers__carrier-deadline text-xs text-muted-foreground", children: [
5898
+ getLabel(props.labels, "deliveryDeadline", "Delivery deadline:"),
5899
+ carrier.deliveryDeadline
5900
+ ] }) : null
5901
+ ]
5902
+ },
5903
+ `${carrier.name}-${index}`
5904
+ );
5905
+ }) }) : null,
5902
5906
  carriers.length === 0 ? /* @__PURE__ */ jsx("p", { className: "propeller-cart-carriers__empty text-muted-foreground italic", children: getLabel(props.labels, "noCarriers", "No carriers available.") }) : null
5903
5907
  ] });
5904
5908
  }
@@ -7147,11 +7151,15 @@ function isOnAccountMethod(method) {
7147
7151
  const code = (method.code || "").toLowerCase();
7148
7152
  return code === "on_account" || code === "onaccount" || code === "on-account";
7149
7153
  }
7154
+ function getLogoUrl(method) {
7155
+ return method.logo || "";
7156
+ }
7150
7157
  function CartPaymethods(rawProps) {
7151
7158
  const props = useInfraProps(rawProps);
7152
7159
  const [selectedCode, setSelectedCode] = useState("");
7153
7160
  const containerClass = props.paymentsContainerClass || "cart-paymethods";
7154
7161
  const showOnAccountForGuests = !!props.showOnAccountForGuests;
7162
+ const showLogo = props.showPaymethodLogo !== false;
7155
7163
  const isGuest = !props.user;
7156
7164
  const payMethods = (props.cart?.payMethods || []).filter((m) => {
7157
7165
  if (!m?.code) return false;
@@ -7177,19 +7185,30 @@ function CartPaymethods(rawProps) {
7177
7185
  }
7178
7186
  }, [props.cart, selectedCode, props.onPaymethodSelect, payMethods, props]);
7179
7187
  return /* @__PURE__ */ jsxs("div", { className: `propeller-cart-paymethods ${containerClass}`, children: [
7180
- payMethods.length > 0 ? /* @__PURE__ */ jsx("div", { className: "propeller-cart-paymethods__grid grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3", children: payMethods.map((method, index) => /* @__PURE__ */ jsx(
7181
- "div",
7182
- {
7183
- onClick: () => handleSelect(method),
7184
- "data-selected": selectedCode === method.code ? "true" : "false",
7185
- className: `propeller-cart-paymethods__method cursor-pointer border border-border rounded-container p-4 flex flex-col gap-2 transition-all ${selectedCode === method.code ? "border-secondary bg-secondary/5 shadow-sm" : "hover:border-secondary/30"}`,
7186
- children: /* @__PURE__ */ jsxs("div", { className: "propeller-cart-paymethods__method-row flex justify-between items-center", children: [
7187
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx("span", { className: "propeller-cart-paymethods__method-name font-medium", children: method.name || method.code }) }),
7188
- method.price > 0 ? /* @__PURE__ */ jsx("span", { className: "propeller-cart-paymethods__method-price text-xs bg-surface-hover text-muted-foreground px-2 py-1 rounded-full", children: formatMethodPrice(method.price) }) : null
7189
- ] })
7190
- },
7191
- method.code
7192
- )) }) : null,
7188
+ payMethods.length > 0 ? /* @__PURE__ */ jsx("div", { className: "propeller-cart-paymethods__grid grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3", children: payMethods.map((method, index) => {
7189
+ const logoUrl = showLogo ? getLogoUrl(method) : "";
7190
+ return /* @__PURE__ */ jsxs(
7191
+ "div",
7192
+ {
7193
+ onClick: () => handleSelect(method),
7194
+ "data-selected": selectedCode === method.code ? "true" : "false",
7195
+ className: `propeller-cart-paymethods__method relative cursor-pointer border rounded-container p-3 flex flex-col items-center justify-center gap-2 text-center aspect-square transition-all ${selectedCode === method.code ? "border-secondary bg-secondary/5 shadow-sm" : "border-border hover:border-secondary/30"}`,
7196
+ children: [
7197
+ method.price > 0 ? /* @__PURE__ */ jsx("span", { className: "propeller-cart-paymethods__method-price absolute top-2 right-2 text-xs bg-surface-hover text-muted-foreground px-2 py-0.5 rounded-full", children: formatMethodPrice(method.price) }) : null,
7198
+ logoUrl ? /* @__PURE__ */ jsx("span", { className: "propeller-cart-paymethods__method-logo-wrap flex items-center justify-center h-10 w-full", children: /* @__PURE__ */ jsx(
7199
+ "img",
7200
+ {
7201
+ className: "propeller-cart-paymethods__method-logo max-h-10 max-w-[80%] w-auto object-contain",
7202
+ src: logoUrl,
7203
+ alt: method.name || method.code
7204
+ }
7205
+ ) }) : null,
7206
+ /* @__PURE__ */ jsx("span", { className: "propeller-cart-paymethods__method-name font-medium text-sm", children: method.name || method.code })
7207
+ ]
7208
+ },
7209
+ method.code
7210
+ );
7211
+ }) }) : null,
7193
7212
  payMethods.length === 0 ? /* @__PURE__ */ jsx("p", { className: "propeller-cart-paymethods__empty text-muted-foreground italic", children: getLabel(props.labels, "noMethods", "No payment methods available.") }) : null
7194
7213
  ] });
7195
7214
  }