@tallyui/connector-woocommerce 3.3.0 → 3.4.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/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { SyncContext, ServerCapabilities, StoreSettings, Customer, CustomerInput
2
2
  export { ConnectorUnauthorizedError } from '@tallyui/core';
3
3
  import { RxJsonSchema } from 'rxdb';
4
4
 
5
- /** The highest order.create version the WooCommerce transport maps: discounts and the customer (v3). It rises when the transport maps split payments (v4) and fees, shipping and custom lines (v5). */
6
- declare const WOO_ORDER_CREATE_VERSION = 3;
5
+ /** The highest order.create version the WooCommerce transport maps: v4's net discounts, and v5's fees, shipping and custom lines (WCPOS push/orders; orders #146, #161). */
6
+ declare const WOO_ORDER_CREATE_VERSION = 5;
7
7
  declare function wooStoreSettings(context: SyncContext): Promise<StoreSettings>;
8
8
  declare function readWooCapabilities(context: SyncContext): Promise<ServerCapabilities | undefined>;
9
9
 
@@ -62,6 +62,14 @@ interface WooLocalOrder {
62
62
  id: string;
63
63
  netMicros?: string;
64
64
  }>;
65
+ fees?: ReadonlyArray<{
66
+ id: string;
67
+ netMicros?: string;
68
+ }>;
69
+ shipping?: ReadonlyArray<{
70
+ id: string;
71
+ netMicros?: string;
72
+ }>;
65
73
  }
