@kizlo/woocommerce 0.1.9 → 0.2.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 +6 -6
- package/dist/index.js +231 -170
- package/dist/test.js +38 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1329,7 +1329,7 @@ declare function woocommerce(): kizlo0.Extension<"woocommerce", {
|
|
|
1329
1329
|
attributes: {
|
|
1330
1330
|
id: number;
|
|
1331
1331
|
name: string;
|
|
1332
|
-
taxonomy: string;
|
|
1332
|
+
taxonomy: string | null;
|
|
1333
1333
|
hasVariations: boolean;
|
|
1334
1334
|
terms: {
|
|
1335
1335
|
id: number;
|
|
@@ -1537,7 +1537,7 @@ declare function woocommerce(): kizlo0.Extension<"woocommerce", {
|
|
|
1537
1537
|
attributes: {
|
|
1538
1538
|
id: number;
|
|
1539
1539
|
name: string;
|
|
1540
|
-
taxonomy: string;
|
|
1540
|
+
taxonomy: string | null;
|
|
1541
1541
|
hasVariations: boolean;
|
|
1542
1542
|
terms: {
|
|
1543
1543
|
id: number;
|
|
@@ -1719,7 +1719,7 @@ declare function woocommerce(): kizlo0.Extension<"woocommerce", {
|
|
|
1719
1719
|
taxonomy: string;
|
|
1720
1720
|
description: string;
|
|
1721
1721
|
count: number;
|
|
1722
|
-
type: "
|
|
1722
|
+
type: "image" | "text" | "color";
|
|
1723
1723
|
swatch: string | null;
|
|
1724
1724
|
}[];
|
|
1725
1725
|
taxonomyTerms: {
|
|
@@ -2424,8 +2424,7 @@ declare function woocommerce(): kizlo0.Extension<"woocommerce", {
|
|
|
2424
2424
|
retry: kizlo0.Procedure<"api", {
|
|
2425
2425
|
body: {
|
|
2426
2426
|
key: string;
|
|
2427
|
-
|
|
2428
|
-
billingAddress?: {
|
|
2427
|
+
billingAddress: {
|
|
2429
2428
|
firstName: string;
|
|
2430
2429
|
lastName: string;
|
|
2431
2430
|
phone: string;
|
|
@@ -2437,7 +2436,8 @@ declare function woocommerce(): kizlo0.Extension<"woocommerce", {
|
|
|
2437
2436
|
email: string;
|
|
2438
2437
|
company?: string | undefined;
|
|
2439
2438
|
address2?: string | undefined;
|
|
2440
|
-
}
|
|
2439
|
+
};
|
|
2440
|
+
paymentMethod: string;
|
|
2441
2441
|
shippingAddress?: {
|
|
2442
2442
|
firstName: string;
|
|
2443
2443
|
lastName: string;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CurrencyFormat, IdentifierInput, KizloError, ListMetadata, Seo,
|
|
1
|
+
import { CurrencyFormat, IdentifierInput, KizloError, ListMetadata, Seo, createExtension, createMiddleware, createProcedure, defineErrorMap, deserializeCurrencyFormat, deserializeListMetadata } from "kizlo";
|
|
2
2
|
import { BooleanLike, Media, Metadata, NumberLike, arrayable, normalizeArrayableValue, random, seconds, timestampSec, toPublicMetadata, tryCatch } from "@kizlo/shared";
|
|
3
3
|
import { SignJWT, jwtVerify } from "jose";
|
|
4
4
|
import z$1, { z } from "zod/v4";
|
|
@@ -80,14 +80,11 @@ function sessionMiddleware(options) {
|
|
|
80
80
|
if (foundToken) {
|
|
81
81
|
const [err, data] = await tryCatch(verifyToken(foundToken, context.config.siteSecret));
|
|
82
82
|
if (!err) {
|
|
83
|
-
const response = await context.wordpress.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
connInfo
|
|
89
|
-
})
|
|
90
|
-
});
|
|
83
|
+
const response = await context.wordpress.woocommerce.kizlo.cart.merge({}, { headers: getCartHeaders({
|
|
84
|
+
userId: auth.id,
|
|
85
|
+
token: data.sub,
|
|
86
|
+
connInfo
|
|
87
|
+
}) });
|
|
91
88
|
if (response.error) context.logger.error("CART_MERGE_FAILED", response.error);
|
|
92
89
|
}
|
|
93
90
|
await context.cookies.delete(cookieName);
|
|
@@ -228,7 +225,7 @@ const ProductAttributeTermRef = z.object({
|
|
|
228
225
|
const ProductAttributeRef = z.object({
|
|
229
226
|
id: z.number(),
|
|
230
227
|
name: z.string(),
|
|
231
|
-
taxonomy: z.string(),
|
|
228
|
+
taxonomy: z.string().nullable(),
|
|
232
229
|
hasVariations: z.boolean(),
|
|
233
230
|
terms: z.array(ProductAttributeTermRef)
|
|
234
231
|
});
|
|
@@ -753,10 +750,7 @@ const CART_ROUTER = {
|
|
|
753
750
|
errors: GET_CART_ERROR_MAP,
|
|
754
751
|
middlewares: [sessionMiddleware()]
|
|
755
752
|
}, async ({ context, errors }) => {
|
|
756
|
-
const response = await context.wordpress.get(
|
|
757
|
-
base: WC_STORE_BASE,
|
|
758
|
-
headers: context.sessionHeaders
|
|
759
|
-
});
|
|
753
|
+
const response = await context.wordpress.woocommerce.store.cart.get({}, { headers: context.sessionHeaders });
|
|
760
754
|
if (response.error) switch (response.error.code) {
|
|
761
755
|
default:
|
|
762
756
|
context.logger.error("Get cart unhandled error", response.error, { code: response.error.code });
|
|
@@ -774,45 +768,41 @@ const CART_ROUTER = {
|
|
|
774
768
|
middlewares: [sessionMiddleware()]
|
|
775
769
|
}, async ({ context, input: { body: input }, errors }) => {
|
|
776
770
|
const connInfo = await context.getConnInfo();
|
|
777
|
-
const
|
|
771
|
+
const billing = input.billing ?? {
|
|
778
772
|
...input.shipping,
|
|
779
773
|
email: void 0
|
|
780
774
|
};
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
country: defaultBilling?.country ?? connInfo?.country ?? void 0,
|
|
793
|
-
postcode: defaultBilling?.postcode ?? connInfo?.postcode ?? ""
|
|
794
|
-
} : {},
|
|
795
|
-
shipping_address: input.shipping !== void 0 ? {
|
|
796
|
-
first_name: input.shipping?.firstName ?? "",
|
|
797
|
-
last_name: input.shipping?.lastName ?? "",
|
|
798
|
-
address_1: input.shipping?.address1 ?? "",
|
|
799
|
-
address_2: input.shipping?.address2 ?? "",
|
|
800
|
-
company: input.shipping?.company ?? "",
|
|
801
|
-
phone: input.shipping?.phone ?? "",
|
|
802
|
-
city: input.shipping?.city ?? "",
|
|
803
|
-
state: input.shipping?.state ?? connInfo?.state ?? void 0,
|
|
804
|
-
country: input.shipping?.country ?? connInfo?.country ?? void 0,
|
|
805
|
-
postcode: input.shipping?.postcode ?? connInfo?.postcode ?? ""
|
|
806
|
-
} : {}
|
|
775
|
+
const address = {
|
|
776
|
+
first_name: billing.firstName ?? "",
|
|
777
|
+
last_name: billing.lastName ?? "",
|
|
778
|
+
address_1: billing.address1 ?? "",
|
|
779
|
+
address_2: billing.address2 ?? "",
|
|
780
|
+
company: billing.company ?? "",
|
|
781
|
+
phone: billing.phone ?? "",
|
|
782
|
+
city: billing.city ?? "",
|
|
783
|
+
state: billing.state ?? connInfo?.state ?? "",
|
|
784
|
+
country: billing.country ?? connInfo?.country ?? "",
|
|
785
|
+
postcode: billing.postcode ?? connInfo?.postcode ?? ""
|
|
807
786
|
};
|
|
808
|
-
const response = await context.wordpress.
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
shipping_address: updateData.shipping_address
|
|
787
|
+
const response = await context.wordpress.woocommerce.store.cart.updateCustomer({
|
|
788
|
+
billing_address: {
|
|
789
|
+
...address,
|
|
790
|
+
email: billing.email ?? ""
|
|
813
791
|
},
|
|
814
|
-
|
|
815
|
-
|
|
792
|
+
...input.shipping && { shipping_address: {
|
|
793
|
+
...address,
|
|
794
|
+
first_name: input.shipping.firstName ?? "",
|
|
795
|
+
last_name: input.shipping.lastName ?? "",
|
|
796
|
+
address_1: input.shipping.address1 ?? "",
|
|
797
|
+
address_2: input.shipping.address2 ?? "",
|
|
798
|
+
company: input.shipping.company ?? "",
|
|
799
|
+
phone: input.shipping.phone ?? "",
|
|
800
|
+
city: input.shipping.city ?? "",
|
|
801
|
+
state: input.shipping.state ?? connInfo?.state ?? "",
|
|
802
|
+
country: input.shipping.country ?? connInfo?.country ?? "",
|
|
803
|
+
postcode: input.shipping.postcode ?? connInfo?.postcode ?? ""
|
|
804
|
+
} }
|
|
805
|
+
}, { headers: context.sessionHeaders });
|
|
816
806
|
if (response.error) switch (response.error.code) {
|
|
817
807
|
default:
|
|
818
808
|
context.logger.error("Update cart customer unhandled error", response.error, { code: response.error.code });
|
|
@@ -829,14 +819,10 @@ const CART_ROUTER = {
|
|
|
829
819
|
errors: SELECT_SHIPPING_RATE_ERROR_MAP,
|
|
830
820
|
middlewares: [sessionMiddleware()]
|
|
831
821
|
}, async ({ context, input: { body }, errors }) => {
|
|
832
|
-
const response = await context.wordpress.
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
rate_id: body.rateId,
|
|
837
|
-
package_id: body.packageId
|
|
838
|
-
}
|
|
839
|
-
});
|
|
822
|
+
const response = await context.wordpress.woocommerce.store.cart.selectShippingRate({
|
|
823
|
+
rate_id: body.rateId,
|
|
824
|
+
package_id: body.packageId
|
|
825
|
+
}, { headers: context.sessionHeaders });
|
|
840
826
|
if (response.error) switch (response.error.code) {
|
|
841
827
|
case "woocommerce_rest_cart_shipping_rate_not_found": throw errors.CART_SHIPPING_RATE_NOT_FOUND({ message: response.error.message });
|
|
842
828
|
case "woocommerce_rest_shipping_disabled": throw errors.CART_SHIPPING_DISABLED({ message: response.error.message });
|
|
@@ -856,15 +842,11 @@ const CART_ROUTER = {
|
|
|
856
842
|
errors: ADD_CART_ITEM_ERROR_MAP,
|
|
857
843
|
middlewares: [sessionMiddleware()]
|
|
858
844
|
}, async ({ context, input: { body: input }, errors }) => {
|
|
859
|
-
const response = await context.wordpress.
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
},
|
|
865
|
-
base: WC_STORE_BASE,
|
|
866
|
-
headers: context.sessionHeaders
|
|
867
|
-
});
|
|
845
|
+
const response = await context.wordpress.woocommerce.store.cart.addItem({
|
|
846
|
+
id: input.productId,
|
|
847
|
+
quantity: input.quantity,
|
|
848
|
+
variation: input.variations ?? []
|
|
849
|
+
}, { headers: context.sessionHeaders });
|
|
868
850
|
if (response.error) switch (response.error.code) {
|
|
869
851
|
case "woocommerce_rest_product_out_of_stock": throw errors.CART_ITEM_OUT_OF_STOCK({ message: response.error.message });
|
|
870
852
|
case "woocommerce_rest_product_partially_out_of_stock": throw errors.CART_ITEM_INSUFFICIENT_STOCK({ message: response.error.message });
|
|
@@ -893,14 +875,10 @@ const CART_ROUTER = {
|
|
|
893
875
|
errors: UPDATE_CART_ITEM_ERROR_MAP,
|
|
894
876
|
middlewares: [sessionMiddleware()]
|
|
895
877
|
}, async ({ context, input: { params, body }, errors }) => {
|
|
896
|
-
const response = await context.wordpress.
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
},
|
|
901
|
-
base: WC_STORE_BASE,
|
|
902
|
-
headers: context.sessionHeaders
|
|
903
|
-
});
|
|
878
|
+
const response = await context.wordpress.woocommerce.store.cart.updateItem({
|
|
879
|
+
key: params.key,
|
|
880
|
+
quantity: body.quantity
|
|
881
|
+
}, { headers: context.sessionHeaders });
|
|
904
882
|
if (response.error) switch (response.error.code) {
|
|
905
883
|
case "woocommerce_rest_cart_invalid_key": throw errors.CART_ITEM_NOT_FOUND({ message: response.error.message });
|
|
906
884
|
case "woocommerce_rest_cart_invalid_product": throw errors.CART_PRODUCT_INVALID({ message: response.error.message });
|
|
@@ -922,11 +900,7 @@ const CART_ROUTER = {
|
|
|
922
900
|
errors: REMOVE_CART_ITEM_ERROR_MAP,
|
|
923
901
|
middlewares: [sessionMiddleware()]
|
|
924
902
|
}, async ({ context, input: { params }, errors }) => {
|
|
925
|
-
const response = await context.wordpress.
|
|
926
|
-
body: { key: params.key },
|
|
927
|
-
base: WC_STORE_BASE,
|
|
928
|
-
headers: context.sessionHeaders
|
|
929
|
-
});
|
|
903
|
+
const response = await context.wordpress.woocommerce.store.cart.removeItem({ key: params.key }, { headers: context.sessionHeaders });
|
|
930
904
|
if (response.error) switch (response.error.code) {
|
|
931
905
|
case "woocommerce_rest_cart_invalid_key": throw errors.CART_ITEM_NOT_FOUND({ message: response.error.message });
|
|
932
906
|
default:
|
|
@@ -946,11 +920,7 @@ const CART_ROUTER = {
|
|
|
946
920
|
errors: APPLY_COUPON_ERROR_MAP,
|
|
947
921
|
middlewares: [sessionMiddleware()]
|
|
948
922
|
}, async ({ context, input: { body }, errors }) => {
|
|
949
|
-
const response = await context.wordpress.
|
|
950
|
-
body: { code: body.code },
|
|
951
|
-
base: WC_STORE_BASE,
|
|
952
|
-
headers: context.sessionHeaders
|
|
953
|
-
});
|
|
923
|
+
const response = await context.wordpress.woocommerce.store.cart.applyCoupon({ code: body.code }, { headers: context.sessionHeaders });
|
|
954
924
|
if (response.error) switch (response.error.code) {
|
|
955
925
|
case "woocommerce_rest_cart_coupon_error": throw errors.CART_COUPON_INVALID({ message: response.error.message });
|
|
956
926
|
case "woocommerce_rest_cart_coupon_disabled": throw errors.CART_COUPON_DISABLED({ message: response.error.message });
|
|
@@ -969,11 +939,7 @@ const CART_ROUTER = {
|
|
|
969
939
|
errors: REMOVE_COUPON_ERROR_MAP,
|
|
970
940
|
middlewares: [sessionMiddleware()]
|
|
971
941
|
}, async ({ context, input, errors }) => {
|
|
972
|
-
const response = await context.wordpress.
|
|
973
|
-
body: { code: input.params.code },
|
|
974
|
-
base: WC_STORE_BASE,
|
|
975
|
-
headers: context.sessionHeaders
|
|
976
|
-
});
|
|
942
|
+
const response = await context.wordpress.woocommerce.store.cart.removeCoupon({ code: input.params.code }, { headers: context.sessionHeaders });
|
|
977
943
|
if (response.error) switch (response.error.code) {
|
|
978
944
|
case "woocommerce_rest_cart_coupon_error": throw errors.CART_COUPON_INVALID({ message: response.error.message });
|
|
979
945
|
case "woocommerce_rest_cart_coupon_disabled": throw errors.CART_COUPON_DISABLED({ message: response.error.message });
|
|
@@ -1175,13 +1141,61 @@ const RetryCheckoutInput = z$2.object({
|
|
|
1175
1141
|
orderId: NumberLike,
|
|
1176
1142
|
paymentMethod: z$2.string(),
|
|
1177
1143
|
billingEmail: z$2.email().optional(),
|
|
1178
|
-
billingAddress: BillingAddress
|
|
1144
|
+
billingAddress: BillingAddress,
|
|
1179
1145
|
paymentData: CheckoutPaymentData.optional(),
|
|
1180
1146
|
shippingAddress: ShippingAddress.optional()
|
|
1181
1147
|
});
|
|
1182
1148
|
|
|
1183
1149
|
//#endregion
|
|
1184
1150
|
//#region src/checkout/utils.ts
|
|
1151
|
+
/**
|
|
1152
|
+
* A payment method the caller named, passed to WooCommerce as one.
|
|
1153
|
+
*
|
|
1154
|
+
* WooCommerce builds this argument's enum from `get_payment_gateway_ids()`, so the contract
|
|
1155
|
+
* enumerates the gateways enabled on the WordPress the client was generated against. That is true of
|
|
1156
|
+
* that install and says nothing about the one a build actually talks to, and WooCommerce agrees: its
|
|
1157
|
+
* own comment on the enum is that further validation happens during the request.
|
|
1158
|
+
*
|
|
1159
|
+
* So the enum is documentation here rather than a constraint, and the gateway a shopper chose is
|
|
1160
|
+
* sent whatever it is. WooCommerce rejects an unknown one with
|
|
1161
|
+
* `woocommerce_rest_checkout_payment_method_disabled`, which both callers already map.
|
|
1162
|
+
*/
|
|
1163
|
+
function gateway(method) {
|
|
1164
|
+
return method;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* A Kizlo address in the shape WooCommerce registered its argument in.
|
|
1168
|
+
*
|
|
1169
|
+
* Kizlo's addresses are camelCase and WooCommerce's are snake_case, and until the contract described
|
|
1170
|
+
* the route nothing said so: the retry call passed a Kizlo address straight through as
|
|
1171
|
+
* `billing_address`, where every key missed and WooCommerce kept the address already on the order.
|
|
1172
|
+
*
|
|
1173
|
+
* Every field is required there, so an address given in part is sent filled out. An address that was
|
|
1174
|
+
* not given at all is a different thing and never reaches here: WooCommerce reads an absent
|
|
1175
|
+
* `shipping_address` as "use the billing address", and a blank one as an address, so a caller who
|
|
1176
|
+
* omitted it must leave the argument off the call rather than send this filled with empty strings.
|
|
1177
|
+
*/
|
|
1178
|
+
function serializeAddress(address) {
|
|
1179
|
+
return {
|
|
1180
|
+
first_name: address.firstName,
|
|
1181
|
+
last_name: address.lastName,
|
|
1182
|
+
address_1: address.address1,
|
|
1183
|
+
address_2: address.address2 ?? "",
|
|
1184
|
+
company: address.company ?? "",
|
|
1185
|
+
city: address.city,
|
|
1186
|
+
state: address.state,
|
|
1187
|
+
country: address.country,
|
|
1188
|
+
postcode: address.postcode,
|
|
1189
|
+
phone: address.phone
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
/** The same, plus the email WooCommerce carries on the billing address alone. */
|
|
1193
|
+
function serializeBillingAddress(address) {
|
|
1194
|
+
return {
|
|
1195
|
+
...serializeAddress(address),
|
|
1196
|
+
email: address.email
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1185
1199
|
function deserializeCheckout(data) {
|
|
1186
1200
|
return {
|
|
1187
1201
|
cart: data.__experimentalCart ? deserializeCart(data.__experimentalCart) : null,
|
|
@@ -1210,16 +1224,40 @@ function deserializeCheckout(data) {
|
|
|
1210
1224
|
postcode: data.billing_address.postcode,
|
|
1211
1225
|
state: data.billing_address.state
|
|
1212
1226
|
},
|
|
1213
|
-
additionalFields: data.additional_fields,
|
|
1227
|
+
additionalFields: additionalFields(data.additional_fields),
|
|
1214
1228
|
customerNote: data.customer_note,
|
|
1215
1229
|
paymentMethod: data.payment_method,
|
|
1216
1230
|
paymentResult: data.payment_result?.payment_status ? {
|
|
1217
|
-
status: data.payment_result.payment_status,
|
|
1231
|
+
status: paymentStatus(data.payment_result.payment_status),
|
|
1218
1232
|
redirectUrl: data.payment_result.redirect_url,
|
|
1219
1233
|
data: data.payment_result.payment_details
|
|
1220
1234
|
} : null
|
|
1221
1235
|
};
|
|
1222
1236
|
}
|
|
1237
|
+
/**
|
|
1238
|
+
* WooCommerce registers `additional_fields` as a bare object, so the contract describes it as one
|
|
1239
|
+
* with undescribed contents: what a site's checkout fields are is a per-site question that no schema
|
|
1240
|
+
* can answer ahead of time. The values are scalars in practice, and anything else is dropped rather
|
|
1241
|
+
* than passed on to fail the procedure's own output validation with a less useful message.
|
|
1242
|
+
*/
|
|
1243
|
+
function additionalFields(fields) {
|
|
1244
|
+
const kept = {};
|
|
1245
|
+
for (const [key, value] of Object.entries(fields ?? {})) if (typeof value === "string" || typeof value === "boolean") kept[key] = value;
|
|
1246
|
+
return kept;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* WooCommerce names the four statuses in the field's description and declares the field a plain
|
|
1250
|
+
* string, so the contract cannot narrow it. `error` is the safe reading of anything unrecognized: a
|
|
1251
|
+
* gateway that answered with something new has not taken payment as far as this checkout knows.
|
|
1252
|
+
*/
|
|
1253
|
+
function paymentStatus(status) {
|
|
1254
|
+
switch (status) {
|
|
1255
|
+
case "success":
|
|
1256
|
+
case "pending":
|
|
1257
|
+
case "failure": return status;
|
|
1258
|
+
default: return "error";
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1223
1261
|
|
|
1224
1262
|
//#endregion
|
|
1225
1263
|
//#region src/checkout/index.ts
|
|
@@ -1232,10 +1270,7 @@ const CHECKOUT_ROUTER = {
|
|
|
1232
1270
|
errors: GET_CHECKOUT_ERROR_MAP,
|
|
1233
1271
|
middlewares: [sessionMiddleware()]
|
|
1234
1272
|
}, async ({ context, errors }) => {
|
|
1235
|
-
const response = await context.wordpress.get(
|
|
1236
|
-
base: WC_STORE_BASE,
|
|
1237
|
-
headers: context.sessionHeaders
|
|
1238
|
-
});
|
|
1273
|
+
const response = await context.wordpress.woocommerce.store.checkout.get({}, { headers: context.sessionHeaders });
|
|
1239
1274
|
if (response.error) switch (response.error.code) {
|
|
1240
1275
|
case "woocommerce_rest_checkout_missing_order": throw errors.CHECKOUT_ORDER_NOT_FOUND({ message: response.error.message });
|
|
1241
1276
|
default:
|
|
@@ -1253,16 +1288,12 @@ const CHECKOUT_ROUTER = {
|
|
|
1253
1288
|
errors: UPDATE_CHECKOUT_ERROR_MAP,
|
|
1254
1289
|
middlewares: [sessionMiddleware()]
|
|
1255
1290
|
}, async ({ context, input, errors }) => {
|
|
1256
|
-
const response = await context.wordpress.
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
__experimental_calc_totals: input.body.recalculateTotals
|
|
1263
|
-
},
|
|
1264
|
-
headers: context.sessionHeaders
|
|
1265
|
-
});
|
|
1291
|
+
const response = await context.wordpress.woocommerce.store.checkout.update({
|
|
1292
|
+
order_notes: input.body.customerNote,
|
|
1293
|
+
payment_method: gateway(input.body.paymentMethod),
|
|
1294
|
+
additional_fields: input.body.additionalFields,
|
|
1295
|
+
__experimental_calc_totals: input.body.recalculateTotals
|
|
1296
|
+
}, { headers: context.sessionHeaders });
|
|
1266
1297
|
if (response.error) switch (response.error.code) {
|
|
1267
1298
|
case "woocommerce_rest_cart_coupon_error": throw errors.CHECKOUT_COUPON_INVALID({ message: response.error.message });
|
|
1268
1299
|
case "woocommerce_rest_checkout_payment_method_disabled": throw errors.CHECKOUT_PAYMENT_METHOD_DISABLED({ message: response.error.message });
|
|
@@ -1287,30 +1318,23 @@ const CHECKOUT_ROUTER = {
|
|
|
1287
1318
|
errors: CONFIRM_CHECKOUT_ERROR_MAP,
|
|
1288
1319
|
middlewares: [sessionMiddleware()]
|
|
1289
1320
|
}, async ({ context, input, errors }) => {
|
|
1290
|
-
const checkoutResponse = await context.wordpress.get(
|
|
1291
|
-
base: WC_STORE_BASE,
|
|
1292
|
-
headers: context.sessionHeaders
|
|
1293
|
-
});
|
|
1321
|
+
const checkoutResponse = await context.wordpress.woocommerce.store.checkout.get({}, { headers: context.sessionHeaders });
|
|
1294
1322
|
if (checkoutResponse.error) switch (checkoutResponse.error.code) {
|
|
1295
1323
|
case "woocommerce_rest_checkout_missing_order": throw errors.CHECKOUT_ORDER_NOT_FOUND({ message: checkoutResponse.error.message });
|
|
1296
1324
|
default:
|
|
1297
1325
|
context.logger.error("Get checkout for confirm unhandled error", checkoutResponse.error, { code: checkoutResponse.error.code });
|
|
1298
1326
|
throw errors.INTERNAL_SERVER_ERROR();
|
|
1299
1327
|
}
|
|
1300
|
-
const confirmResponse = await context.wordpress.
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
additional_fields: checkoutResponse.data.additional_fields
|
|
1311
|
-
},
|
|
1312
|
-
headers: context.sessionHeaders
|
|
1313
|
-
});
|
|
1328
|
+
const confirmResponse = await context.wordpress.woocommerce.store.checkout.process({
|
|
1329
|
+
payment_data: input.body.paymentData,
|
|
1330
|
+
customer_password: input.body.customerPassword,
|
|
1331
|
+
customer_note: checkoutResponse.data.customer_note,
|
|
1332
|
+
payment_method: checkoutResponse.data.payment_method,
|
|
1333
|
+
create_account: !!input.body.customerPassword?.length,
|
|
1334
|
+
billing_address: checkoutResponse.data.billing_address,
|
|
1335
|
+
shipping_address: checkoutResponse.data.shipping_address,
|
|
1336
|
+
additional_fields: checkoutResponse.data.additional_fields
|
|
1337
|
+
}, { headers: context.sessionHeaders });
|
|
1314
1338
|
if (confirmResponse.error) switch (confirmResponse.error.code) {
|
|
1315
1339
|
case "woocommerce_rest_invalid_address": throw errors.CHECKOUT_ADDRESS_INVALID({ message: confirmResponse.error.message });
|
|
1316
1340
|
case "woocommerce_rest_invalid_address_country": throw errors.CHECKOUT_ADDRESS_COUNTRY_INVALID({ message: confirmResponse.error.message });
|
|
@@ -1346,19 +1370,15 @@ const CHECKOUT_ROUTER = {
|
|
|
1346
1370
|
errors: RETRY_CHECKOUT_ERROR_MAP,
|
|
1347
1371
|
middlewares: [sessionMiddleware()]
|
|
1348
1372
|
}, async ({ context, input, errors }) => {
|
|
1349
|
-
const response = await context.wordpress.
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
shipping_address: input.body.shippingAddress ?? {}
|
|
1359
|
-
},
|
|
1360
|
-
headers: context.sessionHeaders
|
|
1361
|
-
});
|
|
1373
|
+
const response = await context.wordpress.woocommerce.store.checkout.processOrder({
|
|
1374
|
+
key: input.body.key,
|
|
1375
|
+
id: input.params.orderId,
|
|
1376
|
+
payment_data: input.body.paymentData,
|
|
1377
|
+
billing_email: input.body.billingEmail,
|
|
1378
|
+
payment_method: gateway(input.body.paymentMethod),
|
|
1379
|
+
billing_address: serializeBillingAddress(input.body.billingAddress),
|
|
1380
|
+
shipping_address: input.body.shippingAddress ? serializeAddress(input.body.shippingAddress) : void 0
|
|
1381
|
+
}, { headers: context.sessionHeaders });
|
|
1362
1382
|
if (response.error) switch (response.error.code) {
|
|
1363
1383
|
case "woocommerce_rest_invalid_billing_email": throw errors.CHECKOUT_EMAIL_INVALID({ message: response.error.message });
|
|
1364
1384
|
case "woocommerce_rest_checkout_process_payment_error": throw errors.CHECKOUT_PAYMENT_FAILED({ message: response.error.message });
|
|
@@ -1447,8 +1467,17 @@ const CUSTOMER_ROUTER = { get: createProcedure({
|
|
|
1447
1467
|
}, async ({ context, errors }) => {
|
|
1448
1468
|
const auth = await context.getAuthUser();
|
|
1449
1469
|
if (!auth) throw errors.FORBIDDEN();
|
|
1450
|
-
const response = await context.wordpress.
|
|
1451
|
-
if (response.error)
|
|
1470
|
+
const response = await context.wordpress.woocommerce.customers.retrieve({ id: auth.id });
|
|
1471
|
+
if (response.error) switch (response.error.code) {
|
|
1472
|
+
case "wc_user_invalid_id": throw errors.NOT_FOUND();
|
|
1473
|
+
case "woocommerce_rest_cannot_view": throw errors.FORBIDDEN();
|
|
1474
|
+
default:
|
|
1475
|
+
context.logger.error("Get customer unhandled error", response.error, {
|
|
1476
|
+
userId: auth.id,
|
|
1477
|
+
code: response.error.code
|
|
1478
|
+
});
|
|
1479
|
+
throw errors.INTERNAL_SERVER_ERROR();
|
|
1480
|
+
}
|
|
1452
1481
|
return deserializeCustomer(response.data);
|
|
1453
1482
|
}) };
|
|
1454
1483
|
|
|
@@ -1472,13 +1501,13 @@ function deserializeProduct(data) {
|
|
|
1472
1501
|
description: data.description,
|
|
1473
1502
|
shortDescription: data.short_description,
|
|
1474
1503
|
prices: {
|
|
1475
|
-
price:
|
|
1476
|
-
salePrice:
|
|
1477
|
-
regularPrice:
|
|
1504
|
+
price: data.kizlo.prices.price,
|
|
1505
|
+
salePrice: data.kizlo.prices.sale_price,
|
|
1506
|
+
regularPrice: data.kizlo.prices.regular_price
|
|
1478
1507
|
},
|
|
1479
1508
|
isSoldIndividually: data.sold_individually,
|
|
1480
1509
|
onSaleFrom: data.date_on_sale_from ? toTimestamp(data.date_on_sale_from) : null,
|
|
1481
|
-
lowStockRemaining:
|
|
1510
|
+
lowStockRemaining: data.low_stock_amount,
|
|
1482
1511
|
onSaleTo: data.date_on_sale_to ? toTimestamp(data.date_on_sale_to) : null,
|
|
1483
1512
|
isInStock: data.stock_status === "instock",
|
|
1484
1513
|
stock: data.stock_quantity,
|
|
@@ -1508,9 +1537,15 @@ function deserializeProduct(data) {
|
|
|
1508
1537
|
};
|
|
1509
1538
|
}
|
|
1510
1539
|
function deserializeStoreProduct(data) {
|
|
1540
|
+
const kizlo = data.extensions.kizlo ?? {
|
|
1541
|
+
stock: null,
|
|
1542
|
+
on_sale_from: null,
|
|
1543
|
+
on_sale_to: null,
|
|
1544
|
+
hs_code: null
|
|
1545
|
+
};
|
|
1511
1546
|
return {
|
|
1512
1547
|
id: data.id,
|
|
1513
|
-
type: data.type,
|
|
1548
|
+
type: productType(data.type),
|
|
1514
1549
|
name: data.name,
|
|
1515
1550
|
slug: data.slug,
|
|
1516
1551
|
sku: data.sku,
|
|
@@ -1545,9 +1580,9 @@ function deserializeStoreProduct(data) {
|
|
|
1545
1580
|
isOnSale: data.on_sale,
|
|
1546
1581
|
parentId: data.parent,
|
|
1547
1582
|
variations: data.variations,
|
|
1548
|
-
stock:
|
|
1549
|
-
onSaleFrom:
|
|
1550
|
-
onSaleTo:
|
|
1583
|
+
stock: kizlo.stock,
|
|
1584
|
+
onSaleFrom: kizlo.on_sale_from ? toTimestamp(kizlo.on_sale_from) : null,
|
|
1585
|
+
onSaleTo: kizlo.on_sale_to ? toTimestamp(kizlo.on_sale_to) : null,
|
|
1551
1586
|
seo: null,
|
|
1552
1587
|
meta: {}
|
|
1553
1588
|
};
|
|
@@ -1557,7 +1592,13 @@ function deserializeProductFilters(data) {
|
|
|
1557
1592
|
const maxPrice = +data.price_range.max_price;
|
|
1558
1593
|
const minPrice = +data.price_range.min_price;
|
|
1559
1594
|
return {
|
|
1560
|
-
stockStatuses: data.stock_status_counts ?? []
|
|
1595
|
+
stockStatuses: (data.stock_status_counts ?? []).flatMap((entry) => {
|
|
1596
|
+
const status = stockStatus(entry.status);
|
|
1597
|
+
return status ? [{
|
|
1598
|
+
count: entry.count,
|
|
1599
|
+
status
|
|
1600
|
+
}] : [];
|
|
1601
|
+
}),
|
|
1561
1602
|
taxonomyTerms: data.kizlo.taxonomy_counts.map((item) => ({
|
|
1562
1603
|
id: item.id,
|
|
1563
1604
|
name: item.name,
|
|
@@ -1608,7 +1649,7 @@ function serializeProductListInput(data) {
|
|
|
1608
1649
|
orderby: data?.orderby,
|
|
1609
1650
|
parent: normalizeArrayableValue(data?.parent),
|
|
1610
1651
|
parent_exclude: normalizeArrayableValue(data?.parentExclude),
|
|
1611
|
-
rating: normalizeArrayableValue(data?.rating)?.map(Number),
|
|
1652
|
+
rating: normalizeArrayableValue(data?.rating)?.map((value) => Number(value)),
|
|
1612
1653
|
sku: data?.sku,
|
|
1613
1654
|
slug: data?.slug,
|
|
1614
1655
|
stock_status: normalizeArrayableValue(data?.stockStatus),
|
|
@@ -1631,6 +1672,20 @@ function serializeProductListInput(data) {
|
|
|
1631
1672
|
search: data?.search
|
|
1632
1673
|
};
|
|
1633
1674
|
}
|
|
1675
|
+
/**
|
|
1676
|
+
* WooCommerce declares both of these fields as plain strings, so the contract cannot narrow them and
|
|
1677
|
+
* neither can this without saying what it does with a value it does not recognize.
|
|
1678
|
+
*
|
|
1679
|
+
* A product type Kizlo has no name for reads as `simple`, which is what every gateway to a product
|
|
1680
|
+
* page needs it to behave like. An unrecognized stock status is dropped from the filter list
|
|
1681
|
+
* instead: a count nobody can label is not a filter anyone can offer.
|
|
1682
|
+
*/
|
|
1683
|
+
function productType(type) {
|
|
1684
|
+
return PRODUCT_TYPES.includes(type) ? type : "simple";
|
|
1685
|
+
}
|
|
1686
|
+
function stockStatus(status) {
|
|
1687
|
+
return PRODUCT_STOCK_STATUSES.includes(status) ? status : null;
|
|
1688
|
+
}
|
|
1634
1689
|
function deserializeImages(images) {
|
|
1635
1690
|
return images.map((item) => ({
|
|
1636
1691
|
id: item.id,
|
|
@@ -1665,7 +1720,7 @@ const PRODUCT_ROUTER = {
|
|
|
1665
1720
|
if (input.query?.previewToken) {
|
|
1666
1721
|
const result = await context.verifyPreviewToken(input.query.previewToken);
|
|
1667
1722
|
if (!result) throw errors.PRODUCT_NOT_FOUND();
|
|
1668
|
-
const response$1 = await context.wordpress.
|
|
1723
|
+
const response$1 = await context.wordpress.woocommerce.products.retrieve({ id: Number(result.id) });
|
|
1669
1724
|
if (response$1.error) switch (response$1.error.code) {
|
|
1670
1725
|
case "woocommerce_rest_product_invalid_id": throw errors.PRODUCT_NOT_FOUND({ message: response$1.error.message });
|
|
1671
1726
|
default:
|
|
@@ -1677,10 +1732,7 @@ const PRODUCT_ROUTER = {
|
|
|
1677
1732
|
}
|
|
1678
1733
|
return deserializeProduct(response$1.data);
|
|
1679
1734
|
}
|
|
1680
|
-
const response = await context.wordpress.
|
|
1681
|
-
searchParams: { slug: input.params.identifier },
|
|
1682
|
-
base: WC_CORE_BASE
|
|
1683
|
-
});
|
|
1735
|
+
const response = await context.wordpress.woocommerce.products.list({ slug: String(input.params.identifier) });
|
|
1684
1736
|
if (response.error) switch (response.error.code) {
|
|
1685
1737
|
default:
|
|
1686
1738
|
context.logger.error("Get product unhandled error", response.error, {
|
|
@@ -1702,10 +1754,7 @@ const PRODUCT_ROUTER = {
|
|
|
1702
1754
|
errors: LIST_PRODUCT_ERROR_MAP
|
|
1703
1755
|
}, async ({ context, input, errors }) => {
|
|
1704
1756
|
const searchParams = serializeProductListInput(input.query);
|
|
1705
|
-
const response = await context.wordpress.
|
|
1706
|
-
base: WC_STORE_BASE,
|
|
1707
|
-
searchParams: { ...searchParams }
|
|
1708
|
-
});
|
|
1757
|
+
const response = await context.wordpress.woocommerce.store.products.list(searchParams);
|
|
1709
1758
|
if (response.error) switch (response.error.code) {
|
|
1710
1759
|
default:
|
|
1711
1760
|
context.logger.error("List products unhandled error", response.error, { code: response.error.code });
|
|
@@ -1729,19 +1778,16 @@ const PRODUCT_ROUTER = {
|
|
|
1729
1778
|
output: ProductFilters.nullable()
|
|
1730
1779
|
}, async ({ context, errors, input }) => {
|
|
1731
1780
|
const searchParams = serializeProductListInput(input.query);
|
|
1732
|
-
const response = await context.wordpress.
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
query_type: item.queryType
|
|
1743
|
-
}))
|
|
1744
|
-
}
|
|
1781
|
+
const response = await context.wordpress.woocommerce.store.products.collectionData({
|
|
1782
|
+
...searchParams,
|
|
1783
|
+
calculate_price_range: true,
|
|
1784
|
+
calculate_rating_counts: input.query?.ratingFilters,
|
|
1785
|
+
calculate_taxonomy_counts: input.query?.taxonomyFilters,
|
|
1786
|
+
calculate_stock_status_counts: input.query?.stockStatusFilters,
|
|
1787
|
+
calculate_attribute_counts: input.query?.attributeFilters?.map((item) => ({
|
|
1788
|
+
taxonomy: item.taxonomy,
|
|
1789
|
+
query_type: item.queryType
|
|
1790
|
+
}))
|
|
1745
1791
|
});
|
|
1746
1792
|
if (response.error) switch (response.error.code) {
|
|
1747
1793
|
default:
|
|
@@ -1757,6 +1803,21 @@ const PRODUCT_ROUTER = {
|
|
|
1757
1803
|
function woocommerce() {
|
|
1758
1804
|
return createExtension({
|
|
1759
1805
|
id: "woocommerce",
|
|
1806
|
+
requires: {
|
|
1807
|
+
plugin: {
|
|
1808
|
+
slug: "kizlo-woocommerce",
|
|
1809
|
+
name: "Kizlo WooCommerce",
|
|
1810
|
+
version: "0.2.0"
|
|
1811
|
+
},
|
|
1812
|
+
endpoints: [
|
|
1813
|
+
"woocommerce.customers",
|
|
1814
|
+
"woocommerce.products",
|
|
1815
|
+
"woocommerce.kizlo.cart",
|
|
1816
|
+
"woocommerce.store.cart",
|
|
1817
|
+
"woocommerce.store.checkout",
|
|
1818
|
+
"woocommerce.store.products"
|
|
1819
|
+
]
|
|
1820
|
+
},
|
|
1760
1821
|
init: () => {
|
|
1761
1822
|
return { router: {
|
|
1762
1823
|
cart: CART_ROUTER,
|
package/dist/test.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { WC_CORE_BASE, WC_STORE_BASE } from "kizlo";
|
|
2
1
|
import { defineFixture, kizloRelease } from "kizlo/test";
|
|
3
2
|
|
|
3
|
+
//#region src/constants.ts
|
|
4
|
+
/**
|
|
5
|
+
* Base paths for the two WooCommerce APIs, used only where this package seeds or inspects a store
|
|
6
|
+
* directly. Everything a router or service reaches goes through a generated endpoint, which derives
|
|
7
|
+
* its own prefix from the namespace the plugin declares.
|
|
8
|
+
*/
|
|
9
|
+
const WC_CORE_BASE = "/wp-json/wc/v3";
|
|
10
|
+
const WC_STORE_BASE = "/wp-json/wc/store/v1";
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
4
13
|
//#region src/test/index.ts
|
|
5
14
|
const PRODUCTS = [
|
|
6
15
|
{
|
|
@@ -51,6 +60,30 @@ async function upsertCoupon(service, coupon) {
|
|
|
51
60
|
if (created.error) throw created.error;
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
63
|
+
* WooCommerce ships every gateway disabled, and paying an order needs an available one, so the
|
|
64
|
+
* checkout tests have nothing to reach without this. Bank transfer is the gateway with no shipping
|
|
65
|
+
* method or country conditions attached to it, so enabling it says the least about the rest.
|
|
66
|
+
*/
|
|
67
|
+
async function enableBankTransfer(service) {
|
|
68
|
+
const updated = await service.put(`${WC_CORE_BASE}/payment_gateways/bacs`, { body: { enabled: true } });
|
|
69
|
+
if (updated.error) throw updated.error;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* WooCommerce bundles Action Scheduler, which keeps its actions in the posts table until it
|
|
73
|
+
* migrates to its own tables, and registers two global post statuses (`in-progress`, `failed`)
|
|
74
|
+
* for as long as that legacy store is live. The migration is scheduled a minute after activation
|
|
75
|
+
* and left to cron, so a WordPress reports those two statuses for roughly its first minute and
|
|
76
|
+
* never again. Every post type's status enum is built from the global list, so a client generated
|
|
77
|
+
* inside that window carries two statuses that one generated outside it does not.
|
|
78
|
+
*
|
|
79
|
+
* Firing Action Scheduler's own migration callback settles it: the runner finds nothing to move
|
|
80
|
+
* on a fresh install and marks the migration complete itself, so this is its cron path run now
|
|
81
|
+
* rather than its bookkeeping written by hand.
|
|
82
|
+
*/
|
|
83
|
+
async function settleActionScheduler(wpEval) {
|
|
84
|
+
await wpEval("if (class_exists(\"ActionScheduler_DataController\") && !ActionScheduler_DataController::is_migration_complete()) { do_action(\"action_scheduler/migration_hook\"); }");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
54
87
|
* WooCommerce test fixture: installs WooCommerce + the kizlo-woocommerce plugin, seeds
|
|
55
88
|
* products/coupons. Pass `plugins` to override the defaults — e.g. bind-mount your local
|
|
56
89
|
* source with `{ path: "plugins/kizlo-woocommerce" }` to develop/test against live files.
|
|
@@ -62,6 +95,9 @@ function woocommerce(opts = {}) {
|
|
|
62
95
|
name: "kizlo-woocommerce",
|
|
63
96
|
source: kizloRelease("kizlo-woocommerce")
|
|
64
97
|
}],
|
|
98
|
+
async settle({ wpEval }) {
|
|
99
|
+
await settleActionScheduler(wpEval);
|
|
100
|
+
},
|
|
65
101
|
async seed({ service }) {
|
|
66
102
|
let productId = 0;
|
|
67
103
|
for (const product of PRODUCTS) {
|
|
@@ -69,6 +105,7 @@ function woocommerce(opts = {}) {
|
|
|
69
105
|
if (!productId) productId = id;
|
|
70
106
|
}
|
|
71
107
|
for (const coupon of COUPONS) await upsertCoupon(service, coupon);
|
|
108
|
+
await enableBankTransfer(service);
|
|
72
109
|
return { productId };
|
|
73
110
|
},
|
|
74
111
|
async cleanup({ service, userId }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kizlo/woocommerce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "WooCommerce extension for Kizlo.",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@kizlo/shared": "0.
|
|
52
|
+
"@kizlo/shared": "0.7.0",
|
|
53
53
|
"jose": "6.1.0",
|
|
54
54
|
"zod": "^4.3.6"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"kizlo": ">=0.
|
|
57
|
+
"kizlo": ">=0.15.0"
|
|
58
58
|
}
|
|
59
59
|
}
|