66
74
  declare function toWooOrderPayload(envelope: OrderCreateEnvelope, local?: WooLocalOrder, options?: {
67
75
  paymentsList?: boolean;
package/dist/index.js CHANGED
@@ -718,7 +718,7 @@ function createWooCustomers(options) {
718
718
 
719
719
  // src/store-settings.ts
720
720
  import { ConnectorUnauthorizedError as ConnectorUnauthorizedError2, StoreSettingsError, minorUnitDigits, woocommerceTax } from "@tallyui/core";
721
- var WOO_ORDER_CREATE_VERSION = 3;
721
+ var WOO_ORDER_CREATE_VERSION = 5;
722
722
  async function wooStoreSettings(context) {
723
723
  try {
724
724
  const init = { method: "GET", headers: context.headers, signal: context.signal };
@@ -841,7 +841,7 @@ async function readWooCapabilities(context) {
841
841
  }
842
842
 
843
843
  // package.json
844
- var version = "3.3.0";
844
+ var version = "3.4.0";
845
845
 
846
846
  // src/commands/transport.ts
847
847
  import { minorUnitDigits as minorUnitDigits2, moneyFromDecimalString, moneyFromMajor as moneyFromMajor2 } from "@tallyui/core";
@@ -857,9 +857,6 @@ function wooMinorFromDecimal(text, currency) {
857
857
  }
858
858
  function toWooOrderPayload(envelope, local, options) {
859
859
  const p = envelope.payload;
860
- if (envelope.version === 5 && (p.fees?.length || p.shipping?.length || p.lines.some((line) => line.custom))) {
861
- return { error: { code: "invalid_payload", message: "Fees, shipping and custom lines are not supported by this connector yet." } };
862
- }
863
860
  if (p.payments.length === 0 || p.payments.length > 1 && !options?.paymentsList) {
864
861
  return { error: { code: "invalid_payload", message: "WooCommerce orders take one payment." } };
865
862
  }
@@ -870,35 +867,61 @@ function toWooOrderPayload(envelope, local, options) {
870
867
  return { error: { code: "invalid_payload", message: "Payments do not add up to the order total." } };
871
868
  }
872
869
  for (const line of p.lines) {
873
- if (!/^[1-9]\d*$/.test(line.variantId ?? "") || !Number.isSafeInteger(Number(line.variantId))) {
870
+ if (!line.custom && (!/^[1-9]\d*$/.test(line.variantId ?? "") || !Number.isSafeInteger(Number(line.variantId)))) {
874
871
  return { error: { code: "invalid_payload", message: `Line ${line.clientLineId} has no WooCommerce product id.` } };
875
872
  }
876
873
  }
877
874
  const digits = p.display?.exponent ?? minorUnitDigits2(p.currency);
878
875
  const major = (minor) => (minor / 10 ** digits).toFixed(digits);
876
+ const inclusiveNet = (netMicros, id) => {
877
+ if (netMicros === void 0) {
878
+ return { error: { code: "unsupported_tax_mode", message: "Tax-inclusive prices need the till's WooCommerce tax figures; update the till." } };
879
+ }
880
+ const net = BigInt(netMicros), divisor = 10n ** BigInt(digits);
881
+ if (net < 0n) {
882
+ return { error: { code: "invalid_payload", message: `Line ${id} has a negative net.` } };
883
+ }
884
+ if (net % divisor !== 0n) {
885
+ return { error: { code: "invalid_payload", message: `Line ${id} has a net finer than 6 decimals.` } };
886
+ }
887
+ const micros = net / divisor;
888
+ return `${micros / 1000000n}.${(micros % 1000000n).toString().padStart(6, "0")}`;
889
+ };
879
890
  const line_items = [];
880
891
  for (const line of p.lines) {
881
- let subtotal = major(line.unitPriceMinor * line.quantity);
882
892
  let total = major(line.unitPriceMinor * line.quantity - (line.discountMinor ?? 0));
893
+ let subtotal = total;
883
894
  if (line.taxInclusive ?? p.pricesIncludeTax) {
884
- if ((line.discountMinor ?? 0) > 0) {
885
- return { error: { code: "unsupported_tax_mode", message: "Discounted tax-inclusive lines are not supported for WooCommerce yet." } };
886
- }
887
- const netMicros = local?.lines.find((stored) => stored.id === line.clientLineId)?.netMicros;
888
- if (netMicros === void 0) {
889
- return { error: { code: "unsupported_tax_mode", message: "Tax-inclusive prices need the till's WooCommerce tax figures; update the till." } };
890
- }
891
- const net = BigInt(netMicros), divisor = 10n ** BigInt(digits);
892
- if (net < 0n) {
893
- return { error: { code: "invalid_payload", message: `Line ${line.clientLineId} has a negative net.` } };
894
- }
895
- if (net % divisor !== 0n) {
896
- return { error: { code: "invalid_payload", message: `Line ${line.clientLineId} has a net finer than 6 decimals.` } };
897
- }
898
- const micros = net / divisor;
899
- subtotal = total = `${micros / 1000000n}.${(micros % 1000000n).toString().padStart(6, "0")}`;
895
+ const net = inclusiveNet(local?.lines.find((stored) => stored.id === line.clientLineId)?.netMicros, line.clientLineId);
896
+ if (typeof net !== "string") return net;
897
+ subtotal = total = net;
900
898
  }
901
- line_items.push({ product_id: Number(line.variantId), quantity: line.quantity, subtotal, total });
899
+ const custom = line.custom;
900
+ const price = major(line.unitPriceMinor);
901
+ line_items.push(custom ? {
902
+ product_id: 0,
903
+ name: custom.name,
904
+ ...custom.sku ? { sku: custom.sku } : {},
905
+ quantity: line.quantity,
906
+ subtotal,
907
+ total,
908
+ tax_class: custom.taxClass ?? "",
909
+ meta_data: [{
910
+ key: "_woocommerce_pos_data",
911
+ value: JSON.stringify({ price, regular_price: price, tax_status: custom.taxStatus })
912
+ }]
913
+ } : { product_id: Number(line.variantId), quantity: line.quantity, subtotal, total });
914
+ }
915
+ const fee_lines = [], shipping_lines = [];
916
+ for (const fee of p.fees ?? []) {
917
+ const total = p.pricesIncludeTax ? inclusiveNet(local?.fees?.find((stored) => stored.id === fee.clientFeeId)?.netMicros, fee.clientFeeId) : major(fee.amountMinor);
918
+ if (typeof total !== "string") return total;
919
+ fee_lines.push({ name: fee.name, total, tax_status: fee.taxStatus, tax_class: fee.taxClass ?? "" });
920
+ }
921
+ for (const shipping of p.shipping ?? []) {
922
+ const total = p.pricesIncludeTax ? inclusiveNet(local?.shipping?.find((stored) => stored.id === shipping.clientShippingId)?.netMicros, shipping.clientShippingId) : major(shipping.amountMinor);
923
+ if (typeof total !== "string") return total;
924
+ shipping_lines.push({ method_id: shipping.methodId ?? "pos", method_title: shipping.name, total });
902
925
  }
903
926
  const primary = p.payments.reduce((largest, payment) => payment.amountMinor > largest.amountMinor ? payment : largest);
904
927
  const cash = primary.method === "cash";
@@ -920,6 +943,8 @@ function toWooOrderPayload(envelope, local, options) {
920
943
  payment_method: cash ? "pos_cash" : "pos_card",
921
944
  payment_method_title: cash ? "Cash" : "Card",
922
945
  line_items,
946
+ ...fee_lines.length ? { fee_lines } : {},
947
+ ...shipping_lines.length ? { shipping_lines } : {},
923
948
  meta_data,
924
949
  ...p.customer?.customerId && /^\d+$/.test(p.customer.customerId) ? { customer_id: Number(p.customer.customerId) } : {},
925
950
  ...p.customer?.email ? { billing: { email: p.customer.email } } : {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyui/connector-woocommerce",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "type": "module",
5
5
  "description": "WooCommerce connector for Tally UI",
6
6
  "main": "./dist/index.js",
@@ -26,10 +26,10 @@
26
26
  "directory": "connectors/woocommerce"
27
27
  },
28
28
  "peerDependencies": {
29
- "@tallyui/core": "^3.3.0"
29
+ "@tallyui/core": "^3.4.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@tallyui/database": "3.3.0",
32
+ "@tallyui/database": "3.4.0",
33
33
  "rxdb": "17.5.0"
34
34
  },
35
35
  "scripts": {
@@ -28,13 +28,14 @@ export function wooMinorFromDecimal(text: string, currency: string): number | un
28
28
  return moneyFromDecimalString(end ? `${whole}.${fraction.slice(0, end)}` : whole, currency)?.amount;
29
29
  }
30
30
 
31
- export interface WooLocalOrder { lines: ReadonlyArray<{ id: string; netMicros?: string }> }
31
+ export interface WooLocalOrder {
32
+ lines: ReadonlyArray<{ id: string; netMicros?: string }>;
33
+ fees?: ReadonlyArray<{ id: string; netMicros?: string }>;
34
+ shipping?: ReadonlyArray<{ id: string; netMicros?: string }>;
35
+ }
32
36
 
33
37
  export function toWooOrderPayload(envelope: OrderCreateEnvelope, local?: WooLocalOrder, options?: { paymentsList?: boolean }): { payload: Record<string, unknown> } | { error: { code: string; message: string } } {
34
38
  const p = envelope.payload;
35
- if (envelope.version === 5 && (p.fees?.length || p.shipping?.length || p.lines.some((line) => line.custom))) {
36
- return { error: { code: 'invalid_payload', message: 'Fees, shipping and custom lines are not supported by this connector yet.' } };
37
- }
38
39
  if (p.payments.length === 0 || (p.payments.length > 1 && !options?.paymentsList)) {
39
40
  return { error: { code: 'invalid_payload', message: 'WooCommerce orders take one payment.' } };
40
41
  }
@@ -45,35 +46,55 @@ export function toWooOrderPayload(envelope: OrderCreateEnvelope, local?: WooLoca
45
46
  return { error: { code: 'invalid_payload', message: 'Payments do not add up to the order total.' } };
46
47
  }
47
48
  for (const line of p.lines) {
48
- if (!/^[1-9]\d*$/.test(line.variantId ?? '') || !Number.isSafeInteger(Number(line.variantId))) {
49
+ if (!line.custom && (!/^[1-9]\d*$/.test(line.variantId ?? '') || !Number.isSafeInteger(Number(line.variantId)))) {
49
50
  return { error: { code: 'invalid_payload', message: `Line ${line.clientLineId} has no WooCommerce product id.` } };
50
51
  }
51
52
  }
52
53
  const digits = p.display?.exponent ?? minorUnitDigits(p.currency);
53
54
  const major = (minor: number) => (minor / 10 ** digits).toFixed(digits);
55
+ const inclusiveNet = (netMicros: string | undefined, id: string) => {
56
+ if (netMicros === undefined) {
57
+ return { error: { code: 'unsupported_tax_mode', message: "Tax-inclusive prices need the till's WooCommerce tax figures; update the till." } };
58
+ }
59
+ const net = BigInt(netMicros), divisor = 10n ** BigInt(digits);
60
+ if (net < 0n) {
61
+ return { error: { code: 'invalid_payload', message: `Line ${id} has a negative net.` } };
62
+ }
63
+ if (net % divisor !== 0n) {
64
+ return { error: { code: 'invalid_payload', message: `Line ${id} has a net finer than 6 decimals.` } };
65
+ }
66
+ const micros = net / divisor;
67
+ return `${micros / 1000000n}.${(micros % 1000000n).toString().padStart(6, '0')}`;
68
+ };
54
69
  const line_items = [];
55
70
  for (const line of p.lines) {
56
- let subtotal = major(line.unitPriceMinor * line.quantity);
57
71
  let total = major(line.unitPriceMinor * line.quantity - (line.discountMinor ?? 0));
72
+ let subtotal = total;
58
73
  if (line.taxInclusive ?? p.pricesIncludeTax) {
59
- if ((line.discountMinor ?? 0) > 0) {
60
- return { error: { code: 'unsupported_tax_mode', message: 'Discounted tax-inclusive lines are not supported for WooCommerce yet.' } };
61
- }
62
- const netMicros = local?.lines.find((stored) => stored.id === line.clientLineId)?.netMicros;
63
- if (netMicros === undefined) {
64
- return { error: { code: 'unsupported_tax_mode', message: "Tax-inclusive prices need the till's WooCommerce tax figures; update the till." } };
65
- }
66
- const net = BigInt(netMicros), divisor = 10n ** BigInt(digits);
67
- if (net < 0n) {
68
- return { error: { code: 'invalid_payload', message: `Line ${line.clientLineId} has a negative net.` } };
69
- }
70
- if (net % divisor !== 0n) {
71
- return { error: { code: 'invalid_payload', message: `Line ${line.clientLineId} has a net finer than 6 decimals.` } };
72
- }
73
- const micros = net / divisor;
74
- subtotal = total = `${micros / 1000000n}.${(micros % 1000000n).toString().padStart(6, '0')}`;
74
+ const net = inclusiveNet(local?.lines.find((stored) => stored.id === line.clientLineId)?.netMicros, line.clientLineId);
75
+ if (typeof net !== 'string') return net;
76
+ subtotal = total = net;
75
77
  }
76
- line_items.push({ product_id: Number(line.variantId), quantity: line.quantity, subtotal, total });
78
+ const custom = line.custom;
79
+ const price = major(line.unitPriceMinor);
80
+ line_items.push(custom ? {
81
+ product_id: 0, name: custom.name, ...(custom.sku ? { sku: custom.sku } : {}), quantity: line.quantity, subtotal, total,
82
+ tax_class: custom.taxClass ?? '', meta_data: [{ key: '_woocommerce_pos_data',
83
+ value: JSON.stringify({ price, regular_price: price, tax_status: custom.taxStatus }) }],
84
+ } : { product_id: Number(line.variantId), quantity: line.quantity, subtotal, total });
85
+ }
86
+ const fee_lines = [], shipping_lines = [];
87
+ for (const fee of p.fees ?? []) {
88
+ const total = p.pricesIncludeTax
89
+ ? inclusiveNet(local?.fees?.find((stored) => stored.id === fee.clientFeeId)?.netMicros, fee.clientFeeId) : major(fee.amountMinor);
90
+ if (typeof total !== 'string') return total;
91
+ fee_lines.push({ name: fee.name, total, tax_status: fee.taxStatus, tax_class: fee.taxClass ?? '' });
92
+ }
93
+ for (const shipping of p.shipping ?? []) {
94
+ const total = p.pricesIncludeTax
95
+ ? inclusiveNet(local?.shipping?.find((stored) => stored.id === shipping.clientShippingId)?.netMicros, shipping.clientShippingId) : major(shipping.amountMinor);
96
+ if (typeof total !== 'string') return total;
97
+ shipping_lines.push({ method_id: shipping.methodId ?? 'pos', method_title: shipping.name, total });
77
98
  }
78
99
  const primary = p.payments.reduce((largest, payment) => payment.amountMinor > largest.amountMinor ? payment : largest);
79
100
  const cash = primary.method === 'cash';
@@ -91,6 +112,8 @@ export function toWooOrderPayload(envelope: OrderCreateEnvelope, local?: WooLoca
91
112
  status: 'completed', set_paid: true, currency: p.currency,
92
113
  payment_method: cash ? 'pos_cash' : 'pos_card', payment_method_title: cash ? 'Cash' : 'Card',
93
114
  line_items,
115
+ ...(fee_lines.length ? { fee_lines } : {}),
116
+ ...(shipping_lines.length ? { shipping_lines } : {}),
94
117
  meta_data,
95
118
  ...(p.customer?.customerId && /^\d+$/.test(p.customer.customerId) ? { customer_id: Number(p.customer.customerId) } : {}),
96
119
  ...(p.customer?.email ? { billing: { email: p.customer.email } } : {}),
@@ -1,8 +1,8 @@
1
1
  import { ConnectorUnauthorizedError, StoreSettingsError, minorUnitDigits, woocommerceTax } from '@tallyui/core';
2
2
  import type { ServerCapabilities, StoreSettings, SyncContext } from '@tallyui/core';
3
3
 
4
- /** The highest order.create version the WooCommerce transport maps: discounts and the customer (v3). It rises when the transport maps split payments (v4) and fees, shipping and custom lines (v5). */
5
- export const WOO_ORDER_CREATE_VERSION = 3;
4
+ /** The highest order.create version the WooCommerce transport maps: v4's net discounts, and v5's fees, shipping and custom lines (WCPOS push/orders; orders #146, #161). */
5
+ export const WOO_ORDER_CREATE_VERSION = 5;
6
6
 
7
7
  export async function wooStoreSettings(context: SyncContext): Promise<StoreSettings> {
8
8
  try {