diff --git a/.husky/pre-commit b/.husky/pre-commit index 9c96ce93..3cff7d0e 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -pnpm build +pnpm build && pnpm test diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 00000000..d974312f --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "storybook": { + "command": "pnpm", + "args": ["--filter", "document", "run", "mcp"] + } + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 6eb5b69d..5991e486 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,8 @@ { - "typescript.tsdk": "node_modules/typescript/lib", "editor.formatOnSave": true, - "javascript.format.enable": true, - "eslint.workingDirectories": [ - "packages/react-components", - "packages/docs", - ], - "eslint.validate": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], - "css.validate": false, - "less.validate": false, - "scss.validate": false, + "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - } -} \ No newline at end of file + "source.fixAll.biome": "explicit" + }, + "biome.configurationPath": "./biome.json" +} diff --git a/biome.json b/biome.json index f44ba47b..3d90f046 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.9/schema.json", "vcs": { "enabled": false, "clientKind": "git", @@ -18,17 +18,42 @@ "enabled": true, "rules": { "recommended": true, + "correctness": { + "useExhaustiveDependencies": "warn", + "noUnusedVariables": "warn", + "useHookAtTopLevel": "warn", + "useParseIntRadix": "warn" + }, + "suspicious": { + "noExplicitAny": "warn", + "noArrayIndexKey": "warn", + "noGlobalIsNan": "warn", + "noConfusingVoidType": "warn" + }, + "complexity": { + "useLiteralKeys": "warn", + "noUselessFragments": "warn", + "useOptionalChain": "warn", + "noUselessSwitchCase": "warn" + }, "style": { - "noParameterAssign": "error", + "noParameterAssign": "warn", "useAsConstAssertion": "error", - "useDefaultParameterLast": "error", + "useDefaultParameterLast": "warn", "useEnumInitializers": "error", "useSelfClosingElements": "error", "useSingleVarDeclarator": "error", - "noUnusedTemplateLiteral": "error", + "noUnusedTemplateLiteral": "warn", "useNumberNamespace": "error", - "noInferrableTypes": "error", - "noUselessElse": "error" + "noInferrableTypes": "warn", + "noUselessElse": "warn" + }, + "a11y": { + "noStaticElementInteractions": "warn", + "useKeyWithClickEvents": "warn", + "useValidAnchor": "warn", + "noSvgWithoutTitle": "warn", + "useAltText": "warn" } } }, diff --git a/examples/checkout/shipments.tsx b/examples/checkout/shipments.tsx index 98213c4d..fecebe81 100644 --- a/examples/checkout/shipments.tsx +++ b/examples/checkout/shipments.tsx @@ -22,7 +22,7 @@ import { ShippingMethodRadioButtonType, Errors, } from 'packages/react-components/src' -import isEmpty from 'lodash/isEmpty' +import { isEmpty } from 'packages/react-components/src/utils/isEmpty' import { useRouter } from 'next/router' import getSdk from '#utils/getSdk' diff --git a/lerna.json b/lerna.json index 46842ec7..1a9ee8f7 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useNx": false, "npmClient": "pnpm", - "version": "4.29.4", + "version": "4.29.6", "command": { "version": { "preid": "beta" diff --git a/package.json b/package.json index 4fc9c6f0..7d347a52 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "preinstall": "npx only-allow pnpm", "build": "pnpm -r build", "prepare": "husky", - "test": "pnpm -r test", + "test": "pnpm -r --workspace-concurrency=1 --no-bail test", "docs:dev": "pnpm --filter docs storybook", "docs:build": "pnpm --filter docs build-storybook", "components:build:dev": "pnpm --filter react-components build:dev", @@ -21,9 +21,26 @@ "clean": "pnpm dlx rimraf --glob **/node_modules **/pnpm-lock.yaml" }, "devDependencies": { - "@biomejs/biome": "^2.3.11", + "@biomejs/biome": "^2.4.7", "husky": "^9.1.7", - "lerna": "^9.0.3", + "lerna": "^9.0.7", "typescript": "^5.9.3" + }, + "pnpm": { + "overrides": { + "tar": "^7.5.11", + "rollup": ">=4.59.0", + "@isaacs/brace-expansion@<=5.0.0": ">=5.0.1", + "axios@>=1.0.0 <=1.7.7": ">=1.13.5", + "minimatch@<3.1.4": ">=3.1.4", + "minimatch@>=5.0.0 <5.1.8": ">=5.1.8", + "minimatch@>=9.0.0 <9.0.7": ">=9.0.7", + "minimatch@>=10.0.0 <10.2.3": ">=10.2.3", + "ajv@>=7.0.0-alpha.0 <8.18.0": "^8.18.0", + "esbuild@<=0.24.2": "^0.25.0", + "picomatch@<2.3.2": ">=2.3.2", + "picomatch@>=4.0.0 <4.0.4": ">=4.0.4", + "storybook@>=10.0.0-beta.0 <10.2.10": ">=10.2.10" + } } } diff --git a/packages/core/extender.ts b/packages/core/extender.ts new file mode 100644 index 00000000..33085079 --- /dev/null +++ b/packages/core/extender.ts @@ -0,0 +1,70 @@ +import { test } from "vitest" +import { getAccessToken } from "./src/auth/getAccessToken.js" + +const clientId = import.meta.env.VITE_SALES_CHANNEL_CLIENT_ID +const integrationClientId = import.meta.env.VITE_INTEGRATION_CLIENT_ID +const integrationClientSecret = import.meta.env.VITE_INTEGRATION_CLIENT_SECRET +const scope = import.meta.env.VITE_SALES_CHANNEL_SCOPE +const domain = import.meta.env.VITE_DOMAIN + +// Separate caches per token type to avoid cross-contamination between fixtures +let salesChannelToken: Awaited> | undefined +let integrationToken: Awaited> | undefined + +export interface CoreTestInterface { + accessToken: Awaited> + config: { + clientId: string + scope?: string + domain: string + } +} + +/** + * This test is used to run integration tests with the sales channel client. + */ +export const coreTest = test.extend({ + // biome-ignore lint/correctness/noEmptyPattern: need to object destructure as the first argument + accessToken: async ({}, use) => { + if (salesChannelToken === undefined) { + salesChannelToken = await getAccessToken({ + grantType: "client_credentials", + config: { + clientId, + scope, + domain, + }, + }) + } + use(salesChannelToken) + }, + config: { + clientId, + scope, + domain, + }, +}) + +/** + * This test is used to run integration tests with the integration client. + */ +export const coreIntegrationTest = test.extend({ + // biome-ignore lint/correctness/noEmptyPattern: need to object destructure as the first argument + accessToken: async ({}, use) => { + if (integrationToken === undefined) { + integrationToken = await getAccessToken({ + grantType: "client_credentials", + config: { + clientId: integrationClientId, + clientSecret: integrationClientSecret, + domain, + }, + }) + } + use(integrationToken) + }, + config: { + clientId: integrationClientId, + domain, + }, +}) diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 00000000..f322cdf4 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,51 @@ +{ + "name": "@commercelayer/core", + "version": "1.0.0", + "description": "Commerce Layer Core", + "type": "module", + "main": "./dist/index.js", + "exports": { + "./package.json": "./package.json", + ".": { + "import": "./dist/index.js", + "default": "./dist/index.cjs" + } + }, + "keywords": [ + "jamstack", + "headless", + "ecommerce", + "api", + "components" + ], + "scripts": { + "check-exports": "attw --pack .", + "lint": "biome lint --error-on-warnings ./src && tsc", + "lint:fix": "pnpm biome lint --write ./src", + "test": "pnpm run lint && vitest run --silent", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "build": "tsup", + "ci": "pnpm build && pnpm check-exports && pnpm lint" + }, + "publishConfig": { + "access": "public" + }, + "author": { + "name": "Alessandro Casazza", + "email": "alessandro@commercelayer.io" + }, + "license": "MIT", + "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", + "@vitest/coverage-v8": "^4.1.0", + "tsup": "^8.5.1", + "typescript": "^5.9.3", + "vite-tsconfig-paths": "^6.1.1", + "vitest": "^4.1.0" + }, + "dependencies": { + "@commercelayer/js-auth": "^7.3.0", + "@commercelayer/sdk": "7.9.0" + } +} diff --git a/packages/core/src/auth/getAccessToken.spec.ts b/packages/core/src/auth/getAccessToken.spec.ts new file mode 100644 index 00000000..415c3abb --- /dev/null +++ b/packages/core/src/auth/getAccessToken.spec.ts @@ -0,0 +1,40 @@ +import { authenticate } from "@commercelayer/js-auth" +import { describe, expect, vi } from "vitest" +import { coreTest } from "#extender" +import { getAccessToken } from "./getAccessToken" + +vi.mock("@commercelayer/js-auth", () => ({ + authenticate: vi.fn(), +})) + +describe("getAccessToken", () => { + coreTest( + "should call authenticate with the correct parameters", + async ({ accessToken, config }) => { + const token = accessToken?.accessToken + const grantType = "client_credentials" + const mockToken = { accessToken: token } + // @ts-expect-error No types for this function + authenticate.mockResolvedValue(mockToken) + const result = await getAccessToken({ grantType, config }) + await expect(authenticate).toHaveBeenCalledWith(grantType, config) + expect(result).toEqual(mockToken) + expect(result).toHaveProperty("accessToken") + expect(result.accessToken).toBe(mockToken.accessToken) + }, + ) + + coreTest("should throw an error if authenticate fails", async () => { + const grantType = "client_credentials" + const config = { + clientId: "test-client-id", + clientSecret: "test-client-secret", + } + const mockError = new Error("Authentication failed") + // @ts-expect-error No types for this function + authenticate.mockRejectedValue(mockError) + await expect(getAccessToken({ grantType, config })).rejects.toThrow( + "Authentication failed", + ) + }) +}) diff --git a/packages/core/src/auth/getAccessToken.ts b/packages/core/src/auth/getAccessToken.ts new file mode 100644 index 00000000..97f41bfa --- /dev/null +++ b/packages/core/src/auth/getAccessToken.ts @@ -0,0 +1,20 @@ +import { authenticate } from "@commercelayer/js-auth" + +interface AuthenticateProps { + grantType: Parameters[0] + config: Parameters[1] +} + +/** + * Retrieves an access token using the provided grant type and configuration. + * + * @param {AuthenticateProps['grantType']} grantType - The type of grant to use for authentication. + * @param {AuthenticateProps['config']} config - The configuration object for authentication. + * @returns {Promise>} A promise that resolves to the access token. + */ +export async function getAccessToken({ + grantType, + config, +}: AuthenticateProps): ReturnType { + return await authenticate(grantType, config) +} diff --git a/packages/core/src/auth/index.ts b/packages/core/src/auth/index.ts new file mode 100644 index 00000000..91c54e8e --- /dev/null +++ b/packages/core/src/auth/index.ts @@ -0,0 +1 @@ +export { getAccessToken } from "./getAccessToken" diff --git a/packages/core/src/availability/getSkuAvailability.spec.ts b/packages/core/src/availability/getSkuAvailability.spec.ts new file mode 100644 index 00000000..17b1eff3 --- /dev/null +++ b/packages/core/src/availability/getSkuAvailability.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect } from "vitest" +import { coreTest } from "#extender" +import { getSkuAvailability } from "./getSkuAvailability.js" + +describe("getSkuAvailability", () => { + coreTest( + "should return availability for a sku code", + async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const result = await getSkuAvailability({ + accessToken: token, + skuCode: "BABYONBU000000E63E7412MX", + }) + expect(result).toBeDefined() + if (result != null) { + expect(typeof result.quantity).toBe("number") + expect(result.skuCode).toBe("BABYONBU000000E63E7412MX") + } + }, + ) + + coreTest( + "should return null for a non-existent sku code", + async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const result = await getSkuAvailability({ + accessToken: token, + skuCode: "NON_EXISTENT_SKU_CODE_XYZ", + }) + expect(result).toBeNull() + }, + ) +}) diff --git a/packages/core/src/availability/getSkuAvailability.ts b/packages/core/src/availability/getSkuAvailability.ts new file mode 100644 index 00000000..a9ad3ec8 --- /dev/null +++ b/packages/core/src/availability/getSkuAvailability.ts @@ -0,0 +1,75 @@ +import { type Sku, skus } from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +export interface LeadTimes { + hours: number + days: number +} + +export interface DeliveryLeadTime { + shipping_method: { + name: string + reference: string + price_amount_cents: number + free_over_amount_cents: number + formatted_price_amount: string + formatted_free_over_amount: string + } + min: LeadTimes + max: LeadTimes +} + +export interface SkuAvailability { + skuCode?: string + quantity: number + min?: LeadTimes + max?: LeadTimes + shipping_method?: DeliveryLeadTime["shipping_method"] +} + +interface GetSkuAvailability extends RequestConfig { + skuCode?: string + skuId?: string +} + +/** + * Retrieve availability information for a SKU by code or ID. + * Returns quantity and delivery lead time from the first inventory level. + * + * @param {string} accessToken - The access token to use for authentication. + * @param {string} skuCode - The code of the SKU. + * @param {string} skuId - The ID of the SKU (takes precedence over skuCode). + * @returns {Promise} - Availability info or null if not found. + */ +export async function getSkuAvailability({ + accessToken, + skuCode, + skuId, +}: GetSkuAvailability): Promise { + getSdk({ accessToken }) + const [sku] = + skuId != null + ? [{ id: skuId } as Sku] + : skuCode != null + ? await skus.list({ + fields: { skus: ["id"] }, + filters: { code_in: skuCode }, + }) + : [] + if (sku == null) return null + const skuInventory = await skus.retrieve(sku.id, { + fields: { skus: ["inventory", "code"] }, + }) + const inventory = (skuInventory as Sku & { inventory?: { available: boolean; quantity: number; levels: Array<{ quantity: number; delivery_lead_times: DeliveryLeadTime[] }> } }).inventory + if (inventory == null) return null + const [level] = inventory.levels ?? [] + const [delivery] = level?.delivery_lead_times ?? [] + return { + skuCode: skuInventory.code, + quantity: inventory.quantity, + min: delivery?.min, + max: delivery?.max, + shipping_method: delivery?.shipping_method, + } +} diff --git a/packages/core/src/availability/index.ts b/packages/core/src/availability/index.ts new file mode 100644 index 00000000..c2ede731 --- /dev/null +++ b/packages/core/src/availability/index.ts @@ -0,0 +1,6 @@ +export type { + SkuAvailability, + DeliveryLeadTime, + LeadTimes, +} from "./getSkuAvailability" +export { getSkuAvailability } from "./getSkuAvailability" diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 00000000..f3cfbb17 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,5 @@ +export * from "./auth" +export * from "./availability" +export * from "./prices" +export * from "./sku_lists" +export * from "./skus" diff --git a/packages/core/src/prices/getPrices.spec.ts b/packages/core/src/prices/getPrices.spec.ts new file mode 100644 index 00000000..a44a6052 --- /dev/null +++ b/packages/core/src/prices/getPrices.spec.ts @@ -0,0 +1,31 @@ +import type { Price, QueryParamsList } from "@commercelayer/sdk" +import { describe, expect } from "vitest" +import { coreTest } from "#extender" +import { getPrices } from "./getPrices.js" + +describe("getPrices", () => { + coreTest("should return a list of prices", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const result = await getPrices({ accessToken: token }) + expect(result).toBeDefined() + }) + + coreTest("should return a single price", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const params = { + filters: { + sku_code_eq: "DIGITALPRODUCT", + }, + } satisfies QueryParamsList + // Call the getPrices function + const result = await getPrices({ accessToken: token, params }) + // Assert the expected result + expect(result).toBeDefined() + expect(result.getRecordCount()).toBe(1) + // Add more assertions based on the expected behavior of the getPrices function + }) + + // Add more test cases for different scenarios +}) diff --git a/packages/core/src/prices/getPrices.ts b/packages/core/src/prices/getPrices.ts new file mode 100644 index 00000000..a64031a9 --- /dev/null +++ b/packages/core/src/prices/getPrices.ts @@ -0,0 +1,33 @@ +import { + type ListResponse, + type Price, + prices, + type QueryParamsList, + type ResourcesConfig, +} from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface GetPrices extends RequestConfig { + params?: QueryParamsList + options?: ResourcesConfig +} + +type GetPricesParams = GetPrices + +/** + * Get a list of prices + * + * @param {string} accessToken - The access token to use for authentication. + * @param {QueryParamsList} params - Optional query parameters for the request. + * @param {ResourcesConfig} options - Optional request configuration. + * @returns {Promise>} - A promise that resolves to a list of price resources. + */ +export async function getPrices({ + accessToken, + params, + options, +}: GetPricesParams): Promise> { + getSdk({ accessToken }) + return await prices.list(params, options) +} diff --git a/packages/core/src/prices/index.ts b/packages/core/src/prices/index.ts new file mode 100644 index 00000000..6a7a83b4 --- /dev/null +++ b/packages/core/src/prices/index.ts @@ -0,0 +1,4 @@ +export type { Price, PriceUpdate } from "@commercelayer/sdk" +export { getPrices } from "./getPrices" +export { retrievePrice } from "./retrievePrice" +export { updatePrice } from "./updatePrice" diff --git a/packages/core/src/prices/retrievePrice.spec.ts b/packages/core/src/prices/retrievePrice.spec.ts new file mode 100644 index 00000000..c83ba41e --- /dev/null +++ b/packages/core/src/prices/retrievePrice.spec.ts @@ -0,0 +1,24 @@ +import { describe, expect } from "vitest" +import { coreTest } from "#extender" +import { getPrices } from "./getPrices.js" +import { retrievePrice } from "./retrievePrice.js" + +describe("retrievePrice", () => { + coreTest("should return a single price", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const firstPrice = (await getPrices({ accessToken: token })).first() + expect(firstPrice).toBeDefined() + if (!firstPrice) { + throw new Error("No price found") + } + const id = firstPrice?.id + const result = await retrievePrice({ + id: id, + accessToken: token, + }) + expect(result).toBeDefined() + expect(result.id).toBe(id) + expect(result.sku_code).toBe(firstPrice.sku_code) + }) +}) diff --git a/packages/core/src/prices/retrievePrice.ts b/packages/core/src/prices/retrievePrice.ts new file mode 100644 index 00000000..815b98ae --- /dev/null +++ b/packages/core/src/prices/retrievePrice.ts @@ -0,0 +1,33 @@ +import { + type Price, + prices, + type QueryParamsRetrieve, +} from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface RetrievePrice extends RequestConfig { + id: string + params?: QueryParamsRetrieve +} + +type RetrievePriceParams = RetrievePrice & QueryParamsRetrieve + +/** + * Retrieve a price + * + * @param {string} accessToken - The access token to use for authentication. + * @param {string} id - The ID of the price resource to retrieve. + * @param {QueryParamsRetrieve} params - Optional query parameters for the request. + * @param {RequestConfig} options - Optional request configuration. + * @returns {Promise} - The retrieved price resource. + */ +export async function retrievePrice({ + accessToken, + id, + params, + options, +}: RetrievePriceParams): Promise { + getSdk({ accessToken }) + return await prices.retrieve(id, params, options) +} diff --git a/packages/core/src/prices/updatePrice.spec.ts b/packages/core/src/prices/updatePrice.spec.ts new file mode 100644 index 00000000..be94dd50 --- /dev/null +++ b/packages/core/src/prices/updatePrice.spec.ts @@ -0,0 +1,40 @@ +import { describe, expect } from "vitest" +import { coreIntegrationTest } from "#extender" +import { getPrices } from "./getPrices" +import { updatePrice } from "./updatePrice" + +describe("updatePrice", () => { + coreIntegrationTest( + "should update a single price", + async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const firstPrice = (await getPrices({ accessToken: token })).first() + expect(firstPrice).toBeDefined() + if (!firstPrice) { + throw new Error("No price found") + } + const id = firstPrice?.id + const result = await updatePrice({ + accessToken: token, + resource: { + id, + reference: "test-price", + }, + }) + expect(result).toBeDefined() + expect(result.id).toBe(id) + expect(result.reference).toBe("test-price") + const clean = await updatePrice({ + accessToken: token, + resource: { + id, + reference: "", + }, + }) + expect(clean).toBeDefined() + expect(clean.id).toBe(id) + expect(clean.reference).toBe("") + }, + ) +}) diff --git a/packages/core/src/prices/updatePrice.ts b/packages/core/src/prices/updatePrice.ts new file mode 100644 index 00000000..6defbc4b --- /dev/null +++ b/packages/core/src/prices/updatePrice.ts @@ -0,0 +1,34 @@ +import { + type Price, + type PriceUpdate, + prices, + type QueryParamsRetrieve, +} from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface UpdatePrice extends RequestConfig { + resource: PriceUpdate + params?: QueryParamsRetrieve +} + +type UpdatePriceParams = UpdatePrice + +/** + * Update a price + * + * @param {string} accessToken - The access token to use for authentication, must be an integration application. + * @param {PriceUpdate} resource - The price resource to update. + * @param {QueryParamsRetrieve} params - Optional query parameters for the request. + * @param {RequestConfig} options - Optional request configuration. + * @returns {Promise} - The updated price resource. + */ +export async function updatePrice({ + accessToken, + resource, + params, + options, +}: UpdatePriceParams): Promise { + getSdk({ accessToken }) + return await prices.update(resource, params, options) +} diff --git a/packages/core/src/sdk/index.ts b/packages/core/src/sdk/index.ts new file mode 100644 index 00000000..5a7336d3 --- /dev/null +++ b/packages/core/src/sdk/index.ts @@ -0,0 +1,25 @@ +import { + type JWTIntegration, + type JWTSalesChannel, + type JWTWebApp, + jwtDecode, +} from "@commercelayer/js-auth" +import sdk from "@commercelayer/sdk" +import type { RequestConfig } from "#types" + +/** + * Get the Commerce Layer SDK instance + * + * @param {string} accessToken - The access token to use for authentication. + * @returns {void} + */ +export function getSdk({ accessToken }: RequestConfig): void { + const { payload } = jwtDecode(accessToken) + const { organization } = payload as + | JWTIntegration + | JWTWebApp + | JWTSalesChannel + const slug = organization.slug + const cl = sdk({ accessToken, organization: slug }) + cl.addRawResponseReader() +} diff --git a/packages/core/src/sku_lists/getSkuLists.spec.ts b/packages/core/src/sku_lists/getSkuLists.spec.ts new file mode 100644 index 00000000..6e134067 --- /dev/null +++ b/packages/core/src/sku_lists/getSkuLists.spec.ts @@ -0,0 +1,12 @@ +import { describe, expect } from "vitest" +import { coreIntegrationTest } from "#extender" +import { getSkuLists } from "./getSkuLists.js" + +describe("getSkuLists", () => { + coreIntegrationTest("should return a list of SKU lists", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const result = await getSkuLists({ accessToken: token }) + expect(result).toBeDefined() + }) +}) diff --git a/packages/core/src/sku_lists/getSkuLists.ts b/packages/core/src/sku_lists/getSkuLists.ts new file mode 100644 index 00000000..904ca7af --- /dev/null +++ b/packages/core/src/sku_lists/getSkuLists.ts @@ -0,0 +1,27 @@ +import { + type ListResponse, + type QueryParamsList, + type SkuList, + sku_lists, +} from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface GetSkuLists extends RequestConfig { + params?: QueryParamsList +} + +/** + * Get a list of SKU lists + * + * @param {string} accessToken - The access token to use for authentication. + * @param {QueryParamsList} params - Optional query parameters for the request. + * @returns {Promise>} - A promise that resolves to a list of SKU list resources. + */ +export async function getSkuLists({ + accessToken, + params, +}: GetSkuLists): Promise> { + getSdk({ accessToken }) + return await sku_lists.list(params) +} diff --git a/packages/core/src/sku_lists/index.ts b/packages/core/src/sku_lists/index.ts new file mode 100644 index 00000000..4638cc24 --- /dev/null +++ b/packages/core/src/sku_lists/index.ts @@ -0,0 +1,3 @@ +export type { SkuList } from "@commercelayer/sdk" +export { getSkuLists } from "./getSkuLists" +export { retrieveSkuList } from "./retrieveSkuList" diff --git a/packages/core/src/sku_lists/retrieveSkuList.spec.ts b/packages/core/src/sku_lists/retrieveSkuList.spec.ts new file mode 100644 index 00000000..ca74e26e --- /dev/null +++ b/packages/core/src/sku_lists/retrieveSkuList.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect } from "vitest" +import { coreIntegrationTest } from "#extender" +import { getSkuLists } from "./getSkuLists.js" +import { retrieveSkuList } from "./retrieveSkuList.js" + +describe("retrieveSkuList", () => { + coreIntegrationTest( + "should retrieve a SKU list by id with included skus", + async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const lists = await getSkuLists({ accessToken: token }) + const first = lists.first() + if (!first) { + console.warn("No SKU lists available, skipping test") + return + } + const result = await retrieveSkuList({ + accessToken: token, + id: first.id, + params: { include: ["skus"], fields: { skus: ["code"] } }, + }) + expect(result).toBeDefined() + expect(result.id).toBe(first.id) + }, + ) +}) diff --git a/packages/core/src/sku_lists/retrieveSkuList.ts b/packages/core/src/sku_lists/retrieveSkuList.ts new file mode 100644 index 00000000..81ededad --- /dev/null +++ b/packages/core/src/sku_lists/retrieveSkuList.ts @@ -0,0 +1,29 @@ +import { + type QueryParamsRetrieve, + type SkuList, + sku_lists, +} from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface RetrieveSkuList extends RequestConfig { + id: string + params?: QueryParamsRetrieve +} + +/** + * Retrieve a SKU list by ID, optionally including its skus + * + * @param {string} accessToken - The access token to use for authentication. + * @param {string} id - The ID of the SKU list resource to retrieve. + * @param {QueryParamsRetrieve} params - Optional query parameters for the request. + * @returns {Promise} - The retrieved SKU list resource. + */ +export async function retrieveSkuList({ + accessToken, + id, + params, +}: RetrieveSkuList): Promise { + getSdk({ accessToken }) + return await sku_lists.retrieve(id, params) +} diff --git a/packages/core/src/skus/getSkus.spec.ts b/packages/core/src/skus/getSkus.spec.ts new file mode 100644 index 00000000..4de499bf --- /dev/null +++ b/packages/core/src/skus/getSkus.spec.ts @@ -0,0 +1,26 @@ +import type { QueryParamsList, Sku } from "@commercelayer/sdk" +import { describe, expect } from "vitest" +import { coreTest } from "#extender" +import { getSkus } from "./getSkus.js" + +describe("getSkus", () => { + coreTest("should return a list of SKUs", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const result = await getSkus({ accessToken: token }) + expect(result).toBeDefined() + }) + + coreTest("should return a filtered list of SKUs", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const params = { + filters: { + code_eq: "DIGITALPRODUCT", + }, + } satisfies QueryParamsList + const result = await getSkus({ accessToken: token, params }) + expect(result).toBeDefined() + expect(result.getRecordCount()).toBeGreaterThanOrEqual(0) + }) +}) diff --git a/packages/core/src/skus/getSkus.ts b/packages/core/src/skus/getSkus.ts new file mode 100644 index 00000000..b4f1a7f8 --- /dev/null +++ b/packages/core/src/skus/getSkus.ts @@ -0,0 +1,33 @@ +import { + type ListResponse, + type QueryParamsList, + type ResourcesConfig, + type Sku, + skus, +} from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface GetSkus extends RequestConfig { + params?: QueryParamsList + options?: ResourcesConfig +} + +type GetSkusParams = GetSkus + +/** + * Get a list of SKUs + * + * @param {string} accessToken - The access token to use for authentication. + * @param {QueryParamsList} params - Optional query parameters for the request. + * @param {ResourcesConfig} options - Optional request configuration. + * @returns {Promise>} - A promise that resolves to a list of SKU resources. + */ +export async function getSkus({ + accessToken, + params, + options, +}: GetSkusParams): Promise> { + getSdk({ accessToken }) + return await skus.list(params, options) +} diff --git a/packages/core/src/skus/index.ts b/packages/core/src/skus/index.ts new file mode 100644 index 00000000..28b5bef2 --- /dev/null +++ b/packages/core/src/skus/index.ts @@ -0,0 +1,4 @@ +export type { Sku, SkuUpdate } from "@commercelayer/sdk" +export { getSkus } from "./getSkus" +export { retrieveSku } from "./retrieveSku" +export { updateSku } from "./updateSku" diff --git a/packages/core/src/skus/retrieveSku.spec.ts b/packages/core/src/skus/retrieveSku.spec.ts new file mode 100644 index 00000000..c5340677 --- /dev/null +++ b/packages/core/src/skus/retrieveSku.spec.ts @@ -0,0 +1,23 @@ +import { describe, expect } from "vitest" +import { coreIntegrationTest } from "#extender" +import { getSkus } from "./getSkus.js" +import { retrieveSku } from "./retrieveSku.js" + +describe("retrieveSku", () => { + coreIntegrationTest("should return a single SKU", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const firstSku = (await getSkus({ accessToken: token })).first() + expect(firstSku).toBeDefined() + if (!firstSku) { + throw new Error("No SKU found") + } + const result = await retrieveSku({ + id: firstSku.id, + accessToken: token, + }) + expect(result).toBeDefined() + expect(result.id).toBe(firstSku.id) + expect(result.code).toBe(firstSku.code) + }) +}) diff --git a/packages/core/src/skus/retrieveSku.ts b/packages/core/src/skus/retrieveSku.ts new file mode 100644 index 00000000..8b9962bc --- /dev/null +++ b/packages/core/src/skus/retrieveSku.ts @@ -0,0 +1,29 @@ +import { type QueryParamsRetrieve, type Sku, skus } from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface RetrieveSku extends RequestConfig { + id: string + params?: QueryParamsRetrieve +} + +type RetrieveSkuParams = RetrieveSku & QueryParamsRetrieve + +/** + * Retrieve a SKU + * + * @param {string} accessToken - The access token to use for authentication. + * @param {string} id - The ID of the SKU resource to retrieve. + * @param {QueryParamsRetrieve} params - Optional query parameters for the request. + * @param {RequestConfig} options - Optional request configuration. + * @returns {Promise} - The retrieved SKU resource. + */ +export async function retrieveSku({ + accessToken, + id, + params, + options, +}: RetrieveSkuParams): Promise { + getSdk({ accessToken }) + return await skus.retrieve(id, params, options) +} diff --git a/packages/core/src/skus/updateSku.spec.ts b/packages/core/src/skus/updateSku.spec.ts new file mode 100644 index 00000000..a044e189 --- /dev/null +++ b/packages/core/src/skus/updateSku.spec.ts @@ -0,0 +1,36 @@ +import { describe, expect } from "vitest" +import { coreIntegrationTest } from "#extender" +import { getSkus } from "./getSkus" +import { updateSku } from "./updateSku" + +describe("updateSku", () => { + coreIntegrationTest("should update a single SKU", async ({ accessToken }) => { + const token = accessToken?.accessToken + if (token == null) return + const firstSku = (await getSkus({ accessToken: token })).first() + expect(firstSku).toBeDefined() + if (!firstSku) { + throw new Error("No SKU found") + } + const result = await updateSku({ + accessToken: token, + resource: { + id: firstSku.id, + reference: "test-sku", + }, + }) + expect(result).toBeDefined() + expect(result.id).toBe(firstSku.id) + expect(result.reference).toBe("test-sku") + const clean = await updateSku({ + accessToken: token, + resource: { + id: firstSku.id, + reference: "", + }, + }) + expect(clean).toBeDefined() + expect(clean.id).toBe(firstSku.id) + expect(clean.reference).toBe("") + }) +}) diff --git a/packages/core/src/skus/updateSku.ts b/packages/core/src/skus/updateSku.ts new file mode 100644 index 00000000..5cc46ab4 --- /dev/null +++ b/packages/core/src/skus/updateSku.ts @@ -0,0 +1,34 @@ +import { + type QueryParamsRetrieve, + type Sku, + type SkuUpdate, + skus, +} from "@commercelayer/sdk" +import { getSdk } from "#sdk" +import type { RequestConfig } from "#types" + +interface UpdateSku extends RequestConfig { + resource: SkuUpdate + params?: QueryParamsRetrieve +} + +type UpdateSkuParams = UpdateSku + +/** + * Update a SKU + * + * @param {string} accessToken - The access token to use for authentication, must be an integration application. + * @param {SkuUpdate} resource - The SKU resource to update. + * @param {QueryParamsRetrieve} params - Optional query parameters for the request. + * @param {RequestConfig} options - Optional request configuration. + * @returns {Promise} - The updated SKU resource. + */ +export async function updateSku({ + accessToken, + resource, + params, + options, +}: UpdateSkuParams): Promise { + getSdk({ accessToken }) + return await skus.update(resource, params, options) +} diff --git a/packages/core/src/types/base.ts b/packages/core/src/types/base.ts new file mode 100644 index 00000000..d97b408d --- /dev/null +++ b/packages/core/src/types/base.ts @@ -0,0 +1,8 @@ +import type { ResourcesConfig } from "@commercelayer/sdk" + +export interface RequestConfig { + accessToken: string + id?: string + params?: unknown + options?: ResourcesConfig +} diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts new file mode 100644 index 00000000..637e47a5 --- /dev/null +++ b/packages/core/src/types/index.ts @@ -0,0 +1 @@ +export type { RequestConfig } from "./base" diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 00000000..366986a2 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + "lib": ["es2022"], + "noEmit": true, + + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + /* If transpiling with TypeScript: */ + "module": "Preserve", + + /* Relative Paths */ + "baseUrl": ".", + "paths": { + "#sdk": ["src/sdk/index.ts"], + "#types": ["src/types/index.ts"], + "#extender": ["extender.ts"] + } + }, + "exclude": ["node_modules", "dist", "coverage", "*.spec.ts"] +} diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts new file mode 100644 index 00000000..26e341d9 --- /dev/null +++ b/packages/core/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsup" + +export default defineConfig(() => ({ + entryPoints: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + splitting: true, + outDir: "dist", + clean: true, + treeshake: true, +})) diff --git a/packages/core/vite-env.d.ts b/packages/core/vite-env.d.ts new file mode 100644 index 00000000..c16c20fd --- /dev/null +++ b/packages/core/vite-env.d.ts @@ -0,0 +1,13 @@ +/// + +interface ImportMetaEnv { + readonly VITE_SALES_CHANNEL_CLIENT_ID: string + readonly VITE_SALES_CHANNEL_SCOPE: string + readonly VITE_INTEGRATION_CLIENT_ID: string + readonly VITE_INTEGRATION_CLIENT_SECRET: string + readonly VITE_DOMAIN: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 00000000..a0f1e3e6 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,15 @@ +import tsconfigPaths from "vite-tsconfig-paths" +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + name: "core", + environment: "node", + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + exclude: ["**/extender.ts"], + }, + }, + plugins: [tsconfigPaths()], +}) diff --git a/packages/docs/package.json b/packages/docs/package.json index cd65180f..29620253 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,48 +1,48 @@ { "private": true, "name": "docs", - "version": "4.29.4", + "version": "4.28.3", "devDependencies": { - "@babel/core": "^7.26.9", - "@babel/preset-env": "^7.26.9", - "@commercelayer/js-auth": "^6.7.1", - "@commercelayer/sdk": "^6.32.0", - "@mdx-js/react": "^3.1.0", - "@storybook/addon-actions": "^7.6.17", - "@storybook/addon-backgrounds": "^7.6.17", - "@storybook/addon-docs": "^7.6.17", - "@storybook/addon-essentials": "^7.6.17", - "@storybook/addon-interactions": "^7.6.17", - "@storybook/addon-links": "^7.6.17", - "@storybook/addon-mdx-gfm": "^7.6.17", - "@storybook/addon-measure": "^7.6.17", - "@storybook/addon-outline": "^7.6.17", + "@babel/core": "^7.28.6", + "@babel/preset-env": "^7.28.6", + "@commercelayer/js-auth": "^7.1.2", + "@commercelayer/sdk": "^7.4.1", + "@mdx-js/react": "^3.1.1", + "@storybook/addon-actions": "^9.0.8", + "@storybook/addon-backgrounds": "^9.0.8", + "@storybook/addon-docs": "^10.1.11", + "@storybook/addon-essentials": "^8.6.14", + "@storybook/addon-interactions": "^8.6.14", + "@storybook/addon-links": "^10.1.11", + "@storybook/addon-mdx-gfm": "^8.6.14", + "@storybook/addon-measure": "^9.0.8", + "@storybook/addon-outline": "^9.0.8", "@storybook/addons": "^7.6.17", "@storybook/api": "^7.6.17", - "@storybook/blocks": "^7.6.17", + "@storybook/blocks": "^8.6.14", "@storybook/client-api": "^7.6.17", - "@storybook/client-logger": "^7.6.17", - "@storybook/manager-api": "^7.6.17", - "@storybook/node-logger": "^8.4.2", - "@storybook/react": "^7.6.17", - "@storybook/react-vite": "^7.6.17", + "@storybook/client-logger": "^8.6.14", + "@storybook/manager-api": "^8.6.14", + "@storybook/node-logger": "^8.6.14", + "@storybook/react": "^10.1.11", + "@storybook/react-vite": "^10.1.11", "@storybook/testing-library": "^0.2.2", - "@storybook/theming": "^7.6.17", + "@storybook/theming": "^8.6.14", "@types/js-cookie": "^3.0.6", - "@types/react": "^18.3.3", - "@vitejs/plugin-react": "^4.3.4", - "babel-loader": "^9.2.1", + "@types/react": "^19.2.8", + "@vitejs/plugin-react": "^5.1.2", + "babel-loader": "^10.0.0", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", - "msw": "^2.7.0", + "msw": "^2.12.7", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "storybook": "^8.0.0", - "type-fest": "^4.35.0", - "typescript": "^5.7.3", - "vite": "^6.1.0", - "vite-tsconfig-paths": "^5.1.4" + "react": "^19.2.3", + "react-dom": "^19.2.3", + "storybook": "^10.1.11", + "type-fest": "^5.4.1", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vite-tsconfig-paths": "^6.0.4" }, "scripts": { "lint": "eslint src --ext .ts,.tsx", diff --git a/packages/document/.gitignore b/packages/document/.gitignore new file mode 100644 index 00000000..f940a995 --- /dev/null +++ b/packages/document/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*storybook.log diff --git a/packages/document/.storybook/addon-gh-repository/Tool.tsx b/packages/document/.storybook/addon-gh-repository/Tool.tsx new file mode 100644 index 00000000..f7de5a97 --- /dev/null +++ b/packages/document/.storybook/addon-gh-repository/Tool.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { GithubIcon } from '@storybook/icons' +import { A, IconButton, Separator } from 'storybook/internal/components' +import { ADDON_NAME, REPOSITORY_URL, TOOL_ID } from './constants' + +export const Tool = () => { + return ( + <> + + + + +   repository + + + + ) +} diff --git a/packages/document/.storybook/addon-gh-repository/constants.ts b/packages/document/.storybook/addon-gh-repository/constants.ts new file mode 100644 index 00000000..da0fb807 --- /dev/null +++ b/packages/document/.storybook/addon-gh-repository/constants.ts @@ -0,0 +1,5 @@ +export const ADDON_ID = 'addon-gh-repository' +export const ADDON_NAME = 'View repository' +export const TOOL_ID = `${ADDON_ID}/tool` +export const REPOSITORY_URL = + 'https://github.com/commercelayer/commercelayer-react-components' diff --git a/packages/document/.storybook/addon-gh-repository/manager.tsx b/packages/document/.storybook/addon-gh-repository/manager.tsx new file mode 100644 index 00000000..f46adde9 --- /dev/null +++ b/packages/document/.storybook/addon-gh-repository/manager.tsx @@ -0,0 +1,13 @@ +import { addons, types } from "storybook/manager-api" +import React from "react" +import { Tool } from "./Tool" +import { ADDON_ID, ADDON_NAME } from "./constants" + +addons.register(ADDON_ID, () => { + addons.add(ADDON_ID, { + title: ADDON_NAME, + type: types.TOOL, + match: ({ viewMode }) => !!viewMode?.match(/^(story|docs)$/), + render: () => , + }) +}) diff --git a/packages/document/.storybook/commercelayer.theme.ts b/packages/document/.storybook/commercelayer.theme.ts new file mode 100644 index 00000000..f7ceed75 --- /dev/null +++ b/packages/document/.storybook/commercelayer.theme.ts @@ -0,0 +1,11 @@ +import { create } from 'storybook/theming' + +export default create({ + base: 'light', + brandTitle: 'Commerce Layer', + // brandUrl: 'https://example.com', + brandImage: './app-logo.png', + brandTarget: '_self', + + textColor: '#101111' +}) diff --git a/packages/document/.storybook/main.ts b/packages/document/.storybook/main.ts new file mode 100644 index 00000000..d78999e9 --- /dev/null +++ b/packages/document/.storybook/main.ts @@ -0,0 +1,87 @@ +import { resolve } from "node:path" +import type { StorybookConfig } from "@storybook/react-vite" +import remarkGfm from "remark-gfm" +import { type UserConfig, mergeConfig } from "vite" +import tsconfigPaths from "vite-tsconfig-paths" + +const rcSrc = resolve(import.meta.dirname, "../../react-components/src") + +const viteOverrides: UserConfig = { + base: process.env.VITE_BASE_URL, + resolve: { + alias: { + "#components": `${rcSrc}/components`, + "#components-utils": `${rcSrc}/components/utils`, + "#context": `${rcSrc}/context`, + "#hooks": `${rcSrc}/hooks`, + "#typings": `${rcSrc}/typings`, + "#utils": `${rcSrc}/utils`, + "#config": `${rcSrc}/config`, + "#reducers": `${rcSrc}/reducers`, + }, + }, + plugins: [ + tsconfigPaths({ + projects: [ + resolve(import.meta.dirname, "../../react-components/tsconfig.json"), + resolve(import.meta.dirname, "../tsconfig.json"), + ], + }), + ], + resolve: { + dedupe: ["react", "react-dom"], + }, +} + +const storybookConfig: StorybookConfig = { + async viteFinal(config) { + return mergeConfig(config, viteOverrides) + }, + stories: ["../src/stories/**/*.mdx", "../src/stories/**/*.stories.@(js|jsx|ts|tsx)"], + addons: ["@storybook/addon-links", { + name: "@storybook/addon-docs", + options: { + mdxPluginOptions: { + mdxCompileOptions: { + remarkPlugins: [remarkGfm], + }, + }, + }, + }], + // @ts-expect-error This 'managerEntries' exists. + managerEntries: [ + resolve(import.meta.dirname, "./addon-gh-repository/manager.tsx"), + ], + framework: { + name: "@storybook/react-vite", + options: {}, + }, + core: { + disableTelemetry: true, + }, + docs: { + docsMode: true + }, + typescript: { + check: false, + reactDocgen: "react-docgen-typescript", + reactDocgenTypescriptOptions: { + tsconfigPath: resolve(import.meta.dirname, "../tsconfig.app.json"), + propFilter: (prop) => { + if (["children", "className"].includes(prop.name)) { + return true + } + + if (prop.parent != null) { + return ( + !prop.parent.fileName.includes("@types/react") && + !prop.parent.fileName.includes("@emotion") + ) + } + return true + }, + }, + }, +} + +export default storybookConfig diff --git a/packages/document/.storybook/manager-head.html b/packages/document/.storybook/manager-head.html new file mode 100644 index 00000000..ece446c3 --- /dev/null +++ b/packages/document/.storybook/manager-head.html @@ -0,0 +1,3 @@ + + + diff --git a/packages/document/.storybook/preview-head.html b/packages/document/.storybook/preview-head.html new file mode 100644 index 00000000..6448e887 --- /dev/null +++ b/packages/document/.storybook/preview-head.html @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/packages/document/.storybook/preview.tsx b/packages/document/.storybook/preview.tsx new file mode 100644 index 00000000..deea745d --- /dev/null +++ b/packages/document/.storybook/preview.tsx @@ -0,0 +1,121 @@ +import { + Controls, + Description, + Primary, + Stories, + Subtitle, + Title, +} from "@storybook/addon-docs/blocks" +import type { Parameters, Preview } from "@storybook/react-vite" +import React from "react" + +export const parameters: Parameters = { + layout: "centered", + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + backgrounds: { + options: { + overlay: { + name: "overlay", + value: "#F8F8F8", + }, + }, + }, + options: { + storySort: { + method: "alphabetical", + order: [ + "Getting Started", + "Skus", + // [ + // "Welcome", + // "Applications", + // "Custom apps", + // "Token provider", + // "Core SDK provider", + // ], + // "Atoms", + // "Forms", + // ["react-hook-form"], + // "Hooks", + // "Lists", + // "Composite", + // "Resources", + // "Examples", + ], + }, + }, + docs: { + page: () => ( + + + <Subtitle /> + <Description /> + <Primary /> + <Controls /> + <Stories includePrimary={false} /> + </React.Fragment> + ), + // source: { + // transform: (input: string) => + // prettier.format(input, { + // parser: 'babel', + // plugins: [prettierBabel] + // }), + // }, + }, +} + +// export const withContainer: Decorator = (Story, context) => { +// const { containerEnabled } = context.globals +// if (containerEnabled === true) { +// return ( +// <Container minHeight={false}> +// <Story /> +// </Container> +// ) +// } + +// return <Story /> +// } + +// export const withLocale: Decorator = (Story, context) => { +// const locale = "en-US" +// return ( +// <I18NProvider enforcedLocaleCode={locale}> +// <Story /> +// </I18NProvider> +// ) +// } + +// export const decorators: Decorator[] = [withLocale, withContainer] + +// export const globals = { +// [PARAM_KEY]: true, +// } + +const argTypesEnhancers: Preview["argTypesEnhancers"] = [ + (context) => { + // when the className prop comes from `JSX.IntrinsicElements['div' | 'span']` + // and is not documented, we add a default description + if ( + "className" in context.argTypes && + context.argTypes.className.description === "" + ) { + context.argTypes.className.description = + "CSS class name for the base component" + } + + return context.argTypes + }, +] + +export default { + parameters, + argTypesEnhancers, + tags: ["autodocs"], +} diff --git a/packages/document/README.md b/packages/document/README.md new file mode 100644 index 00000000..74872fd4 --- /dev/null +++ b/packages/document/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/packages/document/index.html b/packages/document/index.html new file mode 100644 index 00000000..e4b78eae --- /dev/null +++ b/packages/document/index.html @@ -0,0 +1,13 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <link rel="icon" type="image/svg+xml" href="/vite.svg" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Vite + React + TS + + +
+ + + diff --git a/packages/document/package.json b/packages/document/package.json new file mode 100644 index 00000000..508ed245 --- /dev/null +++ b/packages/document/package.json @@ -0,0 +1,41 @@ +{ + "name": "document", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "lint": "biome lint ./src", + "preview": "vite preview", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", + "mcp": "storybook mcp" + }, + "dependencies": { + "@commercelayer/react-components": "workspace:*", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@chromatic-com/storybook": "^5.1.1", + "@commercelayer/js-auth": "^7.3.0", + "@storybook/addon-docs": "^10.3.5", + "@storybook/addon-links": "^10.3.5", + "@storybook/addon-mcp": "^0.5.0", + "@storybook/addon-onboarding": "^10.3.5", + "@storybook/icons": "^2.0.1", + "@storybook/react": "^10.3.5", + "@storybook/react-vite": "^10.3.5", + "@types/js-cookie": "^3.0.6", + "@types/react": "^19.2.8", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.2", + "js-cookie": "^3.0.5", + "jwt-decode": "^4.0.0", + "remark-gfm": "^4.0.1", + "storybook": "^10.3.5", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-tsconfig-paths": "^6.0.4" + } +} \ No newline at end of file diff --git a/packages/document/public/app-logo.png b/packages/document/public/app-logo.png new file mode 100644 index 00000000..77e678b5 Binary files /dev/null and b/packages/document/public/app-logo.png differ diff --git a/packages/document/public/storybook-preview.css b/packages/document/public/storybook-preview.css new file mode 100644 index 00000000..3d591793 --- /dev/null +++ b/packages/document/public/storybook-preview.css @@ -0,0 +1,38 @@ +/* Global */ +.sbdocs-wrapper ol { + list-style: decimal; +} + +/** Blockquote */ +span[type] { + display: block; + padding: 16px !important; + font-size: 14px !important; + color: #2e3438 !important; + margin: 16px 0; + border-left: 4px solid; +} +span[type]::before { + content: attr(title); + display: block; + font-weight: bold; +} +span[type] > p { + margin: 0; +} +span[type='info'] { + border-color: #3b82f6; + background-color: #dbebfe; +} +span[type='warning'] { + border-color: #f97317; + background-color: #ffedd5; +} +span[type='success'] { + border-color: #22c55f; + background-color: #ddfce7; +} +span[type='danger'] { + border-color: #ef4544; + background-color: #fee2e3; +} diff --git a/packages/document/public/welcome-hero.png b/packages/document/public/welcome-hero.png new file mode 100644 index 00000000..57c9193b Binary files /dev/null and b/packages/document/public/welcome-hero.png differ diff --git a/packages/document/src/App.css b/packages/document/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/packages/document/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/packages/document/src/App.tsx b/packages/document/src/App.tsx new file mode 100644 index 00000000..3d7ded3f --- /dev/null +++ b/packages/document/src/App.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/packages/document/src/assets/react.svg b/packages/document/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/packages/document/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/document/src/index.css b/packages/document/src/index.css new file mode 100644 index 00000000..6119ad9a --- /dev/null +++ b/packages/document/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/packages/document/src/main.tsx b/packages/document/src/main.tsx new file mode 100644 index 00000000..86e0ef88 --- /dev/null +++ b/packages/document/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import "./index.css" +import App from "./App.tsx" + +createRoot(document.getElementById("root")!).render( + + + , +) diff --git a/packages/document/src/stories/_internals/Code.tsx b/packages/document/src/stories/_internals/Code.tsx new file mode 100644 index 00000000..383c69b0 --- /dev/null +++ b/packages/document/src/stories/_internals/Code.tsx @@ -0,0 +1,3 @@ +export const Code: React.FC<{ children?: string }> = ({ children }) => { + return {children} +} diff --git a/packages/document/src/stories/_internals/CommerceLayer.tsx b/packages/document/src/stories/_internals/CommerceLayer.tsx new file mode 100644 index 00000000..c947e1bd --- /dev/null +++ b/packages/document/src/stories/_internals/CommerceLayer.tsx @@ -0,0 +1,36 @@ +import { CommerceLayer as CommerceLayerComponent } from '@commercelayer/react-components' +import { useGetToken } from './useGetToken' + +type DefaultChildrenType = JSX.Element[] | JSX.Element | null + +interface Props { + children: DefaultChildrenType + accessToken: + | 'customer-access-token' + | 'customer-orders-access-token' + | 'my-access-token' // guest token + endpoint?: string +} + +/** + * Custom setup for the `CommerceLayer` component that can be used in Storybook. + * without exposing the `accessToken` and `endpoint` props. + */ +function CommerceLayer({ children, ...props }: Props): JSX.Element { + const { accessToken, endpoint } = useGetToken({ + mode: + props.accessToken === 'customer-access-token' + ? 'customer' + : props.accessToken === 'customer-orders-access-token' + ? 'customer-orders' + : 'guest' + }) + + return ( + + {children} + + ) +} + +export default CommerceLayer diff --git a/packages/document/src/stories/_internals/OrderStorage.tsx b/packages/document/src/stories/_internals/OrderStorage.tsx new file mode 100644 index 00000000..41df3c7b --- /dev/null +++ b/packages/document/src/stories/_internals/OrderStorage.tsx @@ -0,0 +1,96 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ +import OrderStorageComponent from "#components/orders/OrderStorage"; +import useCommerceLayer from "#hooks/useCommerceLayer"; +import { useState, useEffect } from "react"; +import useOrderContainer from "#hooks/useOrderContainer"; +import type { CommerceLayerClient } from "@commercelayer/sdk"; + +export const OrderStorage = ({ + persistKey, + children, +}: { + persistKey: string; + children: React.ReactNode; +}): JSX.Element => { + const [orderId, setOrderId] = useState(localStorage.getItem(persistKey)); + const { sdkClient, accessToken } = useCommerceLayer(); + const cl = + accessToken != null && accessToken !== "" && sdkClient != null + ? sdkClient() + : undefined; + + useEffect(() => { + if (cl != null && orderId == null) { + createOrderWithItems(cl).then((orderId) => { + setOrderId(orderId); + localStorage.setItem(persistKey, orderId); + }); + } + }, [cl, persistKey]); + + if (cl == null || orderId == null) { + return
; + } + + return ( + + {children} + + ); +}; + +export const AddSampleItems = (): JSX.Element => { + const { sdkClient, accessToken } = useCommerceLayer(); + const { order, addToCart } = useOrderContainer(); + const cl = accessToken != null && accessToken !== "" && sdkClient(); + + if (cl == null || cl === false || order == null) return
loading...
; + + return ( +
+

Cart is empty

+ +
+ ); +}; + +async function createOrderWithItems(cl: CommerceLayerClient): Promise { + const order = await cl.orders.create({ + language_code: "en", + }); + await fillOrder(order.id, cl); + return order.id; +} + +async function fillOrder( + orderId: string, + cl: CommerceLayerClient, +): Promise { + await cl.line_items.create({ + item_type: "skus", + sku_code: "5PANECAP9D9CA1FFFFFFXXXX", + quantity: 2, + order: cl.orders.relationship(orderId), + }); + + await cl.line_items.create({ + item_type: "skus", + sku_code: "BACKPACK000000FFFFFFXXXX", + quantity: 3, + order: cl.orders.relationship(orderId), + }); +} diff --git a/packages/document/src/stories/_internals/useGetToken.ts b/packages/document/src/stories/_internals/useGetToken.ts new file mode 100644 index 00000000..966dd14f --- /dev/null +++ b/packages/document/src/stories/_internals/useGetToken.ts @@ -0,0 +1,261 @@ +import { authenticate } from '@commercelayer/js-auth' +import { useEffect, useMemo, useState } from 'react' +import Cookie from 'js-cookie' +import { jwtDecode } from 'jwt-decode' + +const salesChannel = { + clientId: 'Z5ypiDlsqgV8twWRz0GabrJvTKXad4U-PMoVAU-XvV0', + slug: 'react-components-store', + scope: 'market:15283', + domain: 'commercelayer.io' +} +const savedCustomerWithOrders = { + username: 'bruce@wayne.com', + password: '123456' +} + +type UserMode = 'customer' | 'customer-orders' | 'guest' +interface UseGetTokenOptions { + mode?: UserMode +} + +const getAccessTokenCookieName = (mode: UserMode): string => + `clToken.${salesChannel.slug}.${mode}` + +const getCustomerLoginCookieName = (mode: UserMode): string => + `clToken.customerLogin.${mode}` + +export function useGetToken( + options?: T +): { + accessToken: string + endpoint: string +} { + const mode = options?.mode ?? 'guest' + const [accessToken, setAccessToken] = useState( + Cookie.get(getAccessTokenCookieName(mode)) ?? '' + ) + const clientId = salesChannel.clientId + const slug = salesChannel.slug + const scope = salesChannel.scope + const domain = salesChannel.domain + + const initToken = useMemo(() => { + return async () => { + const user = + mode === 'customer' + ? await retrieveCustomerData({ + clientId, + slug, + scope, + domain, + mode + }) + : mode === 'customer-orders' + ? savedCustomerWithOrders + : undefined + + await generateNewToken({ + clientId, + slug, + scope, + domain, + user, + mode + }).then(({ accessToken, expires }) => { + setAccessToken(accessToken) + Cookie.set(getAccessTokenCookieName(mode), accessToken, { expires }) + }) + } + }, []) + + useEffect(() => { + if ( + accessToken == null || + accessToken === '' || + isTokenExpired({ accessToken, compareTo: new Date() }) + ) { + initToken() + } + }, [accessToken]) + + return { + accessToken, + endpoint: `https://${slug}.${domain}` + } +} + +async function retrieveCustomerData({ + clientId, + slug, + scope, + domain, + mode +}: { + clientId: string + slug: string + scope: string + domain: string + mode: UserMode +}): Promise<{ + username: string + password: string +}> { + const existingUser = Cookie.get(getCustomerLoginCookieName(mode)) + const savedEmail = parseEmailAddress(existingUser?.split(':')[0]) + const savedPassword = parsePassword(existingUser?.split(':')[1]) + + if (savedEmail != null && savedPassword != null) { + return { + username: savedEmail, + password: savedPassword + } + } + + const newEmail = `user-${generateRandomString(5)}-${generateRandomString( + 5 + )}@domain.com` + const newPassword = generateRandomString(10) + + const guestToken = await generateNewToken({ + clientId, + slug, + scope, + domain, + mode + }) + + await createNewCustomer({ + email: newEmail, + password: newPassword, + salesChannelToken: guestToken.accessToken, + slug, + domain + }) + + Cookie.set(getCustomerLoginCookieName(mode), `${newEmail}:${newPassword}`) + + return { + username: newEmail, + password: newPassword + } +} + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +async function generateNewToken({ + clientId, + slug, + scope, + domain, + user, + mode +}: { + clientId: string + slug: string + scope: string + domain: string + user?: { username: string; password: string } + mode: UserMode +}) { + return user == null + ? await authenticate('client_credentials', { + clientId, + scope, + domain + }) + : await authenticate('password', { + clientId, + scope, + domain, + ...user + }).then((res) => { + if (res != null && 'error' in res) { + Cookie.remove(getCustomerLoginCookieName('customer')) + Cookie.remove(getCustomerLoginCookieName('customer-orders')) + Cookie.remove(getAccessTokenCookieName(mode)) + } + return res + }) +} + +function isTokenExpired({ + accessToken, + compareTo +}: { + accessToken?: string + compareTo: Date +}): boolean { + if (accessToken == null || accessToken === '') { + return true + } + + try { + const { exp } = jwtDecode<{ exp: number }>(accessToken) + + if (exp == null) { + return true + } + + const nowTime = Math.trunc(compareTo.getTime() / 1000) + return nowTime > exp + } catch { + return true + } +} + +function generateRandomString(length = 10): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let result = '' + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)) + } + return result +} + +function parseEmailAddress(email?: string): string | undefined { + const re = /^[a-zA-Z0-9._%+-]+@domain\.com$/ + if (email == null) { + return undefined + } + return re.test(email) ? email : undefined +} + +function parsePassword(password?: string): string | undefined { + return password?.length === 10 ? password : undefined +} + +async function createNewCustomer({ + email, + password, + salesChannelToken, + slug, + domain +}: { + email: string + password: string + salesChannelToken: string + slug: string + domain: string +}): Promise { + const newCustomer = await fetch(`https://${slug}.${domain}/api/customers`, { + method: 'POST', + headers: { + Accept: 'application/vnd.api+json', + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${salesChannelToken}` + }, + body: JSON.stringify({ + data: { + type: 'customers', + attributes: { + email, + password + } + } + }) + }) + + if (newCustomer.status !== 201) { + throw new Error('Error creating customer') + } +} diff --git a/packages/document/src/stories/availability/001.availability.mdx b/packages/document/src/stories/availability/001.availability.mdx new file mode 100644 index 00000000..a0e10d26 --- /dev/null +++ b/packages/document/src/stories/availability/001.availability.mdx @@ -0,0 +1,155 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# Availability + +The Availability components let you display real-time stock quantity and delivery lead times +for any SKU. They are powered by the Commerce Layer inventory model and work by fetching +availability data through the `useAvailability` hook from `@commercelayer/hooks`. + +All Availability components must be nested inside the `` context. + +--- + +## AvailabilityContainer + +`AvailabilityContainer` is the root component of the Availability tree. +It fetches inventory data for a given SKU (by code or ID) and exposes the result +to its children through the Availability context. + + +Must be a child of the `` component. +Can also be a child of `` inside a ``, in which case `skuCode` is inherited automatically. + + + +`` + + +**Props** + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `skuCode` | `string` | — | The SKU code to fetch availability for | +| `skuId` | `string` | — | The SKU ID (takes precedence over `skuCode`; improves performance) | +| `getQuantity` | `(quantity: number) => void` | — | Callback fired whenever the available quantity changes | + + + + + + +`} +/> + +--- + +## AvailabilityTemplate + +`AvailabilityTemplate` reads from the parent `AvailabilityContainer` context and renders +a `` with availability text. You can customise the label shown for each state +(`available`, `outOfStock`, `negativeStock`) and optionally include delivery lead time +and shipping method details. + + +Must be a descendant of the `` component. + + +**Props** + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `labels.available` | `string` | `"Available"` | Text shown when quantity > 0 | +| `labels.outOfStock` | `string` | `"Out of stock"` | Text shown when quantity is 0 | +| `labels.negativeStock` | `string` | `"Not available"` | Text shown when quantity is negative | +| `timeFormat` | `"days" \| "hours"` | — | When set, delivery lead time is appended to the label | +| `showShippingMethodName` | `boolean` | `false` | Requires `timeFormat`. Appends the shipping method name | +| `showShippingMethodPrice` | `boolean` | `false` | Requires `timeFormat`. Appends the formatted shipping price | + + + + +`} +/> + +### Custom render via children + +You can fully control the rendered output by passing a function as `children`. +The function receives the full availability context including `quantity`, `text`, +`min`, `max`, and `shipping_method`. + + + + {({ quantity, text, min, max }) => ( +
+ {text} + {quantity > 0 && min != null && ( +

Ships in {min.days}–{max?.days ?? min.days} days

+ )} +
+ )} +
+ +`} +/> + +--- + +## Usage inside SkusContainer + +When used inside a `` → `` tree, `AvailabilityContainer` +automatically inherits the `skuCode` from the current SKU context — +no need to pass `skuCode` explicitly. + + + + + + + + + + + +`} +/> diff --git a/packages/document/src/stories/availability/availability.stories.tsx b/packages/document/src/stories/availability/availability.stories.tsx new file mode 100644 index 00000000..1681f3c4 --- /dev/null +++ b/packages/document/src/stories/availability/availability.stories.tsx @@ -0,0 +1,160 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import CommerceLayer from '../_internals/CommerceLayer' +import { + AvailabilityContainer, + AvailabilityTemplate, + SkusContainer, + Skus, + SkuField, +} from '@commercelayer/react-components' + +const meta = { + title: 'Availability/Stories', + parameters: { + layout: 'centered', + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const BasicAvailability: Story = { + name: 'AvailabilityContainer — basic', + render: () => ( + + + + + + ), +} + +export const CustomLabels: Story = { + name: 'AvailabilityTemplate — custom labels', + render: () => ( + + + + + + ), +} + +export const WithDeliveryLeadTimeDays: Story = { + name: 'AvailabilityTemplate — lead time in days', + render: () => ( + + + + + + ), +} + +export const WithDeliveryLeadTimeHours: Story = { + name: 'AvailabilityTemplate — lead time in hours', + render: () => ( + + + + + + ), +} + +export const WithShippingMethodName: Story = { + name: 'AvailabilityTemplate — with shipping method name', + render: () => ( + + + + + + ), +} + +export const WithShippingMethodPrice: Story = { + name: 'AvailabilityTemplate — with shipping method price', + render: () => ( + + + + + + ), +} + +export const WithGetQuantityCallback: Story = { + name: 'AvailabilityContainer — getQuantity callback', + render: () => ( + + { + console.log('quantity updated:', quantity) + }} + > + + + + ), +} + +export const WithChildrenRenderProp: Story = { + name: 'AvailabilityTemplate — children render prop', + render: () => ( + + + + {({ quantity, text, min, max }) => ( +
+ {text} + {quantity > 0 && min != null && ( +

+ Ships in {min.days}–{max?.days ?? min.days} day(s) +

+ )} +
+ )} +
+
+
+ ), +} + +export const InsideSkusContainer: Story = { + name: 'AvailabilityContainer — inside SkusContainer', + render: () => ( + + + +
+ + + + +
+
+
+
+ ), +} diff --git a/packages/document/src/stories/getting-started/001.introduction.mdx b/packages/document/src/stories/getting-started/001.introduction.mdx new file mode 100644 index 00000000..d0db855e --- /dev/null +++ b/packages/document/src/stories/getting-started/001.introduction.mdx @@ -0,0 +1,55 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +![App Element splashscreen](welcome-hero.png) + +A collection of reusable React components that makes it super fast and simple to build your own custom commerce UI, leveraging Commerce Layer API. + +Under the hood, our React components are built on top of [Commerce Layer JS SDK](https://github.com/commercelayer/commercelayer-sdk) — feel free to use it if you want to develop your custom ones. + + +## Installation + +This library is [open sourced](https://github.com/commercelayer/commercelayer-react-components/) and served as [npm package](https://www.npmjs.com/package/@commercelayer/react-components) and need to be installed as dependency inside your project. + + + + + +## Import components into your project + +You can use ES6 named import with every single component you plan to use (in addition to `CommerceLayer` one), as follow: + + + +But you can also leverage treeshaking by importing only the components you need from its folder using either default or named export, as follow: + + diff --git a/packages/document/src/stories/getting-started/002.authentication.mdx b/packages/document/src/stories/getting-started/002.authentication.mdx new file mode 100644 index 00000000..fb512fd2 --- /dev/null +++ b/packages/document/src/stories/getting-started/002.authentication.mdx @@ -0,0 +1,61 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# Authentication + +To get started with **Commerce Layer React Components** you need get the credentials that will allow you to perform the API calls they wrap. + +All requests to Commerce Layer API must be authenticated with an [OAuth2](https://oauth.net/2/) bearer token. +Hence, to use these components, you need to get a valid access token. + + +## Getting an access token + +If you are new to Commerce Layer, we suggest you to read the [Overview of Commerce Layer's OAuth 2.0](https://docs.commercelayer.io/core/applications) guide. + + +There are many ways to get an access token and the one you choose depends on your specific needs. + +You can get an access token by using one of the following methods: +- [API/OAuth requests](https://docs.commercelayer.io/core/authentication/client-credentials#getting-an-access-token) (i.e. `curl` or `postman`) +- [Commerce Layer CLI](https://github.com/commercelayer/commercelayer-cli) +- [Commerce Layer JS Auth Library](https://github.com/commercelayer/commercelayer-js-auth) + + +If you want to retrieve the access token from the **command line**, we suggest you to use the [Commerce Layer CLI](https://github.com/commercelayer/commercelayer-cli) +using the `commercelayer application:login` command ([view example](https://github.com/commercelayer/commercelayer-cli/blob/main/docs/applications.md#commercelayer-applicationslogin)), +followed by `commercelayer application:token` + +
+Otherwise, if you need to get it from a **web application**, you can use the Commerce Layer JS Auth library that works both in the browser and in Node.js environments. +
+ + + + +## Configure the `CommerceLayer` component +Once you got it, you can pass it as prop to the `CommerceLayer` component, as follow: + + ( + + {/* ... child components */} + +) +`} +/> + + +This token will be used to authorize the API calls of all its child components. +That's why the presence of (at least) one `CommerceLayer` component is mandatory — it must wrap every other component you need to use. + + +In case you need to fetch data with different tokens (i.e. from different organizations or using apps with different roles and permissions) +— nothing prevents you from putting as many `` components you want in the same page. + diff --git a/packages/document/src/stories/getting-started/003.microfrontends.mdx b/packages/document/src/stories/getting-started/003.microfrontends.mdx new file mode 100644 index 00000000..b528af57 --- /dev/null +++ b/packages/document/src/stories/getting-started/003.microfrontends.mdx @@ -0,0 +1,17 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# Micro frontends + +We use **Commerce Layer React Components** library in our official open sourced hosted applications. + +Feel free to check them out and see how it works in a real world application. + + +|Application|Description|Source| +|:-----------|:-----------|:----| +| Checkout | Checkout application that you can integrate with just a single link or use as an open-source reference for your projects. | [GitHub](https://github.com/commercelayer/mfe-checkout) +| Cart | Shopping cart application that you can integrate with just a single link or use as an open-source reference for your projects. | [GitHub](https://github.com/commercelayer/mfe-cart) +| My account | Customer portal application with personal account information and management that you can integrate with just a single link or use as an open-source reference for your projects. | [GitHub](https://github.com/commercelayer/mfe-my-account) +| Microstore | Production-ready, self-contained store. Each microstore will be accessible at a unique URL and configurable via URL query strings, with no development required. | [GitHub](https://github.com/commercelayer/mfe-microstore) diff --git a/packages/document/src/stories/getting-started/004.styling.mdx b/packages/document/src/stories/getting-started/004.styling.mdx new file mode 100644 index 00000000..9dab3a3d --- /dev/null +++ b/packages/document/src/stories/getting-started/004.styling.mdx @@ -0,0 +1,16 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# Styling the components + +This library does not provide any styling. They return simple html/jsx tags filled with fetched data. + +**It is up to you to style the components as you want**. + +Almost all components expose a `className` prop that allows you to add your own css classes. +Some components that renders multiple elements also expose other props to add classes to each specific elements. + + +All the examples in this documentation use [Tailwind CSS](https://tailwindcss.com/) to demostrate how the components can be styled. + diff --git a/packages/document/src/stories/getting-started/005.containers.mdx b/packages/document/src/stories/getting-started/005.containers.mdx new file mode 100644 index 00000000..a1cc9902 --- /dev/null +++ b/packages/document/src/stories/getting-started/005.containers.mdx @@ -0,0 +1,36 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# Containers + +Getting used to the components hierarchy is important to understand how to use this library. + +All components need to be wrapped inside the main `` context that handles the authentication with the API layer. +**It needs to be placed at the top of the application**. + +Other components need to be wrapped inside their own containers in order to access to their specific context. +As example the `` component needs to be wrapped inside the `` or it won't work. +At the same time, the `` will not render any HTML since it just holds the data for the `` component. + + +To amultiple requests to the API, the library uses a cache system that stores the data in React contexts that we refer as containers.
+Less re-rendering of those containers means better performance and less requests to the API. +
+ + +## Hierarchy +Each container documentend in the Components section of this guide, highlights a list of **Requirements** and **Children** that are needed to make it work. + +Example: + + +Must be a child of `` component. + + + + +`` +`` +`` + diff --git a/packages/document/src/stories/getting-started/006.core.mdx b/packages/document/src/stories/getting-started/006.core.mdx new file mode 100644 index 00000000..9204c811 --- /dev/null +++ b/packages/document/src/stories/getting-started/006.core.mdx @@ -0,0 +1,136 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# Core package + +The `@commercelayer/core` package is a collection of **low-level async functions** that wrap the [Commerce Layer SDK](https://github.com/commercelayer/commercelayer-sdk). + +It is the foundation layer used internally by the `@commercelayer/hooks` package and by the React components. You can use it directly if you need to fetch or mutate Commerce Layer resources outside of a React component. + + +This package has no React dependency — it can be used in any JavaScript/TypeScript environment (Node.js, edge functions, plain scripts, etc.). + + +## Installation + +The package is published to npm as part of this monorepo and listed as a workspace dependency. To install it in a standalone project: + + + +## All exports + +| Function | Description | +|---|---| +| `getAccessToken` | Retrieve an OAuth access token via `@commercelayer/js-auth` | +| `getSkus` | Fetch a paginated list of SKUs | +| `retrieveSku` | Fetch a single SKU by ID | +| `updateSku` | Update a single SKU by ID | +| `getPrices` | Fetch a paginated list of prices | +| `retrievePrice` | Fetch a single price by ID | +| `updatePrice` | Update a single price | +| `getSkuAvailability` | Fetch availability for a given SKU code or ID | +| `getSkuLists` | Fetch a paginated list of SKU lists | +| `retrieveSkuList` | Fetch a single SKU list by ID (with optional includes) | + +## Function signature + +Every function follows the same pattern: + + + +The first argument is always an object with: + +| Property | Type | Required | Description | +|---|---|---|---| +| `accessToken` | `string` | ✅ | Commerce Layer API access token | +| `params` | `QueryParamsList` or `QueryParamsRetrieve` | ❌ | Optional SDK query params (filters, fields, include, pagination) | +| `options` | `ResourcesConfig` | ❌ | Optional SDK request configuration | + +## Examples + +### Authentication + + + +### Fetch a filtered list of SKUs + + + +### Retrieve a SKU list with included SKUs + + diff --git a/packages/document/src/stories/getting-started/007.hooks.mdx b/packages/document/src/stories/getting-started/007.hooks.mdx new file mode 100644 index 00000000..e6b2331d --- /dev/null +++ b/packages/document/src/stories/getting-started/007.hooks.mdx @@ -0,0 +1,171 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# Hooks package + +The `@commercelayer/hooks` package provides **SWR-based React hooks** built on top of the `@commercelayer/core` package. + +These hooks handle caching, deduplication, loading states, and error handling automatically. They are used internally by the React components in this library and are available for direct use if you want to build custom UI on top of Commerce Layer data. + + +This package requires React 18+ and depends on `swr` for data fetching and caching. + + +## Installation + + + +## All exports + +| Hook | Description | +|---|---| +| `useSkus` | Fetch, retrieve, and update SKUs | +| `usePrices` | Fetch, retrieve, and update prices | +| `useSkuLists` | Fetch and retrieve SKU lists | +| `useAvailability` | Fetch availability for a SKU code or ID | + +## Return shape + +Every hook returns a consistent object shape: + +| Property | Type | Description | +|---|---|---| +| `data` (e.g. `skus`, `prices`) | `Resource[]` | The fetched list of resources. Empty array until loaded. | +| `isLoading` | `boolean` | `true` while the first fetch is in-flight | +| `isValidating` | `boolean` | `true` during any background revalidation | +| `error` | `string \| null` | Error message if the last request failed | +| `fetchXxx` | `(params?) => void` | Triggers the list fetch. Calling it again with new params re-fetches. | +| `retrieveXxx` | `(id) => Promise` | Fetches a single resource by ID | +| `updateXxx` | `(resource) => Promise` | Mutates a resource and updates the local cache | +| `clearXxx` | `() => void` | Resets the cache and stops auto-revalidation | +| `mutate` | `KeyedMutator` | Direct access to the underlying SWR mutate function | + +## Caching behaviour + +All hooks use [SWR](https://swr.vercel.app/) with `revalidateOnFocus: false` and `revalidateOnReconnect: false` by default. This means: + +- Data is cached per `[resource, action, accessToken, params]` key +- A second call with the same arguments is a no-op (served from cache) +- Calling `fetchXxx` with different params triggers a new request + +## Examples + +### useSkus — fetch and render a filtered list + + { + fetchSkus({ + filters: { code_in: 'TSHIRTWS000000FFFFFFLXXX,TSHIRTWKFFFFFF000000MXXX' }, + fields: { skus: ['name', 'code', 'image_url'] }, + }) + }, []) + + if (isLoading) return

Loading…

+ + return ( +
    + {skus.map((sku) => ( +
  • {sku.name} — {sku.code}
  • + ))} +
+ ) +} +`} +/> + +### useSkuLists — retrieve a SKU list with included SKUs + + { + retrieveSkuList('yZjQIDxrly', { + include: ['skus'], + fields: { skus: ['name', 'code'] }, + }).then((list) => setSkus(list?.skus ?? [])) + }, []) + + return ( +
    + {skus.map((sku) => ( +
  • {sku.name}
  • + ))} +
+ ) +} +`} +/> + +### usePrices — fetch prices for a set of SKU codes + + { + fetchPrices({ filters: { sku_code_in: 'TSHIRTWS000000FFFFFFLXXX' } }) + }, []) + + if (isLoading) return

Loading…

+ + return ( +
    + {prices.map((price) => ( +
  • {price.sku_code} — {price.formatted_amount}
  • + ))} +
+ ) +} +`} +/> + +### useAvailability — check stock for a SKU + + { + fetchAvailability({ skuCode }) + }, [skuCode]) + + if (isLoading) return null + + return {quantity != null && quantity > 0 ? 'In stock' : 'Out of stock'} +} +`} +/> diff --git a/packages/document/src/stories/skus/001.skus.mdx b/packages/document/src/stories/skus/001.skus.mdx new file mode 100644 index 00000000..18cb2c0d --- /dev/null +++ b/packages/document/src/stories/skus/001.skus.mdx @@ -0,0 +1,196 @@ +import { Meta, Source } from '@storybook/addon-docs/blocks'; + + + +# SKUs + +The SKU components let you fetch and display product data from the Commerce Layer API. +All SKU components must be nested inside the `` context that handles API authentication. + +Refer to the [SKUs API reference](https://docs.commercelayer.io/core/v/api-reference/skus/object) +for the full list of available attributes. + +--- + +## SkusContainer + +`SkusContainer` is the main container for SKU data. It accepts an array of SKU codes, fetches the +corresponding resources from the API, and stores them in a React context for its children. + + +Must be a child of the `` component. + + + +`` + + +**Props** + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `skus` | `string[]` | ✓ | Array of SKU codes to fetch | +| `queryParams` | `QueryParamsList` | — | Optional SDK query params (pagination, sorting, fields) | + + + + {/* goes here */} + + +`} +/> + +--- + +## Skus + +`Skus` loops through all SKU records stored in the parent `SkusContainer` context and renders +its children once for each SKU. You do not need to loop manually — the component handles iteration. + + +Must be a child of the `` component. + + + +``, `` + + + + + {/* rendered once per SKU */} + +
+`} +/> + +--- + +## SkuField + +`SkuField` renders any attribute of the current SKU from the parent `SkusContainer` context. +Use the `attribute` prop to select which field to display and `tagElement` to control the HTML tag +(defaults to `span`). When `tagElement="img"`, additional `` props such as `width` and `height` are accepted. + + +Must be a descendant of the `` component. + + + +See the [SKUs API object](https://docs.commercelayer.io/core/v/api-reference/skus/object) for all +available attributes (e.g. `name`, `description`, `image_url`, `code`). + + +**Props** + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `attribute` | `keyof Sku` | — | The SKU attribute to display | +| `tagElement` | `string` | `"span"` | HTML tag used to render the value | + + + + + + + + + + +`} +/> + +--- + +## SkuListsContainer + +`SkuListsContainer` fetches one or more SKU lists by ID and makes their SKU data available to +child `` components. Each `` registers its own ID with this container on mount. + + +Must be a child of the `` component. + + + +`` + + + + + {/* components go here */} + + +`} +/> + +--- + +## SkuList + +`SkuList` registers its `id` with the parent `SkuListsContainer` and renders its children using +the SKUs that belong to that list. Nest `` and `` inside to display list items. + + +Must be a child of the `` component. + + + +``, `` + + +**Props** + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `id` | `string` | ✓ | The Commerce Layer ID of the SKU list to fetch | + + + + + + + + + + + +`} +/> diff --git a/packages/document/src/stories/skus/skus.stories.tsx b/packages/document/src/stories/skus/skus.stories.tsx new file mode 100644 index 00000000..9d37ffc7 --- /dev/null +++ b/packages/document/src/stories/skus/skus.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from "@storybook/react-vite" +import CommerceLayer from "../_internals/CommerceLayer" +import { + SkuField, + SkuList, + SkuListsContainer, + Skus, + SkusContainer, +} from "@commercelayer/react-components" + +const meta = { + title: "Skus/Stories", + parameters: { + layout: "centered", + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const SkusContainerStory: Story = { + name: "SkusContainer — name and code", + render: () => ( + + + +
+ + +
+
+
+
+ ), +} + +export const SkuListsContainerStory: Story = { + name: "SkuListsContainer — list items", + render: () => ( + + + + +
+ + +
+
+
+
+
+ ), +} + +export const SkuFieldImageStory: Story = { + name: "SkuField — image", + render: () => ( + + + + + + + + ), +} diff --git a/packages/document/src/vite-env.d.ts b/packages/document/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/packages/document/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/document/tsconfig.app.json b/packages/document/tsconfig.app.json new file mode 100644 index 00000000..21f1bbd6 --- /dev/null +++ b/packages/document/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src", ".storybook/**/*"] +} diff --git a/packages/document/tsconfig.json b/packages/document/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/packages/document/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/packages/document/tsconfig.node.json b/packages/document/tsconfig.node.json new file mode 100644 index 00000000..db0becc8 --- /dev/null +++ b/packages/document/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/document/vite.config.ts b/packages/document/vite.config.ts new file mode 100644 index 00000000..8b0f57b9 --- /dev/null +++ b/packages/document/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/packages/hooks/extender.ts b/packages/hooks/extender.ts new file mode 100644 index 00000000..5525f6b1 --- /dev/null +++ b/packages/hooks/extender.ts @@ -0,0 +1,70 @@ +import { getAccessToken } from "@commercelayer/core" +import { test } from "vitest" + +const clientId = import.meta.env.VITE_SALES_CHANNEL_CLIENT_ID +const integrationClientId = import.meta.env.VITE_INTEGRATION_CLIENT_ID +const integrationClientSecret = import.meta.env.VITE_INTEGRATION_CLIENT_SECRET +const scope = import.meta.env.VITE_SALES_CHANNEL_SCOPE +const domain = import.meta.env.VITE_DOMAIN + +// Separate caches per token type to avoid cross-contamination between fixtures +let salesChannelToken: Awaited> | undefined +let integrationToken: Awaited> | undefined + +export interface CoreTestInterface { + accessToken: Awaited> + config: { + clientId: string + scope?: string + domain: string + } +} + +/** + * This test is used to run integration tests with the sales channel client. + */ +export const coreTest = test.extend({ + // biome-ignore lint/correctness/noEmptyPattern: need to object destructure as the first argument + accessToken: async ({}, use) => { + if (salesChannelToken === undefined) { + salesChannelToken = await getAccessToken({ + grantType: "client_credentials", + config: { + clientId, + scope, + domain, + }, + }) + } + use(salesChannelToken) + }, + config: { + clientId, + scope, + domain, + }, +}) + +/** + * This test is used to run integration tests with the integration client. + */ +export const coreIntegrationTest = test.extend({ + // biome-ignore lint/correctness/noEmptyPattern: need to object destructure as the first argument + accessToken: async ({}, use) => { + if (integrationToken === undefined) { + integrationToken = await getAccessToken({ + grantType: "client_credentials", + config: { + clientId: integrationClientId, + clientSecret: integrationClientSecret, + domain, + }, + }) + } + use(integrationToken) + }, + config: { + clientId: integrationClientId, + domain, + }, +}) diff --git a/packages/hooks/package.json b/packages/hooks/package.json new file mode 100644 index 00000000..324e5503 --- /dev/null +++ b/packages/hooks/package.json @@ -0,0 +1,62 @@ +{ + "name": "@commercelayer/hooks", + "version": "1.0.0", + "description": "Commerce Layer React Hooks", + "type": "module", + "main": "./dist/index.js", + "exports": { + "./package.json": "./package.json", + ".": { + "import": "./dist/index.js", + "default": "./dist/index.cjs" + } + }, + "keywords": [ + "jamstack", + "headless", + "ecommerce", + "api", + "components" + ], + "scripts": { + "check-exports": "attw --pack .", + "lint": "biome lint --error-on-warnings ./src && tsc", + "lint:fix": "pnpm biome lint --write ./src", + "test": "pnpm run lint && vitest run --silent", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "build": "tsup", + "ci": "pnpm build && pnpm check-exports && pnpm lint" + }, + "publishConfig": { + "access": "public" + }, + "author": { + "name": "Alessandro Casazza", + "email": "alessandro@commercelayer.io" + }, + "license": "MIT", + "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", + "@babel/core": "^7.29.0", + "@commercelayer/sdk": "^7.9.0", + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitest/coverage-v8": "^4.1.0", + "babel-plugin-react-compiler": "^1.0.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "tsup": "^8.5.1", + "typescript": "^5.9.3", + "vite-tsconfig-paths": "^6.1.1", + "vitest": "^4.1.0" + }, + "dependencies": { + "@commercelayer/core": "workspace:*", + "swr": "^2.4.1" + }, + "peerDependencies": { + "react": ">=18" + } +} diff --git a/packages/hooks/src/availability/useAvailability.test.ts b/packages/hooks/src/availability/useAvailability.test.ts new file mode 100644 index 00000000..e1466320 --- /dev/null +++ b/packages/hooks/src/availability/useAvailability.test.ts @@ -0,0 +1,193 @@ +/** + * @vitest-environment jsdom + */ +import { act, renderHook, waitFor } from "@testing-library/react" +import type { ReactNode } from "react" +import { createElement } from "react" +import { SWRConfig } from "swr" +import { describe, expect, it, vi } from "vitest" +import { useAvailability } from "./useAvailability" + +const SKU_CODE = "BABYONBU000000E63E7412MX" + +const mockAvailability = { + skuCode: SKU_CODE, + quantity: 5, + min: { hours: 24, days: 1 }, + max: { hours: 72, days: 3 }, + shipping_method: undefined, +} + +vi.mock("@commercelayer/core", () => ({ + getSkuAvailability: vi.fn(), +})) + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +async function getCoreMock() { + const mod = await import("@commercelayer/core") + return vi.mocked(mod.getSkuAvailability) +} + +describe("useAvailability", () => { + it("should start with null availability", () => { + const { result } = renderHook(() => useAvailability("test-token"), { + wrapper: swrWrapper, + }) + + expect(result.current.availability).toBeNull() + expect(result.current.isLoading).toBe(false) + expect(result.current.isValidating).toBe(false) + expect(result.current.error).toBeNull() + }) + + it("should fetch availability by skuCode", async () => { + const mock = await getCoreMock() + mock.mockResolvedValueOnce(mockAvailability) + + const { result } = renderHook(() => useAvailability("test-token"), { + wrapper: swrWrapper, + }) + + act(() => { + result.current.fetchAvailability({ skuCode: SKU_CODE }) + }) + + await waitFor(() => { + expect(result.current.availability).not.toBeNull() + }) + + expect(result.current.availability?.quantity).toBe(5) + expect(result.current.availability?.skuCode).toBe(SKU_CODE) + expect(result.current.error).toBeNull() + expect(mock).toHaveBeenCalledWith( + expect.objectContaining({ skuCode: SKU_CODE }), + ) + }) + + it("should clear availability after fetch", async () => { + const mock = await getCoreMock() + mock.mockResolvedValueOnce(mockAvailability) + + const { result } = renderHook(() => useAvailability("test-token"), { + wrapper: swrWrapper, + }) + + act(() => { + result.current.fetchAvailability({ skuCode: SKU_CODE }) + }) + + await waitFor(() => { + expect(result.current.availability).not.toBeNull() + }) + + act(() => { + result.current.clearAvailability() + }) + + await waitFor(() => { + expect(result.current.availability).toBeNull() + }) + }) + + it("should set error when fetcher throws", async () => { + const mock = await getCoreMock() + mock.mockRejectedValueOnce(new Error("Unauthorized")) + + const { result } = renderHook(() => useAvailability("bad-token"), { + wrapper: swrWrapper, + }) + + act(() => { + result.current.fetchAvailability({ skuCode: SKU_CODE }) + }) + + await waitFor(() => { + expect(result.current.isLoading).toBe(false) + }) + + expect(result.current.error).toBe("Unauthorized") + expect(result.current.availability).toBeNull() + }) + + it("should not fetch before fetchAvailability is called", () => { + const { result } = renderHook(() => useAvailability("test-token"), { + wrapper: swrWrapper, + }) + + expect(result.current.availability).toBeNull() + expect(result.current.isLoading).toBe(false) + }) + + it("should set isLoading true while fetching", async () => { + const mock = await getCoreMock() + let resolve: (v: typeof mockAvailability) => void + mock.mockReturnValueOnce( + new Promise((res) => { + resolve = res + }), + ) + + const { result } = renderHook(() => useAvailability("test-token"), { + wrapper: swrWrapper, + }) + + act(() => { + result.current.fetchAvailability({ skuCode: SKU_CODE }) + }) + + await waitFor(() => { + expect(result.current.isLoading).toBe(true) + }) + + act(() => { + resolve?.(mockAvailability) + }) + + await waitFor(() => { + expect(result.current.isLoading).toBe(false) + }) + }) + + it("should return null when fetcher returns null (SKU not found)", async () => { + const mock = await getCoreMock() + mock.mockResolvedValueOnce(null) + + const { result } = renderHook(() => useAvailability("test-token"), { + wrapper: swrWrapper, + }) + + act(() => { + result.current.fetchAvailability({ skuCode: "NON_EXISTENT_SKU" }) + }) + + await waitFor(() => { + expect(result.current.isLoading).toBe(false) + }) + + expect(result.current.availability).toBeNull() + expect(result.current.error).toBeNull() + }) + + it("should not trigger fetch when fetchAvailability is called with empty params", async () => { + const mock = await getCoreMock() + + const { result } = renderHook(() => useAvailability("test-token"), { + wrapper: swrWrapper, + }) + + // fetchParams will be set but skuCode and skuId are both undefined + // SWR key will include them — but getSkuAvailability returns null for undefined inputs + mock.mockResolvedValueOnce(null) + act(() => { + result.current.fetchAvailability({}) + }) + + await waitFor(() => { + expect(result.current.isLoading).toBe(false) + }) + + expect(result.current.availability).toBeNull() + }) +}) diff --git a/packages/hooks/src/availability/useAvailability.ts b/packages/hooks/src/availability/useAvailability.ts new file mode 100644 index 00000000..9c288e00 --- /dev/null +++ b/packages/hooks/src/availability/useAvailability.ts @@ -0,0 +1,77 @@ +import { + getSkuAvailability, + type SkuAvailability, +} from "@commercelayer/core" +import { useCallback, useState } from "react" +import useSWR, { type KeyedMutator } from "swr" + +interface UseAvailabilityReturn { + availability: SkuAvailability | null + error: string | null + isLoading: boolean + isValidating: boolean + fetchAvailability: (params: { skuCode?: string; skuId?: string }) => void + clearAvailability: () => void + mutate: KeyedMutator +} + +/** + * Custom hook for fetching Commerce Layer SKU availability with SWR caching. + * Returns inventory quantity and delivery lead time for a given SKU. + * + * @param accessToken - Commerce Layer API access token + * @returns Object containing availability data, loading states, and action methods + */ +export function useAvailability(accessToken: string): UseAvailabilityReturn { + const [fetchParams, setFetchParams] = useState<{ + skuCode?: string + skuId?: string + } | null>(null) + + const { data, error, isLoading, isValidating, mutate } = + useSWR( + fetchParams && accessToken + ? [ + "availability", + accessToken, + fetchParams.skuCode, + fetchParams.skuId, + ] + : null, + async (): Promise => { + return await getSkuAvailability({ + accessToken, + skuCode: fetchParams?.skuCode, + skuId: fetchParams?.skuId, + }) + }, + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + }, + ) + + const fetchAvailability = useCallback( + (params: { skuCode?: string; skuId?: string }) => { + setFetchParams(params) + }, + [], + ) + + const clearAvailability = useCallback(() => { + setFetchParams(null) + mutate(undefined, false)?.catch(() => { + // cache may be destroyed (e.g. isolated SWRConfig in tests) + }) + }, [mutate]) + + return { + availability: data ?? null, + error: error?.message ?? null, + isLoading, + isValidating, + fetchAvailability, + clearAvailability, + mutate, + } +} diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts new file mode 100644 index 00000000..f18f5b4a --- /dev/null +++ b/packages/hooks/src/index.ts @@ -0,0 +1,5 @@ +export { useAvailability } from "./availability/useAvailability" +export { usePrices } from "./prices/usePrices" +export { useSkuLists } from "./sku_lists/useSkuLists" +export type { Sku, SkuUpdate } from "./skus/index" +export { useSkus } from "./skus/useSkus" diff --git a/packages/hooks/src/prices/usePrices.test.ts b/packages/hooks/src/prices/usePrices.test.ts new file mode 100644 index 00000000..52d2ab59 --- /dev/null +++ b/packages/hooks/src/prices/usePrices.test.ts @@ -0,0 +1,362 @@ +/** + * @vitest-environment jsdom + */ +import { act, renderHook, waitFor } from "@testing-library/react" +import type { ReactNode } from "react" +import { createElement } from "react" +import { SWRConfig } from "swr" +import { describe, expect } from "vitest" +import { coreIntegrationTest, coreTest } from "#extender" +import { usePrices } from "./usePrices" + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +describe("usePrices", () => { + coreTest("should return a list of prices", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + expect(result.current.prices).toEqual([]) + expect(result.current.isLoading).toBe(false) + expect(result.current.action).toBeNull() + + act(() => { + result.current.fetchPrices() + }) + + await waitFor(() => { + expect(result.current.prices.length).toBeGreaterThan(0) + expect(result.current.error).toBeNull() + expect(result.current.action).toBe("get") + }) + }) + + coreTest("should retrieve a single price", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + // First fetch prices + act(() => { + result.current.fetchPrices() + }) + + await waitFor(() => { + expect(result.current.prices.length).toBeGreaterThan(0) + }) + + // Get an ID of one of the fetched prices + const testPriceId = result.current.prices[0]?.id + + if (!testPriceId) { + throw new Error("No price available to retrieve") + } + + // Retrieve a specific price + let retrievedPrice: Awaited> + await act(async () => { + retrievedPrice = await result.current.retrievePrice(testPriceId) + }) + + await waitFor(() => { + expect(result.current.action).toBe("retrieve") + expect(retrievedPrice).toBeDefined() + expect(retrievedPrice?.id).toBe(testPriceId) + }) + }) + + coreIntegrationTest("should update a price", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + // First fetch prices + act(() => { + result.current.fetchPrices() + }) + + await waitFor(() => { + expect(result.current.prices.length).toBeGreaterThan(0) + }) + // Get an ID of one of the fetched prices + const priceToUpdate = result.current.prices[0] + + if (!priceToUpdate) { + throw new Error("No price available to update") + } + + // Update the price + let updatedPrice: Awaited> + await act(async () => { + updatedPrice = await result.current.updatePrice({ + id: priceToUpdate.id, + }) + }) + + await waitFor(() => { + expect(result.current.action).toBe("update") + expect(updatedPrice).toBeDefined() + expect(updatedPrice?.id).toBe(priceToUpdate.id) + }) + }) + + coreIntegrationTest( + "should return a list of prices with an integration token", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + expect(result.current.prices).toEqual([]) + expect(result.current.isLoading).toBe(false) + + act(() => { + result.current.fetchPrices() + }) + + await waitFor(() => { + expect(result.current.prices.length).toBeGreaterThan(0) + expect(result.current.error).toBeNull() + }) + }, + ) + + coreTest("should handle errors gracefully", async () => { + const token = "invalid-token" + const { result } = renderHook(() => usePrices(token)) + + act(() => { + result.current.fetchPrices() + }) + + await waitFor( + () => { + expect(result.current.error).toBeDefined() + expect(result.current.prices).toEqual([]) + }, + { timeout: 5000 }, + ) + }) + + coreTest("should clear prices", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + // First fetch some prices + act(() => { + result.current.fetchPrices() + }) + + await waitFor(() => { + expect(result.current.prices.length).toBeGreaterThan(0) + }) + + // Then clear them + act(() => { + result.current.clearPrices() + }) + + await waitFor(() => { + expect(result.current.prices).toEqual([]) + }) + }) + + coreTest("should clear errors", async () => { + const token = "invalid-token" + const { result } = renderHook(() => usePrices(token)) + + // Trigger an error + act(() => { + result.current.fetchPrices() + }) + + await waitFor( + () => { + expect(result.current.error).toBeDefined() + }, + { timeout: 5000 }, + ) + + // Clear the error + act(() => { + result.current.clearError() + }) + + await waitFor(() => { + expect(result.current.error).toBeNull() + }) + }) + + coreTest("should filter prices by parameters", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + act(() => { + result.current.fetchPrices({ + filters: { + sku_code_eq: "DIGITALPRODUCT", + }, + }) + }) + + await waitFor(() => { + expect(result.current.prices).toBeDefined() + expect(result.current.error).toBe(null) + }) + }) + + coreTest("should maintain error state until cleared", async () => { + const token = "invalid-token" + const { result } = renderHook(() => usePrices(token)) + + act(() => { + result.current.fetchPrices() + }) + + await waitFor( + () => { + expect(result.current.error).toBeDefined() + }, + { timeout: 5000 }, + ) + + const errorMessage = result.current.error + + // Error should persist + expect(result.current.error).toBe(errorMessage) + + // Clear the error + act(() => { + result.current.clearError() + }) + + await waitFor(() => { + expect(result.current.error).toBeNull() + }) + }) + + coreTest("should support pagination parameters", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + act(() => { + result.current.fetchPrices({ + pageSize: 5, + pageNumber: 1, + }) + }) + + await waitFor(() => { + expect(result.current.prices).toBeDefined() + expect(result.current.error).toBeNull() + }) + }) + + coreTest("should support include parameters", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + act(() => { + result.current.fetchPrices({ + include: ["price_list"], + }) + }) + + await waitFor(() => { + expect(result.current.prices).toBeDefined() + expect(result.current.error).toBeNull() + }) + }) + + coreTest("should track action state", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token)) + + expect(result.current.action).toBeNull() + + act(() => { + result.current.fetchPrices() + }) + + await waitFor(() => { + expect(result.current.action).toBe("get") + }) + + act(() => { + result.current.clearPrices() + }) + + await waitFor(() => { + expect(result.current.action).toBeNull() + }) + }) + + coreTest( + "should throw error when retrieving price with empty ID", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token), { + wrapper: swrWrapper, + }) + + await expect( + act(async () => { + await result.current.retrievePrice("") + }), + ).rejects.toThrow("Price ID is required for retrieve") + }, + ) + + coreTest( + "should throw error when updating price without an ID", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => usePrices(token), { + wrapper: swrWrapper, + }) + + await expect( + act(async () => { + await result.current.updatePrice( + {} as Parameters[0], + ) + }), + ).rejects.toThrow("Price resource ID is required for update") + }, + ) + + coreIntegrationTest( + "should update a price without prior fetch (no cached list)", + async ({ accessToken }) => { + const token = accessToken?.accessToken + + // Use an isolated SWR provider so mutate receives undefined as current (covers ?? [result] branch) + const { result } = renderHook(() => usePrices(token), { + wrapper: swrWrapper, + }) + + // First fetch to have a valid price ID to use + act(() => { + result.current.fetchPrices() + }) + await waitFor(() => { + expect(result.current.prices.length).toBeGreaterThan(0) + }) + const priceId = result.current.prices[0]?.id + if (!priceId) throw new Error("No price available") + + // Clear cache so mutate current will be undefined when updating + act(() => { + result.current.clearPrices() + }) + await waitFor(() => { + expect(result.current.prices).toEqual([]) + }) + + let updatedPrice: Awaited> + await act(async () => { + updatedPrice = await result.current.updatePrice({ id: priceId }) + }) + + expect(updatedPrice).toBeDefined() + expect(updatedPrice?.id).toBe(priceId) + }, + ) +}) diff --git a/packages/hooks/src/prices/usePrices.ts b/packages/hooks/src/prices/usePrices.ts new file mode 100644 index 00000000..ab7a4123 --- /dev/null +++ b/packages/hooks/src/prices/usePrices.ts @@ -0,0 +1,122 @@ +import { + getPrices, + type Price, + type PriceUpdate, + retrievePrice, + updatePrice, +} from "@commercelayer/core" +import { useCallback, useState } from "react" +import useSWR, { type KeyedMutator } from "swr" + +interface UsePricesReturn { + prices: Price[] + error: string | null + isLoading: boolean + isValidating: boolean + action: UseAction + fetchPrices: (params?: Parameters[0]["params"]) => void + retrievePrice: (id: string) => Promise + updatePrice: (resource: PriceUpdate) => Promise + clearPrices: () => void + clearError: () => void + mutate: KeyedMutator +} + +type UseAction = "get" | "retrieve" | "update" | null + +/** + * Custom hook for managing Commerce Layer prices with SWR caching. + * Provides methods to fetch, retrieve, update, and clear prices. + * + * @param accessToken - Commerce Layer API access token + * @returns Object containing prices data, loading states, and action methods + * + * @example + * ```typescript + * const { prices, fetchPrices, updatePrice } = usePrices(accessToken); + * + * // Fetch prices with filters + * fetchPrices({ filters: { currency_code_eq: 'USD' } }); + * + * // Update a specific price + * await updatePrice({ id: 'price_123', amount_cents: 1000 }); + * ``` + */ +export function usePrices(accessToken: string): UsePricesReturn { + const [params, setParams] = + useState[0]["params"]>() + const [shouldFetch, setShouldFetch] = useState(false) + const [action, setAction] = useState(null) + + const { data, error, isLoading, isValidating, mutate } = useSWR( + shouldFetch && accessToken ? ["prices", "get", accessToken, params] : null, + async (): Promise => { + return await getPrices({ accessToken, params }) + }, + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + }, + ) + + const fetchPrices = useCallback( + (newParams?: Parameters[0]["params"]) => { + setParams(newParams) + setShouldFetch(true) + setAction("get") + }, + [], + ) + + const handleRetrievePrice = useCallback( + async (id: string): Promise => { + if (!id) throw new Error("Price ID is required for retrieve") + setAction("retrieve") + const result = await retrievePrice({ accessToken, id }) + return result + }, + [accessToken], + ) + + const handleUpdatePrice = useCallback( + async (resource: PriceUpdate): Promise => { + if (!resource?.id) + throw new Error("Price resource ID is required for update") + setAction("update") + const result = await updatePrice({ accessToken, resource }) + await mutate( + (current) => + current?.map((p: Price) => (p.id === result.id ? result : p)) ?? [ + result, + ], + { revalidate: false }, + ) + return result + }, + [accessToken, mutate], + ) + + const clearPrices = useCallback(() => { + setShouldFetch(false) + setAction(null) + mutate(undefined, false) + }, [mutate]) + + const clearError = useCallback(() => { + mutate(data, false) + }, [mutate, data]) + + return { + prices: data ?? [], + error: error?.message ?? null, + isLoading, + isValidating, + action, + fetchPrices, + retrievePrice: handleRetrievePrice, + updatePrice: handleUpdatePrice, + clearPrices, + clearError, + mutate, + } +} diff --git a/packages/hooks/src/sku_lists/useSkuLists.ts b/packages/hooks/src/sku_lists/useSkuLists.ts new file mode 100644 index 00000000..0c1245fa --- /dev/null +++ b/packages/hooks/src/sku_lists/useSkuLists.ts @@ -0,0 +1,85 @@ +import { + getSkuLists, + retrieveSkuList, + type SkuList, +} from "@commercelayer/core" +import { useCallback, useState } from "react" +import useSWR, { type KeyedMutator } from "swr" +import type { QueryParamsList, QueryParamsRetrieve } from "@commercelayer/sdk" + +interface UseSkuListsReturn { + skuLists: SkuList[] + error: string | null + isLoading: boolean + isValidating: boolean + fetchSkuLists: (params?: QueryParamsList) => void + retrieveSkuList: ( + id: string, + params?: QueryParamsRetrieve, + ) => Promise + clearSkuLists: () => void + mutate: KeyedMutator +} + +/** + * Custom hook for managing Commerce Layer SKU lists with SWR caching. + * Provides methods to fetch and retrieve SKU lists. + * + * @param accessToken - Commerce Layer API access token + * @returns Object containing SKU lists data, loading states, and action methods + */ +export function useSkuLists(accessToken: string): UseSkuListsReturn { + const [params, setParams] = useState>() + const [shouldFetch, setShouldFetch] = useState(false) + + const { data, error, isLoading, isValidating, mutate } = useSWR( + shouldFetch && accessToken + ? ["sku_lists", "get", accessToken, params] + : null, + async (): Promise => { + const result = await getSkuLists({ accessToken, params }) + return result + }, + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + }, + ) + + const fetchSkuLists = useCallback( + (newParams?: QueryParamsList) => { + setParams(newParams) + setShouldFetch(true) + }, + [], + ) + + const handleRetrieveSkuList = useCallback( + async ( + id: string, + params?: QueryParamsRetrieve, + ): Promise => { + if (!id) throw new Error("SKU list ID is required for retrieve") + return await retrieveSkuList({ accessToken, id, params }) + }, + [accessToken], + ) + + const clearSkuLists = useCallback(() => { + setShouldFetch(false) + mutate(undefined, false)?.catch(() => { + // cache may be destroyed (e.g. isolated SWRConfig in tests) + }) + }, [mutate]) + + return { + skuLists: data ?? [], + error: error?.message ?? null, + isLoading, + isValidating, + fetchSkuLists, + retrieveSkuList: handleRetrieveSkuList, + clearSkuLists, + mutate, + } +} diff --git a/packages/hooks/src/skus/index.ts b/packages/hooks/src/skus/index.ts new file mode 100644 index 00000000..06ea59da --- /dev/null +++ b/packages/hooks/src/skus/index.ts @@ -0,0 +1,2 @@ +export type { Sku, SkuUpdate } from "@commercelayer/core" +export { useSkus } from "./useSkus" diff --git a/packages/hooks/src/skus/useSkus.test.ts b/packages/hooks/src/skus/useSkus.test.ts new file mode 100644 index 00000000..d62f8b1a --- /dev/null +++ b/packages/hooks/src/skus/useSkus.test.ts @@ -0,0 +1,314 @@ +/** + * @vitest-environment jsdom + */ +import { act, renderHook, waitFor } from "@testing-library/react" +import type { ReactNode } from "react" +import { createElement } from "react" +import { SWRConfig } from "swr" +import { describe, expect } from "vitest" +import { coreIntegrationTest, coreTest } from "#extender" +import { useSkus } from "./useSkus" + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +describe("useSkus", () => { + coreIntegrationTest( + "should return a list of SKUs", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + expect(result.current.skus).toEqual([]) + expect(result.current.isLoading).toBe(false) + expect(result.current.action).toBeNull() + + act(() => { + result.current.fetchSkus() + }) + + await waitFor(() => { + expect(result.current.skus.length).toBeGreaterThan(0) + expect(result.current.error).toBeNull() + expect(result.current.action).toBe("get") + }) + }, + ) + + coreIntegrationTest( + "should retrieve a single SKU", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + act(() => { + result.current.fetchSkus() + }) + + await waitFor(() => { + expect(result.current.skus.length).toBeGreaterThan(0) + }) + + const testSkuId = result.current.skus[0]?.id + if (!testSkuId) { + throw new Error("No SKU available to retrieve") + } + + let retrievedSku: Awaited> + await act(async () => { + retrievedSku = await result.current.retrieveSku(testSkuId) + }) + + await waitFor(() => { + expect(result.current.action).toBe("retrieve") + expect(retrievedSku).toBeDefined() + expect(retrievedSku?.id).toBe(testSkuId) + }) + }, + ) + + coreIntegrationTest("should update a SKU", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + act(() => { + result.current.fetchSkus() + }) + + await waitFor(() => { + expect(result.current.skus.length).toBeGreaterThan(0) + }) + + const skuToUpdate = result.current.skus[0] + if (!skuToUpdate) { + throw new Error("No SKU available to update") + } + + let updatedSku: Awaited> + await act(async () => { + updatedSku = await result.current.updateSku({ + id: skuToUpdate.id, + }) + }) + + await waitFor(() => { + expect(result.current.action).toBe("update") + expect(updatedSku).toBeDefined() + expect(updatedSku?.id).toBe(skuToUpdate.id) + }) + }) + + coreIntegrationTest( + "should return a list of SKUs with an integration token", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + expect(result.current.skus).toEqual([]) + expect(result.current.isLoading).toBe(false) + + act(() => { + result.current.fetchSkus() + }) + + await waitFor(() => { + expect(result.current.skus.length).toBeGreaterThan(0) + expect(result.current.error).toBeNull() + }) + }, + ) + + coreTest("should handle errors gracefully", async () => { + const token = "invalid-token" + const { result } = renderHook(() => useSkus(token)) + + act(() => { + result.current.fetchSkus() + }) + + await waitFor( + () => { + expect(result.current.error).toBeDefined() + expect(result.current.skus).toEqual([]) + }, + { timeout: 5000 }, + ) + }) + + coreIntegrationTest("should clear SKUs", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + act(() => { + result.current.fetchSkus() + }) + + await waitFor(() => { + expect(result.current.skus.length).toBeGreaterThan(0) + }) + + act(() => { + result.current.clearSkus() + }) + + await waitFor(() => { + expect(result.current.skus).toEqual([]) + }) + }) + + coreTest("should clear errors", async () => { + const token = "invalid-token" + const { result } = renderHook(() => useSkus(token)) + + act(() => { + result.current.fetchSkus() + }) + + await waitFor( + () => { + expect(result.current.error).toBeDefined() + }, + { timeout: 5000 }, + ) + + act(() => { + result.current.clearError() + }) + + await waitFor(() => { + expect(result.current.error).toBeNull() + }) + }) + + coreTest("should filter SKUs by parameters", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + act(() => { + result.current.fetchSkus({ + filters: { + code_eq: "DIGITALPRODUCT", + }, + }) + }) + + await waitFor(() => { + expect(result.current.skus).toBeDefined() + expect(result.current.error).toBe(null) + }) + }) + + coreTest("should support pagination parameters", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + act(() => { + result.current.fetchSkus({ + pageSize: 5, + pageNumber: 1, + }) + }) + + await waitFor(() => { + expect(result.current.skus).toBeDefined() + expect(result.current.error).toBeNull() + }) + }) + + coreTest("should track action state", async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token)) + + expect(result.current.action).toBeNull() + + act(() => { + result.current.fetchSkus() + }) + + await waitFor(() => { + expect(result.current.action).toBe("get") + }) + + act(() => { + result.current.clearSkus() + }) + + await waitFor(() => { + expect(result.current.action).toBeNull() + }) + }) + + coreIntegrationTest( + "should set error when retrieving SKU with empty ID", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token), { + wrapper: swrWrapper, + }) + + await expect( + act(async () => { + await result.current.retrieveSku("") + }), + ).rejects.toThrow("SKU ID is required for retrieve") + }, + ) + + coreIntegrationTest( + "should throw error when updating SKU without an ID", + async ({ accessToken }) => { + const token = accessToken?.accessToken + const { result } = renderHook(() => useSkus(token), { + wrapper: swrWrapper, + }) + + await expect( + act(async () => { + await result.current.updateSku( + {} as Parameters[0], + ) + }), + ).rejects.toThrow("SKU resource ID is required for update") + }, + ) + + coreIntegrationTest( + "should update a SKU without prior fetch (no cached list)", + async ({ accessToken }) => { + const token = accessToken?.accessToken + if (!token) return + + // First get a valid SKU ID via a shared-cache hook instance + const { result: sharedResult } = renderHook(() => useSkus(token)) + act(() => { + sharedResult.current.fetchSkus() + }) + try { + await waitFor( + () => { + expect(sharedResult.current.skus.length).toBeGreaterThan(0) + }, + { timeout: 10000 }, + ) + } catch { + return // graceful skip if API is rate-limited or unavailable + } + const skuId = sharedResult.current.skus[0]?.id + if (!skuId) return + + // Use an isolated SWR provider so mutate receives undefined as current (covers ?? [result] branch) + const { result } = renderHook(() => useSkus(token), { + wrapper: swrWrapper, + }) + + let updatedSku: Awaited> + await act(async () => { + updatedSku = await result.current.updateSku({ id: skuId }) + }) + + expect(updatedSku).toBeDefined() + expect(updatedSku?.id).toBe(skuId) + }, + 15000, + ) +}) diff --git a/packages/hooks/src/skus/useSkus.ts b/packages/hooks/src/skus/useSkus.ts new file mode 100644 index 00000000..431312c7 --- /dev/null +++ b/packages/hooks/src/skus/useSkus.ts @@ -0,0 +1,124 @@ +import { + getSkus, + retrieveSku, + type Sku, + type SkuUpdate, + updateSku, +} from "@commercelayer/core" +import { useCallback, useState } from "react" +import useSWR, { type KeyedMutator } from "swr" + +interface UseSkusReturn { + skus: Sku[] + error: string | null + isLoading: boolean + isValidating: boolean + action: UseAction + fetchSkus: (params?: Parameters[0]["params"]) => void + retrieveSku: (id: string) => Promise + updateSku: (resource: SkuUpdate) => Promise + clearSkus: () => void + clearError: () => void + mutate: KeyedMutator +} + +type UseAction = "get" | "retrieve" | "update" | null + +/** + * Custom hook for managing Commerce Layer SKUs with SWR caching. + * Provides methods to fetch, retrieve, update, and clear SKUs. + * + * @param accessToken - Commerce Layer API access token + * @returns Object containing SKUs data, loading states, and action methods + * + * @example + * ```typescript + * const { skus, fetchSkus, updateSku } = useSkus(accessToken); + * + * // Fetch SKUs with filters + * fetchSkus({ filters: { code_start: 'SHIRT' } }); + * + * // Update a specific SKU + * await updateSku({ id: 'sku_123', reference: 'my-ref' }); + * ``` + */ +export function useSkus(accessToken: string): UseSkusReturn { + const [params, setParams] = + useState[0]["params"]>() + const [shouldFetch, setShouldFetch] = useState(false) + const [action, setAction] = useState(null) + + const { data, error, isLoading, isValidating, mutate } = useSWR( + shouldFetch && accessToken ? ["skus", "get", accessToken, params] : null, + async (): Promise => { + return await getSkus({ accessToken, params }) + }, + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + }, + ) + + const fetchSkus = useCallback( + (newParams?: Parameters[0]["params"]) => { + setParams(newParams) + setShouldFetch(true) + setAction("get") + }, + [], + ) + + const handleRetrieveSku = useCallback( + async (id: string): Promise => { + if (!id) throw new Error("SKU ID is required for retrieve") + setAction("retrieve") + const result = await retrieveSku({ accessToken, id }) + return result + }, + [accessToken], + ) + + const handleUpdateSku = useCallback( + async (resource: SkuUpdate): Promise => { + if (!resource?.id) + throw new Error("SKU resource ID is required for update") + setAction("update") + const result = await updateSku({ accessToken, resource }) + await mutate( + (current) => + current?.map((s: Sku) => (s.id === result.id ? result : s)) ?? [ + result, + ], + { revalidate: false }, + ) + return result + }, + [accessToken, mutate], + ) + + const clearSkus = useCallback(() => { + setShouldFetch(false) + setAction(null) + mutate(undefined, false)?.catch(() => { + // cache may be destroyed (e.g. isolated SWRConfig in tests) + }) + }, [mutate]) + + const clearError = useCallback(() => { + mutate(data, false) + }, [mutate, data]) + + return { + skus: data ?? [], + error: error?.message ?? null, + isLoading, + isValidating, + action, + fetchSkus, + retrieveSku: handleRetrieveSku, + updateSku: handleUpdateSku, + clearSkus, + clearError, + mutate, + } +} diff --git a/packages/hooks/src/vitest.setup.ts b/packages/hooks/src/vitest.setup.ts new file mode 100644 index 00000000..5c797a2d --- /dev/null +++ b/packages/hooks/src/vitest.setup.ts @@ -0,0 +1,3 @@ +import { configure } from "@testing-library/react" + +configure({ asyncUtilTimeout: 20000 }) diff --git a/packages/hooks/tsconfig.json b/packages/hooks/tsconfig.json new file mode 100644 index 00000000..366986a2 --- /dev/null +++ b/packages/hooks/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + "lib": ["es2022"], + "noEmit": true, + + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + /* If transpiling with TypeScript: */ + "module": "Preserve", + + /* Relative Paths */ + "baseUrl": ".", + "paths": { + "#sdk": ["src/sdk/index.ts"], + "#types": ["src/types/index.ts"], + "#extender": ["extender.ts"] + } + }, + "exclude": ["node_modules", "dist", "coverage", "*.spec.ts"] +} diff --git a/packages/hooks/tsup.config.ts b/packages/hooks/tsup.config.ts new file mode 100644 index 00000000..e9d233a5 --- /dev/null +++ b/packages/hooks/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "tsup" + +export default defineConfig(() => ({ + entryPoints: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + outDir: "dist", + clean: true, + treeshake: true, + babelOptions: { + plugins: [["babel-plugin-react-compiler"]], + }, +})) diff --git a/packages/hooks/vite-env.d.ts b/packages/hooks/vite-env.d.ts new file mode 100644 index 00000000..c16c20fd --- /dev/null +++ b/packages/hooks/vite-env.d.ts @@ -0,0 +1,13 @@ +/// + +interface ImportMetaEnv { + readonly VITE_SALES_CHANNEL_CLIENT_ID: string + readonly VITE_SALES_CHANNEL_SCOPE: string + readonly VITE_INTEGRATION_CLIENT_ID: string + readonly VITE_INTEGRATION_CLIENT_SECRET: string + readonly VITE_DOMAIN: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/packages/hooks/vitest.config.ts b/packages/hooks/vitest.config.ts new file mode 100644 index 00000000..d710de70 --- /dev/null +++ b/packages/hooks/vitest.config.ts @@ -0,0 +1,24 @@ +import tsconfigPaths from "vite-tsconfig-paths" +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + name: "hooks", + environment: "jsdom", + testTimeout: 30000, + fileParallelism: false, + setupFiles: ["./src/vitest.setup.ts"], + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + exclude: ["**/extender.ts"], + thresholds: { + statements: 100, + branches: 95, + functions: 100, + lines: 100, + }, + }, + }, + plugins: [tsconfigPaths()], +}) diff --git a/packages/react-components/_vitest.config.mts b/packages/react-components/_vitest.config.mts new file mode 100644 index 00000000..62d3ba1d --- /dev/null +++ b/packages/react-components/_vitest.config.mts @@ -0,0 +1,78 @@ + // File-level aliases for test imports + '#components/auth/CommerceLayer': path.resolve(__dirname, 'src/components/auth/CommerceLayer.tsx'), + '#components/skus/AvailabilityTemplate': path.resolve(__dirname, 'src/components/skus/AvailabilityTemplate.tsx'), +import { defineConfig } from 'vitest/config' +import tsconfigPaths from 'vite-tsconfig-paths' +import react from '@vitejs/plugin-react' +import path from 'node:path' + +export default defineConfig({ + resolve: { + alias: { + '@commercelayer/hooks': path.resolve('../hooks/src/index.ts'), + '@commercelayer/core': path.resolve('../core/src/index.ts'), + '#components': path.resolve(__dirname, 'src/components'), + '#components/auth': path.resolve(__dirname, 'src/components/auth'), + '#components/auth/*': path.resolve(__dirname, 'src/components/auth/*'), + '#components/skus': path.resolve(__dirname, 'src/components/skus'), + '#components/skus/*': path.resolve(__dirname, 'src/components/skus/*'), + '#components/utils': path.resolve(__dirname, 'src/components/utils'), + '#components/utils/*': path.resolve(__dirname, 'src/components/utils/*'), + '#components/errors': path.resolve(__dirname, 'src/components/errors'), + '#components/errors/*': path.resolve(__dirname, 'src/components/errors/*'), + '#components/orders': path.resolve(__dirname, 'src/components/orders'), + '#components/orders/*': path.resolve(__dirname, 'src/components/orders/*'), + '#components/addresses': path.resolve(__dirname, 'src/components/addresses'), + '#components/addresses/*': path.resolve(__dirname, 'src/components/addresses/*'), + '#components/gift_cards': path.resolve(__dirname, 'src/components/gift_cards'), + '#components/gift_cards/*': path.resolve(__dirname, 'src/components/gift_cards/*'), + '#components/payment_methods': path.resolve(__dirname, 'src/components/payment_methods'), + '#components/payment_methods/*': path.resolve(__dirname, 'src/components/payment_methods/*'), + '#components/payment_source': path.resolve(__dirname, 'src/components/payment_source'), + '#components/payment_source/*': path.resolve(__dirname, 'src/components/payment_source/*'), + '#components/customers': path.resolve(__dirname, 'src/components/customers'), + '#components/customers/*': path.resolve(__dirname, 'src/components/customers/*'), + '#components/in_stock_subscriptions': path.resolve(__dirname, 'src/components/in_stock_subscriptions'), + '#components/in_stock_subscriptions/*': path.resolve(__dirname, 'src/components/in_stock_subscriptions/*'), + '#components/line_items': path.resolve(__dirname, 'src/components/line_items'), + '#components/line_items/*': path.resolve(__dirname, 'src/components/line_items/*'), + '#components/parcels': path.resolve(__dirname, 'src/components/parcels'), + '#components/parcels/*': path.resolve(__dirname, 'src/components/parcels/*'), + '#components/stock_items': path.resolve(__dirname, 'src/components/stock_items'), + '#components/stock_items/*': path.resolve(__dirname, 'src/components/stock_items/*'), + '#components/subscriptions': path.resolve(__dirname, 'src/components/subscriptions'), + '#components/subscriptions/*': path.resolve(__dirname, 'src/components/subscriptions/*'), + '#components/variants': path.resolve(__dirname, 'src/components/variants'), + '#components/variants/*': path.resolve(__dirname, 'src/components/variants/*'), + '#components-utils/*': path.resolve(__dirname, 'src/components/utils/*'), + '#reducers/*': path.resolve(__dirname, 'src/reducers/*'), + '#context/*': path.resolve(__dirname, 'src/context/*'), + '#typings/*': path.resolve(__dirname, 'src/typings/*'), + '#typings': path.resolve(__dirname, 'src/typings/index'), + '#utils/*': path.resolve(__dirname, 'src/utils/*'), + '#config/*': path.resolve(__dirname, 'src/config/*'), + '#hooks/*': path.resolve(__dirname, 'src/hooks/*') + } + }, + test: { + globals: true, + environment: 'jsdom', + testTimeout: 30000, + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + include: ['src/**'], + exclude: ['mocks', 'specs', 'src/**/*.spec.*'] + }, + setupFiles: ['./mocks/setup.ts'], + exclude: ['**/e2e/**', '**/node_modules/**'] + }, + plugins: [ + tsconfigPaths(), + react({ + babel: { + plugins: [['babel-plugin-react-compiler']] + } + }) + ] +}) diff --git a/packages/react-components/mocks/handlers.ts b/packages/react-components/mocks/handlers.ts index edabdf5a..9f227fda 100644 --- a/packages/react-components/mocks/handlers.ts +++ b/packages/react-components/mocks/handlers.ts @@ -9,6 +9,7 @@ const handlerPaths = [ `https://*.commercelayer.*/oauth/token`, `${baseUrl}/prices*`, `${baseUrl}/skus*`, + `${baseUrl}/sku_lists*`, `${baseUrl}/sku_options*`, `${baseUrl}/orders*`, `${baseUrl}/line_items*`, diff --git a/packages/react-components/package.json b/packages/react-components/package.json index a2fd8932..37b86dee 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,163 +1,21 @@ { "name": "@commercelayer/react-components", - "version": "4.29.4", + "version": "4.29.6", "description": "The Official Commerce Layer React Components", - "main": "lib/cjs/index.js", - "module": "lib/esm/index.js", - "types": "lib/esm/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", "files": [ - "lib", + "dist", "package.json", "README.md" ], "exports": { + "./package.json": "./package.json", ".": { - "require": "./lib/cjs/index.js", - "import": "./lib/esm/index.js" - }, - "./addresses/*": { - "require": "./lib/cjs/components/addresses/*.js", - "import": "./lib/esm/components/addresses/*.js" - }, - "./auth/*": { - "require": "./lib/cjs/components/auth/*.js", - "import": "./lib/esm/components/auth/*.js" - }, - "./customers/*": { - "require": "./lib/cjs/components/customers/*.js", - "import": "./lib/esm/components/customers/*.js" - }, - "./errors/*": { - "require": "./lib/cjs/components/errors/*.js", - "import": "./lib/esm/components/errors/*.js" - }, - "./gift_cards/*": { - "require": "./lib/cjs/components/gift_cards/*.js", - "import": "./lib/esm/components/gift_cards/*.js" - }, - "./in_stock_subscriptions/*": { - "require": "./lib/cjs/components/in_stock_subscriptions/*.js", - "import": "./lib/esm/components/in_stock_subscriptions/*.js" - }, - "./hooks/*": { - "require": "./lib/cjs/hooks/*.js", - "import": "./lib/esm/hooks/*.js" - }, - "./line_items/*": { - "require": "./lib/cjs/components/line_items/*.js", - "import": "./lib/esm/components/line_items/*.js" - }, - "./orders/*": { - "require": "./lib/cjs/components/orders/*.js", - "import": "./lib/esm/components/orders/*.js" - }, - "./parcels/*": { - "require": "./lib/cjs/components/parcels/*.js", - "import": "./lib/esm/components/parcels/*.js" - }, - "./payment_methods/*": { - "require": "./lib/cjs/components/payment_methods/*.js", - "import": "./lib/esm/components/payment_methods/*.js" - }, - "./payment_source/*": { - "require": "./lib/cjs/components/payment_source/*.js", - "import": "./lib/esm/components/payment_source/*.js" - }, - "./prices/*": { - "require": "./lib/cjs/components/prices/*.js", - "import": "./lib/esm/components/prices/*.js" - }, - "./shipments/*": { - "require": "./lib/cjs/components/shipments/*.js", - "import": "./lib/esm/components/shipments/*.js" - }, - "./shipping_methods/*": { - "require": "./lib/cjs/components/shipping_methods/*.js", - "import": "./lib/esm/components/shipping_methods/*.js" - }, - "./skus/*": { - "require": "./lib/cjs/components/skus/*.js", - "import": "./lib/esm/components/skus/*.js" - }, - "./stock_transfers/*": { - "require": "./lib/cjs/components/stock_transfers/*.js", - "import": "./lib/esm/components/stock_transfers/*.js" - }, - "./context/*": { - "require": "./lib/cjs/context/*.js", - "import": "./lib/esm/context/*.js" - }, - "./utils/*": { - "require": "./lib/cjs/utils/*.js", - "import": "./lib/esm/utils/*.js" - }, - "./component_utils/*": { - "require": "./lib/cjs/components/utils/*.js", - "import": "./lib/esm/components/utils/*.js" - } - }, - "typesVersions": { - "*": { - "addresses/*": [ - "lib/esm/components/addresses/*.d.ts" - ], - "auth/*": [ - "lib/esm/components/auth/*.d.ts" - ], - "customers/*": [ - "lib/esm/components/customers/*.d.ts" - ], - "errors/*": [ - "lib/esm/components/errors/*.d.ts" - ], - "gift_cards/*": [ - "lib/esm/components/gift_cards/*.d.ts" - ], - "in_stock_subscriptions/*": [ - "lib/esm/components/in_stock_subscriptions/*.d.ts" - ], - "hooks/*": [ - "lib/esm/hooks/*.d.ts" - ], - "line_items/*": [ - "lib/esm/components/line_items/*.d.ts" - ], - "orders/*": [ - "lib/esm/components/orders/*.d.ts" - ], - "parcels/*": [ - "lib/esm/components/parcels/*.d.ts" - ], - "payment_methods/*": [ - "lib/esm/components/payment_methods/*.d.ts" - ], - "payment_source/*": [ - "lib/esm/components/payment_source/*.d.ts" - ], - "prices/*": [ - "lib/esm/components/prices/*.d.ts" - ], - "shipments/*": [ - "lib/esm/components/shipments/*.d.ts" - ], - "shipping_methods/*": [ - "lib/esm/components/shipping_methods/*.d.ts" - ], - "skus/*": [ - "lib/esm/components/skus/*.d.ts" - ], - "stock_transfers/*": [ - "lib/esm/components/stock_transfers/*.d.ts" - ], - "context/*": [ - "lib/esm/context/*.d.ts" - ], - "utils/*": [ - "lib/esm/utils/*.d.ts" - ], - "component_utils/*": [ - "lib/esm/components/utils/*.d.ts" - ] + "import": "./dist/index.js", + "default": "./dist/index.cjs" } }, "publishConfig": { @@ -166,14 +24,12 @@ "scripts": { "lint": "biome lint ./src", "lint:fix": "pnpm biome lint --write ./src", - "test": "pnpm audit --audit-level high && (pnpm audit || exit 0) && pnpm lint && vitest run --silent", + "test": "pnpm audit --prod --audit-level high && (pnpm audit || exit 0) && pnpm lint && vitest run --silent", "coverage": "vitest run --coverage", "test:e2e": "NODE_ENV=test playwright test", "test:e2e:coverage": "nyc pnpm test:e2e && pnpm coverage:report", "coverage:report": "nyc report --reporter=html", - "build": "tsc -b tsconfig.prod.json tsconfig.prod.esm.json --verbose && pnpm postbuild", - "build:dev": "tsc -b tsconfig.prod.json tsconfig.prod.esm.json --verbose && tsc-alias -p tsconfig.prod.json && tsc-alias -p tsconfig.prod.esm.json", - "postbuild": "tsc-alias -p tsconfig.prod.json && tsc-alias -p tsconfig.prod.esm.json && minimize-js lib -w -s -b '\"use client\";'", + "build": "tsup", "dev": "NODE_OPTIONS='--inspect' next dev" }, "repository": { @@ -200,8 +56,10 @@ "homepage": "https://github.com/commercelayer/commercelayer-react-components#readme", "dependencies": { "@adyen/adyen-web": "^6.28.0", - "@commercelayer/organization-config": "^2.4.0", - "@commercelayer/sdk": "^6.46.0", + "@commercelayer/core": "workspace:*", + "@commercelayer/hooks": "workspace:*", + "@commercelayer/organization-config": "^2.8.4", + "@commercelayer/sdk": "^7.4.1", "@stripe/react-stripe-js": "^5.4.1", "@stripe/stripe-js": "^8.6.1", "@tanstack/react-table": "^8.21.3", @@ -210,38 +68,38 @@ "frames-react": "^1.2.3", "iframe-resizer": "^4.3.6", "jwt-decode": "^4.0.0", - "lodash": "^4.17.21", - "rapid-form": "2.1.0" + "rapid-form": "3.1.0" }, "devDependencies": { + "@babel/core": "^7.29.0", "@commercelayer/js-auth": "^6.7.2", "@faker-js/faker": "^10.2.0", "@playwright/test": "^1.57.0", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.1", "@types/braintree-web": "^3.96.17", - "@types/lodash": "^4.17.21", "@types/node": "^25.0.3", "@types/prop-types": "^15.7.15", - "@types/react": "^18.3.1", - "@types/react-test-renderer": "^18.3.1", - "@types/react-window": "^1.8.8", + "@types/react": "^19.1.8", + "@types/react-test-renderer": "^19.1.0", + "@types/react-window": "^2.0.0", "@vitejs/plugin-react": "^5.1.2", - "@vitest/coverage-v8": "^4.0.16", + "@vitest/coverage-v8": "^4.1.0", + "babel-plugin-react-compiler": "^1.0.0", "jsdom": "^27.4.0", - "minimize-js": "^1.4.0", "msw": "^2.12.7", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-test-renderer": "^18.3.1", - "tsc-alias": "^1.8.16", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-test-renderer": "^19.2.4", + "swr": "^2.4.1", "tslib": "^2.8.1", + "tsup": "^8.5.1", "typescript": "^5.9.3", "vite": "^7.3.1", "vite-tsconfig-paths": "^6.0.3", - "vitest": "^4.0.16" + "vitest": "^4.1.0" }, "peerDependencies": { - "react": ">=18.0.0" + "react": ">=19.0.0" } } diff --git a/packages/react-components/specs/addresses/billing-info.spec.tsx b/packages/react-components/specs/addresses/billing-info.spec.tsx deleted file mode 100644 index 434509c6..00000000 --- a/packages/react-components/specs/addresses/billing-info.spec.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import getToken from '../utils/getToken' -import { render, screen } from '@testing-library/react' -import { type LocalContext, type OrderContext } from '../utils/context' -import AddressesContainer from '#components/addresses/AddressesContainer' -import AddressInput from '#components/addresses/AddressInput' -import BillingAddressForm from '#components/addresses/BillingAddressForm' -import OrderContainer from '#components/orders/OrderContainer' -import OrderNumber from '#components/orders/OrderNumber' - -describe('Billing info input', () => { - let token: string | undefined - let domain: string | undefined - beforeAll(async () => { - const { accessToken, endpoint } = await getToken('customer') - if (accessToken !== undefined) { - token = accessToken - domain = endpoint - } - }) - beforeEach(async (ctx) => { - if (token != null && domain != null) { - ctx.accessToken = token - ctx.endpoint = domain - ctx.orderId = 'wxzYheVAAY' - } - }) - it.skip('Show billing info passing required false', async (ctx) => { - render( - - - - - - - - ) - const billingInfo = screen.getByTestId('billing-info') - expect(billingInfo).toBeDefined() - }) - it.skip('Show billing info if requires_billing_info is true', async (ctx) => { - render( - - - - - - - - - - - ) - await screen.findByText('2454728') - const billingInfo = screen.getByTestId('billing-info') - expect(billingInfo).toBeDefined() - }) - it.skip('Hide billing info if requires_billing_info is false and required is undefined', async (ctx) => { - render( - - - - - - - - - - - ) - const billingInfo = screen.queryByTestId('billing-info') - expect(billingInfo).toBeNull() - }) -}) diff --git a/packages/react-components/specs/addresses/invert-addresses.spec.tsx b/packages/react-components/specs/addresses/invert-addresses.spec.tsx deleted file mode 100644 index 687dbd3d..00000000 --- a/packages/react-components/specs/addresses/invert-addresses.spec.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import getToken from '../utils/getToken' -import { render, screen } from '@testing-library/react' -import { type OrderContext } from '../utils/context' -import AddressesContainer from '#components/addresses/AddressesContainer' -import AddressInput from '#components/addresses/AddressInput' -import ShippingAddressForm from '#components/addresses/ShippingAddressForm' -import AddressCountrySelector from '#components/addresses/AddressCountrySelector' -import AddressStateSelector from '#components/addresses/AddressStateSelector' -import OrderContainer from '#components/orders/OrderContainer' -import OrderNumber from '#components/orders/OrderNumber' - -describe('Billing info input', () => { - let token: string | undefined - let domain: string | undefined - beforeAll(async () => { - const { accessToken, endpoint } = await getToken() - if (accessToken !== undefined) { - token = accessToken - domain = endpoint - } - }) - beforeEach(async (ctx) => { - if (token != null && domain != null) { - ctx.accessToken = token - ctx.endpoint = domain - ctx.orderId = 'wxzYheVAAY' - } - }) - it.skip('Use shipping address as billing address', async (ctx) => { - render( - - - - - - - - - - - - - - - - - - - - ) - await screen.findByText('2454728') - const firstName = screen.getByTestId('first-name') - const lastName = screen.getByTestId('last-name') - const line1 = screen.getByTestId('line-1') - const line2 = screen.getByTestId('line-2') - const city = screen.getByTestId('city') - const countryCode = screen.getByTestId('country-code') - const stateCode = screen.getByTestId('state-code') - const zipCode = screen.getByTestId('zip-code') - const phone = screen.getByTestId('phone') - const billingInfo = screen.getByTestId('billing-info') - expect(firstName).toBeDefined() - expect(lastName).toBeDefined() - expect(line1).toBeDefined() - expect(line2).toBeDefined() - expect(city).toBeDefined() - expect(countryCode).toBeDefined() - expect(stateCode).toBeDefined() - expect(zipCode).toBeDefined() - expect(phone).toBeDefined() - expect(billingInfo).toBeDefined() - }) - // it('Hide billing info if requires_billing_info is false and required is undefined', async (ctx) => { - // render( - // - // - // - // - // - // - // - // - // - // - // ) - // const billingInfo = screen.queryByTestId('billing-info') - // expect(billingInfo).toBeNull() - // }) -}) diff --git a/packages/react-components/specs/auth/commerce-layer.spec.tsx b/packages/react-components/specs/auth/commerce-layer.spec.tsx new file mode 100644 index 00000000..88d16960 --- /dev/null +++ b/packages/react-components/specs/auth/commerce-layer.spec.tsx @@ -0,0 +1,82 @@ +import CommerceLayer from '#components/auth/CommerceLayer' +import CommerceLayerContext from '#context/CommerceLayerContext' +import { render, screen } from '@testing-library/react' +import { useContext } from 'react' + +function ContextInspector({ + onContext +}: { + onContext: (ctx: ReturnType>) => void +}) { + const ctx = useContext(CommerceLayerContext) + onContext(ctx) + return null +} + +function makeFakeToken(slug: string): string { + const header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' + const payload = btoa(JSON.stringify({ organization: { slug } })) + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_') + return `${header}.${payload}.fakesig` +} + +describe('CommerceLayer component', () => { + it('renders children', () => { + render( + + hello + + ) + expect(screen.getByTestId('child').textContent).toBe('hello') + }) + + it('provides accessToken and endpoint to context', () => { + let captured: { accessToken?: string; endpoint?: string } = {} + render( + + { captured = ctx }} /> + + ) + expect(captured.accessToken).toBe('my-token') + expect(captured.endpoint).toBe('https://explicit.commercelayer.io') + }) + + it('derives endpoint from JWT when endpoint is not provided', () => { + const token = makeFakeToken('my-org') + let captured: { endpoint?: string } = {} + render( + + { captured = ctx }} /> + + ) + expect(captured.endpoint).toBe('https://my-org.commercelayer.io') + }) + + it('uses custom domain with JWT-derived endpoint', () => { + const token = makeFakeToken('my-org') + let captured: { endpoint?: string } = {} + render( + + { captured = ctx }} /> + + ) + expect(captured.endpoint).toBe('https://my-org.custom.domain.io') + }) + + it('re-renders with same props (cache-hit path)', () => { + const child = stable + const { rerender } = render( + + {child} + + ) + rerender( + + {child} + + ) + expect(screen.getByTestId('stable-child').textContent).toBe('stable') + }) +}) diff --git a/packages/react-components/specs/customers/customer-payments.spec.tsx b/packages/react-components/specs/customers/customer-payments.spec.tsx deleted file mode 100644 index 7985acc4..00000000 --- a/packages/react-components/specs/customers/customer-payments.spec.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import CustomerContainer from '#components/customers/CustomerContainer' -import CustomerPaymentSource from '#components/customers/CustomerPaymentSource' -import CustomerPaymentSourceEmpty from '#components/customers/CustomerPaymentSourceEmpty' -import PaymentSourceBrandIcon from '#components/payment_source/PaymentSourceBrandIcon' -import PaymentSourceBrandName from '#components/payment_source/PaymentSourceBrandName' -import PaymentSourceDetail from '#components/payment_source/PaymentSourceDetail' -import { - render, - screen, - waitForElementToBeRemoved -} from '@testing-library/react' -import { type LocalContext } from '../utils/context' -import getToken from '../utils/getToken' - -describe('Customer payments', () => { - let token: string | undefined - let domain: string | undefined - const timeout = 10000 - beforeAll(async () => { - const { accessToken, endpoint } = await getToken('customer') - if (accessToken !== undefined) { - token = accessToken - domain = endpoint - } - }) - beforeEach(async (ctx) => { - if (token != null && domain != null) { - ctx.accessToken = token - ctx.endpoint = domain - } - }) - it('CustomerPaymentSource outside of CustomerContainer', (ctx) => { - expect(() => - render( - - - - ) - ).toThrow( - 'Cannot use outside of ' - ) - }) - it( - 'Show customer payment sources', - async (ctx) => { - render( - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.getByText('Loading...'), { - timeout - }) - const brandIcons = screen.getAllByTestId('payment-source-brand-icon') - const brandNames = screen.getAllByTestId('payment-source-brand-name') - const last4Numbers = screen.getAllByTestId('payment-source-last4') - const expMonthNumbers = screen.getAllByTestId('payment-source-exp-month') - const expYearNumbers = screen.getAllByTestId('payment-source-exp-year') - for (const brandIcon of brandIcons) { - expect(brandIcon).toBeDefined() - expect(brandIcon?.getAttribute('src')).toBeDefined() - } - for (const brandName of brandNames) { - expect(brandName).toBeDefined() - expect(brandName?.textContent).not.toBe('') - } - for (const last4 of last4Numbers) { - expect(last4).toBeDefined() - expect(last4?.textContent).not.toBe('') - expect(last4?.textContent).toMatch(/[0-9]{4}|[*]{4}/gm) - } - for (const expMonth of expMonthNumbers) { - expect(expMonth).toBeDefined() - expect(expMonth?.textContent).not.toBe('') - } - for (const expYear of expYearNumbers) { - expect(expYear).toBeDefined() - expect(expYear?.textContent).not.toBe('') - } - }, - { timeout } - ) - it('Show customer payment sources empty', async (ctx) => { - const { accessToken, endpoint } = await getToken('customer_empty') - if (accessToken !== undefined) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - } - render( - - - - - - - ) - expect(screen.getByText('Loading...')) - expect(screen.queryByText('No payments available')).toBeNull() - await waitForElementToBeRemoved(() => screen.getByText('Loading...')) - expect(screen.getByText('No payments available')) - }) - it('Show customer payment sources empty with custom component', async (ctx) => { - const { accessToken, endpoint } = await getToken('customer_empty') - if (accessToken !== undefined) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - } - render( - - - - {() => { - return Nessun pagamento disponibile - }} - - Caricamento...} /> - - - ) - expect(screen.getByText('Caricamento...')) - expect(screen.queryByText('Nessun pagamento disponibile')).toBeNull() - await waitForElementToBeRemoved(() => screen.getByText('Caricamento...')) - expect(screen.getByText('Nessun pagamento disponibile')) - }) - it( - 'Show customer payment sources with custom component', - async (ctx) => { - render( - - - Caricamento...}> - - {({ url, brand }) => { - return ( - {brand} - ) - }} - - - {({ brand }) => { - return ( - {brand} - ) - }} - - - {({ text }) => { - return {text} - }} - - - - - ) - expect(screen.getByText('Caricamento...')) - await waitForElementToBeRemoved( - () => screen.getByText('Caricamento...'), - { - timeout - } - ) - const [brandIcon] = screen.getAllByTestId('payment-source-brand-icon') - const [brandName] = screen.getAllByTestId('payment-source-brand-name') - const [last4] = screen.getAllByTestId('payment-source-last4') - expect(brandIcon).toBeDefined() - expect(brandIcon?.getAttribute('src')).toBeDefined() - expect(brandName).toBeDefined() - expect(brandName?.textContent).not.toBe('') - expect(last4).toBeDefined() - expect(last4?.textContent).not.toBe('') - }, - { timeout } - ) -}) diff --git a/packages/react-components/specs/e2e/baseFixtures.ts b/packages/react-components/specs/e2e/baseFixtures.ts deleted file mode 100644 index 51250ae2..00000000 --- a/packages/react-components/specs/e2e/baseFixtures.ts +++ /dev/null @@ -1,47 +0,0 @@ -import fs from 'fs' -import path from 'path' -import crypto from 'crypto' -import { test as baseTest } from '@playwright/test' - -const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output') - -export function generateUUID(): string { - return crypto.randomBytes(16).toString('hex') -} - -export const test = baseTest.extend({ - context: async ({ context }, use) => { - await context.addInitScript(() => - window.addEventListener('beforeunload', () => - (window as any).collectIstanbulCoverage( - JSON.stringify((window as any).__coverage__) - ) - ) - ) - await fs.promises.mkdir(istanbulCLIOutput, { recursive: true }) - await context.exposeFunction( - 'collectIstanbulCoverage', - (coverageJSON: string) => { - if (coverageJSON) - fs.writeFileSync( - path.join( - istanbulCLIOutput, - `playwright_coverage_${generateUUID()}.json` - ), - coverageJSON - ) - } - ) - await use(context) - for (const page of context.pages()) { - await page.evaluate(() => - (window as any).collectIstanbulCoverage( - JSON.stringify((window as any).__coverage__) - ) - ) - await page.close() - } - }, -}) - -export const expect = test.expect diff --git a/packages/react-components/specs/e2e/checkout/customer/addresses-country-lock.spec.ts b/packages/react-components/specs/e2e/checkout/customer/addresses-country-lock.spec.ts deleted file mode 100644 index e719bd27..00000000 --- a/packages/react-components/specs/e2e/checkout/customer/addresses-country-lock.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { test, expect } from '../../baseFixtures' -const endpoint = `checkout/customer/addresses-country-lock` -import mock from '../../fixtures/checkout/customer/addresses-country-lock.json' -import { getScreenshotPath } from '../../utils/response' - -test('Customer address country lock', async ({ page }) => { - await page.route('**/oauth/token', (route) => { - route.fulfill({ - status: 200, - headers: { 'access-control-allow-origin': '*' }, - contentType: 'application/vnd.api+json', - body: JSON.stringify(mock.token), - }) - }) - await page.route('**/customer_addresses?include=address', (route) => { - route.fulfill({ - status: 200, - headers: { 'access-control-allow-origin': '*' }, - contentType: 'application/vnd.api+json', - body: JSON.stringify(mock.customer_addresses), - }) - }) - await page.route('**/orders/*', (route) => { - route.fulfill({ - status: 200, - headers: { 'access-control-allow-origin': '*' }, - contentType: 'application/vnd.api+json', - body: JSON.stringify(mock.orders), - }) - }) - await page.coverage.startJSCoverage() - await page.goto(endpoint) - const shipToDifferentAddressButton = await page.waitForSelector( - '[data-test=ship-to-different-address-button]' - ) - await shipToDifferentAddressButton.click() - const shippingAddresses = await page.locator( - '[data-test=customer-shipping-address]' - ) - const textContents = await shippingAddresses.allTextContents() - expect(textContents.length).toBe(2) - textContents.map((t) => { - expect(t).toContain('(IT)') - }) - await page.screenshot({ - path: getScreenshotPath('customer-addresses-country-lock.jpg'), - }) -}) diff --git a/packages/react-components/specs/e2e/config/dotenv-config.ts b/packages/react-components/specs/e2e/config/dotenv-config.ts deleted file mode 100644 index d10882e3..00000000 --- a/packages/react-components/specs/e2e/config/dotenv-config.ts +++ /dev/null @@ -1,2 +0,0 @@ -import dotenv from 'dotenv' -dotenv.config({ path: './config.env.test' }) diff --git a/packages/react-components/specs/e2e/fixtures/checkout/customer/addresses-country-lock.json b/packages/react-components/specs/e2e/fixtures/checkout/customer/addresses-country-lock.json deleted file mode 100644 index 30f9ea1a..00000000 --- a/packages/react-components/specs/e2e/fixtures/checkout/customer/addresses-country-lock.json +++ /dev/null @@ -1,2648 +0,0 @@ -{ - "token": { - "access_token": "eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJlbldveEZNT25wIiwic2x1ZyI6InRoZS1ibHVlLWJyYW5kLTMifSwiYXBwbGljYXRpb24iOnsiaWQiOiJuR1ZxYWlFWU5BIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJvd25lciI6eyJpZCI6ImdPcXpaaFpybVEiLCJ0eXBlIjoiQ3VzdG9tZXIifSwiZXhwIjoxNjQzMjIzNzQyLCJtYXJrZXQiOnsiaWQiOlsiQmp4ckpoeW1sTSJdLCJwcmljZV9saXN0X2lkIjoiVkJ5VnBDZ3ZrZyIsInN0b2NrX2xvY2F0aW9uX2lkcyI6WyJ4R1hCWHVyRE1FIiwiZE1xWHl1VlZrTiJdLCJnZW9jb2Rlcl9pZCI6bnVsbCwiYWxsb3dzX2V4dGVybmFsX3ByaWNlcyI6ZmFsc2V9LCJyYW5kIjowLjkzMzU2NDk0NjYwMTI4MX0.eKIpxa8ljWnbJoOzVB7dimWSOwEvxRoOJkgXfyC94fmjRwUysYv08hzAZ5sHWpzEvHKB6nq7yG41uJvME_Zkdg", - "token_type": "Bearer", - "expires_in": 12568, - "refresh_token": "nKLXp2dkyd9IjO7WrgwsAdmDyfsLpgja3VdntpNWGSM", - "scope": "market:58", - "created_at": 1643209342, - "owner_id": "gOqzZhZrmQ", - "owner_type": "customer" - }, - "customer_addresses": { - "data": [ - { - "id": "BGDejhVYze", - "type": "customer_addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze" - }, - "attributes": { - "name": "Alessandro Parker, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532", - "created_at": "2021-09-29T16:27:27.611Z", - "updated_at": "2021-09-29T16:27:27.611Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/customer" - } - }, - "address": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/relationships/address", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/address" - }, - "data": { "type": "addresses", "id": "brQLungxgW" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "nWYqmhBwWY", - "type": "customer_addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY" - }, - "attributes": { - "name": "Bruce Wayne, Bat Caverna, 432432 Gotham city CA (US) 3892472932", - "created_at": "2021-10-07T14:07:09.145Z", - "updated_at": "2021-10-07T14:07:09.145Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/customer" - } - }, - "address": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/relationships/address", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/address" - }, - "data": { "type": "addresses", "id": "baZYuDlyAB" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "AzJeOhopGk", - "type": "customer_addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk" - }, - "attributes": { - "name": "Tony Stark, Stark Tower 1 , 1234 Florence FI (IT) 1122334455", - "created_at": "2021-10-20T10:10:37.223Z", - "updated_at": "2021-10-20T10:10:37.223Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/customer" - } - }, - "address": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/relationships/address", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/address" - }, - "data": { "type": "addresses", "id": "drQLungOmB" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "included": [ - { - "id": "brQLungxgW", - "type": "addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/brQLungxgW" - }, - "attributes": { - "business": false, - "first_name": "Alessandro", - "last_name": "Parker", - "company": null, - "full_name": "Alessandro Parker", - "line_1": "Via Umberto Podestà 40B", - "line_2": null, - "city": "Cogorno", - "zip_code": "16030", - "state_code": "GE", - "country_code": "IT", - "phone": "(348) 1234532", - "full_address": "Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532", - "name": "Alessandro Casazza, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532", - "email": null, - "notes": null, - "lat": null, - "lng": null, - "is_localized": false, - "is_geocoded": false, - "provider_name": null, - "map_url": null, - "static_map_url": null, - "billing_info": null, - "created_at": "2021-09-28T08:44:32.698Z", - "updated_at": "2021-10-19T17:39:46.421Z", - "reference": "BGDejhVYze", - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "geocoder": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/brQLungxgW/relationships/geocoder", - "related": "https://the-blue-brand-3.commercelayer.co/api/addresses/brQLungxgW/geocoder" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "baZYuDlyAB", - "type": "addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/baZYuDlyAB" - }, - "attributes": { - "business": false, - "first_name": "Bruce", - "last_name": "Wayne", - "company": null, - "full_name": "Bruce Wayne", - "line_1": "Bat Caverna", - "line_2": null, - "city": "Gotham city", - "zip_code": "432432", - "state_code": "CA", - "country_code": "US", - "phone": "3892472932", - "full_address": "Bat Caverna, 432432 Gotham city CA (US) 3892472932", - "name": "Bruce Wayne, Bat Caverna, 432432 Gotham city CA (US) 3892472932", - "email": null, - "notes": null, - "lat": null, - "lng": null, - "is_localized": false, - "is_geocoded": false, - "provider_name": null, - "map_url": null, - "static_map_url": null, - "billing_info": null, - "created_at": "2021-10-07T14:07:08.894Z", - "updated_at": "2021-10-21T17:31:29.392Z", - "reference": "nWYqmhBwWY", - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "geocoder": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/baZYuDlyAB/relationships/geocoder", - "related": "https://the-blue-brand-3.commercelayer.co/api/addresses/baZYuDlyAB/geocoder" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "drQLungOmB", - "type": "addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/drQLungOmB" - }, - "attributes": { - "business": false, - "first_name": "Tony", - "last_name": "Stark", - "company": null, - "full_name": "Tony Stark", - "line_1": "Stark Tower 1 ", - "line_2": null, - "city": "Florence", - "zip_code": "1234", - "state_code": "FI", - "country_code": "IT", - "phone": "1122334455", - "full_address": "Stark Tower 1 , 1234 Florence FI (IT) 1122334455", - "name": "Tony Stark, Stark Tower 1 , 1234 Florence FI (IT) 1122334455", - "email": null, - "notes": null, - "lat": null, - "lng": null, - "is_localized": false, - "is_geocoded": false, - "provider_name": null, - "map_url": null, - "static_map_url": null, - "billing_info": null, - "created_at": "2021-10-20T10:10:37.002Z", - "updated_at": "2021-10-26T07:57:03.419Z", - "reference": "AzJeOhopGk", - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "geocoder": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/drQLungOmB/relationships/geocoder", - "related": "https://the-blue-brand-3.commercelayer.co/api/addresses/drQLungOmB/geocoder" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 3, "page_count": 1 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses?include=address&page%5Bnumber%5D=1&page%5Bsize%5D=10", - "last": "https://the-blue-brand-3.commercelayer.co/api/customer_addresses?include=address&page%5Bnumber%5D=1&page%5Bsize%5D=10" - } - }, - "orders": { - "data": { - "id": "JwXQehvvyP", - "type": "orders", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP" - }, - "attributes": { - "number": 1199, - "autorefresh": true, - "status": "placed", - "payment_status": "authorized", - "fulfillment_status": "unfulfilled", - "guest": false, - "editable": false, - "customer_email": "bruce@wayne.com", - "language_code": "en", - "currency_code": "EUR", - "tax_included": true, - "tax_rate": "0.22", - "freight_taxable": false, - "requires_billing_info": false, - "country_code": "IT", - "shipping_country_code_lock": "IT", - "coupon_code": null, - "gift_card_code": null, - "gift_card_or_coupon_code": null, - "subtotal_amount_cents": 19800, - "subtotal_amount_float": 198.0, - "formatted_subtotal_amount": "€198,00", - "shipping_amount_cents": 0, - "shipping_amount_float": 0.0, - "formatted_shipping_amount": "€0,00", - "payment_method_amount_cents": 1000, - "payment_method_amount_float": 10.0, - "formatted_payment_method_amount": "€10,00", - "discount_amount_cents": 0, - "discount_amount_float": 0.0, - "formatted_discount_amount": "€0,00", - "adjustment_amount_cents": 0, - "adjustment_amount_float": 0.0, - "formatted_adjustment_amount": "€0,00", - "gift_card_amount_cents": 0, - "gift_card_amount_float": 0.0, - "formatted_gift_card_amount": "€0,00", - "total_tax_amount_cents": 3570, - "total_tax_amount_float": 35.7, - "formatted_total_tax_amount": "€35,70", - "subtotal_tax_amount_cents": 3570, - "subtotal_tax_amount_float": 35.7, - "formatted_subtotal_tax_amount": "€35,70", - "shipping_tax_amount_cents": 0, - "shipping_tax_amount_float": 0.0, - "formatted_shipping_tax_amount": "€0,00", - "payment_method_tax_amount_cents": 0, - "payment_method_tax_amount_float": 0.0, - "formatted_payment_method_tax_amount": "€0,00", - "adjustment_tax_amount_cents": 0, - "adjustment_tax_amount_float": 0.0, - "formatted_adjustment_tax_amount": "€0,00", - "total_amount_cents": 20800, - "total_amount_float": 208.0, - "formatted_total_amount": "€208,00", - "total_taxable_amount_cents": 17230, - "total_taxable_amount_float": 172.3, - "formatted_total_taxable_amount": "€172,30", - "subtotal_taxable_amount_cents": 16230, - "subtotal_taxable_amount_float": 162.3, - "formatted_subtotal_taxable_amount": "€162,30", - "shipping_taxable_amount_cents": 0, - "shipping_taxable_amount_float": 0.0, - "formatted_shipping_taxable_amount": "€0,00", - "payment_method_taxable_amount_cents": 1000, - "payment_method_taxable_amount_float": 10.0, - "formatted_payment_method_taxable_amount": "€10,00", - "adjustment_taxable_amount_cents": 0, - "adjustment_taxable_amount_float": 0.0, - "formatted_adjustment_taxable_amount": "€0,00", - "total_amount_with_taxes_cents": 20800, - "total_amount_with_taxes_float": 208.0, - "formatted_total_amount_with_taxes": "€208,00", - "fees_amount_cents": 0, - "fees_amount_float": 0.0, - "formatted_fees_amount": "€0,00", - "duty_amount_cents": null, - "duty_amount_float": null, - "formatted_duty_amount": null, - "skus_count": 6, - "line_item_options_count": 0, - "shipments_count": 2, - "payment_source_details": { - "type": "stripe_payment", - "payment_method_type": "card", - "payment_method_details": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "unchecked", - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2022, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - } - }, - "token": "0c06a1a583dfba1063d611e939f8c7da", - "cart_url": null, - "return_url": null, - "terms_url": null, - "privacy_url": null, - "checkout_url": null, - "placed_at": "2021-03-10T16:35:13.642Z", - "approved_at": null, - "cancelled_at": null, - "payment_updated_at": "2021-03-01T16:18:28.845Z", - "fulfillment_updated_at": null, - "refreshed_at": null, - "archived_at": null, - "expires_at": null, - "created_at": "2019-11-07T18:28:04.414Z", - "updated_at": "2021-12-09T01:46:01.981Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "market": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/market", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/market" - } - }, - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/customer" - } - }, - "shipping_address": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/shipping_address", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/shipping_address" - }, - "data": { "type": "addresses", "id": "wBvoVuaVDd" } - }, - "billing_address": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/billing_address", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/billing_address" - }, - "data": { "type": "addresses", "id": "YWoelupeXB" } - }, - "available_payment_methods": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/available_payment_methods", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/available_payment_methods" - } - }, - "available_customer_payment_sources": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/available_customer_payment_sources", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/available_customer_payment_sources" - }, - "data": [ - { "type": "customer_payment_sources", "id": "OKVWrsgrDx" }, - { "type": "customer_payment_sources", "id": "yDkNlsJkKr" }, - { "type": "customer_payment_sources", "id": "XvJoWsqeKB" }, - { "type": "customer_payment_sources", "id": "XLnYNsdZDz" }, - { "type": "customer_payment_sources", "id": "YKqxdsMPvg" }, - { "type": "customer_payment_sources", "id": "ZDGBwsqaKl" }, - { "type": "customer_payment_sources", "id": "QLeqRswNLm" }, - { "type": "customer_payment_sources", "id": "ZvgXnsJrKX" }, - { "type": "customer_payment_sources", "id": "bvWkPspQvG" }, - { "type": "customer_payment_sources", "id": "BvlbEswxDl" }, - { "type": "customer_payment_sources", "id": "EKRwpsnXDb" }, - { "type": "customer_payment_sources", "id": "PLQobsVmKp" }, - { "type": "customer_payment_sources", "id": "yvkNlsdkLr" }, - { "type": "customer_payment_sources", "id": "XDJoWsNeLB" }, - { "type": "customer_payment_sources", "id": "XKnYNsqZvz" } - ] - }, - "payment_method": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/payment_method", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/payment_method" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/payment_source" - } - }, - "line_items": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/line_items", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/line_items" - } - }, - "shipments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/shipments", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/shipments" - } - }, - "transactions": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/transactions", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/transactions" - } - }, - "authorizations": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/authorizations", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/authorizations" - } - }, - "captures": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/captures", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/captures" - } - }, - "voids": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/voids", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/voids" - } - }, - "refunds": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/refunds", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/refunds" - } - }, - "order_subscriptions": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/order_subscriptions", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/order_subscriptions" - } - }, - "order_copies": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/order_copies", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/order_copies" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - "included": [ - { - "id": "wBvoVuaVDd", - "type": "addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/wBvoVuaVDd" - }, - "attributes": { - "business": false, - "first_name": "Giacom", - "last_name": "Sardo", - "company": null, - "full_name": "Giacom Sardo", - "line_1": "via rosselli 23", - "line_2": null, - "city": "Acqui Terme", - "zip_code": "15011", - "state_code": "Italia", - "country_code": "IT", - "phone": "3290539293", - "full_address": "via rosselli 23, 15011 Acqui Terme Italia (IT) 3290539293", - "name": "Giacom Sardo, via rosselli 23, 15011 Acqui Terme Italia (IT) 3290539293", - "email": null, - "notes": null, - "lat": null, - "lng": null, - "is_localized": false, - "is_geocoded": false, - "provider_name": null, - "map_url": null, - "static_map_url": null, - "billing_info": null, - "created_at": "2021-02-22T09:55:17.650Z", - "updated_at": "2021-02-22T09:55:17.650Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "geocoder": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/wBvoVuaVDd/relationships/geocoder", - "related": "https://the-blue-brand-3.commercelayer.co/api/addresses/wBvoVuaVDd/geocoder" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "YWoelupeXB", - "type": "addresses", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/YWoelupeXB" - }, - "attributes": { - "business": false, - "first_name": "Bruce", - "last_name": "Wayne", - "company": "The Red Brand Inc.", - "full_name": "Bruce Wayne", - "line_1": "2883 Geraldine Lane", - "line_2": null, - "city": "New York", - "zip_code": "10013", - "state_code": "NY", - "country_code": "US", - "phone": "(212) 646-338-1228", - "full_address": "2883 Geraldine Lane, 10013 New York NY (US) (212) 646-338-1228", - "name": "Bruce Wayne, 2883 Geraldine Lane, 10013 New York NY (US) (212) 646-338-1228", - "email": null, - "notes": null, - "lat": null, - "lng": null, - "is_localized": false, - "is_geocoded": false, - "provider_name": null, - "map_url": null, - "static_map_url": null, - "billing_info": null, - "created_at": "2021-02-22T09:55:17.945Z", - "updated_at": "2021-02-22T09:55:17.945Z", - "reference": "QxnpQhVRzy", - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "geocoder": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/addresses/YWoelupeXB/relationships/geocoder", - "related": "https://the-blue-brand-3.commercelayer.co/api/addresses/YWoelupeXB/geocoder" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "OKVWrsgrDx", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx" - }, - "attributes": { - "name": "stripe_payment #911", - "customer_token": "cus_KmpCpMqzrgRtvc", - "payment_source_token": "pm_1K7FXpEw0yMev2I0766HeRIO", - "created_at": "2021-12-16T08:42:37.548Z", - "updated_at": "2021-12-16T08:42:37.548Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/payment_source" - }, - "data": { "type": "stripe_payments", "id": "KYZGRSVjqG" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "yDkNlsJkKr", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr" - }, - "attributes": { - "name": "stripe_payment #909", - "customer_token": "cus_KmXc2R6cfqi9pB", - "payment_source_token": "pm_1K6yWqEw0yMev2I0ICDe7y1x", - "created_at": "2021-12-15T14:32:29.381Z", - "updated_at": "2021-12-15T14:32:29.381Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/payment_source" - }, - "data": { "type": "stripe_payments", "id": "mYPjMSoXnK" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "XvJoWsqeKB", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB" - }, - "attributes": { - "name": "stripe_payment #908", - "customer_token": "cus_KmXXBlpZHlmW2B", - "payment_source_token": "pm_1K6yS1Ew0yMev2I0vPXDCOQF", - "created_at": "2021-12-15T14:27:29.633Z", - "updated_at": "2021-12-15T14:27:29.633Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/payment_source" - }, - "data": { "type": "stripe_payments", "id": "NYxQzSWjqM" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "XLnYNsdZDz", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz" - }, - "attributes": { - "name": "stripe_payment #907", - "customer_token": "cus_KmXQMJggwqwsCQ", - "payment_source_token": "pm_1K6yLlEw0yMev2I08st9nZ31", - "created_at": "2021-12-15T14:21:02.420Z", - "updated_at": "2021-12-15T14:21:02.420Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/payment_source" - }, - "data": { "type": "stripe_payments", "id": "gyLzJSlxqV" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "YKqxdsMPvg", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg" - }, - "attributes": { - "name": "stripe_payment #896", - "customer_token": "cus_Kkhzz4qtJw7SaK", - "payment_source_token": "pm_1K5CZKEw0yMev2I0b8Tf71Jp", - "created_at": "2021-12-10T17:08:16.927Z", - "updated_at": "2021-12-10T17:08:16.927Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/payment_source" - }, - "data": { "type": "stripe_payments", "id": "mnbdOSRdYX" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "ZDGBwsqaKl", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl" - }, - "attributes": { - "name": "stripe_payment #894", - "customer_token": "cus_Kkh5NT8z54voaF", - "payment_source_token": "pm_1K5BaYEw0yMev2I0sQoU363m", - "created_at": "2021-12-10T16:11:32.014Z", - "updated_at": "2021-12-10T16:11:32.014Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/payment_source" - }, - "data": { "type": "stripe_payments", "id": "jqOjkSLGYJ" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "QLeqRswNLm", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm" - }, - "attributes": { - "name": "stripe_payment #893", - "customer_token": "cus_KkgqTGCZhyRqHX", - "payment_source_token": "pm_1K5BSaEw0yMev2I0rI4ZFhHd", - "created_at": "2021-12-10T15:57:19.477Z", - "updated_at": "2021-12-10T15:57:19.477Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/payment_source" - }, - "data": { "type": "stripe_payments", "id": "ayNaJSNpnW" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "ZvgXnsJrKX", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX" - }, - "attributes": { - "name": "stripe_payment #891", - "customer_token": "cus_KkfYdVzZ3vDsEj", - "payment_source_token": "pm_1K5A0xEw0yMev2I0MNg4ixXY", - "created_at": "2021-12-10T14:37:03.572Z", - "updated_at": "2021-12-10T14:37:03.572Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/payment_source" - }, - "data": { "type": "stripe_payments", "id": "RqBXrSpAyB" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "bvWkPspQvG", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG" - }, - "attributes": { - "name": "stripe_payment #769", - "customer_token": "cus_KK3x5wYKlqcHwi", - "payment_source_token": "pm_1JfPpZEw0yMev2I0eEPMMuID", - "created_at": "2021-09-30T14:02:10.960Z", - "updated_at": "2021-09-30T14:02:10.960Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/payment_source" - }, - "data": { "type": "stripe_payments", "id": "pnljASKbYv" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "BvlbEswxDl", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl" - }, - "attributes": { - "name": "stripe_payment #297", - "customer_token": "cus_JTFRp8trtV32e4", - "payment_source_token": "pm_1IqIw3Ew0yMev2I0iYE0MEJa", - "created_at": "2021-05-12T14:21:24.006Z", - "updated_at": "2021-05-12T14:21:24.006Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/payment_source" - }, - "data": { "type": "stripe_payments", "id": "EYAWBSMOnj" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "EKRwpsnXDb", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb" - }, - "attributes": { - "name": "stripe_payment #296", - "customer_token": "cus_JTFC8aMGEwfy4z", - "payment_source_token": "pm_1IqIiLEw0yMev2I06wBkWOnQ", - "created_at": "2021-05-12T14:07:12.931Z", - "updated_at": "2021-05-12T14:07:12.931Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/payment_source" - }, - "data": { "type": "stripe_payments", "id": "RqwkzSJDqM" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "PLQobsVmKp", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp" - }, - "attributes": { - "name": "stripe_payment #295", - "customer_token": "cus_JTF4hYkWAU6ivJ", - "payment_source_token": "pm_1IqIabEw0yMev2I0RkfqInaD", - "created_at": "2021-05-12T13:59:16.634Z", - "updated_at": "2021-05-12T13:59:16.634Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/payment_source" - }, - "data": { "type": "stripe_payments", "id": "znmBlSKgyZ" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "yvkNlsdkLr", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr" - }, - "attributes": { - "name": "stripe_payment #294", - "customer_token": "cus_JTEvMOlfe2yvDn", - "payment_source_token": "pm_1IpxwREw0yMev2I0rno6YCxN", - "created_at": "2021-05-12T13:49:23.661Z", - "updated_at": "2021-05-12T13:49:23.661Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/payment_source" - }, - "data": { "type": "stripe_payments", "id": "ZnJwvSOLyX" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "XDJoWsNeLB", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB" - }, - "attributes": { - "name": "stripe_payment #271", - "customer_token": "cus_JLfEyLaqgKoIn4", - "payment_source_token": "pm_1IixtoEw0yMev2I0jGzoT9Js", - "created_at": "2021-04-22T08:29:02.499Z", - "updated_at": "2021-04-22T08:29:02.499Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/payment_source" - }, - "data": { "type": "stripe_payments", "id": "dYGxVSVkqL" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "XKnYNsqZvz", - "type": "customer_payment_sources", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz" - }, - "attributes": { - "name": "stripe_payment #270", - "customer_token": "cus_JLfAzal1QVV5YU", - "payment_source_token": "pm_1IixpGEw0yMev2I0i8cPyZjM", - "created_at": "2021-04-22T08:24:32.326Z", - "updated_at": "2021-04-22T08:24:32.326Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "customer": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/relationships/customer", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/customer" - } - }, - "payment_source": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/relationships/payment_source", - "related": "https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/payment_source" - }, - "data": { "type": "stripe_payments", "id": "mYPjMSvdnK" } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "KYZGRSVjqG", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG" - }, - "attributes": { - "client_secret": "pi_3K7FXPEw0yMev2I01ryNtu5r_secret_lWo912XTYHzo5Y2eRbsPfY7uH", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1K7FXpEw0yMev2I0766HeRIO", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2030, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 3, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639644153, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "3408604726", - "address": { - "city": "Cogorno", - "line1": " Via polivalente", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1K7FXpEw0yMev2I0E9xnXnxr", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2030, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 3, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639644154, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "3408604726", - "address": { - "city": "Cogorno", - "line1": " Via polivalente", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-16T08:42:08.164Z", - "updated_at": "2021-12-16T08:42:36.721Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "mYPjMSoXnK", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK" - }, - "attributes": { - "client_secret": "pi_3K6yTEEw0yMev2I001LS1FRw_secret_HsyP1LQ4VVzN8YNlrGJlOOG2x", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1K6yWqEw0yMev2I0ICDe7y1x", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2023, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 12, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639578745, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1K6yWrEw0yMev2I0PVzPRLIw", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2023, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 12, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639578745, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-15T14:28:40.454Z", - "updated_at": "2021-12-15T14:32:28.571Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "NYxQzSWjqM", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM" - }, - "attributes": { - "client_secret": "pi_3K6yRhEw0yMev2I01TCWyzaH_secret_0qVAlq00ofeUMdt08qeTwIUGw", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1K6yS1Ew0yMev2I0vPXDCOQF", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2030, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 3, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639578445, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1K6yS2Ew0yMev2I06v0gGsqN", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2030, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 3, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639578446, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-15T14:27:05.765Z", - "updated_at": "2021-12-15T14:27:28.775Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "gyLzJSlxqV", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV" - }, - "attributes": { - "client_secret": "pi_3K6yLHEw0yMev2I01IIElTaA_secret_m6MxVx0P8g8SlkODkNUWQwhh2", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1K6yLlEw0yMev2I08st9nZ31", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2030, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 3, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639578057, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1K6yLmEw0yMev2I0BiicdK37", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2030, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 3, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639578059, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-15T14:20:27.993Z", - "updated_at": "2021-12-15T14:21:01.583Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "mnbdOSRdYX", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX" - }, - "attributes": { - "client_secret": "pi_3K5CYyEw0yMev2I01E95WSZU_secret_iuBB3KXsh9pY8BXSSmu0tMape", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1K5CZJEw0yMev2I0IP0kfpSp", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2023, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 2, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639156058, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1K5CZKEw0yMev2I0b8Tf71Jp", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2023, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 2, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639156059, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-10T17:07:16.923Z", - "updated_at": "2021-12-10T17:07:41.475Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "jqOjkSLGYJ", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ" - }, - "attributes": { - "client_secret": "pi_3K5BZQEw0yMev2I01kORHcv1_secret_5hduXmP9lYWI557bbjuPXehXN", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": {}, - "payment_method": { - "id": "pm_1K5BaYEw0yMev2I0sQoU363m", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2023, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 12, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639152291, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-10T16:03:40.878Z", - "updated_at": "2021-12-10T16:04:53.724Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "ayNaJSNpnW", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW" - }, - "attributes": { - "client_secret": "pi_3K5BPPEw0yMev2I01OYZUO97_secret_47Pcd69ghKm5bFqM01x6rTP8o", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": {}, - "payment_method": { - "id": "pm_1K5BSaEw0yMev2I0rI4ZFhHd", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2023, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 12, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639151796, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-10T15:53:19.612Z", - "updated_at": "2021-12-10T15:56:39.675Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "RqBXrSpAyB", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB" - }, - "attributes": { - "client_secret": "pi_3K5A07Ew0yMev2I0012rg4hD_secret_XiGFsz0vWTMRodBgbaiQ8Ogl4", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": {}, - "payment_method": { - "id": "pm_1K5A0xEw0yMev2I0MNg4ixXY", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2025, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 12, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1639146239, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-12-10T14:23:07.930Z", - "updated_at": "2021-12-10T14:24:02.404Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "pnljASKbYv", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv" - }, - "attributes": { - "client_secret": "pi_3JfPo9Ew0yMev2I01bA3469v_secret_q75Aw1JpZBJbAH90TS4U6302b", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1JfPpYEw0yMev2I0eD5BEP21", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1633010508, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1JfPpZEw0yMev2I0eEPMMuID", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1633010509, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-09-30T14:00:21.968Z", - "updated_at": "2021-09-30T14:01:53.371Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "EYAWBSMOnj", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj" - }, - "attributes": { - "client_secret": "pi_1IqIw3Ew0yMev2I0Py6q6szS_secret_rgvmwG7gg4uMr5FOG4Lp04znc", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1IqIw3Ew0yMev2I0iYE0MEJa", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620829275, - "customer": "cus_JTFRp8trtV32e4", - "livemode": false, - "billing_details": { - "name": null, - "email": null, - "phone": null, - "address": { - "city": null, - "line1": null, - "line2": null, - "state": null, - "country": null, - "postal_code": null - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1IqIw3Ew0yMev2I0iYE0MEJa", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "pass", - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620829275, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": null, - "email": null, - "phone": null, - "address": { - "city": null, - "line1": null, - "line2": null, - "state": null, - "country": null, - "postal_code": null - } - } - }, - "created_at": "2021-05-12T14:21:16.165Z", - "updated_at": "2021-08-25T16:40:02.610Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "RqwkzSJDqM", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM" - }, - "attributes": { - "client_secret": "pi_1IqIiMEw0yMev2I0SuC0LE7h_secret_xQ3nLjD5m116NYi61DWC1Br50", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1IqIiLEw0yMev2I06wBkWOnQ", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620828426, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1IqIiLEw0yMev2I06wBkWOnQ", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620828426, - "customer": "cus_JTFC8aMGEwfy4z", - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-05-12T14:07:07.056Z", - "updated_at": "2021-05-12T14:07:13.161Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "znmBlSKgyZ", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ" - }, - "attributes": { - "client_secret": "pi_1IqIacEw0yMev2I0o9Oj67Pn_secret_BXHUbfSYFPY0wi18Mha43I2tp", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1IqIabEw0yMev2I0RkfqInaD", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620827945, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1IqIabEw0yMev2I0RkfqInaD", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620827945, - "customer": null, - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-05-12T13:59:06.761Z", - "updated_at": "2021-05-12T13:59:16.613Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "ZnJwvSOLyX", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX" - }, - "attributes": { - "client_secret": "pi_1IpxwSEw0yMev2I0ecOqW6hO_secret_IQfB8lqO7WfyZ899msFZYwlY5", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1IpxwREw0yMev2I0rno6YCxN", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 11, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620748576, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "3892472932", - "address": { - "city": "Cogorno", - "line1": "Via Umberto podesta", - "line2": null, - "state": "Genova", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1IpxwREw0yMev2I0rno6YCxN", - "card": { - "brand": "visa", - "last4": "1111", - "checks": { - "cvc_check": "unchecked", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 11, - "fingerprint": "VBpIjk8uXFyOk9Kd", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1620748576, - "customer": "cus_JTEvMOlfe2yvDn", - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "3892472932", - "address": { - "city": "Cogorno", - "line1": "Via Umberto podesta", - "line2": null, - "state": "Genova", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-05-11T15:56:16.949Z", - "updated_at": "2021-05-12T13:49:23.848Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "dYGxVSVkqL", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL" - }, - "attributes": { - "client_secret": "pi_1IixtpEw0yMev2I0IyPJ42BQ_secret_5kJFSLkQ29kIfGWkFcP5L707y", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1IixtoEw0yMev2I0jGzoT9Js", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1619080117, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1IixtoEw0yMev2I0jGzoT9Js", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1619080117, - "customer": "cus_JLfEyLaqgKoIn4", - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-04-22T08:28:37.744Z", - "updated_at": "2021-04-22T08:29:02.754Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "mYPjMSvdnK", - "type": "stripe_payments", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK" - }, - "attributes": { - "client_secret": "pi_1IixpLEw0yMev2I0GdirHNMf_secret_q3zStXpnJZ3Q477l3YyT3jff1", - "publishable_key": "pk_test_UArgJuzBMSppFkvAkATXTNT5", - "options": { - "id": "pm_1IixpGEw0yMev2I0i8cPyZjM", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": null, - "address_line1_check": null, - "address_postal_code_check": null - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1619079834, - "customer": null, - "livemode": false, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - }, - "setup_future_usage": "off_session" - }, - "payment_method": { - "id": "pm_1IixpGEw0yMev2I0i8cPyZjM", - "card": { - "brand": "visa", - "last4": "4242", - "checks": { - "cvc_check": "pass", - "address_line1_check": "pass", - "address_postal_code_check": "pass" - }, - "wallet": null, - "country": "US", - "funding": "credit", - "exp_year": 2024, - "networks": { "available": ["visa"], "preferred": null }, - "exp_month": 4, - "fingerprint": "6OnuUOFYXuHF9ffk", - "generated_from": null, - "three_d_secure_usage": { "supported": true } - }, - "type": "card", - "object": "payment_method", - "created": 1619079834, - "customer": "cus_JLfAzal1QVV5YU", - "livemode": false, - "metadata": {}, - "billing_details": { - "name": "Alessandro Casazza", - "email": "bruce@wayne.com", - "phone": "(348) 1234532", - "address": { - "city": "Cogorno", - "line1": "Via Umberto Podestà 40B", - "line2": null, - "state": "GE", - "country": "IT", - "postal_code": "16030" - } - } - }, - "created_at": "2021-04-22T08:23:59.823Z", - "updated_at": "2021-04-22T08:24:32.687Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "order": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/relationships/order", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/order" - } - }, - "payment_gateway": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/relationships/payment_gateway", - "related": "https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/payment_gateway" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ] - } -} diff --git a/packages/react-components/specs/e2e/fixtures/prices-requests.json b/packages/react-components/specs/e2e/fixtures/prices-requests.json deleted file mode 100644 index 8cf37a4c..00000000 --- a/packages/react-components/specs/e2e/fixtures/prices-requests.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "access_token": "eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJ6WG1iWkZQUG5PIiwic2x1ZyI6InRoZS1ibHVlLWJyYW5kLTIifSwiYXBwbGljYXRpb24iOnsiaWQiOiJncFpiRGlYUnBiIiwia2luZCI6ImludGVncmF0aW9uIiwicHVibGljIjpmYWxzZX0sInRlc3QiOnRydWUsImV4cCI6MTYyMzY5NDQ3NCwibWFya2V0Ijp7ImlkIjpbIkFqUmV2aFdhb2EiXSwicHJpY2VfbGlzdF9pZCI6ImprRHFRQ1ZabG0iLCJzdG9ja19sb2NhdGlvbl9pZHMiOlsiYm5FZVF1cXlucCIsInFucE95dXlETVIiXSwiZ2VvY29kZXJfaWQiOm51bGwsImFsbG93c19leHRlcm5hbF9wcmljZXMiOmZhbHNlfSwicmFuZCI6MC4yMzgxMDc1NzA5ODI0NTM3fQ.zZtaDuR1hQ8Y2TCd0t2gtX1NG57SGEN7HSXs-s0lHZd5iyzeCEqaz_prKfvc2Hl1HZFQ_ri3npV3vR6VaPtzug", - "token_type": "Bearer", - "expires_in": 7200, - "scope": "market:48", - "created_at": 1623687274 -} diff --git a/packages/react-components/specs/e2e/mocks/address-country-lock.mock.json b/packages/react-components/specs/e2e/mocks/address-country-lock.mock.json deleted file mode 100644 index a29affa5..00000000 --- a/packages/react-components/specs/e2e/mocks/address-country-lock.mock.json +++ /dev/null @@ -1 +0,0 @@ -[{"0":{"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJlbldveEZNT25wIiwic2x1ZyI6InRoZS1ibHVlLWJyYW5kLTMifSwiYXBwbGljYXRpb24iOnsiaWQiOiJuR1ZxYWlFWU5BIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJvd25lciI6eyJpZCI6ImdPcXpaaFpybVEiLCJ0eXBlIjoiQ3VzdG9tZXIifSwiZXhwIjoxNjQ3NDY4MTk5LCJtYXJrZXQiOnsiaWQiOlsiQmp4ckpoeW1sTSJdLCJwcmljZV9saXN0X2lkIjoiVkJ5VnBDZ3ZrZyIsInN0b2NrX2xvY2F0aW9uX2lkcyI6WyJ4R1hCWHVyRE1FIiwiZE1xWHl1VlZrTiJdLCJnZW9jb2Rlcl9pZCI6bnVsbCwiYWxsb3dzX2V4dGVybmFsX3ByaWNlcyI6ZmFsc2V9LCJyYW5kIjowLjEwNTE3NjU5MzgxNTIzNDd9.bf5G0ysGFMhwxcpjhcaHMrTo-ufuHsYIIFhuFfEnUfUClYS9Z8C2KCVwwMkejTAvK5mpvcwPgSG3CXPGCNH-9A","token_type":"Bearer","expires_in":11842,"refresh_token":"KxWzU8Q6FyXCPNEs-70-7aBgkQzour01rtNEv8F1uFU","scope":"market:58","created_at":1647453799,"owner_id":"gOqzZhZrmQ","owner_type":"customer"}},{"1":{"data":[{"id":"BGDejhVYze","type":"customer_addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze"},"attributes":{"name":"Alessandro Casazza, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","created_at":"2021-09-29T16:27:27.611Z","updated_at":"2021-09-29T16:27:27.611Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/customer"}},"address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/relationships/address","related":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/BGDejhVYze/address"},"data":{"type":"addresses","id":"brQLungxgW"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"nWYqmhBwWY","type":"customer_addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY"},"attributes":{"name":"Bruce Wayne, Bat Caverna, 432432 Gotham city CA (US) 3892472932","created_at":"2021-10-07T14:07:09.145Z","updated_at":"2021-10-07T14:07:09.145Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/customer"}},"address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/relationships/address","related":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/nWYqmhBwWY/address"},"data":{"type":"addresses","id":"baZYuDlyAB"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"AzJeOhopGk","type":"customer_addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk"},"attributes":{"name":"Tony Stark, Stark Tower 1 , 1234 Florence FI (IT) 1122334455","created_at":"2021-10-20T10:10:37.223Z","updated_at":"2021-10-20T10:10:37.223Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/customer"}},"address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/relationships/address","related":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses/AzJeOhopGk/address"},"data":{"type":"addresses","id":"drQLungOmB"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}],"included":[{"id":"brQLungxgW","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/brQLungxgW"},"attributes":{"business":false,"first_name":"Alessandro","last_name":"Casazza","company":null,"full_name":"Alessandro Casazza","line_1":"Via Umberto Podestà 40B","line_2":null,"city":"Cogorno","zip_code":"16030","state_code":"GE","country_code":"IT","phone":"(348) 1234532","full_address":"Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","name":"Alessandro Casazza, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2021-09-28T08:44:32.698Z","updated_at":"2021-10-19T17:39:46.421Z","reference":"BGDejhVYze","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/brQLungxgW/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/brQLungxgW/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"baZYuDlyAB","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/baZYuDlyAB"},"attributes":{"business":false,"first_name":"Bruce","last_name":"Wayne","company":null,"full_name":"Bruce Wayne","line_1":"Bat Caverna","line_2":null,"city":"Gotham city","zip_code":"432432","state_code":"CA","country_code":"US","phone":"3892472932","full_address":"Bat Caverna, 432432 Gotham city CA (US) 3892472932","name":"Bruce Wayne, Bat Caverna, 432432 Gotham city CA (US) 3892472932","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2021-10-07T14:07:08.894Z","updated_at":"2021-10-21T17:31:29.392Z","reference":"nWYqmhBwWY","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/baZYuDlyAB/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/baZYuDlyAB/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"drQLungOmB","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/drQLungOmB"},"attributes":{"business":false,"first_name":"Tony","last_name":"Stark","company":null,"full_name":"Tony Stark","line_1":"Stark Tower 1 ","line_2":null,"city":"Florence","zip_code":"1234","state_code":"FI","country_code":"IT","phone":"1122334455","full_address":"Stark Tower 1 , 1234 Florence FI (IT) 1122334455","name":"Tony Stark, Stark Tower 1 , 1234 Florence FI (IT) 1122334455","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2021-10-20T10:10:37.002Z","updated_at":"2021-10-26T07:57:03.419Z","reference":"AzJeOhopGk","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/drQLungOmB/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/drQLungOmB/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}],"meta":{"record_count":3,"page_count":1},"links":{"first":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses?include=address&page%5Bnumber%5D=1&page%5Bsize%5D=10","last":"https://the-blue-brand-3.commercelayer.co/api/customer_addresses?include=address&page%5Bnumber%5D=1&page%5Bsize%5D=10"}}},{"2":{"data":{"id":"JwXQehvvyP","type":"orders","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP"},"attributes":{"number":1199,"autorefresh":true,"status":"placed","payment_status":"authorized","fulfillment_status":"unfulfilled","guest":false,"editable":false,"customer_email":"bruce@wayne.com","language_code":"en","currency_code":"EUR","tax_included":true,"tax_rate":"0.22","freight_taxable":false,"requires_billing_info":false,"country_code":"IT","shipping_country_code_lock":"IT","coupon_code":null,"gift_card_code":null,"gift_card_or_coupon_code":null,"subtotal_amount_cents":19800,"subtotal_amount_float":198,"formatted_subtotal_amount":"€198,00","shipping_amount_cents":0,"shipping_amount_float":0,"formatted_shipping_amount":"€0,00","payment_method_amount_cents":1000,"payment_method_amount_float":10,"formatted_payment_method_amount":"€10,00","discount_amount_cents":0,"discount_amount_float":0,"formatted_discount_amount":"€0,00","adjustment_amount_cents":0,"adjustment_amount_float":0,"formatted_adjustment_amount":"€0,00","gift_card_amount_cents":0,"gift_card_amount_float":0,"formatted_gift_card_amount":"€0,00","total_tax_amount_cents":3570,"total_tax_amount_float":35.7,"formatted_total_tax_amount":"€35,70","subtotal_tax_amount_cents":3570,"subtotal_tax_amount_float":35.7,"formatted_subtotal_tax_amount":"€35,70","shipping_tax_amount_cents":0,"shipping_tax_amount_float":0,"formatted_shipping_tax_amount":"€0,00","payment_method_tax_amount_cents":0,"payment_method_tax_amount_float":0,"formatted_payment_method_tax_amount":"€0,00","adjustment_tax_amount_cents":0,"adjustment_tax_amount_float":0,"formatted_adjustment_tax_amount":"€0,00","total_amount_cents":20800,"total_amount_float":208,"formatted_total_amount":"€208,00","total_taxable_amount_cents":17230,"total_taxable_amount_float":172.3,"formatted_total_taxable_amount":"€172,30","subtotal_taxable_amount_cents":16230,"subtotal_taxable_amount_float":162.3,"formatted_subtotal_taxable_amount":"€162,30","shipping_taxable_amount_cents":0,"shipping_taxable_amount_float":0,"formatted_shipping_taxable_amount":"€0,00","payment_method_taxable_amount_cents":1000,"payment_method_taxable_amount_float":10,"formatted_payment_method_taxable_amount":"€10,00","adjustment_taxable_amount_cents":0,"adjustment_taxable_amount_float":0,"formatted_adjustment_taxable_amount":"€0,00","total_amount_with_taxes_cents":20800,"total_amount_with_taxes_float":208,"formatted_total_amount_with_taxes":"€208,00","fees_amount_cents":0,"fees_amount_float":0,"formatted_fees_amount":"€0,00","duty_amount_cents":null,"duty_amount_float":null,"formatted_duty_amount":null,"skus_count":6,"line_item_options_count":0,"shipments_count":2,"payment_source_details":{"type":"stripe_payment","payment_method_type":"card","payment_method_details":{"brand":"visa","last4":"4242","checks":{"cvc_check":"unchecked","address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2022,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}}},"token":"0c06a1a583dfba1063d611e939f8c7da","cart_url":null,"return_url":null,"terms_url":null,"privacy_url":null,"checkout_url":null,"placed_at":"2021-03-10T16:35:13.642Z","approved_at":null,"cancelled_at":null,"payment_updated_at":"2021-03-01T16:18:28.845Z","fulfillment_updated_at":null,"refreshed_at":null,"archived_at":null,"expires_at":null,"created_at":"2019-11-07T18:28:04.414Z","updated_at":"2022-02-17T20:51:33.662Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/market"}},"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/customer"}},"shipping_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/shipping_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/shipping_address"},"data":{"type":"addresses","id":"wBvoVuaVDd"}},"billing_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/billing_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/billing_address"},"data":{"type":"addresses","id":"YWoelupeXB"}},"available_payment_methods":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/available_payment_methods","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/available_payment_methods"}},"available_customer_payment_sources":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/available_customer_payment_sources","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/available_customer_payment_sources"},"data":[{"type":"customer_payment_sources","id":"OKVWrsgrDx"},{"type":"customer_payment_sources","id":"yDkNlsJkKr"},{"type":"customer_payment_sources","id":"XvJoWsqeKB"},{"type":"customer_payment_sources","id":"XLnYNsdZDz"},{"type":"customer_payment_sources","id":"YKqxdsMPvg"},{"type":"customer_payment_sources","id":"ZDGBwsqaKl"},{"type":"customer_payment_sources","id":"QLeqRswNLm"},{"type":"customer_payment_sources","id":"ZvgXnsJrKX"},{"type":"customer_payment_sources","id":"bvWkPspQvG"},{"type":"customer_payment_sources","id":"BvlbEswxDl"},{"type":"customer_payment_sources","id":"EKRwpsnXDb"},{"type":"customer_payment_sources","id":"PLQobsVmKp"},{"type":"customer_payment_sources","id":"yvkNlsdkLr"},{"type":"customer_payment_sources","id":"XDJoWsNeLB"},{"type":"customer_payment_sources","id":"XKnYNsqZvz"}]},"payment_method":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/payment_method","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/payment_method"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/payment_source"}},"line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/line_items","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/line_items"}},"shipments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/shipments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/shipments"}},"transactions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/transactions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/transactions"}},"authorizations":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/authorizations","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/authorizations"}},"captures":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/captures","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/captures"}},"voids":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/voids","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/voids"}},"refunds":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/refunds","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/refunds"}},"order_subscriptions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/order_subscriptions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/order_subscriptions"}},"order_copies":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/order_copies","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/order_copies"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/JwXQehvvyP/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},"included":[{"id":"wBvoVuaVDd","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/wBvoVuaVDd"},"attributes":{"business":false,"first_name":"Giacom","last_name":"Sardo","company":null,"full_name":"Giacom Sardo","line_1":"via rosselli 23","line_2":null,"city":"Acqui Terme","zip_code":"15011","state_code":"Italia","country_code":"IT","phone":"3290539293","full_address":"via rosselli 23, 15011 Acqui Terme Italia (IT) 3290539293","name":"Giacom Sardo, via rosselli 23, 15011 Acqui Terme Italia (IT) 3290539293","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2021-02-22T09:55:17.650Z","updated_at":"2021-02-22T09:55:17.650Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/wBvoVuaVDd/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/wBvoVuaVDd/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"YWoelupeXB","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/YWoelupeXB"},"attributes":{"business":false,"first_name":"Bruce","last_name":"Wayne","company":"The Red Brand Inc.","full_name":"Bruce Wayne","line_1":"2883 Geraldine Lane","line_2":null,"city":"New York","zip_code":"10013","state_code":"NY","country_code":"US","phone":"(212) 646-338-1228","full_address":"2883 Geraldine Lane, 10013 New York NY (US) (212) 646-338-1228","name":"Bruce Wayne, 2883 Geraldine Lane, 10013 New York NY (US) (212) 646-338-1228","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2021-02-22T09:55:17.945Z","updated_at":"2021-02-22T09:55:17.945Z","reference":"QxnpQhVRzy","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/YWoelupeXB/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/YWoelupeXB/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"OKVWrsgrDx","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx"},"attributes":{"name":"stripe_payment #911","customer_token":"cus_KmpCpMqzrgRtvc","payment_source_token":"pm_1K7FXpEw0yMev2I0766HeRIO","created_at":"2021-12-16T08:42:37.548Z","updated_at":"2021-12-16T08:42:37.548Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/OKVWrsgrDx/payment_source"},"data":{"type":"stripe_payments","id":"KYZGRSVjqG"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"yDkNlsJkKr","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr"},"attributes":{"name":"stripe_payment #909","customer_token":"cus_KmXc2R6cfqi9pB","payment_source_token":"pm_1K6yWqEw0yMev2I0ICDe7y1x","created_at":"2021-12-15T14:32:29.381Z","updated_at":"2021-12-15T14:32:29.381Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yDkNlsJkKr/payment_source"},"data":{"type":"stripe_payments","id":"mYPjMSoXnK"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"XvJoWsqeKB","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB"},"attributes":{"name":"stripe_payment #908","customer_token":"cus_KmXXBlpZHlmW2B","payment_source_token":"pm_1K6yS1Ew0yMev2I0vPXDCOQF","created_at":"2021-12-15T14:27:29.633Z","updated_at":"2021-12-15T14:27:29.633Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XvJoWsqeKB/payment_source"},"data":{"type":"stripe_payments","id":"NYxQzSWjqM"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"XLnYNsdZDz","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz"},"attributes":{"name":"stripe_payment #907","customer_token":"cus_KmXQMJggwqwsCQ","payment_source_token":"pm_1K6yLlEw0yMev2I08st9nZ31","created_at":"2021-12-15T14:21:02.420Z","updated_at":"2021-12-15T14:21:02.420Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XLnYNsdZDz/payment_source"},"data":{"type":"stripe_payments","id":"gyLzJSlxqV"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"YKqxdsMPvg","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg"},"attributes":{"name":"stripe_payment #896","customer_token":"cus_Kkhzz4qtJw7SaK","payment_source_token":"pm_1K5CZKEw0yMev2I0b8Tf71Jp","created_at":"2021-12-10T17:08:16.927Z","updated_at":"2021-12-10T17:08:16.927Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/YKqxdsMPvg/payment_source"},"data":{"type":"stripe_payments","id":"mnbdOSRdYX"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"ZDGBwsqaKl","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl"},"attributes":{"name":"stripe_payment #894","customer_token":"cus_Kkh5NT8z54voaF","payment_source_token":"pm_1K5BaYEw0yMev2I0sQoU363m","created_at":"2021-12-10T16:11:32.014Z","updated_at":"2021-12-10T16:11:32.014Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZDGBwsqaKl/payment_source"},"data":{"type":"stripe_payments","id":"jqOjkSLGYJ"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"QLeqRswNLm","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm"},"attributes":{"name":"stripe_payment #893","customer_token":"cus_KkgqTGCZhyRqHX","payment_source_token":"pm_1K5BSaEw0yMev2I0rI4ZFhHd","created_at":"2021-12-10T15:57:19.477Z","updated_at":"2021-12-10T15:57:19.477Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/QLeqRswNLm/payment_source"},"data":{"type":"stripe_payments","id":"ayNaJSNpnW"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"ZvgXnsJrKX","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX"},"attributes":{"name":"stripe_payment #891","customer_token":"cus_KkfYdVzZ3vDsEj","payment_source_token":"pm_1K5A0xEw0yMev2I0MNg4ixXY","created_at":"2021-12-10T14:37:03.572Z","updated_at":"2021-12-10T14:37:03.572Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/ZvgXnsJrKX/payment_source"},"data":{"type":"stripe_payments","id":"RqBXrSpAyB"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"bvWkPspQvG","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG"},"attributes":{"name":"stripe_payment #769","customer_token":"cus_KK3x5wYKlqcHwi","payment_source_token":"pm_1JfPpZEw0yMev2I0eEPMMuID","created_at":"2021-09-30T14:02:10.960Z","updated_at":"2021-09-30T14:02:10.960Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/bvWkPspQvG/payment_source"},"data":{"type":"stripe_payments","id":"pnljASKbYv"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"BvlbEswxDl","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl"},"attributes":{"name":"stripe_payment #297","customer_token":"cus_JTFRp8trtV32e4","payment_source_token":"pm_1IqIw3Ew0yMev2I0iYE0MEJa","created_at":"2021-05-12T14:21:24.006Z","updated_at":"2021-05-12T14:21:24.006Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/BvlbEswxDl/payment_source"},"data":{"type":"stripe_payments","id":"EYAWBSMOnj"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"EKRwpsnXDb","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb"},"attributes":{"name":"stripe_payment #296","customer_token":"cus_JTFC8aMGEwfy4z","payment_source_token":"pm_1IqIiLEw0yMev2I06wBkWOnQ","created_at":"2021-05-12T14:07:12.931Z","updated_at":"2021-05-12T14:07:12.931Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/EKRwpsnXDb/payment_source"},"data":{"type":"stripe_payments","id":"RqwkzSJDqM"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"PLQobsVmKp","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp"},"attributes":{"name":"stripe_payment #295","customer_token":"cus_JTF4hYkWAU6ivJ","payment_source_token":"pm_1IqIabEw0yMev2I0RkfqInaD","created_at":"2021-05-12T13:59:16.634Z","updated_at":"2021-05-12T13:59:16.634Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/PLQobsVmKp/payment_source"},"data":{"type":"stripe_payments","id":"znmBlSKgyZ"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"yvkNlsdkLr","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr"},"attributes":{"name":"stripe_payment #294","customer_token":"cus_JTEvMOlfe2yvDn","payment_source_token":"pm_1IpxwREw0yMev2I0rno6YCxN","created_at":"2021-05-12T13:49:23.661Z","updated_at":"2021-05-12T13:49:23.661Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/yvkNlsdkLr/payment_source"},"data":{"type":"stripe_payments","id":"ZnJwvSOLyX"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"XDJoWsNeLB","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB"},"attributes":{"name":"stripe_payment #271","customer_token":"cus_JLfEyLaqgKoIn4","payment_source_token":"pm_1IixtoEw0yMev2I0jGzoT9Js","created_at":"2021-04-22T08:29:02.499Z","updated_at":"2021-04-22T08:29:02.499Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XDJoWsNeLB/payment_source"},"data":{"type":"stripe_payments","id":"dYGxVSVkqL"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"XKnYNsqZvz","type":"customer_payment_sources","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz"},"attributes":{"name":"stripe_payment #270","customer_token":"cus_JLfAzal1QVV5YU","payment_source_token":"pm_1IixpGEw0yMev2I0i8cPyZjM","created_at":"2021-04-22T08:24:32.326Z","updated_at":"2021-04-22T08:24:32.326Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/customer"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/customer_payment_sources/XKnYNsqZvz/payment_source"},"data":{"type":"stripe_payments","id":"mYPjMSvdnK"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"KYZGRSVjqG","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG"},"attributes":{"client_secret":"pi_3K7FXPEw0yMev2I01ryNtu5r_secret_lWo912XTYHzo5Y2eRbsPfY7uH","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1K7FXpEw0yMev2I0766HeRIO","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2030,"networks":{"available":["visa"],"preferred":null},"exp_month":3,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639644153,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"3408604726","address":{"city":"Cogorno","line1":" Via polivalente","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":3300},"payment_method":{"id":"pm_1K7FXpEw0yMev2I0E9xnXnxr","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2030,"networks":{"available":["visa"],"preferred":null},"exp_month":3,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639644154,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"3408604726","address":{"city":"Cogorno","line1":" Via polivalente","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-16T08:42:08.164Z","updated_at":"2022-03-03T19:15:47.314Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/KYZGRSVjqG/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"mYPjMSoXnK","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK"},"attributes":{"client_secret":"pi_3K6yTEEw0yMev2I001LS1FRw_secret_HsyP1LQ4VVzN8YNlrGJlOOG2x","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1K6yWqEw0yMev2I0ICDe7y1x","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":12,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639578745,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":3300},"payment_method":{"id":"pm_1K6yWrEw0yMev2I0PVzPRLIw","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":12,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639578745,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-15T14:28:40.454Z","updated_at":"2022-03-03T19:15:47.715Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSoXnK/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"NYxQzSWjqM","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM"},"attributes":{"client_secret":"pi_3K6yRhEw0yMev2I01TCWyzaH_secret_0qVAlq00ofeUMdt08qeTwIUGw","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1K6yS1Ew0yMev2I0vPXDCOQF","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2030,"networks":{"available":["visa"],"preferred":null},"exp_month":3,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639578445,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":3300},"payment_method":{"id":"pm_1K6yS2Ew0yMev2I06v0gGsqN","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2030,"networks":{"available":["visa"],"preferred":null},"exp_month":3,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639578446,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-15T14:27:05.765Z","updated_at":"2022-03-03T19:15:48.112Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/NYxQzSWjqM/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"gyLzJSlxqV","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV"},"attributes":{"client_secret":"pi_3K6yLHEw0yMev2I01IIElTaA_secret_m6MxVx0P8g8SlkODkNUWQwhh2","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1K6yLlEw0yMev2I08st9nZ31","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2030,"networks":{"available":["visa"],"preferred":null},"exp_month":3,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639578057,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":3300},"payment_method":{"id":"pm_1K6yLmEw0yMev2I0BiicdK37","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2030,"networks":{"available":["visa"],"preferred":null},"exp_month":3,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639578059,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-15T14:20:27.993Z","updated_at":"2022-03-03T19:15:48.494Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/gyLzJSlxqV/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"mnbdOSRdYX","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX"},"attributes":{"client_secret":"pi_3K5CYyEw0yMev2I01E95WSZU_secret_iuBB3KXsh9pY8BXSSmu0tMape","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1K5CZJEw0yMev2I0IP0kfpSp","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":2,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639156058,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":3300},"payment_method":{"id":"pm_1K5CZKEw0yMev2I0b8Tf71Jp","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":2,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639156059,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":true,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-10T17:07:16.923Z","updated_at":"2022-03-03T19:15:48.895Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mnbdOSRdYX/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"jqOjkSLGYJ","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ"},"attributes":{"client_secret":"pi_3K5BZQEw0yMev2I01kORHcv1_secret_5hduXmP9lYWI557bbjuPXehXN","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"intent_amount_cents":3300},"payment_method":{"id":"pm_1K5BaYEw0yMev2I0sQoU363m","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":12,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639152291,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-10T16:03:40.878Z","updated_at":"2022-03-03T19:15:49.274Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/jqOjkSLGYJ/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"ayNaJSNpnW","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW"},"attributes":{"client_secret":"pi_3K5BPPEw0yMev2I01OYZUO97_secret_47Pcd69ghKm5bFqM01x6rTP8o","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"intent_amount_cents":3300},"payment_method":{"id":"pm_1K5BSaEw0yMev2I0rI4ZFhHd","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":12,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639151796,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-10T15:53:19.612Z","updated_at":"2022-03-03T19:15:49.701Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ayNaJSNpnW/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"RqBXrSpAyB","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB"},"attributes":{"client_secret":"pi_3K5A07Ew0yMev2I0012rg4hD_secret_XiGFsz0vWTMRodBgbaiQ8Ogl4","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"intent_amount_cents":3300},"payment_method":{"id":"pm_1K5A0xEw0yMev2I0MNg4ixXY","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2025,"networks":{"available":["visa"],"preferred":null},"exp_month":12,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1639146239,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":3300,"intent_amount_float":33,"formatted_intent_amount":"€33,00","created_at":"2021-12-10T14:23:07.930Z","updated_at":"2022-03-03T19:15:50.117Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqBXrSpAyB/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"pnljASKbYv","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv"},"attributes":{"client_secret":"pi_3JfPo9Ew0yMev2I01bA3469v_secret_q75Aw1JpZBJbAH90TS4U6302b","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1JfPpYEw0yMev2I0eD5BEP21","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1633010508,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":2820},"payment_method":{"id":"pm_1JfPpZEw0yMev2I0eEPMMuID","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1633010509,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":2820,"intent_amount_float":28.2,"formatted_intent_amount":"€28,20","created_at":"2021-09-30T14:00:21.968Z","updated_at":"2022-03-03T19:15:50.505Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/pnljASKbYv/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"EYAWBSMOnj","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj"},"attributes":{"client_secret":"pi_1IqIw3Ew0yMev2I0Py6q6szS_secret_rgvmwG7gg4uMr5FOG4Lp04znc","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1IqIw3Ew0yMev2I0iYE0MEJa","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620829275,"customer":"cus_JTFRp8trtV32e4","livemode":false,"billing_details":{"name":null,"email":null,"phone":null,"address":{"city":null,"line1":null,"line2":null,"state":null,"country":null,"postal_code":null}},"setup_future_usage":"off_session","intent_amount_cents":2820},"payment_method":{"id":"pm_1IqIw3Ew0yMev2I0iYE0MEJa","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620829275,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":null,"email":null,"phone":null,"address":{"city":null,"line1":null,"line2":null,"state":null,"country":null,"postal_code":null}}},"mismatched_amounts":false,"intent_amount_cents":2820,"intent_amount_float":28.2,"formatted_intent_amount":"€28,20","created_at":"2021-05-12T14:21:16.165Z","updated_at":"2022-03-03T19:15:50.920Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/EYAWBSMOnj/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"RqwkzSJDqM","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM"},"attributes":{"client_secret":"pi_1IqIiMEw0yMev2I0SuC0LE7h_secret_xQ3nLjD5m116NYi61DWC1Br50","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1IqIiLEw0yMev2I06wBkWOnQ","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620828426,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":2820},"payment_method":{"id":"pm_1IqIiLEw0yMev2I06wBkWOnQ","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620828426,"customer":"cus_JTFC8aMGEwfy4z","livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":2820,"intent_amount_float":28.2,"formatted_intent_amount":"€28,20","created_at":"2021-05-12T14:07:07.056Z","updated_at":"2022-03-03T19:15:51.339Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/RqwkzSJDqM/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"znmBlSKgyZ","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ"},"attributes":{"client_secret":"pi_1IqIacEw0yMev2I0o9Oj67Pn_secret_BXHUbfSYFPY0wi18Mha43I2tp","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1IqIabEw0yMev2I0RkfqInaD","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620827945,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":2820},"payment_method":{"id":"pm_1IqIabEw0yMev2I0RkfqInaD","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620827945,"customer":null,"livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":2820,"intent_amount_float":28.2,"formatted_intent_amount":"€28,20","created_at":"2021-05-12T13:59:06.761Z","updated_at":"2022-03-03T19:15:51.747Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/znmBlSKgyZ/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"ZnJwvSOLyX","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX"},"attributes":{"client_secret":"pi_1IpxwSEw0yMev2I0ecOqW6hO_secret_IQfB8lqO7WfyZ899msFZYwlY5","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1IpxwREw0yMev2I0rno6YCxN","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":11,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620748576,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"3892472932","address":{"city":"Cogorno","line1":"Via Umberto podesta","line2":null,"state":"Genova","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":5140},"payment_method":{"id":"pm_1IpxwREw0yMev2I0rno6YCxN","card":{"brand":"visa","last4":"1111","checks":{"cvc_check":"unchecked","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":11,"fingerprint":"VBpIjk8uXFyOk9Kd","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1620748576,"customer":"cus_JTEvMOlfe2yvDn","livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"3892472932","address":{"city":"Cogorno","line1":"Via Umberto podesta","line2":null,"state":"Genova","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":5140,"intent_amount_float":51.4,"formatted_intent_amount":"€51,40","created_at":"2021-05-11T15:56:16.949Z","updated_at":"2022-03-03T19:15:52.138Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/ZnJwvSOLyX/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"dYGxVSVkqL","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL"},"attributes":{"client_secret":"pi_1IixtpEw0yMev2I0IyPJ42BQ_secret_5kJFSLkQ29kIfGWkFcP5L707y","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1IixtoEw0yMev2I0jGzoT9Js","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1619080117,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":2820},"payment_method":{"id":"pm_1IixtoEw0yMev2I0jGzoT9Js","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1619080117,"customer":"cus_JLfEyLaqgKoIn4","livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":2820,"intent_amount_float":28.2,"formatted_intent_amount":"€28,20","created_at":"2021-04-22T08:28:37.744Z","updated_at":"2022-03-03T19:15:52.545Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/dYGxVSVkqL/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"mYPjMSvdnK","type":"stripe_payments","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK"},"attributes":{"client_secret":"pi_1IixpLEw0yMev2I0GdirHNMf_secret_q3zStXpnJZ3Q477l3YyT3jff1","publishable_key":"pk_test_UArgJuzBMSppFkvAkATXTNT5","options":{"id":"pm_1IixpGEw0yMev2I0i8cPyZjM","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":null,"address_line1_check":null,"address_postal_code_check":null},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1619079834,"customer":null,"livemode":false,"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}},"setup_future_usage":"off_session","intent_amount_cents":2820},"payment_method":{"id":"pm_1IixpGEw0yMev2I0i8cPyZjM","card":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2024,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}},"type":"card","object":"payment_method","created":1619079834,"customer":"cus_JLfAzal1QVV5YU","livemode":false,"metadata":{},"billing_details":{"name":"Alessandro Casazza","email":"bruce@wayne.com","phone":"(348) 1234532","address":{"city":"Cogorno","line1":"Via Umberto Podestà 40B","line2":null,"state":"GE","country":"IT","postal_code":"16030"}}},"mismatched_amounts":false,"intent_amount_cents":2820,"intent_amount_float":28.2,"formatted_intent_amount":"€28,20","created_at":"2021-04-22T08:23:59.823Z","updated_at":"2022-03-03T19:15:52.939Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/order"}},"payment_gateway":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/relationships/payment_gateway","related":"https://the-blue-brand-3.commercelayer.co/api/stripe_payments/mYPjMSvdnK/payment_gateway"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}]}}] \ No newline at end of file diff --git a/packages/react-components/specs/e2e/mocks/addresses.mock.json b/packages/react-components/specs/e2e/mocks/addresses.mock.json deleted file mode 100644 index 291521f7..00000000 --- a/packages/react-components/specs/e2e/mocks/addresses.mock.json +++ /dev/null @@ -1 +0,0 @@ -[{"0":{"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJlbldveEZNT25wIiwic2x1ZyI6InRoZS1ibHVlLWJyYW5kLTMifSwiYXBwbGljYXRpb24iOnsiaWQiOiJuR1ZxYWlFWU5BIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NDc0NjgxOTksIm1hcmtldCI6eyJpZCI6WyJCanhySmh5bWxNIl0sInByaWNlX2xpc3RfaWQiOiJWQnlWcENndmtnIiwic3RvY2tfbG9jYXRpb25faWRzIjpbInhHWEJYdXJETUUiLCJkTXFYeXVWVmtOIl0sImdlb2NvZGVyX2lkIjpudWxsLCJhbGxvd3NfZXh0ZXJuYWxfcHJpY2VzIjpmYWxzZX0sInJhbmQiOjAuMDUxNzI3MzU0OTUyNDY5NX0.t5_pharSsPvMyY5T25YIAd-RxN7w6ga9AGY6MSakwSTMuOS3vSKA7uhJI7zsJVp3ehfcRhiBN0wUeZHuR3Qd2Q","token_type":"Bearer","expires_in":11842,"scope":"market:58","created_at":1647453799}},{"1":{"data":{"id":"qRKahzoMbE","type":"orders","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE"},"attributes":{"number":2437061,"autorefresh":true,"status":"placed","payment_status":"authorized","fulfillment_status":"unfulfilled","guest":false,"editable":false,"customer_email":"bruce@wayne.com","language_code":"en","currency_code":"EUR","tax_included":true,"tax_rate":"0.22","freight_taxable":false,"requires_billing_info":false,"country_code":"IT","shipping_country_code_lock":null,"coupon_code":null,"gift_card_code":null,"gift_card_or_coupon_code":null,"subtotal_amount_cents":3500,"subtotal_amount_float":35,"formatted_subtotal_amount":"€35,00","shipping_amount_cents":1200,"shipping_amount_float":12,"formatted_shipping_amount":"€12,00","payment_method_amount_cents":500,"payment_method_amount_float":5,"formatted_payment_method_amount":"€5,00","discount_amount_cents":0,"discount_amount_float":0,"formatted_discount_amount":"€0,00","adjustment_amount_cents":0,"adjustment_amount_float":0,"formatted_adjustment_amount":"€0,00","gift_card_amount_cents":0,"gift_card_amount_float":0,"formatted_gift_card_amount":"€0,00","total_tax_amount_cents":631,"total_tax_amount_float":6.31,"formatted_total_tax_amount":"€6,31","subtotal_tax_amount_cents":631,"subtotal_tax_amount_float":6.31,"formatted_subtotal_tax_amount":"€6,31","shipping_tax_amount_cents":0,"shipping_tax_amount_float":0,"formatted_shipping_tax_amount":"€0,00","payment_method_tax_amount_cents":0,"payment_method_tax_amount_float":0,"formatted_payment_method_tax_amount":"€0,00","adjustment_tax_amount_cents":0,"adjustment_tax_amount_float":0,"formatted_adjustment_tax_amount":"€0,00","total_amount_cents":5200,"total_amount_float":52,"formatted_total_amount":"€52,00","total_taxable_amount_cents":4569,"total_taxable_amount_float":45.69,"formatted_total_taxable_amount":"€45,69","subtotal_taxable_amount_cents":2869,"subtotal_taxable_amount_float":28.69,"formatted_subtotal_taxable_amount":"€28,69","shipping_taxable_amount_cents":1200,"shipping_taxable_amount_float":12,"formatted_shipping_taxable_amount":"€12,00","payment_method_taxable_amount_cents":500,"payment_method_taxable_amount_float":5,"formatted_payment_method_taxable_amount":"€5,00","adjustment_taxable_amount_cents":0,"adjustment_taxable_amount_float":0,"formatted_adjustment_taxable_amount":"€0,00","total_amount_with_taxes_cents":5200,"total_amount_with_taxes_float":52,"formatted_total_amount_with_taxes":"€52,00","fees_amount_cents":0,"fees_amount_float":0,"formatted_fees_amount":"€0,00","duty_amount_cents":null,"duty_amount_float":null,"formatted_duty_amount":null,"skus_count":1,"line_item_options_count":0,"shipments_count":1,"payment_source_details":{"type":"stripe_payment","payment_method_id":"pm_1KdIQOEw0yMev2I0i31AmyS0","payment_method_type":"card","payment_method_details":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}}},"token":"72b794f938a5eb40eb273ee1605c18db","cart_url":null,"return_url":"https://test.co","terms_url":null,"privacy_url":null,"checkout_url":null,"placed_at":"2022-03-14T18:15:43.639Z","approved_at":null,"cancelled_at":null,"payment_updated_at":"2022-03-14T18:15:23.484Z","fulfillment_updated_at":null,"refreshed_at":"2022-03-14T18:14:42.842Z","archived_at":null,"expires_at":null,"created_at":"2022-03-04T10:57:01.808Z","updated_at":"2022-03-14T18:15:43.665Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/market"}},"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/customer"}},"shipping_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/shipping_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/shipping_address"},"data":{"type":"addresses","id":"BnNguYjjgB"}},"billing_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/billing_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/billing_address"},"data":{"type":"addresses","id":"WwgVuxYYoW"}},"available_payment_methods":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/available_payment_methods","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/available_payment_methods"}},"available_customer_payment_sources":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/available_customer_payment_sources","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/available_customer_payment_sources"}},"payment_method":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/payment_method","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/payment_method"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/payment_source"}},"line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/line_items","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/line_items"}},"shipments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/shipments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/shipments"}},"transactions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/transactions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/transactions"}},"authorizations":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/authorizations","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/authorizations"}},"captures":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/captures","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/captures"}},"voids":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/voids","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/voids"}},"refunds":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/refunds","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/refunds"}},"order_subscriptions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/order_subscriptions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/order_subscriptions"}},"order_copies":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/order_copies","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/order_copies"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},"included":[{"id":"BnNguYjjgB","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/BnNguYjjgB"},"attributes":{"business":false,"first_name":"Alessandro","last_name":"Casazza","company":null,"full_name":"Alessandro Casazza","line_1":"Via Umberto Podestà 40B","line_2":null,"city":"Cogorno","zip_code":"16030","state_code":"GE","country_code":"IT","phone":"(348) 1234532","full_address":"Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","name":"Alessandro Casazza, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2022-03-09T11:37:54.122Z","updated_at":"2022-03-09T11:37:54.122Z","reference":"BGDejhVYze","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/BnNguYjjgB/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/BnNguYjjgB/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"WwgVuxYYoW","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/WwgVuxYYoW"},"attributes":{"business":false,"first_name":"Alessandro","last_name":"Casazza","company":null,"full_name":"Alessandro Casazza","line_1":"Via Umberto Podestà 40B","line_2":null,"city":"Cogorno","zip_code":"16030","state_code":"GE","country_code":"IT","phone":"(348) 1234532","full_address":"Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","name":"Alessandro Casazza, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2022-03-09T11:37:54.209Z","updated_at":"2022-03-09T11:37:54.209Z","reference":"BGDejhVYze","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/WwgVuxYYoW/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/WwgVuxYYoW/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}]}},{"2":{"data":{"id":"qRKahzoMbE","type":"orders","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE"},"attributes":{"number":2437061,"autorefresh":true,"status":"placed","payment_status":"authorized","fulfillment_status":"unfulfilled","guest":false,"editable":false,"customer_email":"bruce@wayne.com","language_code":"en","currency_code":"EUR","tax_included":true,"tax_rate":"0.22","freight_taxable":false,"requires_billing_info":false,"country_code":"IT","shipping_country_code_lock":null,"coupon_code":null,"gift_card_code":null,"gift_card_or_coupon_code":null,"subtotal_amount_cents":3500,"subtotal_amount_float":35,"formatted_subtotal_amount":"€35,00","shipping_amount_cents":1200,"shipping_amount_float":12,"formatted_shipping_amount":"€12,00","payment_method_amount_cents":500,"payment_method_amount_float":5,"formatted_payment_method_amount":"€5,00","discount_amount_cents":0,"discount_amount_float":0,"formatted_discount_amount":"€0,00","adjustment_amount_cents":0,"adjustment_amount_float":0,"formatted_adjustment_amount":"€0,00","gift_card_amount_cents":0,"gift_card_amount_float":0,"formatted_gift_card_amount":"€0,00","total_tax_amount_cents":631,"total_tax_amount_float":6.31,"formatted_total_tax_amount":"€6,31","subtotal_tax_amount_cents":631,"subtotal_tax_amount_float":6.31,"formatted_subtotal_tax_amount":"€6,31","shipping_tax_amount_cents":0,"shipping_tax_amount_float":0,"formatted_shipping_tax_amount":"€0,00","payment_method_tax_amount_cents":0,"payment_method_tax_amount_float":0,"formatted_payment_method_tax_amount":"€0,00","adjustment_tax_amount_cents":0,"adjustment_tax_amount_float":0,"formatted_adjustment_tax_amount":"€0,00","total_amount_cents":5200,"total_amount_float":52,"formatted_total_amount":"€52,00","total_taxable_amount_cents":4569,"total_taxable_amount_float":45.69,"formatted_total_taxable_amount":"€45,69","subtotal_taxable_amount_cents":2869,"subtotal_taxable_amount_float":28.69,"formatted_subtotal_taxable_amount":"€28,69","shipping_taxable_amount_cents":1200,"shipping_taxable_amount_float":12,"formatted_shipping_taxable_amount":"€12,00","payment_method_taxable_amount_cents":500,"payment_method_taxable_amount_float":5,"formatted_payment_method_taxable_amount":"€5,00","adjustment_taxable_amount_cents":0,"adjustment_taxable_amount_float":0,"formatted_adjustment_taxable_amount":"€0,00","total_amount_with_taxes_cents":5200,"total_amount_with_taxes_float":52,"formatted_total_amount_with_taxes":"€52,00","fees_amount_cents":0,"fees_amount_float":0,"formatted_fees_amount":"€0,00","duty_amount_cents":null,"duty_amount_float":null,"formatted_duty_amount":null,"skus_count":1,"line_item_options_count":0,"shipments_count":1,"payment_source_details":{"type":"stripe_payment","payment_method_id":"pm_1KdIQOEw0yMev2I0i31AmyS0","payment_method_type":"card","payment_method_details":{"brand":"visa","last4":"4242","checks":{"cvc_check":"pass","address_line1_check":"pass","address_postal_code_check":"pass"},"wallet":null,"country":"US","funding":"credit","exp_year":2023,"networks":{"available":["visa"],"preferred":null},"exp_month":4,"fingerprint":"6OnuUOFYXuHF9ffk","generated_from":null,"three_d_secure_usage":{"supported":true}}},"token":"72b794f938a5eb40eb273ee1605c18db","cart_url":null,"return_url":"https://test.co","terms_url":null,"privacy_url":null,"checkout_url":null,"placed_at":"2022-03-14T18:15:43.639Z","approved_at":null,"cancelled_at":null,"payment_updated_at":"2022-03-14T18:15:23.484Z","fulfillment_updated_at":null,"refreshed_at":"2022-03-14T18:14:42.842Z","archived_at":null,"expires_at":null,"created_at":"2022-03-04T10:57:01.808Z","updated_at":"2022-03-14T18:15:43.665Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/market"}},"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/customer"}},"shipping_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/shipping_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/shipping_address"},"data":{"type":"addresses","id":"BnNguYjjgB"}},"billing_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/billing_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/billing_address"},"data":{"type":"addresses","id":"WwgVuxYYoW"}},"available_payment_methods":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/available_payment_methods","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/available_payment_methods"}},"available_customer_payment_sources":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/available_customer_payment_sources","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/available_customer_payment_sources"}},"payment_method":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/payment_method","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/payment_method"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/payment_source"}},"line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/line_items","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/line_items"}},"shipments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/shipments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/shipments"}},"transactions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/transactions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/transactions"}},"authorizations":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/authorizations","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/authorizations"}},"captures":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/captures","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/captures"}},"voids":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/voids","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/voids"}},"refunds":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/refunds","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/refunds"}},"order_subscriptions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/order_subscriptions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/order_subscriptions"}},"order_copies":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/order_copies","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/order_copies"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qRKahzoMbE/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},"included":[{"id":"BnNguYjjgB","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/BnNguYjjgB"},"attributes":{"business":false,"first_name":"Alessandro","last_name":"Casazza","company":null,"full_name":"Alessandro Casazza","line_1":"Via Umberto Podestà 40B","line_2":null,"city":"Cogorno","zip_code":"16030","state_code":"GE","country_code":"IT","phone":"(348) 1234532","full_address":"Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","name":"Alessandro Casazza, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2022-03-09T11:37:54.122Z","updated_at":"2022-03-09T11:37:54.122Z","reference":"BGDejhVYze","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/BnNguYjjgB/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/BnNguYjjgB/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"WwgVuxYYoW","type":"addresses","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/WwgVuxYYoW"},"attributes":{"business":false,"first_name":"Alessandro","last_name":"Casazza","company":null,"full_name":"Alessandro Casazza","line_1":"Via Umberto Podestà 40B","line_2":null,"city":"Cogorno","zip_code":"16030","state_code":"GE","country_code":"IT","phone":"(348) 1234532","full_address":"Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","name":"Alessandro Casazza, Via Umberto Podestà 40B, 16030 Cogorno GE (IT) (348) 1234532","email":null,"notes":null,"lat":null,"lng":null,"is_localized":false,"is_geocoded":false,"provider_name":null,"map_url":null,"static_map_url":null,"billing_info":null,"created_at":"2022-03-09T11:37:54.209Z","updated_at":"2022-03-09T11:37:54.209Z","reference":"BGDejhVYze","reference_origin":null,"metadata":{}},"relationships":{"geocoder":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/addresses/WwgVuxYYoW/relationships/geocoder","related":"https://the-blue-brand-3.commercelayer.co/api/addresses/WwgVuxYYoW/geocoder"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}]}},{"3":{"errors":[{"title":"Access denied","detail":"You are not authorized to perform this action on the requested resource.","code":"UNAUTHORIZED","status":"401"}]}}] \ No newline at end of file diff --git a/packages/react-components/specs/e2e/mocks/order.mock.json b/packages/react-components/specs/e2e/mocks/order.mock.json deleted file mode 100644 index 66ea78eb..00000000 --- a/packages/react-components/specs/e2e/mocks/order.mock.json +++ /dev/null @@ -1 +0,0 @@ -[{"0":{"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJlbldveEZNT25wIiwic2x1ZyI6InRoZS1ibHVlLWJyYW5kLTMifSwiYXBwbGljYXRpb24iOnsiaWQiOiJuR1ZxYWlFWU5BIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NTA0NzcwNjcsIm1hcmtldCI6eyJpZCI6WyJCanhySmh5bWxNIl0sInByaWNlX2xpc3RfaWQiOiJWQnlWcENndmtnIiwic3RvY2tfbG9jYXRpb25faWRzIjpbInhHWEJYdXJETUUiLCJkTXFYeXVWVmtOIl0sImdlb2NvZGVyX2lkIjpudWxsLCJhbGxvd3NfZXh0ZXJuYWxfcHJpY2VzIjpmYWxzZX0sInJhbmQiOjAuMzE0MjQwNzEwMzQ2NzIzMX0.-73cUqrN2Vgel-fNgq9OEUQINobuI7WZGWpOTmHwtSPgEIeeWunKU-RMoqiFAJpI2ZbjlcbigAUyWXHuRXddDA","token_type":"Bearer","expires_in":12042,"scope":"market:58","created_at":1650462667}},{"1":{"data":[{"id":"nazOWUVMdp","type":"prices","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/nazOWUVMdp"},"attributes":{"currency_code":"EUR","sku_code":"BABYONBU000000E63E746MXX","amount_cents":2900,"amount_float":29,"formatted_amount":"€29,00","compare_at_amount_cents":3770,"compare_at_amount_float":37.7,"formatted_compare_at_amount":"€37,70","created_at":"2019-11-07T18:27:57.371Z","updated_at":"2019-11-07T18:27:57.371Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"price_list":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/nazOWUVMdp/relationships/price_list","related":"https://the-blue-brand-3.commercelayer.co/api/prices/nazOWUVMdp/price_list"}},"sku":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/nazOWUVMdp/relationships/sku","related":"https://the-blue-brand-3.commercelayer.co/api/prices/nazOWUVMdp/sku"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/nazOWUVMdp/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/prices/nazOWUVMdp/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}],"meta":{"record_count":1,"page_count":1},"links":{"first":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E746MXX&page%5Bnumber%5D=1&page%5Bsize%5D=10","last":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E746MXX&page%5Bnumber%5D=1&page%5Bsize%5D=10"}}},{"2":{"data":[{"id":"DZNRJSdwrZ","type":"skus","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ"},"attributes":{"code":"BABYONBU000000E63E746MXX","name":"Black Baby Onesie Short Sleeve with Pink Logo (6 Months)","description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam pellentesque in neque vitae tincidunt. In gravida eu ipsum non condimentum. Curabitur libero leo, gravida a dictum vestibulum, sollicitudin vel quam.","image_url":"https://img.commercelayer.io/skus/BABYONBU000000E63E74.png?fm=jpg&q=90","pieces_per_pack":null,"weight":null,"unit_of_weight":null,"hs_tariff_number":null,"do_not_ship":false,"do_not_track":false,"created_at":"2019-11-07T18:27:57.359Z","updated_at":"2019-11-07T18:27:57.359Z","reference":"BABYONBU000000E63E74","reference_origin":null,"metadata":{}},"relationships":{"shipping_category":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/shipping_category","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/shipping_category"}},"prices":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/prices","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/prices"}},"stock_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/stock_items","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/stock_items"}},"delivery_lead_times":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/delivery_lead_times","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/delivery_lead_times"}},"sku_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/sku_options","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/sku_options"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"wZeDdSamqn","type":"skus","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn"},"attributes":{"code":"BABYONBU000000E63E7412MX","name":"Black Baby Onesie Short Sleeve with Pink Logo (12 Months)","description":"Unit test sync description","image_url":"https://img.commercelayer.io/skus/BABYONBU000000E63E74.png?fm=jpg&q=90","pieces_per_pack":null,"weight":null,"unit_of_weight":"","hs_tariff_number":"","do_not_ship":false,"do_not_track":false,"created_at":"2019-11-07T18:27:57.419Z","updated_at":"2021-05-06T17:10:32.463Z","reference":"BABYONBU000000E63E74","reference_origin":"","metadata":{}},"relationships":{"shipping_category":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/shipping_category","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/shipping_category"}},"prices":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/prices","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/prices"}},"stock_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/stock_items","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/stock_items"}},"delivery_lead_times":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/delivery_lead_times","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/delivery_lead_times"}},"sku_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/sku_options","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/sku_options"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}],"meta":{"record_count":2,"page_count":1},"links":{"first":"https://the-blue-brand-3.commercelayer.co/api/skus?filter%5Bq%5D%5Bcode_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000E63E746MXX%2CBABYONBU000000E63E746MXXFAKE&page%5Bnumber%5D=1&page%5Bsize%5D=10","last":"https://the-blue-brand-3.commercelayer.co/api/skus?filter%5Bq%5D%5Bcode_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000E63E746MXX%2CBABYONBU000000E63E746MXXFAKE&page%5Bnumber%5D=1&page%5Bsize%5D=10"}}},{"3":{"data":{"id":"DZNRJSdwrZ","type":"skus","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ"},"attributes":{"code":"BABYONBU000000E63E746MXX","name":"Black Baby Onesie Short Sleeve with Pink Logo (6 Months)","description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam pellentesque in neque vitae tincidunt. In gravida eu ipsum non condimentum. Curabitur libero leo, gravida a dictum vestibulum, sollicitudin vel quam.","image_url":"https://img.commercelayer.io/skus/BABYONBU000000E63E74.png?fm=jpg&q=90","pieces_per_pack":null,"weight":null,"unit_of_weight":null,"hs_tariff_number":null,"do_not_ship":false,"do_not_track":false,"inventory":{"available":false,"quantity":0,"levels":[]},"created_at":"2019-11-07T18:27:57.359Z","updated_at":"2019-11-07T18:27:57.359Z","reference":"BABYONBU000000E63E74","reference_origin":null,"metadata":{}},"relationships":{"shipping_category":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/shipping_category","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/shipping_category"}},"prices":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/prices","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/prices"}},"stock_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/stock_items","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/stock_items"}},"delivery_lead_times":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/delivery_lead_times","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/delivery_lead_times"}},"sku_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/sku_options","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/sku_options"},"data":[{"type":"sku_options","id":"mNJEgsJwBn"}]},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/skus/DZNRJSdwrZ/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},"included":[{"id":"mNJEgsJwBn","type":"sku_options","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn"},"attributes":{"name":"Customisation","currency_code":"EUR","description":"","price_amount_cents":0,"price_amount_float":0,"formatted_price_amount":"€0,00","delay_hours":0,"delay_days":0,"sku_code_regex":"BABYONBU000000E63E74","created_at":"2021-07-14T16:03:44.460Z","updated_at":"2021-07-14T16:09:34.370Z","reference":"","reference_origin":"","metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/market"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}]}},{"4":{"data":{"id":"wZeDdSamqn","type":"skus","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn"},"attributes":{"code":"BABYONBU000000E63E7412MX","name":"Black Baby Onesie Short Sleeve with Pink Logo (12 Months)","description":"Unit test sync description","image_url":"https://img.commercelayer.io/skus/BABYONBU000000E63E74.png?fm=jpg&q=90","pieces_per_pack":null,"weight":null,"unit_of_weight":"","hs_tariff_number":"","do_not_ship":false,"do_not_track":false,"inventory":{"available":true,"quantity":10016,"levels":[{"quantity":9932,"delivery_lead_times":[{"shipping_method":{"name":"Standard Shipping EU","reference":"","price_amount_cents":500,"free_over_amount_cents":2000,"formatted_price_amount":"€5,00","formatted_free_over_amount":"€20,00"},"min":{"hours":72,"days":3},"max":{"hours":120,"days":5}},{"shipping_method":{"name":"Express Delivery EU","reference":"","price_amount_cents":1200,"free_over_amount_cents":5000,"formatted_price_amount":"€12,00","formatted_free_over_amount":"€50,00"},"min":{"hours":48,"days":2},"max":{"hours":72,"days":3}}]},{"quantity":84,"delivery_lead_times":[{"shipping_method":{"name":"Standard Shipping EU","reference":"","price_amount_cents":500,"free_over_amount_cents":2000,"formatted_price_amount":"€5,00","formatted_free_over_amount":"€20,00"},"min":{"hours":168,"days":7},"max":{"hours":240,"days":10}},{"shipping_method":{"name":"Express Delivery EU","reference":"","price_amount_cents":1200,"free_over_amount_cents":5000,"formatted_price_amount":"€12,00","formatted_free_over_amount":"€50,00"},"min":{"hours":72,"days":3},"max":{"hours":96,"days":4}}]}]},"created_at":"2019-11-07T18:27:57.419Z","updated_at":"2021-05-06T17:10:32.463Z","reference":"BABYONBU000000E63E74","reference_origin":"","metadata":{}},"relationships":{"shipping_category":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/shipping_category","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/shipping_category"}},"prices":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/prices","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/prices"}},"stock_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/stock_items","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/stock_items"}},"delivery_lead_times":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/delivery_lead_times","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/delivery_lead_times"}},"sku_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/sku_options","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/sku_options"},"data":[{"type":"sku_options","id":"mNJEgsJwBn"}]},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},"included":[{"id":"mNJEgsJwBn","type":"sku_options","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn"},"attributes":{"name":"Customisation","currency_code":"EUR","description":"","price_amount_cents":0,"price_amount_float":0,"formatted_price_amount":"€0,00","delay_hours":0,"delay_days":0,"sku_code_regex":"BABYONBU000000E63E74","created_at":"2021-07-14T16:03:44.460Z","updated_at":"2021-07-14T16:09:34.370Z","reference":"","reference_origin":"","metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/market"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/sku_options/mNJEgsJwBn/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}]}},{"5":{"data":[{"id":"MadKYUlJrg","type":"prices","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg"},"attributes":{"currency_code":"EUR","sku_code":"BABYONBU000000E63E7412MX","amount_cents":3500,"amount_float":35,"formatted_amount":"€35,00","compare_at_amount_cents":4500,"compare_at_amount_float":45,"formatted_compare_at_amount":"€45,00","created_at":"2019-11-07T18:27:57.434Z","updated_at":"2021-11-24T09:46:57.129Z","reference":"","reference_origin":"","metadata":{}},"relationships":{"price_list":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/price_list","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/price_list"}},"sku":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/sku","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/sku"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}],"meta":{"record_count":1,"page_count":1},"links":{"first":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX&page%5Bnumber%5D=1&page%5Bsize%5D=10","last":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX&page%5Bnumber%5D=1&page%5Bsize%5D=10"}}},{"6":{"data":[{"id":"MadKYUlJrg","type":"prices","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg"},"attributes":{"currency_code":"EUR","sku_code":"BABYONBU000000E63E7412MX","amount_cents":3500,"amount_float":35,"formatted_amount":"€35,00","compare_at_amount_cents":4500,"compare_at_amount_float":45,"formatted_compare_at_amount":"€45,00","created_at":"2019-11-07T18:27:57.434Z","updated_at":"2021-11-24T09:46:57.129Z","reference":"","reference_origin":"","metadata":{}},"relationships":{"price_list":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/price_list","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/price_list"}},"sku":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/sku","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/sku"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}],"meta":{"record_count":1,"page_count":1},"links":{"first":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX&page%5Bnumber%5D=1&page%5Bsize%5D=10","last":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX&page%5Bnumber%5D=1&page%5Bsize%5D=10"}}},{"7":{"data":{"id":"qdyBhGZKYX","type":"orders","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX"},"attributes":{"number":2437984,"autorefresh":true,"status":"draft","payment_status":"unpaid","fulfillment_status":"unfulfilled","guest":true,"editable":true,"customer_email":null,"language_code":"en","currency_code":"EUR","tax_included":true,"tax_rate":null,"freight_taxable":null,"requires_billing_info":false,"country_code":null,"shipping_country_code_lock":null,"coupon_code":null,"gift_card_code":null,"gift_card_or_coupon_code":null,"subtotal_amount_cents":0,"subtotal_amount_float":0,"formatted_subtotal_amount":"€0,00","shipping_amount_cents":0,"shipping_amount_float":0,"formatted_shipping_amount":"€0,00","payment_method_amount_cents":0,"payment_method_amount_float":0,"formatted_payment_method_amount":"€0,00","discount_amount_cents":0,"discount_amount_float":0,"formatted_discount_amount":"€0,00","adjustment_amount_cents":0,"adjustment_amount_float":0,"formatted_adjustment_amount":"€0,00","gift_card_amount_cents":0,"gift_card_amount_float":0,"formatted_gift_card_amount":"€0,00","total_tax_amount_cents":0,"total_tax_amount_float":0,"formatted_total_tax_amount":"€0,00","subtotal_tax_amount_cents":0,"subtotal_tax_amount_float":0,"formatted_subtotal_tax_amount":"€0,00","shipping_tax_amount_cents":0,"shipping_tax_amount_float":0,"formatted_shipping_tax_amount":"€0,00","payment_method_tax_amount_cents":0,"payment_method_tax_amount_float":0,"formatted_payment_method_tax_amount":"€0,00","adjustment_tax_amount_cents":0,"adjustment_tax_amount_float":0,"formatted_adjustment_tax_amount":"€0,00","total_amount_cents":0,"total_amount_float":0,"formatted_total_amount":"€0,00","total_taxable_amount_cents":0,"total_taxable_amount_float":0,"formatted_total_taxable_amount":"€0,00","subtotal_taxable_amount_cents":0,"subtotal_taxable_amount_float":0,"formatted_subtotal_taxable_amount":"€0,00","shipping_taxable_amount_cents":0,"shipping_taxable_amount_float":0,"formatted_shipping_taxable_amount":"€0,00","payment_method_taxable_amount_cents":0,"payment_method_taxable_amount_float":0,"formatted_payment_method_taxable_amount":"€0,00","adjustment_taxable_amount_cents":0,"adjustment_taxable_amount_float":0,"formatted_adjustment_taxable_amount":"€0,00","total_amount_with_taxes_cents":0,"total_amount_with_taxes_float":0,"formatted_total_amount_with_taxes":"€0,00","fees_amount_cents":0,"fees_amount_float":0,"formatted_fees_amount":"€0,00","duty_amount_cents":null,"duty_amount_float":null,"formatted_duty_amount":null,"skus_count":0,"line_item_options_count":0,"shipments_count":0,"payment_source_details":null,"token":"7ade94195a1149504b7e70e437ff53b7","cart_url":null,"return_url":"https://test.co","terms_url":null,"privacy_url":null,"checkout_url":null,"placed_at":null,"approved_at":null,"cancelled_at":null,"payment_updated_at":null,"fulfillment_updated_at":null,"refreshed_at":null,"archived_at":null,"expires_at":"2022-06-20T14:30:26.111Z","created_at":"2022-04-20T14:30:26.097Z","updated_at":"2022-04-20T14:30:26.097Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/market"}},"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/customer"}},"shipping_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipping_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipping_address"}},"billing_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/billing_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/billing_address"}},"available_payment_methods":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_payment_methods","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_payment_methods"}},"available_customer_payment_sources":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_customer_payment_sources","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_customer_payment_sources"}},"payment_method":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_method","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_method"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_source"}},"line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/line_items","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/line_items"}},"shipments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipments"}},"transactions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/transactions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/transactions"}},"authorizations":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/authorizations","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/authorizations"}},"captures":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/captures","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/captures"}},"voids":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/voids","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/voids"}},"refunds":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/refunds","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/refunds"}},"order_subscriptions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_subscriptions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_subscriptions"}},"order_copies":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_copies","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_copies"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}}},{"8":{"data":{"id":"kVbztYWzEk","type":"line_items","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk"},"attributes":{"sku_code":"BABYONBU000000E63E7412MX","bundle_code":null,"quantity":1,"currency_code":"EUR","unit_amount_cents":3500,"unit_amount_float":35,"formatted_unit_amount":"€35,00","options_amount_cents":0,"options_amount_float":0,"formatted_options_amount":"€0,00","discount_cents":0,"discount_float":0,"formatted_discount":"€0,00","total_amount_cents":3500,"total_amount_float":35,"formatted_total_amount":"€35,00","tax_amount_cents":0,"tax_amount_float":0,"formatted_tax_amount":"€0,00","name":"Darth Vader (12 Months)","image_url":"https://i.pinimg.com/736x/a5/32/de/a532de337eff9b1c1c4bfb8df73acea4--darth-vader-stencil-darth-vader-head.jpg?b=t","discount_breakdown":{},"tax_rate":0,"tax_breakdown":{},"item_type":"skus","created_at":"2022-04-20T14:30:26.265Z","updated_at":"2022-04-20T14:30:26.265Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/order"}},"item":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/item","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/item"}},"line_item_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/line_item_options","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/line_item_options"}},"shipment_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/shipment_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/shipment_line_items"}},"stock_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_line_items"}},"stock_transfers":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_transfers","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_transfers"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}}},{"9":{"data":{"id":"qdyBhGZKYX","type":"orders","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX"},"attributes":{"number":2437984,"autorefresh":true,"status":"draft","payment_status":"unpaid","fulfillment_status":"unfulfilled","guest":true,"editable":true,"customer_email":null,"language_code":"en","currency_code":"EUR","tax_included":true,"tax_rate":null,"freight_taxable":null,"requires_billing_info":false,"country_code":null,"shipping_country_code_lock":null,"coupon_code":null,"gift_card_code":null,"gift_card_or_coupon_code":null,"subtotal_amount_cents":3500,"subtotal_amount_float":35,"formatted_subtotal_amount":"€35,00","shipping_amount_cents":0,"shipping_amount_float":0,"formatted_shipping_amount":"€0,00","payment_method_amount_cents":0,"payment_method_amount_float":0,"formatted_payment_method_amount":"€0,00","discount_amount_cents":0,"discount_amount_float":0,"formatted_discount_amount":"€0,00","adjustment_amount_cents":0,"adjustment_amount_float":0,"formatted_adjustment_amount":"€0,00","gift_card_amount_cents":0,"gift_card_amount_float":0,"formatted_gift_card_amount":"€0,00","total_tax_amount_cents":0,"total_tax_amount_float":0,"formatted_total_tax_amount":"€0,00","subtotal_tax_amount_cents":0,"subtotal_tax_amount_float":0,"formatted_subtotal_tax_amount":"€0,00","shipping_tax_amount_cents":0,"shipping_tax_amount_float":0,"formatted_shipping_tax_amount":"€0,00","payment_method_tax_amount_cents":0,"payment_method_tax_amount_float":0,"formatted_payment_method_tax_amount":"€0,00","adjustment_tax_amount_cents":0,"adjustment_tax_amount_float":0,"formatted_adjustment_tax_amount":"€0,00","total_amount_cents":3500,"total_amount_float":35,"formatted_total_amount":"€35,00","total_taxable_amount_cents":3500,"total_taxable_amount_float":35,"formatted_total_taxable_amount":"€35,00","subtotal_taxable_amount_cents":3500,"subtotal_taxable_amount_float":35,"formatted_subtotal_taxable_amount":"€35,00","shipping_taxable_amount_cents":0,"shipping_taxable_amount_float":0,"formatted_shipping_taxable_amount":"€0,00","payment_method_taxable_amount_cents":0,"payment_method_taxable_amount_float":0,"formatted_payment_method_taxable_amount":"€0,00","adjustment_taxable_amount_cents":0,"adjustment_taxable_amount_float":0,"formatted_adjustment_taxable_amount":"€0,00","total_amount_with_taxes_cents":3500,"total_amount_with_taxes_float":35,"formatted_total_amount_with_taxes":"€35,00","fees_amount_cents":0,"fees_amount_float":0,"formatted_fees_amount":"€0,00","duty_amount_cents":null,"duty_amount_float":null,"formatted_duty_amount":null,"skus_count":1,"line_item_options_count":0,"shipments_count":0,"payment_source_details":null,"token":"7ade94195a1149504b7e70e437ff53b7","cart_url":null,"return_url":"https://test.co","terms_url":null,"privacy_url":null,"checkout_url":null,"placed_at":null,"approved_at":null,"cancelled_at":null,"payment_updated_at":null,"fulfillment_updated_at":null,"refreshed_at":null,"archived_at":null,"expires_at":null,"created_at":"2022-04-20T14:30:26.097Z","updated_at":"2022-04-20T14:30:26.285Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/market"}},"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/customer"}},"shipping_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipping_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipping_address"}},"billing_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/billing_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/billing_address"}},"available_payment_methods":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_payment_methods","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_payment_methods"}},"available_customer_payment_sources":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_customer_payment_sources","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_customer_payment_sources"}},"payment_method":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_method","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_method"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_source"}},"line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/line_items","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/line_items"},"data":[{"type":"line_items","id":"kVbztYWzEk"}]},"shipments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipments"}},"transactions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/transactions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/transactions"}},"authorizations":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/authorizations","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/authorizations"}},"captures":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/captures","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/captures"}},"voids":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/voids","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/voids"}},"refunds":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/refunds","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/refunds"}},"order_subscriptions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_subscriptions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_subscriptions"}},"order_copies":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_copies","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_copies"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},"included":[{"id":"kVbztYWzEk","type":"line_items","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk"},"attributes":{"sku_code":"BABYONBU000000E63E7412MX","bundle_code":null,"quantity":1,"currency_code":"EUR","unit_amount_cents":3500,"unit_amount_float":35,"formatted_unit_amount":"€35,00","options_amount_cents":0,"options_amount_float":0,"formatted_options_amount":"€0,00","discount_cents":0,"discount_float":0,"formatted_discount":"€0,00","total_amount_cents":3500,"total_amount_float":35,"formatted_total_amount":"€35,00","tax_amount_cents":0,"tax_amount_float":0,"formatted_tax_amount":"€0,00","name":"Darth Vader (12 Months)","image_url":"https://i.pinimg.com/736x/a5/32/de/a532de337eff9b1c1c4bfb8df73acea4--darth-vader-stencil-darth-vader-head.jpg?b=t","discount_breakdown":{},"tax_rate":0,"tax_breakdown":{},"item_type":"skus","created_at":"2022-04-20T14:30:26.265Z","updated_at":"2022-04-20T14:30:26.265Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/order"}},"item":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/item","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/item"},"data":{"type":"skus","id":"wZeDdSamqn"}},"line_item_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/line_item_options","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/line_item_options"},"data":[]},"shipment_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/shipment_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/shipment_line_items"}},"stock_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_line_items"}},"stock_transfers":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_transfers","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_transfers"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"wZeDdSamqn","type":"skus","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn"},"attributes":{"code":"BABYONBU000000E63E7412MX","name":"Black Baby Onesie Short Sleeve with Pink Logo (12 Months)","description":"Unit test sync description","image_url":"https://img.commercelayer.io/skus/BABYONBU000000E63E74.png?fm=jpg&q=90","pieces_per_pack":null,"weight":null,"unit_of_weight":"","hs_tariff_number":"","do_not_ship":false,"do_not_track":false,"inventory":{"available":true,"quantity":10016,"levels":[{"quantity":9932,"delivery_lead_times":[{"shipping_method":{"name":"Standard Shipping EU","reference":"","price_amount_cents":500,"free_over_amount_cents":2000,"formatted_price_amount":"€5,00","formatted_free_over_amount":"€20,00"},"min":{"hours":72,"days":3},"max":{"hours":120,"days":5}},{"shipping_method":{"name":"Express Delivery EU","reference":"","price_amount_cents":1200,"free_over_amount_cents":5000,"formatted_price_amount":"€12,00","formatted_free_over_amount":"€50,00"},"min":{"hours":48,"days":2},"max":{"hours":72,"days":3}}]},{"quantity":84,"delivery_lead_times":[{"shipping_method":{"name":"Standard Shipping EU","reference":"","price_amount_cents":500,"free_over_amount_cents":2000,"formatted_price_amount":"€5,00","formatted_free_over_amount":"€20,00"},"min":{"hours":168,"days":7},"max":{"hours":240,"days":10}},{"shipping_method":{"name":"Express Delivery EU","reference":"","price_amount_cents":1200,"free_over_amount_cents":5000,"formatted_price_amount":"€12,00","formatted_free_over_amount":"€50,00"},"min":{"hours":72,"days":3},"max":{"hours":96,"days":4}}]}]},"created_at":"2019-11-07T18:27:57.419Z","updated_at":"2021-05-06T17:10:32.463Z","reference":"BABYONBU000000E63E74","reference_origin":"","metadata":{}},"relationships":{"shipping_category":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/shipping_category","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/shipping_category"}},"prices":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/prices","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/prices"}},"stock_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/stock_items","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/stock_items"}},"delivery_lead_times":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/delivery_lead_times","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/delivery_lead_times"}},"sku_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/sku_options","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/sku_options"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}]}},{"10":{"data":{"id":"kVbztYWzEk","type":"line_items","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk"},"attributes":{"sku_code":"BABYONBU000000E63E7412MX","bundle_code":null,"quantity":2,"currency_code":"EUR","unit_amount_cents":3500,"unit_amount_float":35,"formatted_unit_amount":"€35,00","options_amount_cents":0,"options_amount_float":0,"formatted_options_amount":"€0,00","discount_cents":0,"discount_float":0,"formatted_discount":"€0,00","total_amount_cents":7000,"total_amount_float":70,"formatted_total_amount":"€70,00","tax_amount_cents":0,"tax_amount_float":0,"formatted_tax_amount":"€0,00","name":"Darth Vader (12 Months)","image_url":"https://i.pinimg.com/736x/a5/32/de/a532de337eff9b1c1c4bfb8df73acea4--darth-vader-stencil-darth-vader-head.jpg?b=t","discount_breakdown":{},"tax_rate":0,"tax_breakdown":{},"item_type":"skus","created_at":"2022-04-20T14:30:26.265Z","updated_at":"2022-04-20T14:30:26.829Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/order"}},"item":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/item","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/item"}},"line_item_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/line_item_options","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/line_item_options"}},"shipment_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/shipment_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/shipment_line_items"}},"stock_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_line_items"}},"stock_transfers":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_transfers","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_transfers"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}}},{"11":{"data":{"id":"qdyBhGZKYX","type":"orders","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX"},"attributes":{"number":2437984,"autorefresh":true,"status":"draft","payment_status":"unpaid","fulfillment_status":"unfulfilled","guest":true,"editable":true,"customer_email":null,"language_code":"en","currency_code":"EUR","tax_included":true,"tax_rate":null,"freight_taxable":null,"requires_billing_info":false,"country_code":null,"shipping_country_code_lock":null,"coupon_code":null,"gift_card_code":null,"gift_card_or_coupon_code":null,"subtotal_amount_cents":7000,"subtotal_amount_float":70,"formatted_subtotal_amount":"€70,00","shipping_amount_cents":0,"shipping_amount_float":0,"formatted_shipping_amount":"€0,00","payment_method_amount_cents":0,"payment_method_amount_float":0,"formatted_payment_method_amount":"€0,00","discount_amount_cents":0,"discount_amount_float":0,"formatted_discount_amount":"€0,00","adjustment_amount_cents":0,"adjustment_amount_float":0,"formatted_adjustment_amount":"€0,00","gift_card_amount_cents":0,"gift_card_amount_float":0,"formatted_gift_card_amount":"€0,00","total_tax_amount_cents":0,"total_tax_amount_float":0,"formatted_total_tax_amount":"€0,00","subtotal_tax_amount_cents":0,"subtotal_tax_amount_float":0,"formatted_subtotal_tax_amount":"€0,00","shipping_tax_amount_cents":0,"shipping_tax_amount_float":0,"formatted_shipping_tax_amount":"€0,00","payment_method_tax_amount_cents":0,"payment_method_tax_amount_float":0,"formatted_payment_method_tax_amount":"€0,00","adjustment_tax_amount_cents":0,"adjustment_tax_amount_float":0,"formatted_adjustment_tax_amount":"€0,00","total_amount_cents":7000,"total_amount_float":70,"formatted_total_amount":"€70,00","total_taxable_amount_cents":7000,"total_taxable_amount_float":70,"formatted_total_taxable_amount":"€70,00","subtotal_taxable_amount_cents":7000,"subtotal_taxable_amount_float":70,"formatted_subtotal_taxable_amount":"€70,00","shipping_taxable_amount_cents":0,"shipping_taxable_amount_float":0,"formatted_shipping_taxable_amount":"€0,00","payment_method_taxable_amount_cents":0,"payment_method_taxable_amount_float":0,"formatted_payment_method_taxable_amount":"€0,00","adjustment_taxable_amount_cents":0,"adjustment_taxable_amount_float":0,"formatted_adjustment_taxable_amount":"€0,00","total_amount_with_taxes_cents":7000,"total_amount_with_taxes_float":70,"formatted_total_amount_with_taxes":"€70,00","fees_amount_cents":0,"fees_amount_float":0,"formatted_fees_amount":"€0,00","duty_amount_cents":null,"duty_amount_float":null,"formatted_duty_amount":null,"skus_count":2,"line_item_options_count":0,"shipments_count":0,"payment_source_details":null,"token":"7ade94195a1149504b7e70e437ff53b7","cart_url":null,"return_url":"https://test.co","terms_url":null,"privacy_url":null,"checkout_url":null,"placed_at":null,"approved_at":null,"cancelled_at":null,"payment_updated_at":null,"fulfillment_updated_at":null,"refreshed_at":null,"archived_at":null,"expires_at":null,"created_at":"2022-04-20T14:30:26.097Z","updated_at":"2022-04-20T14:30:26.847Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/market"}},"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/customer"}},"shipping_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipping_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipping_address"}},"billing_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/billing_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/billing_address"}},"available_payment_methods":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_payment_methods","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_payment_methods"}},"available_customer_payment_sources":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_customer_payment_sources","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_customer_payment_sources"}},"payment_method":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_method","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_method"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_source"}},"line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/line_items","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/line_items"},"data":[{"type":"line_items","id":"kVbztYWzEk"}]},"shipments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipments"}},"transactions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/transactions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/transactions"}},"authorizations":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/authorizations","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/authorizations"}},"captures":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/captures","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/captures"}},"voids":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/voids","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/voids"}},"refunds":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/refunds","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/refunds"}},"order_subscriptions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_subscriptions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_subscriptions"}},"order_copies":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_copies","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_copies"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},"included":[{"id":"kVbztYWzEk","type":"line_items","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk"},"attributes":{"sku_code":"BABYONBU000000E63E7412MX","bundle_code":null,"quantity":2,"currency_code":"EUR","unit_amount_cents":3500,"unit_amount_float":35,"formatted_unit_amount":"€35,00","options_amount_cents":0,"options_amount_float":0,"formatted_options_amount":"€0,00","discount_cents":0,"discount_float":0,"formatted_discount":"€0,00","total_amount_cents":7000,"total_amount_float":70,"formatted_total_amount":"€70,00","tax_amount_cents":0,"tax_amount_float":0,"formatted_tax_amount":"€0,00","name":"Darth Vader (12 Months)","image_url":"https://i.pinimg.com/736x/a5/32/de/a532de337eff9b1c1c4bfb8df73acea4--darth-vader-stencil-darth-vader-head.jpg?b=t","discount_breakdown":{},"tax_rate":0,"tax_breakdown":{},"item_type":"skus","created_at":"2022-04-20T14:30:26.265Z","updated_at":"2022-04-20T14:30:26.829Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"order":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/order","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/order"}},"item":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/item","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/item"},"data":{"type":"skus","id":"wZeDdSamqn"}},"line_item_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/line_item_options","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/line_item_options"},"data":[]},"shipment_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/shipment_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/shipment_line_items"}},"stock_line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_line_items","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_line_items"}},"stock_transfers":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/relationships/stock_transfers","related":"https://the-blue-brand-3.commercelayer.co/api/line_items/kVbztYWzEk/stock_transfers"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"wZeDdSamqn","type":"skus","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn"},"attributes":{"code":"BABYONBU000000E63E7412MX","name":"Black Baby Onesie Short Sleeve with Pink Logo (12 Months)","description":"Unit test sync description","image_url":"https://img.commercelayer.io/skus/BABYONBU000000E63E74.png?fm=jpg&q=90","pieces_per_pack":null,"weight":null,"unit_of_weight":"","hs_tariff_number":"","do_not_ship":false,"do_not_track":false,"inventory":{"available":true,"quantity":10016,"levels":[{"quantity":9932,"delivery_lead_times":[{"shipping_method":{"name":"Standard Shipping EU","reference":"","price_amount_cents":500,"free_over_amount_cents":2000,"formatted_price_amount":"€5,00","formatted_free_over_amount":"€20,00"},"min":{"hours":72,"days":3},"max":{"hours":120,"days":5}},{"shipping_method":{"name":"Express Delivery EU","reference":"","price_amount_cents":1200,"free_over_amount_cents":5000,"formatted_price_amount":"€12,00","formatted_free_over_amount":"€50,00"},"min":{"hours":48,"days":2},"max":{"hours":72,"days":3}}]},{"quantity":84,"delivery_lead_times":[{"shipping_method":{"name":"Standard Shipping EU","reference":"","price_amount_cents":500,"free_over_amount_cents":2000,"formatted_price_amount":"€5,00","formatted_free_over_amount":"€20,00"},"min":{"hours":168,"days":7},"max":{"hours":240,"days":10}},{"shipping_method":{"name":"Express Delivery EU","reference":"","price_amount_cents":1200,"free_over_amount_cents":5000,"formatted_price_amount":"€12,00","formatted_free_over_amount":"€50,00"},"min":{"hours":72,"days":3},"max":{"hours":96,"days":4}}]}]},"created_at":"2019-11-07T18:27:57.419Z","updated_at":"2021-05-06T17:10:32.463Z","reference":"BABYONBU000000E63E74","reference_origin":"","metadata":{}},"relationships":{"shipping_category":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/shipping_category","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/shipping_category"}},"prices":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/prices","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/prices"}},"stock_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/stock_items","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/stock_items"}},"delivery_lead_times":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/delivery_lead_times","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/delivery_lead_times"}},"sku_options":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/sku_options","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/sku_options"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/skus/wZeDdSamqn/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}]}},{"13":{"data":{"id":"qdyBhGZKYX","type":"orders","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX"},"attributes":{"number":2437984,"autorefresh":true,"status":"draft","payment_status":"unpaid","fulfillment_status":"unfulfilled","guest":true,"editable":true,"customer_email":null,"language_code":"en","currency_code":"EUR","tax_included":true,"tax_rate":null,"freight_taxable":null,"requires_billing_info":false,"country_code":null,"shipping_country_code_lock":null,"coupon_code":null,"gift_card_code":null,"gift_card_or_coupon_code":null,"subtotal_amount_cents":0,"subtotal_amount_float":0,"formatted_subtotal_amount":"€0,00","shipping_amount_cents":0,"shipping_amount_float":0,"formatted_shipping_amount":"€0,00","payment_method_amount_cents":0,"payment_method_amount_float":0,"formatted_payment_method_amount":"€0,00","discount_amount_cents":0,"discount_amount_float":0,"formatted_discount_amount":"€0,00","adjustment_amount_cents":0,"adjustment_amount_float":0,"formatted_adjustment_amount":"€0,00","gift_card_amount_cents":0,"gift_card_amount_float":0,"formatted_gift_card_amount":"€0,00","total_tax_amount_cents":0,"total_tax_amount_float":0,"formatted_total_tax_amount":"€0,00","subtotal_tax_amount_cents":0,"subtotal_tax_amount_float":0,"formatted_subtotal_tax_amount":"€0,00","shipping_tax_amount_cents":0,"shipping_tax_amount_float":0,"formatted_shipping_tax_amount":"€0,00","payment_method_tax_amount_cents":0,"payment_method_tax_amount_float":0,"formatted_payment_method_tax_amount":"€0,00","adjustment_tax_amount_cents":0,"adjustment_tax_amount_float":0,"formatted_adjustment_tax_amount":"€0,00","total_amount_cents":0,"total_amount_float":0,"formatted_total_amount":"€0,00","total_taxable_amount_cents":0,"total_taxable_amount_float":0,"formatted_total_taxable_amount":"€0,00","subtotal_taxable_amount_cents":0,"subtotal_taxable_amount_float":0,"formatted_subtotal_taxable_amount":"€0,00","shipping_taxable_amount_cents":0,"shipping_taxable_amount_float":0,"formatted_shipping_taxable_amount":"€0,00","payment_method_taxable_amount_cents":0,"payment_method_taxable_amount_float":0,"formatted_payment_method_taxable_amount":"€0,00","adjustment_taxable_amount_cents":0,"adjustment_taxable_amount_float":0,"formatted_adjustment_taxable_amount":"€0,00","total_amount_with_taxes_cents":0,"total_amount_with_taxes_float":0,"formatted_total_amount_with_taxes":"€0,00","fees_amount_cents":0,"fees_amount_float":0,"formatted_fees_amount":"€0,00","duty_amount_cents":null,"duty_amount_float":null,"formatted_duty_amount":null,"skus_count":0,"line_item_options_count":0,"shipments_count":0,"payment_source_details":null,"token":"7ade94195a1149504b7e70e437ff53b7","cart_url":null,"return_url":"https://test.co","terms_url":null,"privacy_url":null,"checkout_url":null,"placed_at":null,"approved_at":null,"cancelled_at":null,"payment_updated_at":null,"fulfillment_updated_at":null,"refreshed_at":null,"archived_at":null,"expires_at":"2022-06-20T14:30:27.375Z","created_at":"2022-04-20T14:30:26.097Z","updated_at":"2022-04-20T14:30:27.392Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"market":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/market","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/market"}},"customer":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/customer","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/customer"}},"shipping_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipping_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipping_address"}},"billing_address":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/billing_address","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/billing_address"}},"available_payment_methods":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_payment_methods","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_payment_methods"}},"available_customer_payment_sources":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/available_customer_payment_sources","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/available_customer_payment_sources"}},"payment_method":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_method","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_method"}},"payment_source":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/payment_source","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/payment_source"}},"line_items":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/line_items","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/line_items"},"data":[]},"shipments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/shipments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/shipments"}},"transactions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/transactions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/transactions"}},"authorizations":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/authorizations","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/authorizations"}},"captures":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/captures","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/captures"}},"voids":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/voids","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/voids"}},"refunds":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/refunds","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/refunds"}},"order_subscriptions":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_subscriptions","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_subscriptions"}},"order_copies":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/order_copies","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/order_copies"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/orders/qdyBhGZKYX/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}}}] \ No newline at end of file diff --git a/packages/react-components/specs/e2e/mocks/prices.mock.json b/packages/react-components/specs/e2e/mocks/prices.mock.json deleted file mode 100644 index 222a6649..00000000 --- a/packages/react-components/specs/e2e/mocks/prices.mock.json +++ /dev/null @@ -1,2175 +0,0 @@ -[ - { - "0": { - "access_token": "eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJlbldveEZNT25wIiwic2x1ZyI6InRoZS1ibHVlLWJyYW5kLTMifSwiYXBwbGljYXRpb24iOnsiaWQiOiJkTW5XbWlnYXBiIiwia2luZCI6ImludGVncmF0aW9uIiwicHVibGljIjpmYWxzZX0sInRlc3QiOnRydWUsImV4cCI6MTY0NzQ2NDMzMiwibWFya2V0Ijp7ImlkIjpbIkJqeHJKaHltbE0iXSwicHJpY2VfbGlzdF9pZCI6IlZCeVZwQ2d2a2ciLCJzdG9ja19sb2NhdGlvbl9pZHMiOlsieEdYQlh1ckRNRSIsImRNcVh5dVZWa04iXSwiZ2VvY29kZXJfaWQiOm51bGwsImFsbG93c19leHRlcm5hbF9wcmljZXMiOmZhbHNlfSwicmFuZCI6MC43NDk1Mzk1OTIxMjkyMTQyfQ.RsKq7nZo6iRRyaJr_YCGVXAUzFcxwuYZtCR3X3-jvEFFF0y0jX2ceWRZ2YZdUvQG8n776leJW8xxaSHXR-yrZQ", - "token_type": "Bearer", - "expires_in": 7200, - "scope": "market:58", - "created_at": 1647457132 - } - }, - { - "1": { - "data": [ - { - "id": "MadKYUlJrg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBU000000E63E7412MX", - "amount_cents": 3500, - "amount_float": 35, - "formatted_amount": "€35,00", - "compare_at_amount_cents": 4500, - "compare_at_amount_float": 45, - "formatted_compare_at_amount": "€45,00", - "created_at": "2019-11-07T18:27:57.434Z", - "updated_at": "2021-11-24T09:46:57.129Z", - "reference": "", - "reference_origin": "", - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "LgqwEUkxZp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBU000000FFFFFF12MX", - "amount_cents": 2900, - "amount_float": 29, - "formatted_amount": "€29,00", - "compare_at_amount_cents": 3770, - "compare_at_amount_float": 37.7, - "formatted_compare_at_amount": "€37,70", - "created_at": "2019-11-07T18:27:57.654Z", - "updated_at": "2019-11-07T18:27:57.654Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "mpMJQUzEQA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBUFFFFFF00000012MX", - "amount_cents": 2900, - "amount_float": 29, - "formatted_amount": "€29,00", - "compare_at_amount_cents": 3770, - "compare_at_amount_float": 37.7, - "formatted_compare_at_amount": "€37,70", - "created_at": "2019-11-07T18:27:58.053Z", - "updated_at": "2019-11-07T18:27:58.053Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "maJJmUrZNa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBUFFFFFFE63E7412MX", - "amount_cents": 2900, - "amount_float": 29, - "formatted_amount": "€29,00", - "compare_at_amount_cents": 3770, - "compare_at_amount_float": 37.7, - "formatted_compare_at_amount": "€37,70", - "created_at": "2019-11-07T18:27:58.223Z", - "updated_at": "2019-11-07T18:27:58.223Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "BAjKQURkQg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "CANVASAU000000FFFFFF1824", - "amount_cents": 9900, - "amount_float": 99, - "formatted_amount": "€99,00", - "compare_at_amount_cents": 12870, - "compare_at_amount_float": 128.7, - "formatted_compare_at_amount": "€128,70", - "created_at": "2019-11-07T18:27:58.272Z", - "updated_at": "2019-11-07T18:27:58.272Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 16, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=5", - "next": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=2&page%5Bsize%5D=5", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=5" - } - } - }, - { - "2": { - "data": [ - { - "id": "MadKYUlJrg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBU000000E63E7412MX", - "amount_cents": 3500, - "amount_float": 35, - "formatted_amount": "€35,00", - "compare_at_amount_cents": 4500, - "compare_at_amount_float": 45, - "formatted_compare_at_amount": "€45,00", - "created_at": "2019-11-07T18:27:57.434Z", - "updated_at": "2021-11-24T09:46:57.129Z", - "reference": "", - "reference_origin": "", - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "zaORmUoqQg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "BABYONBU000000E63E7412MX", - "amount_cents": 3480, - "amount_float": 34.8, - "formatted_amount": "$34.80", - "compare_at_amount_cents": 4524, - "compare_at_amount_float": 45.24, - "formatted_compare_at_amount": "$45.24", - "created_at": "2019-11-07T18:27:57.462Z", - "updated_at": "2019-11-07T18:27:57.462Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "LgqwEUkxZp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBU000000FFFFFF12MX", - "amount_cents": 2900, - "amount_float": 29, - "formatted_amount": "€29,00", - "compare_at_amount_cents": 3770, - "compare_at_amount_float": 37.7, - "formatted_compare_at_amount": "€37,70", - "created_at": "2019-11-07T18:27:57.654Z", - "updated_at": "2019-11-07T18:27:57.654Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LgqwEUkxZp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "GpVOMULzMA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/GpVOMULzMA" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "BABYONBU000000FFFFFF12MX", - "amount_cents": 3480, - "amount_float": 34.8, - "formatted_amount": "$34.80", - "compare_at_amount_cents": 4524, - "compare_at_amount_float": 45.24, - "formatted_compare_at_amount": "$45.24", - "created_at": "2019-11-07T18:27:57.721Z", - "updated_at": "2019-11-07T18:27:57.721Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/GpVOMULzMA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/GpVOMULzMA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/GpVOMULzMA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/GpVOMULzMA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/GpVOMULzMA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/GpVOMULzMA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "mpMJQUzEQA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBUFFFFFF00000012MX", - "amount_cents": 2900, - "amount_float": 29, - "formatted_amount": "€29,00", - "compare_at_amount_cents": 3770, - "compare_at_amount_float": 37.7, - "formatted_compare_at_amount": "€37,70", - "created_at": "2019-11-07T18:27:58.053Z", - "updated_at": "2019-11-07T18:27:58.053Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/mpMJQUzEQA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "QplnlUzGWg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QplnlUzGWg" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "BABYONBUFFFFFF00000012MX", - "amount_cents": 3480, - "amount_float": 34.8, - "formatted_amount": "$34.80", - "compare_at_amount_cents": 4524, - "compare_at_amount_float": 45.24, - "formatted_compare_at_amount": "$45.24", - "created_at": "2019-11-07T18:27:58.080Z", - "updated_at": "2019-11-07T18:27:58.080Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QplnlUzGWg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QplnlUzGWg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QplnlUzGWg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QplnlUzGWg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QplnlUzGWg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QplnlUzGWg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "maJJmUrZNa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "BABYONBUFFFFFFE63E7412MX", - "amount_cents": 2900, - "amount_float": 29, - "formatted_amount": "€29,00", - "compare_at_amount_cents": 3770, - "compare_at_amount_float": 37.7, - "formatted_compare_at_amount": "€37,70", - "created_at": "2019-11-07T18:27:58.223Z", - "updated_at": "2019-11-07T18:27:58.223Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/maJJmUrZNa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "vpNzPUZWyp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vpNzPUZWyp" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "BABYONBUFFFFFFE63E7412MX", - "amount_cents": 3480, - "amount_float": 34.8, - "formatted_amount": "$34.80", - "compare_at_amount_cents": 4524, - "compare_at_amount_float": 45.24, - "formatted_compare_at_amount": "$45.24", - "created_at": "2019-11-07T18:27:58.248Z", - "updated_at": "2019-11-07T18:27:58.248Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vpNzPUZWyp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vpNzPUZWyp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vpNzPUZWyp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vpNzPUZWyp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vpNzPUZWyp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vpNzPUZWyp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "BAjKQURkQg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "CANVASAU000000FFFFFF1824", - "amount_cents": 9900, - "amount_float": 99, - "formatted_amount": "€99,00", - "compare_at_amount_cents": 12870, - "compare_at_amount_float": 128.7, - "formatted_compare_at_amount": "€128,70", - "created_at": "2019-11-07T18:27:58.272Z", - "updated_at": "2019-11-07T18:27:58.272Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BAjKQURkQg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "LaKeQUvbYA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LaKeQUvbYA" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "CANVASAU000000FFFFFF1824", - "amount_cents": 11880, - "amount_float": 118.8, - "formatted_amount": "$118.80", - "compare_at_amount_cents": 15444, - "compare_at_amount_float": 154.44, - "formatted_compare_at_amount": "$154.44", - "created_at": "2019-11-07T18:27:58.297Z", - "updated_at": "2019-11-07T18:27:58.297Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LaKeQUvbYA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LaKeQUvbYA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LaKeQUvbYA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LaKeQUvbYA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LaKeQUvbYA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LaKeQUvbYA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 32, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=10", - "next": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=2&page%5Bsize%5D=10", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=10" - } - } - }, - { - "3": { - "data": [ - { - "id": "ygYwMUZrzp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "CANVASAUE63E74FFFFFF1824", - "amount_cents": 9900, - "amount_float": 99, - "formatted_amount": "€99,00", - "compare_at_amount_cents": 12870, - "compare_at_amount_float": 128.7, - "formatted_compare_at_amount": "€128,70", - "created_at": "2019-11-07T18:27:58.344Z", - "updated_at": "2019-11-07T18:27:58.344Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "QpQVmUzjQA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMU000000E63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.444Z", - "updated_at": "2019-11-07T18:27:58.444Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "LADPrUdWlA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMU000000FFFFFFXXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.541Z", - "updated_at": "2019-11-07T18:27:58.541Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "EgoPlUKxQa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMUB7B7B7E63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.590Z", - "updated_at": "2019-11-07T18:27:58.590Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "BanOlURxxp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMUB7B7B7000000XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.637Z", - "updated_at": "2019-11-07T18:27:58.637Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 16, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=5", - "prev": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=5", - "next": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=3&page%5Bsize%5D=5", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=5" - } - } - }, - { - "4": { - "data": [ - { - "id": "ygYwMUZrzp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "CANVASAUE63E74FFFFFF1824", - "amount_cents": 9900, - "amount_float": 99, - "formatted_amount": "€99,00", - "compare_at_amount_cents": 12870, - "compare_at_amount_float": 128.7, - "formatted_compare_at_amount": "€128,70", - "created_at": "2019-11-07T18:27:58.344Z", - "updated_at": "2019-11-07T18:27:58.344Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/ygYwMUZrzp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "oAWQNUezVa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/oAWQNUezVa" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "CANVASAUE63E74FFFFFF1824", - "amount_cents": 11880, - "amount_float": 118.8, - "formatted_amount": "$118.80", - "compare_at_amount_cents": 15444, - "compare_at_amount_float": 154.44, - "formatted_compare_at_amount": "$154.44", - "created_at": "2019-11-07T18:27:58.411Z", - "updated_at": "2019-11-07T18:27:58.411Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/oAWQNUezVa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/oAWQNUezVa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/oAWQNUezVa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/oAWQNUezVa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/oAWQNUezVa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/oAWQNUezVa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "QpQVmUzjQA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMU000000E63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.444Z", - "updated_at": "2019-11-07T18:27:58.444Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/QpQVmUzjQA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "zpXXxUvGJp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zpXXxUvGJp" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBEAMU000000E63E74XXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.508Z", - "updated_at": "2019-11-07T18:27:58.508Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zpXXxUvGJp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zpXXxUvGJp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zpXXxUvGJp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zpXXxUvGJp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zpXXxUvGJp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zpXXxUvGJp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "LADPrUdWlA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMU000000FFFFFFXXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.541Z", - "updated_at": "2019-11-07T18:27:58.541Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/LADPrUdWlA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "RgxEWUDdLa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/RgxEWUDdLa" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBEAMU000000FFFFFFXXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.566Z", - "updated_at": "2019-11-07T18:27:58.566Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/RgxEWUDdLa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/RgxEWUDdLa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/RgxEWUDdLa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/RgxEWUDdLa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/RgxEWUDdLa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/RgxEWUDdLa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "EgoPlUKxQa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMUB7B7B7E63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.590Z", - "updated_at": "2019-11-07T18:27:58.590Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/EgoPlUKxQa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "XpPOmUBLza", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/XpPOmUBLza" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBEAMUB7B7B7E63E74XXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.612Z", - "updated_at": "2019-11-07T18:27:58.612Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/XpPOmUBLza/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/XpPOmUBLza/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/XpPOmUBLza/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/XpPOmUBLza/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/XpPOmUBLza/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/XpPOmUBLza/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "BanOlURxxp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBEAMUB7B7B7000000XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.637Z", - "updated_at": "2019-11-07T18:27:58.637Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/BanOlURxxp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "daEEoUvQEa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/daEEoUvQEa" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBEAMUB7B7B7000000XXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.661Z", - "updated_at": "2019-11-07T18:27:58.661Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/daEEoUvQEa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/daEEoUvQEa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/daEEoUvQEa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/daEEoUvQEa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/daEEoUvQEa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/daEEoUvQEa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 32, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=10", - "prev": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=10", - "next": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=3&page%5Bsize%5D=10", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=10" - } - } - }, - { - "5": { - "data": [ - { - "id": "nAzOWUVvdg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMU000000E63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.685Z", - "updated_at": "2019-11-07T18:27:58.685Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "MAdKYUlMrp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMU000000FFFFFFXXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.736Z", - "updated_at": "2019-11-07T18:27:58.736Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "eAbDKUoGba", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMUFFFFFF000000XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.787Z", - "updated_at": "2019-11-07T18:27:58.787Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "qpwYzUVERg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMUFFFFFFE63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.836Z", - "updated_at": "2019-11-07T18:27:58.836Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "lpeNYUVYlA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "LSLEEVMM000000E63E74LXXX", - "amount_cents": 4900, - "amount_float": 49, - "formatted_amount": "€49,00", - "compare_at_amount_cents": 6370, - "compare_at_amount_float": 63.7, - "formatted_compare_at_amount": "€63,70", - "created_at": "2019-11-07T18:27:58.982Z", - "updated_at": "2019-11-07T18:27:58.982Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 16, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=5", - "prev": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=2&page%5Bsize%5D=5", - "next": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=5", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=5" - } - } - }, - { - "6": { - "data": [ - { - "id": "nAzOWUVvdg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMU000000E63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.685Z", - "updated_at": "2019-11-07T18:27:58.685Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/nAzOWUVvdg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "yAydWUQYmp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/yAydWUQYmp" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBSBMU000000E63E74XXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.708Z", - "updated_at": "2019-11-07T18:27:58.708Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/yAydWUQYmp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/yAydWUQYmp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/yAydWUQYmp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/yAydWUQYmp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/yAydWUQYmp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/yAydWUQYmp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "MAdKYUlMrp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMU000000FFFFFFXXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.736Z", - "updated_at": "2019-11-07T18:27:58.736Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/MAdKYUlMrp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "zAORmUoOQp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zAORmUoOQp" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBSBMU000000FFFFFFXXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.762Z", - "updated_at": "2019-11-07T18:27:58.762Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zAORmUoOQp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zAORmUoOQp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zAORmUoOQp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zAORmUoOQp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/zAORmUoOQp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/zAORmUoOQp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "eAbDKUoGba", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMUFFFFFF000000XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.787Z", - "updated_at": "2019-11-07T18:27:58.787Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/eAbDKUoGba/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "OgvKWUvEma", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/OgvKWUvEma" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBSBMUFFFFFF000000XXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.812Z", - "updated_at": "2019-11-07T18:27:58.812Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/OgvKWUvEma/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/OgvKWUvEma/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/OgvKWUvEma/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/OgvKWUvEma/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/OgvKWUvEma/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/OgvKWUvEma/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "qpwYzUVERg", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "HATBSBMUFFFFFFE63E74XXXX", - "amount_cents": 2500, - "amount_float": 25, - "formatted_amount": "€25,00", - "compare_at_amount_cents": 3250, - "compare_at_amount_float": 32.5, - "formatted_compare_at_amount": "€32,50", - "created_at": "2019-11-07T18:27:58.836Z", - "updated_at": "2019-11-07T18:27:58.836Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/qpwYzUVERg/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "lpBklUmlvA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpBklUmlvA" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "HATBSBMUFFFFFFE63E74XXXX", - "amount_cents": 3000, - "amount_float": 30, - "formatted_amount": "$30.00", - "compare_at_amount_cents": 3900, - "compare_at_amount_float": 39, - "formatted_compare_at_amount": "$39.00", - "created_at": "2019-11-07T18:27:58.860Z", - "updated_at": "2019-11-07T18:27:58.860Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpBklUmlvA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpBklUmlvA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpBklUmlvA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpBklUmlvA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpBklUmlvA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpBklUmlvA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "lpeNYUVYlA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "LSLEEVMM000000E63E74LXXX", - "amount_cents": 4900, - "amount_float": 49, - "formatted_amount": "€49,00", - "compare_at_amount_cents": 6370, - "compare_at_amount_float": 63.7, - "formatted_compare_at_amount": "€63,70", - "created_at": "2019-11-07T18:27:58.982Z", - "updated_at": "2019-11-07T18:27:58.982Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/lpeNYUVYlA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "laGqWUBkBA", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/laGqWUBkBA" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "LSLEEVMM000000E63E74LXXX", - "amount_cents": 5880, - "amount_float": 58.8, - "formatted_amount": "$58.80", - "compare_at_amount_cents": 7644, - "compare_at_amount_float": 76.44, - "formatted_compare_at_amount": "$76.44", - "created_at": "2019-11-07T18:27:59.010Z", - "updated_at": "2019-11-07T18:27:59.010Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/laGqWUBkBA/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/laGqWUBkBA/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/laGqWUBkBA/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/laGqWUBkBA/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/laGqWUBkBA/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/laGqWUBkBA/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 32, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=10", - "prev": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=2&page%5Bsize%5D=10", - "next": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=10", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=10" - } - } - }, - { - "7": { - "data": [ - { - "id": "vgmnyULqOa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "LSLEEVMM000000FFFFFFLXXX", - "amount_cents": 4900, - "amount_float": 49, - "formatted_amount": "€49,00", - "compare_at_amount_cents": 6370, - "compare_at_amount_float": 63.7, - "formatted_compare_at_amount": "€63,70", - "created_at": "2019-11-07T18:27:59.202Z", - "updated_at": "2019-11-07T18:27:59.202Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 16, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=5", - "prev": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=3&page%5Bsize%5D=5", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bprice_list_currency_code_eq%5D=EUR&filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=5" - } - } - }, - { - "8": { - "data": [ - { - "id": "vgmnyULqOa", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa" - }, - "attributes": { - "currency_code": "EUR", - "sku_code": "LSLEEVMM000000FFFFFFLXXX", - "amount_cents": 4900, - "amount_float": 49, - "formatted_amount": "€49,00", - "compare_at_amount_cents": 6370, - "compare_at_amount_float": 63.7, - "formatted_compare_at_amount": "€63,70", - "created_at": "2019-11-07T18:27:59.202Z", - "updated_at": "2019-11-07T18:27:59.202Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/vgmnyULqOa/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - }, - { - "id": "npZBYURNZp", - "type": "prices", - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/npZBYURNZp" - }, - "attributes": { - "currency_code": "USD", - "sku_code": "LSLEEVMM000000FFFFFFLXXX", - "amount_cents": 5880, - "amount_float": 58.8, - "formatted_amount": "$58.80", - "compare_at_amount_cents": 7644, - "compare_at_amount_float": 76.44, - "formatted_compare_at_amount": "$76.44", - "created_at": "2019-11-07T18:27:59.245Z", - "updated_at": "2019-11-07T18:27:59.245Z", - "reference": null, - "reference_origin": null, - "metadata": {} - }, - "relationships": { - "price_list": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/npZBYURNZp/relationships/price_list", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/npZBYURNZp/price_list" - } - }, - "sku": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/npZBYURNZp/relationships/sku", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/npZBYURNZp/sku" - } - }, - "attachments": { - "links": { - "self": "https://the-blue-brand-3.commercelayer.co/api/prices/npZBYURNZp/relationships/attachments", - "related": "https://the-blue-brand-3.commercelayer.co/api/prices/npZBYURNZp/attachments" - } - } - }, - "meta": { "mode": "test", "organization_id": "enWoxFMOnp" } - } - ], - "meta": { "record_count": 32, "page_count": 4 }, - "links": { - "first": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=1&page%5Bsize%5D=10", - "prev": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=3&page%5Bsize%5D=10", - "last": "https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX%2CBABYONBU000000FFFFFF12MX%2CBABYONBUFFFFFF00000012MX%2CBABYONBUFFFFFFE63E7412MX%2CCANVASAU000000FFFFFF1824%2CCANVASAUE63E74FFFFFF1824%2CHATBEAMU000000E63E74XXXX%2CHATBEAMU000000FFFFFFXXXX%2CHATBEAMUB7B7B7000000XXXX%2CHATBEAMUB7B7B7E63E74XXXX%2CHATBSBMU000000E63E74XXXX%2CHATBSBMU000000FFFFFFXXXX%2CHATBSBMUFFFFFF000000XXXX%2CHATBSBMUFFFFFFE63E74XXXX%2CLSLEEVMM000000E63E74LXXX%2CLSLEEVMM000000FFFFFFLXXX&page%5Bnumber%5D=4&page%5Bsize%5D=10" - } - } - } -] diff --git a/packages/react-components/specs/e2e/mocks/single-price.mock.json b/packages/react-components/specs/e2e/mocks/single-price.mock.json deleted file mode 100644 index 803e09f9..00000000 --- a/packages/react-components/specs/e2e/mocks/single-price.mock.json +++ /dev/null @@ -1 +0,0 @@ -[{"0":{"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJlbldveEZNT25wIiwic2x1ZyI6InRoZS1ibHVlLWJyYW5kLTMifSwiYXBwbGljYXRpb24iOnsiaWQiOiJkTW5XbWlnYXBiIiwia2luZCI6ImludGVncmF0aW9uIiwicHVibGljIjpmYWxzZX0sInRlc3QiOnRydWUsImV4cCI6MTY0NzUzNjc5MywibWFya2V0Ijp7ImlkIjpbIkJqeHJKaHltbE0iXSwicHJpY2VfbGlzdF9pZCI6IlZCeVZwQ2d2a2ciLCJzdG9ja19sb2NhdGlvbl9pZHMiOlsieEdYQlh1ckRNRSIsImRNcVh5dVZWa04iXSwiZ2VvY29kZXJfaWQiOm51bGwsImFsbG93c19leHRlcm5hbF9wcmljZXMiOmZhbHNlfSwicmFuZCI6MC42NDU0ODk3ODkxNjgzNzE2fQ.AQo9m3F5laXkR1uYepshjqzkvvPYfJCuAfotvU7qVEHj-MiZ-MML7tm2bNKvC0ysQvARZrojbqM7kPYfugc74Q","token_type":"Bearer","expires_in":7198,"scope":"market:58","created_at":1647529593}},{"1":{"data":[{"id":"MadKYUlJrg","type":"prices","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg"},"attributes":{"currency_code":"EUR","sku_code":"BABYONBU000000E63E7412MX","amount_cents":3500,"amount_float":35,"formatted_amount":"€35,00","compare_at_amount_cents":4500,"compare_at_amount_float":45,"formatted_compare_at_amount":"€45,00","created_at":"2019-11-07T18:27:57.434Z","updated_at":"2021-11-24T09:46:57.129Z","reference":"","reference_origin":"","metadata":{}},"relationships":{"price_list":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/price_list","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/price_list"}},"sku":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/sku","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/sku"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/prices/MadKYUlJrg/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}},{"id":"zaORmUoqQg","type":"prices","links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg"},"attributes":{"currency_code":"USD","sku_code":"BABYONBU000000E63E7412MX","amount_cents":3480,"amount_float":34.8,"formatted_amount":"$34.80","compare_at_amount_cents":4524,"compare_at_amount_float":45.24,"formatted_compare_at_amount":"$45.24","created_at":"2019-11-07T18:27:57.462Z","updated_at":"2019-11-07T18:27:57.462Z","reference":null,"reference_origin":null,"metadata":{}},"relationships":{"price_list":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/relationships/price_list","related":"https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/price_list"}},"sku":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/relationships/sku","related":"https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/sku"}},"attachments":{"links":{"self":"https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/relationships/attachments","related":"https://the-blue-brand-3.commercelayer.co/api/prices/zaORmUoqQg/attachments"}}},"meta":{"mode":"test","organization_id":"enWoxFMOnp"}}],"meta":{"record_count":2,"page_count":1},"links":{"first":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX&page%5Bnumber%5D=1&page%5Bsize%5D=20","last":"https://the-blue-brand-3.commercelayer.co/api/prices?filter%5Bq%5D%5Bsku_code_in%5D=BABYONBU000000E63E7412MX&page%5Bnumber%5D=1&page%5Bsize%5D=20"}}}] \ No newline at end of file diff --git a/packages/react-components/specs/e2e/models/OrderPage.ts b/packages/react-components/specs/e2e/models/OrderPage.ts deleted file mode 100644 index adb148e2..00000000 --- a/packages/react-components/specs/e2e/models/OrderPage.ts +++ /dev/null @@ -1,35 +0,0 @@ -import DevPage from './Page' -import { expect } from '@playwright/test' - -export class OrderPage extends DevPage { - async clickCartLinkButton() { - const button = this.page.locator('[data-test=cart-link]') - await button.waitFor({ state: 'visible' }) - await button.click() - } - async addItemToCart(code: string) { - const selector = this.page.locator(`[data-test=variant-selector]`) - await selector.waitFor({ state: 'visible' }) - await selector.waitFor({ state: 'attached' }) - await selector.selectOption({ value: code }) - const button = this.page.locator('[data-test=add-to-cart-button]') - await button.waitFor({ state: 'visible' }) - await button.click() - } - async checkCurrentUrl(value: string) { - await this.page.waitForURL( - (url) => - url.toJSON().includes(value) && !url.toJSON().includes('localhost') - ) - await expect(this.page.url()).toMatch(/commercelayer.app\//gm) - } - async checkText(selector: string, text: string) { - const button = this.page.locator(selector) - await button.waitFor({ state: 'visible' }) - await expect(button).toContainText(text) - } - async checkItemsQuantity(quantity: number) { - const itemsCounter = this.page.locator('[data-test=items-count]') - await expect(itemsCounter).toContainText(quantity.toString()) - } -} diff --git a/packages/react-components/specs/e2e/models/Page.ts b/packages/react-components/specs/e2e/models/Page.ts deleted file mode 100644 index ea6ad92c..00000000 --- a/packages/react-components/specs/e2e/models/Page.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Page } from '@playwright/test' - -type PathReference = { - checkout: { - page: [ - { path: 'index' }, - { path: 'addresses' }, - { path: 'payments' }, - { path: 'giftcard-or-coupon-code' }, - { path: 'shipments' } - ] - } - order: { - page: [ - { path: 'buy-now-mode' }, - { path: 'add-item-to-hosted-cart' }, - { path: 'order-with-cart-link-button' }, - { path: 'order-with-cart-link-button?reactNodeLabel=true' }, - { - path: 'add-item-to-hosted-cart?hostedCartUrl=true' - } - ] - } -} - -type PathPages = { - [K in keyof PathReference]: `${K}/${PathReference[K]['page'][number]['path']}` -}[keyof PathReference] - -export default class DevPage { - readonly page: Page - constructor(page: Page) { - this.page = page - } - async goto(url: PathPages) { - await this.page.goto(url) - } - async goBack() { - await this.page.goBack() - } - async isFinished(response, url) { - return ( - response.url().includes(url) && - response.status() === 200 && - (await response.json()).response === 'Completed' - ) - } -} diff --git a/packages/react-components/specs/e2e/models/index.ts b/packages/react-components/specs/e2e/models/index.ts deleted file mode 100644 index 61c8c60d..00000000 --- a/packages/react-components/specs/e2e/models/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './OrderPage' diff --git a/packages/react-components/specs/e2e/order.spec.ts b/packages/react-components/specs/e2e/order.spec.ts deleted file mode 100644 index 739ee3b3..00000000 --- a/packages/react-components/specs/e2e/order.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { test, expect } from './baseFixtures' -import path from 'path' -import { waitForResponse } from './utils/response' -const endpointURL = `order` -test.describe('Orders', () => { - const timeout = 500 - test('Basic order', async ({ page, browser }) => { - await page.coverage.startJSCoverage() - await page.goto(endpointURL) - await Promise.all([ - page.waitForResponse(waitForResponse('/api/prices')), - page.waitForResponse(waitForResponse('/api/skus')), - ]) - const priceItem = await page.textContent('[data-test=price]') - const comparePriceItem = await page.textContent( - ':right-of(:nth-match([data-test=price], 1))' - ) - let itemsCount = await page.textContent('[data-test=items-count]') - let lineItemsEmpty = await page.textContent('[data-test=line-items-empty]') - await expect(itemsCount).toBe('0') - await expect(lineItemsEmpty).toBe('Your shopping bag is empty') - await expect(priceItem).toBe('€29,00') - await expect(comparePriceItem).toBe('€37,70') - await Promise.all([ - await page.selectOption('[data-test=variant-selector]', { - label: '6 months', - }), - await page.waitForResponse(waitForResponse('/api/skus')), - await page.waitForTimeout(timeout), - ]) - let availability = await page.textContent( - '[data-test=availability-template]' - ) - await expect(availability).toBe('Out of stock') - const buttonDisabled = await page.waitForSelector( - '[data-test=add-to-cart-button]' - ) - const disabled = await buttonDisabled.isDisabled() - await expect(disabled).toBe(true) - await Promise.all([ - await page.selectOption('[data-test=variant-selector]', { - label: '12 months', - }), - await page.waitForResponse(waitForResponse('/api/skus')), - await page.waitForTimeout(timeout), - ]) - availability = await page.textContent('[data-test=availability-template]') - await expect(availability).toBe( - 'Available in 3 - 5 days with Standard Shipping EU (€5,00)' - ) - await Promise.all([ - await page.click('[data-test=add-to-cart-button]'), - await page.waitForResponse(waitForResponse('api/orders')), - await page.waitForResponse(waitForResponse('api/line_items')), - await page.waitForResponse(waitForResponse('api/orders')), - await page.waitForTimeout(timeout), - ]) - await page.waitForLoadState('domcontentloaded') - itemsCount = await page.textContent('[data-test=items-count]') - let subTotalAmount = await page.textContent('[data-test=subtotal-amount]') - let totalAmount = await page.textContent('[data-test=total-amount]') - const discountAmount = await page.textContent('[data-test=discount-amount]') - await expect(itemsCount).toBe('1') - await expect(subTotalAmount).toBe('€35,00') - await expect(discountAmount).toBe('-€7,00') - await expect(totalAmount).toBe('€28,00') - await Promise.all([ - await page.selectOption('[data-test=line-item-quantity]', { - value: '2', - }), - await page.waitForResponse(waitForResponse('/api/line_items')), - await page.waitForResponse(waitForResponse('/api/orders')), - await page.waitForTimeout(timeout), - ]) - itemsCount = await page.textContent('[data-test=items-count]') - subTotalAmount = await page.textContent('[data-test=subtotal-amount]') - totalAmount = await page.textContent('[data-test=total-amount]') - await Promise.all([ - await expect(itemsCount).toBe('2'), - await expect(subTotalAmount).toBe('€70,00'), - await expect(totalAmount).toBe('€56,00'), - ]) - await Promise.all([ - await page.click('[data-test=line-item-remove]'), - await page.waitForResponse(waitForResponse('api/line_items')), - await page.waitForResponse(waitForResponse('api/orders')), - await page.waitForTimeout(timeout), - ]) - itemsCount = await page.textContent('[data-test=items-count]') - subTotalAmount = await page.textContent('[data-test=subtotal-amount]') - totalAmount = await page.textContent('[data-test=total-amount]') - lineItemsEmpty = await page.textContent('[data-test=line-items-empty]') - await expect(itemsCount).toBe('0') - await expect(lineItemsEmpty).toBe('Your shopping bag is empty') - await expect(subTotalAmount).toBe('€0,00') - await expect(totalAmount).toBe('€0,00') - await page.screenshot({ - path: path.join(__dirname, 'screenshots', 'basic_order.jpg'), - }) - await page.coverage.stopJSCoverage() - await browser.close() - }) -}) diff --git a/packages/react-components/specs/e2e/order/add-item-to-hosted-cart.spec.ts b/packages/react-components/specs/e2e/order/add-item-to-hosted-cart.spec.ts deleted file mode 100644 index 7397d93a..00000000 --- a/packages/react-components/specs/e2e/order/add-item-to-hosted-cart.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { test } from '../baseFixtures' -import { OrderPage } from '../models' - -test.describe('Add item to hosted cart', () => { - test('Default hosted cart url', async ({ page }) => { - const order = new OrderPage(page) - await order.goto('order/add-item-to-hosted-cart') - await order.addItemToCart('BABYONBU000000E63E7412MX') - await order.checkCurrentUrl('cart') - }) - test('Custom hosted cart url', async ({ page }) => { - const order = new OrderPage(page) - await order.goto('order/add-item-to-hosted-cart?hostedCartUrl=true') - await order.addItemToCart('BABYONBU000000E63E7412MX') - }) -}) diff --git a/packages/react-components/specs/e2e/order/buy-now-mode.spec.ts b/packages/react-components/specs/e2e/order/buy-now-mode.spec.ts deleted file mode 100644 index fd5c89aa..00000000 --- a/packages/react-components/specs/e2e/order/buy-now-mode.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { test } from '../baseFixtures' -import { OrderPage } from '../models' - -test.describe('Buy now mode', () => { - test('Add item', async ({ page }) => { - const order = new OrderPage(page) - await order.goto('order/buy-now-mode') - await order.addItemToCart('BABYONBU000000E63E7412MX') - await order.checkCurrentUrl('checkout') - }) - test('Add and check if the line item is always one', async ({ page }) => { - const order = new OrderPage(page) - await order.goto('order/buy-now-mode') - await order.addItemToCart('BABYONBU000000E63E7412MX') - await order.checkCurrentUrl('checkout') - await order.goBack() - await order.checkItemsQuantity(1) - await order.addItemToCart('BABYONBU000000E63E7412MX') - await order.checkCurrentUrl('checkout') - await order.goBack() - await order.checkItemsQuantity(1) - }) -}) diff --git a/packages/react-components/specs/e2e/order/order-with-cart-link.spec.ts b/packages/react-components/specs/e2e/order/order-with-cart-link.spec.ts deleted file mode 100644 index 89e061ac..00000000 --- a/packages/react-components/specs/e2e/order/order-with-cart-link.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Page } from '@playwright/test' -import { test } from '../baseFixtures' -import { OrderPage } from '../models/' - -test.describe('Order with cart link', () => { - let page: Page - test.beforeAll(async ({ browser }) => { - page = await browser.newPage() - }) - test.afterAll(async () => { - await page.close() - }) - test('Click cart link without an order', async () => { - const order = new OrderPage(page) - await order.goto('order/order-with-cart-link-button') - await order.checkText('[data-test=cart-link]', 'Cart link string') - await order.clickCartLinkButton() - await order.checkCurrentUrl('cart') - }) - test('Click cart link with an order', async () => { - const order = new OrderPage(page) - await order.goBack() - await order.clickCartLinkButton() - await order.checkCurrentUrl('cart') - }) - test('Label button', async ({ page }) => { - const order = new OrderPage(page) - await order.goto('order/order-with-cart-link-button?reactNodeLabel=true') - await order.checkText('[data-test=cart-link]', 'Cart link react node label') - }) -}) diff --git a/packages/react-components/specs/e2e/prices.spec.ts b/packages/react-components/specs/e2e/prices.spec.ts deleted file mode 100644 index ab386f7b..00000000 --- a/packages/react-components/specs/e2e/prices.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { test, expect } from './baseFixtures' -import path from 'path' -const endpoint = `prices` - -test('Prices page', async ({ page, browser }) => { - await page.coverage.startJSCoverage() - await page.goto(endpoint) - const loading = await page.waitForSelector('text=Caricamento...') - expect(await loading.textContent()).toBe('Caricamento...') - const filterdPrice = await page.textContent('data-test=price-filter-0') - const compareFilteredPrice = await page.textContent( - ':right-of(:nth-match([data-test="price-filter-0"], 1))' - ) - const price = await page.textContent('data-test=price-0') - const comparePrice = await page.textContent( - ':right-of(:nth-match([data-test="price-0"], 1))' - ) - const dollarPrice = await page.textContent( - ':nth-match([data-test="price-0"], 3)' - ) - const compareDollarPrice = await page.textContent( - ':nth-match([data-test="price-0"], 4)' - ) - expect(filterdPrice).toBe('€35,00') - expect(filterdPrice).not.toBe('$35,00') - expect(compareFilteredPrice).toBe('€45,00') - expect(price).toBe('€35,00') - expect(comparePrice).toBe('€45,00') - expect(dollarPrice).toBe('$34.80') - expect(compareDollarPrice).toBe('$45.24') - await page.screenshot({ - path: path.join(__dirname, 'screenshots', 'prices.jpg'), - }) - await page.coverage.stopJSCoverage() - await browser.close() -}) diff --git a/packages/react-components/specs/e2e/screenshots/customer-addresses-country-lock.jpg b/packages/react-components/specs/e2e/screenshots/customer-addresses-country-lock.jpg deleted file mode 100644 index f31c515f..00000000 Binary files a/packages/react-components/specs/e2e/screenshots/customer-addresses-country-lock.jpg and /dev/null differ diff --git a/packages/react-components/specs/e2e/screenshots/prices.jpg b/packages/react-components/specs/e2e/screenshots/prices.jpg deleted file mode 100644 index 33c30ffd..00000000 Binary files a/packages/react-components/specs/e2e/screenshots/prices.jpg and /dev/null differ diff --git a/packages/react-components/specs/e2e/single-price.spec.ts b/packages/react-components/specs/e2e/single-price.spec.ts deleted file mode 100644 index d11c1d81..00000000 --- a/packages/react-components/specs/e2e/single-price.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { test, expect } from './baseFixtures' -import path from 'path' -const endpoint = `/` - -test('Prices page', async ({ page, browser }) => { - await page.coverage.startJSCoverage() - await page.goto(endpoint) - const loading = await page.waitForSelector('text=Caricamento...') - expect(await loading.textContent()).toBe('Caricamento...') - const firstPrice = await page.textContent('data-test=price-0') - const compareFirstPrice = await page.textContent( - ':right-of(:nth-match([data-test="price-0"], 1))' - ) - const sndPrice = await page.textContent( - ':right-of(:nth-match([data-test="price-0"], 2))' - ) - const compareSndPrice = await page.textContent( - ':right-of(:nth-match([data-test="price-0"], 3))' - ) - expect(firstPrice).toBe('€35,00') - expect(compareFirstPrice).toBe('€45,00') - expect(sndPrice).toBe('$34.80') - expect(compareSndPrice).toBe('$45.24') - await page.screenshot({ - path: path.join(__dirname, 'screenshots', 'prices.jpg'), - }) - await page.coverage.stopJSCoverage() - await browser.close() -}) diff --git a/packages/react-components/specs/e2e/utils/response.ts b/packages/react-components/specs/e2e/utils/response.ts deleted file mode 100644 index 9c44790a..00000000 --- a/packages/react-components/specs/e2e/utils/response.ts +++ /dev/null @@ -1,8 +0,0 @@ -import path from 'path' -export const waitForResponse = (s) => (resp) => { - return resp.url().includes(s) && [200, 201, 204].includes(resp.status()) -} - -export function getScreenshotPath(img: string): string { - return path.join(process.cwd(), 'specs', 'e2e', 'screenshots', img) -} diff --git a/packages/react-components/specs/hooks/useCommerceLayer.spec.tsx b/packages/react-components/specs/hooks/useCommerceLayer.spec.tsx deleted file mode 100644 index 24611619..00000000 --- a/packages/react-components/specs/hooks/useCommerceLayer.spec.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import CommerceLayer from "#components/auth/CommerceLayer"; -import { render, renderHook, waitFor, screen } from "@testing-library/react"; -import { useEffect, useState } from "react"; -import useCommerceLayer from "src/hooks/useCommerceLayer"; -import getToken from "../utils/getToken"; -import type { SkusContext } from "specs/utils/context"; - -function HookComponent(): JSX.Element { - const ctx = useCommerceLayer(); - const [sku, setSku] = useState(); - useEffect(() => { - if (ctx.sdkClient != null && sku == null) { - ctx - .sdkClient() - ?.skus.list({ filters: { code_eq: "BABYONBU000000E63E7412MX" } }) - .then((res) => { - if (res.first() != null) { - setSku(res.first()?.code); - } - }); - } - return () => { - setSku(undefined); - }; - }, [ctx.accessToken]); - if (sku != null) { - return <>{sku}; - } - return <>Hook component; -} - -describe("useCommerceLayer hook", () => { - let token: string | undefined; - let domain: string | undefined; - beforeAll(async () => { - const { accessToken, endpoint } = await getToken(); - if (accessToken !== undefined) { - token = accessToken; - domain = endpoint; - } - }); - beforeEach(async (ctx) => { - if (token != null && domain != null) { - ctx.accessToken = token; - ctx.endpoint = domain; - ctx.sku = "BABYONBU000000E63E7412MX"; - } - }); - it.skip("useCommerceLayer outside of CommerceLayer", () => { - expect(() => renderHook(() => useCommerceLayer())).toThrow( - "Cannot use `useCommerceLayer` outside of ", - ); - }); - it("get sku by sdk client", async (ctx) => { - render( - - - , - ); - await waitFor(async () => await screen.findByText(ctx.sku), { - timeout: 5000, - }); - const sku = screen.getByText(ctx.sku); - expect(sku.textContent).toEqual(ctx.sku); - }); -}); diff --git a/packages/react-components/specs/hooks/useCustomerContainer.spec.tsx b/packages/react-components/specs/hooks/useCustomerContainer.spec.tsx deleted file mode 100644 index 1eba70b0..00000000 --- a/packages/react-components/specs/hooks/useCustomerContainer.spec.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import CustomerContainer from '#components/customers/CustomerContainer' -import { render, renderHook, waitFor, screen } from '@testing-library/react' -import { useEffect, useState } from 'react' -import { type LocalContext } from 'specs/utils/context' -import getToken from '../utils/getToken' -import useCustomerContainer from '#hooks/useCustomerContainer' - -function HookComponent(): JSX.Element { - const customerCtx = useCustomerContainer() - const [loaded, setLoaded] = useState(false) - useEffect(() => { - if (customerCtx.addresses != null) { - setLoaded(true) - } - if (customerCtx.customers != null) { - setLoaded(true) - } - }, [customerCtx.addresses]) - if (loaded) { - return <>loaded - } - return <>Hook component -} - -describe('useCustomerContainer hook', () => { - let token: string | undefined - let domain: string | undefined - beforeAll(async () => { - const { accessToken, endpoint } = await getToken('customer') - if (accessToken !== undefined) { - token = accessToken - domain = endpoint - } - }) - beforeEach(async (ctx) => { - if (token != null && domain != null) { - ctx.accessToken = token - ctx.endpoint = domain - } - }) - it('useCustomerContainer outside of CustomerContainer', () => { - expect(() => renderHook(() => useCustomerContainer())).toThrow( - 'Cannot use `useCustomerContainer` outside of ' - ) - }) - it('Load customer data by hook', async (ctx) => { - render( - - - - - - ) - await waitFor(async () => await screen.findByText('loaded'), { - timeout: 5000 - }) - const addressesLoaded = screen.getByText('loaded') - expect(addressesLoaded.textContent).toEqual('loaded') - }) -}) diff --git a/packages/react-components/specs/hooks/useOrderContainer.spec.tsx b/packages/react-components/specs/hooks/useOrderContainer.spec.tsx deleted file mode 100644 index fda0cb22..00000000 --- a/packages/react-components/specs/hooks/useOrderContainer.spec.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import CommerceLayer from "#components/auth/CommerceLayer"; -import OrderContainer from "#components/orders/OrderContainer"; -import { render, renderHook, waitFor, screen } from "@testing-library/react"; -import { useEffect } from "react"; -import type { OrderContext } from "specs/utils/context"; -import useOrderContainer from "src/hooks/useOrderContainer"; -import getToken from "../utils/getToken"; - -function HookComponent(): JSX.Element { - const orderCtx = useOrderContainer(); - useEffect(() => { - orderCtx.reloadOrder(); - }, [orderCtx.order]); - if (orderCtx.order) { - return <>{orderCtx.order?.id}; - } - return <>Hook component; -} - -describe("useOrderContainer hook", () => { - let token: string | undefined; - let domain: string | undefined; - beforeAll(async () => { - const { accessToken, endpoint } = await getToken(); - if (accessToken !== undefined) { - token = accessToken; - domain = endpoint; - } - }); - beforeEach(async (ctx) => { - if (token != null && domain != null) { - ctx.accessToken = token; - ctx.endpoint = domain; - // TODO: create a new one? - ctx.orderId = "qQgYhvlDVM"; - } - }); - it("useOrderContainer outside of OrderContainer", () => { - expect(() => renderHook(() => useOrderContainer())).toThrow( - "Cannot use `useOrderContainer` outside of ", - ); - }); - it("reload order by hook", async (ctx) => { - render( - - - - - , - ); - await waitFor(async () => await screen.findByText(ctx.orderId), { - timeout: 5000, - }); - const orderId = screen.getByText(ctx.orderId); - expect(orderId.textContent).toEqual(ctx.orderId); - }); -}); diff --git a/packages/react-components/specs/in_stock_subscriptions/in_stock_subscriptions.spec.tsx b/packages/react-components/specs/in_stock_subscriptions/in_stock_subscriptions.spec.tsx deleted file mode 100644 index 1b4f12d2..00000000 --- a/packages/react-components/specs/in_stock_subscriptions/in_stock_subscriptions.spec.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import InStockSubscriptionsContainer from '#components/in_stock_subscriptions/InStockSubscriptionsContainer' -import InStockSubscriptionButton from '#components/in_stock_subscriptions/InStockSubscriptionButton' -import Errors from '#components/errors/Errors' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import { type SkusContext } from '../utils/context' -import CommerceLayer from '#components/auth/CommerceLayer' -import { faker } from '@faker-js/faker' -import { getAccessToken } from 'mocks/getAccessToken' - -describe('InStockSubscription components', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.sku = 'BABYONBU000000E63E746MXX' - } - }) - it.skip('InStockSubscriptionsContainer outside of CommerceLayer', () => { - expect(() => - render( - - <> - - ) - ).toThrow( - 'Cannot use outside of ' - ) - }) - it.skip('InStockSubscriptionButton outside of InStockSubscriptionsContainer', ({ - sku - }) => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) - it('Button is not visible by default', async (ctx) => { - render( - - - - - - ) - const button = screen.queryByTestId('in-stock-subscription-button') - expect(button).toBeNull() - }) - it('Button is visible', async (ctx) => { - render( - - - - - - ) - const button = screen.queryByTestId('in-stock-subscription-button') - expect(button).toBeDefined() - expect(button?.textContent).toBe('Subscribe') - }) - it('Button is visible with custom label and try to subscribe', async (ctx) => { - let successResponse = false - const email = faker.internet.email().toLowerCase() - render( - - - Subscribe on click
} - show - onClick={({ success }) => { - successResponse = success - }} - /> - - - - ) - const button = screen.getByTestId('in-stock-subscription-button') - const buttonLabel = screen.getByTestId('button-label') - expect(button).toBeDefined() - expect(button?.textContent).toBe('Subscribe on click') - expect(buttonLabel?.tagName).toBe('SPAN') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('Subscribe on click'), { - timeout: 5000 - }) - const errors = screen.queryByTestId('in_stock_subscriptions_errors') - expect(errors?.textContent).toBeUndefined() - expect(successResponse).toBe(true) - }) - it.skip('Subscribe to sku has already been taken', async (ctx) => { - // NOTE: This test is not working because the error is not being returned from the server - let successResponse = false - const email = 'jacinthe.nolan10@hotmail.com' - render( - - - { - successResponse = success - }} - /> - - - - ) - const button = screen.getByTestId('in-stock-subscription-button') - expect(button).toBeDefined() - fireEvent.click(button) - await waitFor(async () => await screen.findByText('Subscribe'), { - timeout: 5000 - }) - const errors = screen.queryByTestId('in_stock_subscriptions_errors') - expect(errors?.textContent).toBe('sku - has already been taken') - expect(successResponse).toBe(false) - }) -}) diff --git a/packages/react-components/specs/line_items/line-items.spec.tsx b/packages/react-components/specs/line_items/line-items.spec.tsx deleted file mode 100644 index 81c376bd..00000000 --- a/packages/react-components/specs/line_items/line-items.spec.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import LineItem from '#components/line_items/LineItem' -import LineItemQuantity from '#components/line_items/LineItemQuantity' -import LineItemsContainer from '#components/line_items/LineItemsContainer' -import LineItemsCount from '#components/line_items/LineItemsCount' -import LineItemCode from '#components/line_items/LineItemCode' -import AddToCartButton from '#components/orders/AddToCartButton' -import OrderContainer from '#components/orders/OrderContainer' -import Errors from '#components/errors/Errors' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import { type LocalContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' - -interface AddToCartContext extends LocalContext { - skuCode: string - quantity: string - lineItem: { - name: string - imageUrl?: string - } - lineItemOption: { - skuOptionId: string - options: Record - quantity?: number - } -} - -describe('Line items components', () => { - const globalTimeout: number = 5000 - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.skuCode = 'BABYONBU000000E63E7412MX' - ctx.quantity = '3' - ctx.lineItem = { - name: 'Darth Vader', - imageUrl: - 'https://i.pinimg.com/736x/a5/32/de/a532de337eff9b1c1c4bfb8df73acea4--darth-vader-stencil-darth-vader-head.jpg?b=t' - } - } - }) - it('LineItemsCount outside of CustomerContainer', (ctx) => { - expect(() => - render( - - - - ) - ).toThrow('Cannot use outside of ') - }) - it('Show out of stock error changing quantity', async (ctx) => { - const skuWithoutStock = 'BABYONBU000000FFFFFFNBXX' - render( - - - - - - - - - - - {() => { - return Errors - }} - - - - - - ) - const button = screen.getByTestId(`add-to-cart-button`) - const secondButton = screen.getByTestId(`second-add-to-cart-button`) - const count = screen.getByTestId(`line-items-count`) - expect(button).toBeDefined() - expect(secondButton).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('0') - fireEvent.click(button) - await waitFor( - () => { - expect(screen.getByTestId(`line-items-count`).textContent).toBe('3') - }, - { timeout: globalTimeout } - ) - fireEvent.click(secondButton) - await waitFor( - () => { - expect(screen.getByTestId(`line-items-count`).textContent).toBe('6') - }, - { timeout: globalTimeout } - ) - const quantitySelector = - screen.getByTestId(skuWithoutStock) - expect(quantitySelector).toBeDefined() - expect(quantitySelector.value).toBe('3') - fireEvent.change(screen.getByTestId(skuWithoutStock), { - target: { value: '6' } - }) - await waitFor( - () => { - expect(screen.getByTestId(`line-items-count`).textContent).toBe('6') - }, - { timeout: globalTimeout } - ) - expect(screen.getByTestId(`line-items-count`).textContent).toBe('6') - // NOTE: Should check if the error component is showing the error message. - }) -}) diff --git a/packages/react-components/specs/orders/add-to-cart-button.spec.tsx b/packages/react-components/specs/orders/add-to-cart-button.spec.tsx deleted file mode 100644 index a7ee0435..00000000 --- a/packages/react-components/specs/orders/add-to-cart-button.spec.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import LineItem from '#components/line_items/LineItem' -import LineItemField from '#components/line_items/LineItemField' -import LineItemImage from '#components/line_items/LineItemImage' -import LineItemName from '#components/line_items/LineItemName' -import LineItemOption from '#components/line_items/LineItemOption' -import LineItemOptions from '#components/line_items/LineItemOptions' -import LineItemsContainer from '#components/line_items/LineItemsContainer' -import LineItemsCount from '#components/line_items/LineItemsCount' -import AddToCartButton from '#components/orders/AddToCartButton' -import OrderContainer from '#components/orders/OrderContainer' -import CartLink from '#components/orders/CartLink' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import { type LocalContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' - -interface AddToCartContext extends LocalContext { - skuCode: string - quantity: string - lineItem: { - name: string - imageUrl?: string - } - lineItemOption: { - skuOptionId: string - options: Record - quantity?: number - } -} - -describe('AddToCartButton component', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.skuCode = 'BABYONBU000000E63E7412MX' - ctx.quantity = '3' - ctx.lineItem = { - name: 'Darth Vader', - imageUrl: - 'https://i.pinimg.com/736x/a5/32/de/a532de337eff9b1c1c4bfb8df73acea4--darth-vader-stencil-darth-vader-head.jpg?b=t' - } - ctx.lineItemOption = { - skuOptionId: 'mNJEgsJwBn', - options: { - message: 'This is a message' - } - } - } - }) - it('Add SKU to the order with quantity', async (ctx) => { - render( - - - - - - - - - ) - const button = screen.getByTestId(`add-to-cart-button`) - const count = screen.getByTestId(`line-items-count`) - expect(button).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('0') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('3'), { timeout: 5000 }) - expect(count.textContent).toBe('3') - }) - it('Add SKU with frequency to the order with quantity', async (ctx) => { - render( - - - - - - - - - - - - ) - const button = screen.getByTestId(`add-to-cart-button`) - const count = screen.getByTestId(`line-items-count`) - expect(button).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('0') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('3'), { timeout: 5000 }) - expect(count.textContent).toBe('3') - const frequency = screen.getByTestId(`line-item-frequency`) - expect(frequency).toBeDefined() - expect(frequency.textContent).toBe('monthly') - }) - it('Add SKU to the order with quantity and check CartLink href', async (ctx) => { - render( - - - - - - - - - - ) - const button = screen.getByTestId(`add-to-cart-button`) - const count = screen.getByTestId(`line-items-count`) - expect(button).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('0') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('3'), { timeout: 5000 }) - expect(count.textContent).toBe('3') - const link = screen.getByTestId(`cart-link`) - expect(link).toBeDefined() - expect(link.getAttribute('href')).toContain('stg.commercelayer') - }) - it('Add SKU to the order with quantity and change quantity', async (ctx) => { - const { rerender } = render( - - - - - - - - - ) - const button = screen.getByTestId(`add-to-cart-button`) - const count = screen.getByTestId(`line-items-count`) - expect(button).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('0') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('3'), { timeout: 5000 }) - expect(count.textContent).toBe('3') - rerender( - - - - - - - - - ) - expect(button).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('3') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('5'), { timeout: 5000 }) - expect(count.textContent).toBe('5') - }) - it('Add SKU to the order with custom name and image', async (ctx) => { - render( - - - - - - - - - - - - - - ) - const button = screen.getByTestId(`add-to-cart-button`) - const count = screen.getByTestId(`line-items-count`) - expect(button).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('0') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('3'), { timeout: 5000 }) - expect(count.textContent).toBe('3') - const lineItemName = screen.getByTestId(`line-item-name-${ctx.skuCode}`) - const lineItemImage = screen.getByTestId(`line-item-image-${ctx.skuCode}`) - const lineItemCode = screen.getByTestId(`line-item-code-${ctx.skuCode}`) - expect(lineItemName).toBeDefined() - expect(lineItemImage).toBeDefined() - expect(lineItemCode).toBeDefined() - expect(lineItemName.textContent).toBe(ctx.lineItem.name) - expect(lineItemImage.getAttribute('src')).toBe(ctx.lineItem.imageUrl) - expect(lineItemCode.textContent).toBe(ctx.skuCode) - }) - it('Add SKU to the order with sku options', async (ctx) => { - render( - - - - - - - - - - - - - - - - ) - const button = screen.getByTestId(`add-to-cart-button`) - const count = screen.getByTestId(`line-items-count`) - expect(button).toBeDefined() - expect(count).toBeDefined() - expect(count.textContent).toBe('0') - fireEvent.click(button) - await waitFor(async () => await screen.findByText('3'), { timeout: 5000 }) - expect(count.textContent).toBe('3') - const lineItemName = screen.getByTestId(`line-item-name-${ctx.skuCode}`) - const lineItemImage = screen.getByTestId(`line-item-image-${ctx.skuCode}`) - const skuOption = screen.findByText('This is a message') - expect(lineItemName).toBeDefined() - expect(lineItemImage).toBeDefined() - expect(skuOption).toBeDefined() - expect(lineItemName.textContent).toContain('Black Baby') - expect(lineItemImage.getAttribute('src')).toContain( - ctx.skuCode.slice(0, ctx.skuCode.length - 4) - ) - }) - it('AddToCartButton outside of CommerceLayer', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) - it('AddToCartButton outside of OrderContainer', (ctx) => { - expect(() => - render( - - - - ) - ).toThrow('Cannot use outside of ') - }) -}) diff --git a/packages/react-components/specs/orders/hosted-cart.spec.tsx b/packages/react-components/specs/orders/hosted-cart.spec.tsx new file mode 100644 index 00000000..eab43a86 --- /dev/null +++ b/packages/react-components/specs/orders/hosted-cart.spec.tsx @@ -0,0 +1,96 @@ +import CommerceLayerContext from "#context/CommerceLayerContext" +import OrderContext, { defaultOrderContext } from "#context/OrderContext" +import OrderStorageContext from "#context/OrderStorageContext" +import { HostedCart } from "#components/orders/HostedCart" +import * as organizationUtils from "#utils/organization" +import * as applicationLinkUtils from "#utils/getApplicationLink" +import { render, waitFor } from "@testing-library/react" +import { vi } from "vitest" + +vi.mock("iframe-resizer", () => ({ + iframeResizer: vi.fn(), +})) + +describe("HostedCart component", () => { + beforeEach(() => { + localStorage.clear() + vi.restoreAllMocks() + }) + + it("updates minicart url when persistKey changes", async () => { + localStorage.setItem("cart-key-1", "order-id-1") + localStorage.setItem("cart-key-2", "order-id-2") + + vi.spyOn(organizationUtils, "getOrganizationConfig").mockResolvedValue(null) + + const getApplicationLinkSpy = vi + .spyOn(applicationLinkUtils, "getApplicationLink") + .mockImplementation( + ({ orderId }) => `https://test-cart.local/cart/${orderId}`, + ) + + const orderContextValue = { + ...defaultOrderContext, + createOrder: vi.fn().mockResolvedValue("created-order-id"), + } + + const commonProps = { + clearWhenPlaced: true, + getLocalOrder: vi.fn(), + setLocalOrder: vi.fn(), + deleteLocalOrder: vi.fn(), + } + + const { rerender } = render( + + + + + + + , + ) + + await waitFor(() => { + expect(getApplicationLinkSpy).toHaveBeenCalledWith( + expect.objectContaining({ orderId: "order-id-1" }), + ) + }) + + rerender( + + + + + + + , + ) + + await waitFor(() => { + expect(getApplicationLinkSpy).toHaveBeenCalledWith( + expect.objectContaining({ orderId: "order-id-2" }), + ) + }) + }) +}) diff --git a/packages/react-components/specs/orders/order-container.spec.tsx b/packages/react-components/specs/orders/order-container.spec.tsx deleted file mode 100644 index a4ce40da..00000000 --- a/packages/react-components/specs/orders/order-container.spec.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import OrderContainer from '#components/orders/OrderContainer' -import { render } from '@testing-library/react' - -describe('OrderContainer component', () => { - it('OrderContainer outside of CommerceLayer', () => { - expect(() => - render( - - <> - - ) - ).toThrow('Cannot use outside of ') - }) -}) diff --git a/packages/react-components/specs/orders/order-list.spec.tsx b/packages/react-components/specs/orders/order-list.spec.tsx deleted file mode 100644 index d893b838..00000000 --- a/packages/react-components/specs/orders/order-list.spec.tsx +++ /dev/null @@ -1,820 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import CustomerContainer from '#components/customers/CustomerContainer' -import OrderList, { type TOrderListColumn } from '#components/orders/OrderList' -import OrderListEmpty from '#components/orders/OrderListEmpty' -import OrderListPaginationButtons from '#components/orders/OrderListPaginationButtons' -import { OrderListPaginationInfo } from '#components/orders/OrderListPaginationInfo' -import OrderListRow from '#components/orders/OrderListRow' -import { - fireEvent, - render, - screen, - waitForElementToBeRemoved -} from '@testing-library/react' -// import { baseUrl } from 'mocks/handlers' -// import { customerOrders } from 'mocks/resources/orders/customer-orders' -// import { customerOrdersEmpty } from 'mocks/resources/orders/customer-orders-empty' -// import { server } from 'mocks/server' -// import { rest } from 'msw' -import { type LocalContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' -import { type TOrderList } from '#context/OrderListChildrenContext' - -interface OrderListContext extends Omit { - columns: Array> - columnsSubscriptions: Array> -} - -const columns = [ - { - header: 'Order', - accessorKey: 'number' - }, - { - header: 'Status', - accessorKey: 'status' - }, - { - header: 'Date', - accessorKey: 'updated_at' - }, - { - header: 'Amount', - accessorKey: 'formatted_total_amount_with_taxes' - } -] satisfies Array> - -const columnsSubscriptions = [ - { - header: 'Order subscription', - accessorKey: 'number' - }, - { - header: 'Status', - accessorKey: 'status' - } -] satisfies Array> - -describe('Orders list', () => { - const globalTimeout: number = 10000 - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken('customer') - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.columns = columns - ctx.columnsSubscriptions = columnsSubscriptions - } - }) - it.skip( - 'Show orders list', - async (ctx) => { - render( - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - const [first] = screen.getAllByTestId(/thead/) - expect(first).toBeDefined() - expect(first?.getAttribute('data-testid')).toBe('thead-0') - expect(screen.getByText('Order')).toBeDefined() - expect(first?.getAttribute('data-sort')).toBe('desc') - fireEvent.click(screen.getByText('Order')) - expect(first?.getAttribute('data-sort')).toBe('asc') - fireEvent.click(screen.getByText('Order')) - expect(first?.getAttribute('data-sort')).toBe('') - const [firstCell] = screen.getAllByTestId(/cell/) - expect(firstCell).toBeDefined() - expect(firstCell?.getAttribute('data-testid')).toBe('cell-0') - expect(first?.textContent).not.toBe('') - }, - globalTimeout - ) - it( - 'Show order subscriptions list', - async (ctx) => { - render( - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - const [first] = screen.getAllByTestId(/thead/) - expect(first).toBeDefined() - expect(first?.getAttribute('data-testid')).toBe('thead-0') - expect(screen.getByText('Order subscription')).toBeDefined() - expect(first?.getAttribute('data-sort')).toBe('desc') - const [firstRow] = screen.getAllByTestId(/status/) - expect(firstRow).toBeDefined() - expect(firstRow?.getAttribute('data-testid')).toBe('status') - expect(firstRow?.textContent).not.toBe('') - }, - globalTimeout - ) - it.skip( - 'Show orders list empty', - async (ctx) => { - const { accessToken, endpoint } = await getAccessToken('customer_empty') - if (accessToken !== undefined) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - } - render( - - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - expect(screen.getByText('No orders available')) - const paginationInfo = screen.queryByTestId('pagination-info') - expect(paginationInfo).toBeNull() - const prevButton = screen.queryByTestId('prev-button') - expect(prevButton).toBeNull() - const nextButton = screen.queryByTestId('next-button') - expect(nextButton).toBeNull() - }, - globalTimeout - ) - it.skip('Show orders list empty with custom component', async (ctx) => { - const { accessToken, endpoint } = await getAccessToken('customer_empty') - if (accessToken != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - } - render( - - - - - {() => <>There are not any orders available} - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - expect(screen.getByText('There are not any orders available')) - }) - it.skip( - 'Show orders list with custom loading even if there is OrderListEmpty', - async (ctx) => { - render( - - - Custom loading...} - > - - - - - - - - - ) - expect(screen.getByText('Custom loading...')) - await waitForElementToBeRemoved( - () => screen.queryByText('Custom loading...'), - { - timeout: globalTimeout - } - ) - const [first] = screen.getAllByTestId(/thead/) - expect(first).toBeDefined() - expect(first?.getAttribute('data-testid')).toBe('thead-0') - expect(first?.textContent).not.toBe('') - expect(first?.tagName).toBe('TH') - expect(screen.getByText('Order')).toBeDefined() - const [firstCell] = screen.getAllByTestId(/cell/) - expect(firstCell).toBeDefined() - expect(firstCell?.getAttribute('data-testid')).toBe('cell-0') - expect(firstCell?.tagName).toBe('TD') - expect(firstCell?.textContent).not.toBe('') - }, - globalTimeout - ) - it.skip( - 'Show orders list with actions and custom Order list row', - async (ctx) => { - render( - - - <>Actions} - actionsContainerClassName='action-container-class' - > - - - {({ cell, order, ...p }) => { - return ( - <> - {cell?.map((cell, k) => { - return ( - -

- Order # {cell.getValue()} -

-

- contains {order.skus_count} items -

- - ) - })} - - ) - }} -
- - - -
-
-
- ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - const [first] = screen.getAllByTestId(/thead/) - expect(first).toBeDefined() - expect(first?.getAttribute('data-testid')).toBe('thead-0') - expect(first?.textContent).not.toBe('') - expect(first?.tagName).toBe('TH') - expect(screen.getByText('Order')).toBeDefined() - const [firstCell] = screen.getAllByTestId(/cell/) - expect(firstCell).toBeDefined() - expect(firstCell?.getAttribute('data-testid')).toBe('custom-cell-0') - expect(firstCell?.tagName).toBe('TD') - expect(firstCell?.textContent).not.toBe('') - expect(firstCell?.textContent).toContain('Order #') - const [action] = screen.getAllByTestId('action-cell') - expect(action).toBeDefined() - expect(action?.getAttribute('data-testid')).toBe('action-cell') - expect(action?.className).toBe('action-container-class') - }, - globalTimeout - ) - it.skip( - 'Show orders list with pagination', - async (ctx) => { - const { rerender } = render( - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - const [first] = screen.getAllByTestId(/thead/) - expect(first).toBeDefined() - expect(first?.getAttribute('data-testid')).toBe('thead-0') - expect(screen.getByText('Order')).toBeDefined() - expect(first?.getAttribute('data-sort')).toBe('desc') - fireEvent.click(screen.getByText('Order')) - expect(first?.getAttribute('data-sort')).toBe('asc') - fireEvent.click(screen.getByText('Order')) - expect(first?.getAttribute('data-sort')).toBe('') - const [firstCell] = screen.getAllByTestId(/cell/) - expect(firstCell).toBeDefined() - expect(firstCell?.getAttribute('data-testid')).toBe('cell-0') - expect(first?.textContent).not.toBe('') - let paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.tagName).toBe('SPAN') - expect(paginationInfo?.textContent).toContain('1 - 10') - let prevButton = screen.getByTestId('prev-button') - expect(prevButton).toBeDefined() - expect(prevButton.textContent).toBe('<') - let nextButton = screen.getByTestId('next-button') - expect(nextButton).toBeDefined() - expect(nextButton.textContent).toBe('>') - let navButtons = screen.getAllByTestId(/page-/) - expect(navButtons).toBeDefined() - expect(navButtons.length).toBe(3) - expect(navButtons[0]?.className).toContain('active') - fireEvent.click(nextButton) - paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.textContent).toContain('11 - 20') - navButtons = screen.getAllByTestId(/page-/) - expect(navButtons).toBeDefined() - expect(navButtons.length).toBe(3) - nextButton = screen.getByTestId('next-button') - fireEvent.click(nextButton) - paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.textContent).toContain('21 - 30') - navButtons = screen.getAllByTestId(/page-/) - expect(navButtons).toBeDefined() - expect(navButtons.length).toBe(3) - expect(navButtons[1]?.className).toContain('active') - prevButton = screen.getByTestId('prev-button') - fireEvent.click(prevButton) - paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.textContent).toContain('11 - 20') - navButtons = screen.getAllByTestId(/page-/) - expect(navButtons).toBeDefined() - expect(navButtons.length).toBe(3) - expect(navButtons[1]?.className).toContain('active') - prevButton = screen.getByTestId('prev-button') - fireEvent.click(prevButton) - paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.textContent).toContain('1 - 10') - navButtons = screen.getAllByTestId(/page-/) - expect(navButtons).toBeDefined() - expect(navButtons.length).toBe(3) - expect(navButtons[0]?.className).toContain('active') - const page = screen.getByTestId('page-3') - fireEvent.click(page) - paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.textContent).toContain('21 - 30') - navButtons = screen.getAllByTestId(/page-/) - expect(navButtons).toBeDefined() - expect(navButtons.length).toBe(3) - expect(navButtons[1]?.className).toContain('active') - rerender( - - - - - - - - - {(props) => ( - - {props.firstRow} - {props.lastRow} - - )} - - - {(props) => ( - - {props.pageIndex} - - )} - - - - - ) - paginationInfo = screen.getByTestId('custom-pagination-info') - expect(paginationInfo?.textContent).toContain('21 - 30') - const customPaginationCustom = screen.getByTestId( - 'custom-pagination-button' - ) - expect(customPaginationCustom?.textContent).toContain('2') - }, - globalTimeout - ) - it.skip( - 'Set default page size', - async (ctx) => { - render( - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - await screen.findByText(/1 - 25/) - let paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.textContent).toContain('1 - 25') - const nextButton = screen.getByTestId('next-button') - expect(nextButton).toBeDefined() - fireEvent.click(nextButton) - paginationInfo = screen.getByTestId('pagination-info') - expect(paginationInfo?.textContent).toContain('26 - 50') - }, - globalTimeout - ) - it.skip( - 'Sort by asc', - async (ctx) => { - render( - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - const [first] = screen.getAllByTestId(/thead/) - expect(first).toBeDefined() - expect(first?.getAttribute('data-sort')).toBe('asc') - }, - globalTimeout - ) - it.skip( - 'Hide previous and next buttons for pagination', - async (ctx) => { - const { accessToken, endpoint } = await getAccessToken( - 'customer_with_low_data' - ) - if (accessToken !== undefined) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - } - // server.use( - // rest.get(`${baseUrl}/customers*`, async (_req, res, ctx) => { - // return await res.once(ctx.status(200), ctx.json(customerOrders)) - // }) - // ) - render( - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - let prevButton = screen.queryByTestId('prev-button') - expect(prevButton).toBeNull() - let nextButton = screen.queryByTestId('next-button') - expect(nextButton).toBeDefined() - if (nextButton != null) { - fireEvent.click(nextButton) - prevButton = screen.queryByTestId('prev-button') - expect(prevButton).toBeDefined() - nextButton = screen.queryByTestId('next-button') - expect(nextButton).toBeNull() - } - }, - globalTimeout - ) - it.skip( - 'Hide previous and next buttons for pagination', - async (ctx) => { - const { accessToken, endpoint } = await getAccessToken( - 'customer_with_low_data' - ) - if (accessToken !== undefined) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - } - // server.use( - // rest.get(`${baseUrl}/customers*`, async (_req, res, ctx) => { - // return await res.once(ctx.status(200), ctx.json(customerOrders)) - // }) - // ) - render( - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - let prevButton = screen.queryByTestId('prev-button') - expect(prevButton).toBeNull() - let nextButton = screen.queryByTestId('next-button') - expect(nextButton).toBeDefined() - if (nextButton) { - fireEvent.click(nextButton) - } - prevButton = screen.queryByTestId('prev-button') - expect(prevButton).toBeDefined() - nextButton = screen.queryByTestId('next-button') - expect(nextButton).toBeNull() - }, - globalTimeout - ) - it.skip( - 'Hide previous and next buttons with few orders for pagination', - async (ctx) => { - const { accessToken, endpoint } = await getAccessToken( - 'customer_with_low_data' - ) - if (accessToken !== undefined) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - } - // server.use( - // rest.get(`${baseUrl}/customers*`, async (_req, res, ctx) => { - // return await res.once(ctx.status(200), ctx.json(customerOrders)) - // }) - // ) - render( - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - const prevButton = screen.queryByTestId('prev-button') - expect(prevButton).toBeNull() - const nextButton = screen.queryByTestId('next-button') - expect(nextButton).toBeNull() - const paginationInfo = screen.queryByTestId('pagination-info') - expect(paginationInfo).toBeNull() - }, - globalTimeout - ) - it.skip('Wrong component as children into ', async (ctx) => { - expect(() => - render( - - - - - - - -
wrong element
- -
-
-
- ) - ).toThrow('Only library components are allowed into ') - }) - it.skip( - 'Hydratate props', - async (ctx) => { - render( - - - - - {(props) => { - return ( - <> - - {props.order.number} - - - {props.row.original?.number} - - - ) - }} - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: globalTimeout - }) - const [orderNumber] = screen.queryAllByTestId('order-number') - const [rowOriginalNumber] = screen.queryAllByTestId('row-original-number') - expect(orderNumber).toBeDefined() - expect(rowOriginalNumber).toBeDefined() - expect(orderNumber?.textContent).toBe(rowOriginalNumber?.textContent) - }, - globalTimeout - ) -}) diff --git a/packages/react-components/specs/orders/place-order-container.spec.tsx b/packages/react-components/specs/orders/place-order-container.spec.tsx deleted file mode 100644 index 79e89c20..00000000 --- a/packages/react-components/specs/orders/place-order-container.spec.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import PlaceOrderContainer from '#components/orders/PlaceOrderContainer' -import { render } from '@testing-library/react' - -describe('PlaceOrderContainer component', () => { - it('PlaceOrderContainer outside of OrderContainer', () => { - expect(() => - render( - - <> - - ) - ).toThrow('Cannot use outside of ') - }) -}) diff --git a/packages/react-components/specs/parcels/parcels.spec.tsx b/packages/react-components/specs/parcels/parcels.spec.tsx deleted file mode 100644 index 114bb11b..00000000 --- a/packages/react-components/specs/parcels/parcels.spec.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import OrderContainer from '#components/orders/OrderContainer' -import ParcelField from '#components/parcels/ParcelField' -import ParcelLineItem from '#components/parcels/ParcelLineItem' -import { ParcelLineItemField } from '#components/parcels/ParcelLineItemField' -import Parcels from '#components/parcels/Parcels' -import { ParcelsCount } from '#components/parcels/ParcelsCount' -import { ParcelLineItemsCount } from '#components/parcels/ParcelLineItemsCount' -import Shipment from '#components/shipments/Shipment' -import ShipmentField from '#components/shipments/ShipmentField' -import ShipmentsContainer from '#components/shipments/ShipmentsContainer' -import ShipmentsCount from '#components/shipments/ShipmentsCount' -import { - render, - screen, - waitForElementToBeRemoved -} from '@testing-library/react' -import { type LocalContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' - -interface ParcelContext extends LocalContext { - orderId: string -} - -describe('Parcels components', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken('customer') - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - // TODO: create a new one in the future - ctx.orderId = 'NrnYhAdEkx' - } - }) - it('Show a parcel', async (ctx) => { - render( - - - - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: 5000 - }) - expect(screen.getByTestId(`shipment-number`)).toBeDefined() - const shipmentsCount = screen.getByTestId(`shipments-count`) - expect(shipmentsCount).toBeDefined() - expect(shipmentsCount.textContent).not.toBe('') - const parcelsCount = screen.getByTestId(`parcels-count`) - expect(parcelsCount).toBeDefined() - expect(parcelsCount.textContent).not.toBe('') - const parcelLineItemsCount = screen.getByTestId(`parcel-line-items-count`) - expect(parcelLineItemsCount).toBeDefined() - expect(parcelLineItemsCount.textContent).not.toBe('') - const parcel = screen.getByTestId(`parcel-number`) - expect(parcel).toBeDefined() - expect(parcel.tagName).toBe('SPAN') - expect(parcel.textContent).not.toBe('') - }) - it('Show a parcel by a filter', async (ctx) => { - render( - - - - - - {(props) => ( - - {props.shipments?.length ?? 0} - - )} - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: 5000 - }) - expect(screen.getByTestId(`shipment-number`)).toBeDefined() - const shipmentsCount = screen.getByTestId(`shipments-count`) - expect(shipmentsCount).toBeDefined() - expect(shipmentsCount.textContent).not.toBe('') - const parcelsCount = screen.getByTestId(`parcels-count`) - expect(parcelsCount).toBeDefined() - expect(parcelsCount.textContent).not.toBe('') - const parcel = screen.getByTestId(`parcel-number`) - expect(parcel).toBeDefined() - expect(parcel.tagName).toBe('SPAN') - expect(parcel.textContent).not.toBe('') - }) - it('Show a parcel with parcel line items', async (ctx) => { - render( - - - - - - - {(props) => <>{props.parcels?.length ?? 0}} - - - - {(props) => ( - - {props.parcel?.parcel_line_items?.length ?? 0} - - )} - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: 5000 - }) - expect(screen.getByTestId(`shipment-number`)).toBeDefined() - const parcelLineItemsCount = screen.getByTestId(`parcel-line-items-count`) - expect(parcelLineItemsCount).toBeDefined() - expect(parcelLineItemsCount.textContent).not.toBe('') - const parcelLineItemSku = screen.getByTestId(`parcel-line-item-sku-code`) - expect(parcelLineItemSku).toBeDefined() - expect(parcelLineItemSku.tagName).toBe('P') - expect(parcelLineItemSku.textContent).not.toBe('') - const parcelLineItemImage = screen.getByTestId(`parcel-line-item-image-url`) - expect(parcelLineItemImage).toBeDefined() - expect(parcelLineItemImage.tagName).toBe('IMG') - expect(parcelLineItemImage.getAttribute('href')).not.toBe('') - }) - it('Show a parcel with parcel line items', async (ctx) => { - render( - - - - - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: 5000 - }) - expect(screen.getByTestId(`shipment-number`)).toBeDefined() - const parcelLineItemSku = screen.getByTestId(`parcel-line-item-sku-code`) - expect(parcelLineItemSku).toBeDefined() - expect(parcelLineItemSku.tagName).toBe('P') - expect(parcelLineItemSku.textContent).not.toBe('') - const parcelLineItemImage = screen.getByTestId(`parcel-line-item-image-url`) - expect(parcelLineItemImage).toBeDefined() - expect(parcelLineItemImage.tagName).toBe('IMG') - expect(parcelLineItemImage.getAttribute('href')).not.toBe('') - }) - it('Show empty parcels', async (ctx) => { - ctx.orderId = 'qXQehvzyxx' - render( - - - - - - - - - - - - - - - ) - expect(screen.getByText('Loading...')) - await waitForElementToBeRemoved(() => screen.queryByText('Loading...'), { - timeout: 5000 - }) - expect(screen.getByTestId(`shipment-number`)).toBeDefined() - const parcelsCount = screen.queryByTestId('parcels-count') - expect(parcelsCount).toBeDefined() - expect(parcelsCount?.textContent).toBe('0') - const parcels = screen.queryByTestId('parcel-number') - expect(parcels).toBeNull() - }) - it('ParcelsCount outside of ShipmentsContainer', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) - it('ShipmentsCount outside of ShipmentsContainer', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) - it('ParcelLineItemsCount outside of Parcels', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) -}) diff --git a/packages/react-components/specs/payment_methods/payment-method-name.spec.tsx b/packages/react-components/specs/payment_methods/payment-method-name.spec.tsx deleted file mode 100644 index 62aca043..00000000 --- a/packages/react-components/specs/payment_methods/payment-method-name.spec.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import PaymentMethodName from '#components/payment_methods/PaymentMethodName' -import { render } from '@testing-library/react' -import { type OrderContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' - -describe('PaymentMethodName component', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.orderId = 'NrnYhAdEkx' - } - }) - it('PaymentMethodName outside of PaymentMethod', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) -}) diff --git a/packages/react-components/specs/payment_methods/payment-method-price.spec.tsx b/packages/react-components/specs/payment_methods/payment-method-price.spec.tsx deleted file mode 100644 index fa2cf523..00000000 --- a/packages/react-components/specs/payment_methods/payment-method-price.spec.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import PaymentMethodPrice from '#components/payment_methods/PaymentMethodPrice' -import { render } from '@testing-library/react' -import { type OrderContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' - -describe('PaymentMethodPrice component', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.orderId = 'NrnYhAdEkx' - } - }) - it('PaymentMethodPrice outside of PaymentMethod', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) -}) diff --git a/packages/react-components/specs/payment_methods/payment-method-radio-button.spec.tsx b/packages/react-components/specs/payment_methods/payment-method-radio-button.spec.tsx deleted file mode 100644 index 4b5f5855..00000000 --- a/packages/react-components/specs/payment_methods/payment-method-radio-button.spec.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import PaymentMethodRadioButton from '#components/payment_methods/PaymentMethodRadioButton' -import { render } from '@testing-library/react' -import { type OrderContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' - -describe('PaymentMethodRadioButton component', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.orderId = 'NrnYhAdEkx' - } - }) - it('PaymentMethodRadioButton outside of PaymentMethod', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) -}) diff --git a/packages/react-components/specs/payment_methods/payment-method.spec.tsx b/packages/react-components/specs/payment_methods/payment-method.spec.tsx deleted file mode 100644 index 68d8741c..00000000 --- a/packages/react-components/specs/payment_methods/payment-method.spec.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import PaymentMethod from '#components/payment_methods/PaymentMethod' -import { render } from '@testing-library/react' - -describe('PaymentMethod component', () => { - it('PaymentMethod outside of PaymentMethodsContainer', () => { - expect(() => - render( - - <> - - ) - ).toThrow( - 'Cannot use outside of ' - ) - }) -}) diff --git a/packages/react-components/specs/payment_methods/payment-methods-container.spec.tsx b/packages/react-components/specs/payment_methods/payment-methods-container.spec.tsx deleted file mode 100644 index d337672c..00000000 --- a/packages/react-components/specs/payment_methods/payment-methods-container.spec.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import PaymentMethodsContainer from '#components/payment_methods/PaymentMethodsContainer' -import { render } from '@testing-library/react' - -describe('PaymentMethodsContainer component', () => { - it('PaymentMethodsContainer outside of OrderContainer', () => { - expect(() => - render( - - <> - - ) - ).toThrow( - 'Cannot use outside of ' - ) - }) -}) diff --git a/packages/react-components/specs/prices/prices.spec.tsx b/packages/react-components/specs/prices/prices.spec.tsx deleted file mode 100644 index c2e58961..00000000 --- a/packages/react-components/specs/prices/prices.spec.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import CommerceLayer from '#components/auth/CommerceLayer' -import PricesContainer from '#components/prices/PricesContainer' -import Price from '#components/prices/Price' -import { render, waitFor, screen } from '@testing-library/react' -import SkusContainer from '#components/skus/SkusContainer' -import Skus from '#components/skus/Skus' -import SkuField from '#components/skus/SkuField' -import { type SkusContext } from '../utils/context' -import { getAccessToken } from 'mocks/getAccessToken' - -describe('Prices components', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.sku = 'BABYONBU000000E63E7412MX' - ctx.skus = ['BABYONBU000000E63E7412MX', 'BABYONBU000000FFFFFF12MX'] - } - }) - it('Show single price', async (ctx) => { - render( - - - - - - ) - expect(screen.getByText('Loading...')) - await waitFor(() => screen.getByTestId(`price-${ctx.sku}`)) - const price = screen.getByTestId(`price-${ctx.sku}`) - const compare = screen.queryByTestId(`compare-${ctx.sku}`) - expect(price.textContent).not.toBe('') - expect(compare?.textContent).not.toBe('') - }) - it('Show single price with custom loading', async (ctx) => { - render( - - Caricamento...}> - - - - ) - expect(screen.getByText('Caricamento...')) - }) - it('Show single price without compare price', async (ctx) => { - render( - - - - - - ) - await waitFor(() => screen.getByTestId(`price-${ctx.sku}`)) - const price = screen.getByTestId(`price-${ctx.sku}`) - const compare = screen.queryByTestId(`compare-${ctx.sku}`) - expect(price).toBeDefined() - expect(compare).toBeNull() - }) - it('Show single price with compare class name', async (ctx) => { - render( - - - - - - ) - await waitFor(() => screen.getByTestId(`price-${ctx.sku}`)) - const price = screen.getByTestId(`price-${ctx.sku}`) - const compare = screen.queryByTestId(`compare-${ctx.sku}`) - expect(price).toBeDefined() - expect(compare?.className).toBe('compare-class-name') - }) - it('Show single price with skuCode on Price container', async (ctx) => { - render( - - - - - - ) - await waitFor(() => screen.getByTestId(`price-${ctx.sku}`)) - const price = screen.getByTestId(`price-${ctx.sku}`) - const compare = screen.queryByTestId(`compare-${ctx.sku}`) - expect(price.textContent).not.toBe('') - expect(compare?.textContent).not.toBe('') - }) - it('Show twice prices', async (ctx) => { - render( - - - {ctx.skus.map((sku, index) => ( - - ))} - - - ) - for await (const sku of ctx.skus) { - await waitFor(() => screen.getByTestId(`price-${sku}`)) - const price = screen.getByTestId(`price-${sku}`) - const compare = screen.queryByTestId(`compare-${sku}`) - expect(price.textContent).not.toBe('') - expect(compare?.textContent).not.toBe('') - } - }) - it('Show twice prices using Skus components', async (ctx) => { - render( - - - - - - - - - - - - ) - for await (const sku of ctx.skus) { - await waitFor(() => screen.getByTestId(`price-${sku}`)) - const price = screen.getByTestId(`price-${sku}`) - const compare = screen.queryByTestId(`compare-${sku}`) - expect(price.textContent).not.toBe('') - expect(compare?.textContent).not.toBe('') - } - }) -}) diff --git a/packages/react-components/specs/skus/availability-container.spec.tsx b/packages/react-components/specs/skus/availability-container.spec.tsx new file mode 100644 index 00000000..6a3df1dd --- /dev/null +++ b/packages/react-components/specs/skus/availability-container.spec.tsx @@ -0,0 +1,113 @@ +import CommerceLayer from '#components/auth/CommerceLayer' +import { AvailabilityContainer } from '#components/skus/AvailabilityContainer' +import { AvailabilityTemplate } from '#components/skus/AvailabilityTemplate' +import AvailabilityContext from '#context/AvailabilityContext' +import { getAccessToken } from 'mocks/getAccessToken' +import { render, screen, waitFor } from '@testing-library/react' +import { createElement, useContext, type ReactNode } from 'react' +import { SWRConfig } from 'swr' +import { type AvailabilityContext as AvailabilityCtx } from '../utils/context' + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +function AvailabilityInspector({ + onCapture +}: { + onCapture: (quantity: number | undefined) => void +}) { + const { quantity } = useContext(AvailabilityContext) + onCapture(quantity) + return null +} + +describe('AvailabilityContainer component', () => { + beforeEach(async (ctx) => { + const { accessToken, endpoint } = await getAccessToken() + if (accessToken != null && endpoint != null) { + ctx.accessToken = accessToken + ctx.endpoint = endpoint + ctx.skuCode = 'BABYONBU000000E63E7412MX' + } + }) + + it('renders children', (ctx) => { + const { container } = render( + + + availability + + , + { wrapper: swrWrapper } + ) + expect(container.querySelector('[data-testid="child"]')).not.toBeNull() + }) + + it('fetches availability and exposes quantity in context', async (ctx) => { + let capturedQty: number | undefined + render( + + + { capturedQty = q }} /> + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => expect(capturedQty).toBeDefined(), + { timeout: 10000 } + ) + expect(typeof capturedQty).toBe('number') + }) + + it('renders AvailabilityTemplate with available or out-of-stock text', async (ctx) => { + render( + + + + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => { + const span = screen.getByTestId(`availability-${ctx.skuCode}`) + expect(span.textContent).toMatch(/Available|Out of stock/) + }, + { timeout: 10000 } + ) + }) + + it('calls getQuantity callback when quantity is fetched', async (ctx) => { + const onQuantity = vi.fn() + render( + + + + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => expect(onQuantity).toHaveBeenCalledWith(expect.any(Number)), + { timeout: 10000 } + ) + }) + + it('renders nothing meaningful when skuCode is empty', (ctx) => { + const { container } = render( + + + + + , + { wrapper: swrWrapper } + ) + const spans = container.querySelectorAll('span') + spans.forEach((span) => { + expect(span.textContent).toBe('') + }) + }) +}) diff --git a/packages/react-components/specs/skus/availability.spec.tsx b/packages/react-components/specs/skus/availability.spec.tsx deleted file mode 100644 index 76925fdc..00000000 --- a/packages/react-components/specs/skus/availability.spec.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import AvailabilityContainer from '#components/skus/AvailabilityContainer' -import AvailabilityTemplate from '#components/skus/AvailabilityTemplate' -import CommerceLayer from '#components/auth/CommerceLayer' -import { render, screen, waitFor } from '@testing-library/react' -import { type SkusContext } from '../utils/context' -import Skus from '#components/skus/Skus' -import { SkusContainer } from '#components/skus/SkusContainer' -import SkuField from '#components/skus/SkuField' -import { getAccessToken } from 'mocks/getAccessToken' - -describe('AvailabilityContainer component', () => { - beforeEach(async (ctx) => { - const { accessToken, endpoint } = await getAccessToken() - if (accessToken != null && endpoint != null) { - ctx.accessToken = accessToken - ctx.endpoint = endpoint - ctx.sku = 'BABYONBU000000E63E7412MX' - ctx.skuId = 'wZeDdSamqn' - ctx.skus = ['BABYONBU000000E63E7412MX', 'BABYONBU000000FFFFFF12MX'] - } - }) - it('AvailabilityContainer outside of CommerceLayer', () => { - expect(() => - render( - - <> - - ) - ).toThrow('Cannot use outside of ') - }) - it('AvailabilityTemplate outside of AvailabilityContainer', () => { - expect(() => render()).toThrow( - 'Cannot use outside of ' - ) - }) - it('Show SKU availability', async (ctx) => { - render( - - - - - - ) - await waitFor( - async () => await screen.findByText(`Available`, { exact: false }), - { - timeout: 5000 - } - ) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toContain('Available') - }) - it('Show SKU availability by SKU Id', async (ctx) => { - render( - - - - - - ) - await waitFor( - async () => await screen.findByText(`Available`, { exact: false }), - { - timeout: 5000 - } - ) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toContain('Available') - }) - it('Show SKU availability with loading callback', async (ctx) => { - const mock = vi.fn().mockImplementation((quantity: number) => { - expect(quantity).toBeGreaterThan(0) - }) - render( - - - - - - ) - await waitFor( - async () => await screen.findByText(`Available`, { exact: false }), - { - timeout: 5000 - } - ) - expect(mock).toHaveBeenCalledTimes(1) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toContain('Available') - }) - it('Show SKU availability with shipping method name', async (ctx) => { - render( - - - - - - ) - await waitFor( - async () => await screen.findByText(`Available`, { exact: false }), - { - timeout: 5000 - } - ) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toContain('Available') - expect(template.textContent).toContain('Standard Shipping EU') - }) - it('Show SKU availability with shipping method price', async (ctx) => { - render( - - - - - - ) - await waitFor( - async (): Promise => - await screen.findByText(`Available`, { exact: false }), - { - timeout: 5000 - } - ) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toContain('Available') - expect(template.textContent).toContain('5,00') - }) - it('Show SKU availability with shipping method name and price', async (ctx) => { - render( - - - - - - ) - await waitFor( - async (): Promise => - await screen.findByText(`Available`, { exact: false }), - { - timeout: 5000 - } - ) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toContain('Available') - expect(template.textContent).toContain('Standard Shipping EU') - expect(template.textContent).toContain('5,00') - }) - it('Show SKU availability with custom component', async (ctx) => { - render( - - - - {(props) => ( - - {props.quantity > 1 ? 'Disponibile' : 'Non disponibile'} - - )} - - - - ) - await waitFor(async () => await screen.findByText(`Disponibile`), { - timeout: 5000 - }) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toBe('Disponibile') - }) - it('Show SKU out of stock', async (ctx) => { - render( - - - - - - ) - await waitFor( - async (): Promise => await screen.findByText(`Out of stock`), - { - timeout: 5000 - } - ) - const template = screen.getByTestId('availability-template') - expect(template.textContent).toBe('Out of stock') - }) - it('Show twice availability using Skus components', async (ctx) => { - const skus = ['BABYONBU000000E63E7412MX', 'BABYONBU000000E63E746MXX'] - render( - - - - - - - - - - - - ) - for await (const sku of skus) { - await waitFor(() => screen.getByTestId(sku)) - await waitFor(() => screen.getByTestId(`availability-${sku}`)) - const code = screen.getByTestId(sku) - const compare = screen.getByTestId(`availability-${sku}`) - expect(code.textContent).not.toBe('') - expect(compare.textContent).not.toBe('') - if (sku === skus[1]) { - expect(compare.textContent).toBe('Out of stock') - } else { - expect(compare.textContent).toBe('Available') - } - } - }) -}) diff --git a/packages/react-components/specs/skus/sku-lists-container-unit.spec.tsx b/packages/react-components/specs/skus/sku-lists-container-unit.spec.tsx new file mode 100644 index 00000000..263040db --- /dev/null +++ b/packages/react-components/specs/skus/sku-lists-container-unit.spec.tsx @@ -0,0 +1,55 @@ +import { SkuListsContainer } from '#components/skus/SkuListsContainer' +import { SkuList } from '#components/skus/SkuList' +import CommerceLayerContext from '#context/CommerceLayerContext' +import SkuListsContext from '#context/SkuListsContext' +import { render, waitFor } from '@testing-library/react' +import { createElement, useContext, type ReactNode } from 'react' +import { SWRConfig } from 'swr' +import { vi } from 'vitest' + +vi.mock('@commercelayer/hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useSkuLists: vi.fn(() => ({ + retrieveSkuList: vi.fn().mockResolvedValue({ + skus: [{ code: 'SKU001', id: '1', type: 'skus' }] + }) + })) + } +}) + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +function SkuListsInspector({ + onCapture +}: { + onCapture: (v: Record) => void +}) { + const { skuLists } = useContext(SkuListsContext) + onCapture(skuLists) + return null +} + +describe('SkuListsContainer – unit (mocked useSkuLists)', () => { + it('fetches sku lists and populates skuLists context when accessToken and ids are present', async () => { + let captured: Record = {} + render( + + + + + + { captured = v }} /> + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => expect(Object.keys(captured)).toContain('list-001'), + { timeout: 5000 } + ) + expect(Array.isArray(captured['list-001'])).toBe(true) + }) +}) diff --git a/packages/react-components/specs/skus/sku-lists-container.spec.tsx b/packages/react-components/specs/skus/sku-lists-container.spec.tsx new file mode 100644 index 00000000..d2a14c8a --- /dev/null +++ b/packages/react-components/specs/skus/sku-lists-container.spec.tsx @@ -0,0 +1,87 @@ +import CommerceLayer from '#components/auth/CommerceLayer' +import { SkuList } from '#components/skus/SkuList' +import { SkuListsContainer } from '#components/skus/SkuListsContainer' +import SkuListsContext from '#context/SkuListsContext' +import { getAccessToken } from 'mocks/getAccessToken' +import { render, screen, waitFor } from '@testing-library/react' +import { createElement, useContext, type ReactNode } from 'react' +import { SWRConfig } from 'swr' +import { type SkuListsContext as SkuListsCtx } from '../utils/context' +import { getSkuLists } from '@commercelayer/core' + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +function SkuListsInspector({ + onCapture +}: { + onCapture: (v: Record) => void +}) { + const { skuLists } = useContext(SkuListsContext) + onCapture(skuLists) + return null +} + +describe('SkuListsContainer component', () => { + beforeEach(async (ctx) => { + const { accessToken, endpoint } = await getAccessToken() + if (accessToken != null && endpoint != null) { + ctx.accessToken = accessToken + ctx.endpoint = endpoint + const lists = await getSkuLists({ accessToken, params: { pageSize: 1 } }) + ctx.skuListId = lists.first()?.id ?? '' + } + }) + + it('renders children inside SkuListsContainer', (ctx) => { + const { container } = render( + + +
content
+
+
, + { wrapper: swrWrapper } + ) + expect(container.querySelector('[data-testid="child"]')).not.toBeNull() + }) + + it('registers a SkuList id and renders its children', async (ctx) => { + if (!ctx.skuListId) return + render( + + + + item + + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => expect(screen.getByTestId(`list-${ctx.skuListId}`)).toBeTruthy(), + { timeout: 5000 } + ) + expect(screen.getByTestId(`list-${ctx.skuListId}`).textContent).toBe('item') + }) + + it('fetches sku list and populates skuLists in context', async (ctx) => { + if (!ctx.skuListId) return + let captured: Record = {} + render( + + + + + + { captured = v }} /> + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => expect(Object.keys(captured)).toContain(ctx.skuListId), + { timeout: 10000 } + ) + expect(Array.isArray(captured[ctx.skuListId])).toBe(true) + }) +}) diff --git a/packages/react-components/specs/skus/skus-container.spec.tsx b/packages/react-components/specs/skus/skus-container.spec.tsx new file mode 100644 index 00000000..2381075d --- /dev/null +++ b/packages/react-components/specs/skus/skus-container.spec.tsx @@ -0,0 +1,125 @@ +import CommerceLayer from '#components/auth/CommerceLayer' +import SkuField from '#components/skus/SkuField' +import Skus from '#components/skus/Skus' +import { SkusContainer } from '#components/skus/SkusContainer' +import { getAccessToken } from 'mocks/getAccessToken' +import { render, screen, waitFor } from '@testing-library/react' +import { createElement, type ReactNode } from 'react' +import { SWRConfig } from 'swr' +import { type SkusContext } from '../utils/context' + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +describe('SkusContainer component', () => { + beforeEach(async (ctx) => { + const { accessToken, endpoint } = await getAccessToken() + if (accessToken != null && endpoint != null) { + ctx.accessToken = accessToken + ctx.endpoint = endpoint + ctx.sku = 'BABYONBU000000E63E7412MX' + ctx.skus = ['BABYONBU000000E63E7412MX', 'BABYONBU000000FFFFFF12MX'] + } + }) + + it('renders SKU code fields for all skus', async (ctx) => { + render( + + + + + + + , + { wrapper: swrWrapper } + ) + for await (const sku of ctx.skus) { + await waitFor(() => screen.getByTestId(sku), { timeout: 10000 }) + expect(screen.getByTestId(sku).textContent).toBe(sku) + } + }) + + it('renders correct number of SKU items', async (ctx) => { + render( + + + + + + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => expect(screen.getAllByTestId(/^BABY/)).toHaveLength(ctx.skus.length), + { timeout: 10000 } + ) + }) + + it('renders SKU name field', async (ctx) => { + render( + + + + + + + , + { wrapper: swrWrapper } + ) + await waitFor(() => screen.getByTestId('sku-name'), { timeout: 10000 }) + expect(screen.getByTestId('sku-name').textContent).not.toBe('') + }) + + it('renders SKU image via image_url field', async (ctx) => { + const { container } = render( + + + + + + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => { + const img = container.querySelector('img') + expect(img).not.toBeNull() + expect(img?.getAttribute('src')).toBeTruthy() + }, + { timeout: 10000 } + ) + }) + + it('renders nothing when skus prop is empty', (ctx) => { + const { container } = render( + + + + + + + , + { wrapper: swrWrapper } + ) + expect(container.querySelectorAll('p')).toHaveLength(0) + }) + + it('applies queryParams alongside code_in filter', async (ctx) => { + render( + + + + + + + , + { wrapper: swrWrapper } + ) + await waitFor( + () => expect(screen.getAllByTestId(/^BABY/).length).toBeGreaterThanOrEqual(1), + { timeout: 10000 } + ) + }) +}) diff --git a/packages/react-components/specs/skus/skus-unit.spec.tsx b/packages/react-components/specs/skus/skus-unit.spec.tsx new file mode 100644 index 00000000..05df2614 --- /dev/null +++ b/packages/react-components/specs/skus/skus-unit.spec.tsx @@ -0,0 +1,320 @@ +import { AvailabilityTemplate } from '#components/skus/AvailabilityTemplate' +import { DeliveryLeadTime } from '#components/skus/DeliveryLeadTime' +import { SkuField } from '#components/skus/SkuField' +import { SkuList } from '#components/skus/SkuList' +import { SkuListsContainer } from '#components/skus/SkuListsContainer' +import Parent from '#components/utils/Parent' +import AvailabilityContext from '#context/AvailabilityContext' +import CommerceLayerContext from '#context/CommerceLayerContext' +import ShippingMethodChildrenContext from '#context/ShippingMethodChildrenContext' +import SkuChildrenContext from '#context/SkuChildrenContext' +import SkuListsContext from '#context/SkuListsContext' +import { render, screen, waitFor } from '@testing-library/react' +import { createElement, type ReactNode } from 'react' +import { SWRConfig } from 'swr' + +const swrWrapper = ({ children }: { children: ReactNode }) => + createElement(SWRConfig, { value: { provider: () => new Map() } }, children) + +// --------------------------------------------------------------------------- +// DeliveryLeadTime +// --------------------------------------------------------------------------- + +describe('DeliveryLeadTime component', () => { + it('renders the min_days value from context', () => { + const mockContext = { + deliveryLeadTimeForShipment: { + min_days: 2, + max_days: 5, + min_hours: 48, + max_hours: 120 + } as any + } + render( + + + + ) + expect(screen.getByTestId('lead-time').textContent).toBe('2') + }) + + it('renders nothing when context has no deliveryLeadTime', () => { + render( + + + + ) + expect(screen.getByTestId('lead-time').textContent).toBe('') + }) + + it('clears text on unmount via useEffect cleanup', () => { + const { unmount } = render( + + + + ) + unmount() + // verifies the cleanup function (setText('')) runs without errors + }) + + it('renders via children render prop', () => { + const mockContext = { + deliveryLeadTimeForShipment: { min_days: 3 } as any + } + render( + + + {({ text }) => {text}} + + + ) + expect(screen.getByTestId('custom')).toBeDefined() + }) +}) + +// --------------------------------------------------------------------------- +// AvailabilityTemplate +// --------------------------------------------------------------------------- + +describe('AvailabilityTemplate component', () => { + const wrapWithContext = (qty: number | undefined, min?: number, max?: number) => + createElement( + AvailabilityContext.Provider, + { + value: { + quantity: qty, + skuCode: 'TESTSKU', + parent: true, + min: min != null ? { hours: min * 24, days: min } : undefined, + max: max != null ? { hours: max * 24, days: max } : undefined + } + }, + createElement(AvailabilityTemplate, { + labels: { available: 'Available', outOfStock: 'Out of stock', negativeStock: 'Negative' } + }) + ) + + it('shows available text when quantity > 0', () => { + render(wrapWithContext(5)) + expect(screen.getByTestId('availability-TESTSKU').textContent).toContain('Available') + }) + + it('shows out of stock when quantity === 0', () => { + render(wrapWithContext(0)) + expect(screen.getByTestId('availability-TESTSKU').textContent).toContain('Out of stock') + }) + + it('shows negative stock text when quantity < 0', () => { + render(wrapWithContext(-1)) + expect(screen.getByTestId('availability-TESTSKU').textContent).toContain('Negative') + }) + + it('renders empty span when quantity is undefined', () => { + render( + createElement( + AvailabilityContext.Provider, + { value: { skuCode: 'TESTSKU-UNDEF', parent: true } }, + createElement(AvailabilityTemplate) + ) + ) + expect(screen.getByTestId('availability-TESTSKU-UNDEF').textContent).toBe('') + }) + + it('shows delivery lead time with timeFormat when min/max are set', () => { + render( + createElement( + AvailabilityContext.Provider, + { + value: { + quantity: 10, + skuCode: 'TESTSKU2', + parent: true, + min: { hours: 24, days: 1 }, + max: { hours: 72, days: 3 } + } + }, + createElement(AvailabilityTemplate, { timeFormat: 'days' }) + ) + ) + expect(screen.getByTestId('availability-TESTSKU2').textContent).toContain('1') + }) + + it('shows shipping method name when showShippingMethodName is true', () => { + render( + createElement( + AvailabilityContext.Provider, + { + value: { + quantity: 10, + skuCode: 'TESTSKU-SM', + parent: true, + min: { hours: 24, days: 1 }, + max: { hours: 72, days: 3 }, + shipping_method: { name: 'Express', id: '1', type: 'shipping_methods' } as any + } + }, + createElement(AvailabilityTemplate, { timeFormat: 'days', showShippingMethodName: true }) + ) + ) + expect(screen.getByTestId('availability-TESTSKU-SM').textContent).toContain('with Express') + }) + + it('shows shipping method price when showShippingMethodPrice is true', () => { + render( + createElement( + AvailabilityContext.Provider, + { + value: { + quantity: 10, + skuCode: 'TESTSKU-PRICE', + parent: true, + min: { hours: 24, days: 1 }, + max: { hours: 72, days: 3 }, + shipping_method: { name: 'Standard', formatted_price_amount: '$5.00', id: '1', type: 'shipping_methods' } as any + } + }, + createElement(AvailabilityTemplate, { timeFormat: 'days', showShippingMethodPrice: true }) + ) + ) + expect(screen.getByTestId('availability-TESTSKU-PRICE').textContent).toContain('($5.00)') + }) + + it('renders via children render prop', () => { + render( + createElement( + AvailabilityContext.Provider, + { value: { quantity: 5, skuCode: 'TESTSKU3', parent: true } }, + createElement(AvailabilityTemplate, { + children: ({ quantity }) => + createElement('span', { 'data-testid': 'custom-avail' }, String(quantity)) + }) + ) + ) + expect(screen.getByTestId('custom-avail').textContent).toBe('5') + }) +}) + +// --------------------------------------------------------------------------- +// SkuField children render prop +// --------------------------------------------------------------------------- + +describe('SkuField component', () => { + it('renders via children render prop', () => { + render( + + + {({ attributeValue }) => ( + {String(attributeValue)} + )} + + + ) + expect(screen.getByTestId('custom-field').textContent).toBe('SKU001') + }) + + it('renders default span tag when no children provided', () => { + render( + + + + ) + expect(screen.getByTestId('SKU002').textContent).toBe('SKU002') + }) + + it('renders custom tagElement when provided', () => { + render( + + + + ) + const el = screen.getByTestId('SKU003') + expect(el.tagName.toLowerCase()).toBe('p') + expect(el.textContent).toBe('SKU003') + }) + + it('renders img tag when tagElement is img', () => { + const { container } = render( + + + + ) + const img = container.querySelector('img') + expect(img).not.toBeNull() + expect(img?.getAttribute('src')).toBe('https://example.com/img.jpg') + }) + + it('renders img with defaultImgUrl when attribute value is empty', () => { + const { container } = render( + + + + ) + const img = container.querySelector('img') + expect(img).not.toBeNull() + expect(img?.getAttribute('src')).toBeTruthy() + }) +}) + +// --------------------------------------------------------------------------- +// Parent utility component +// --------------------------------------------------------------------------- + +describe('Parent component', () => { + it('returns null when children is undefined', () => { + const { container } = render() + expect(container.firstChild).toBeNull() + }) + + it('renders children when provided', () => { + const Child = ({ label }: { label: string }) => ( + {label} + ) + render({Child}) + expect(screen.getByTestId('parent-child')).toBeDefined() + }) +}) + +// --------------------------------------------------------------------------- +// SkuList inside SkuListsContainer +// --------------------------------------------------------------------------- + +describe('SkuList component', () => { + it('renders its children', () => { + render( + + + + content + + + , + { wrapper: swrWrapper } + ) + expect(screen.getByTestId('list-child').textContent).toBe('content') + }) + + it('registers the id in SkuListsContext', async () => { + let capturedIds: string[] = [] + render( + + + + + + + {({ listIds }) => { + capturedIds = listIds + return null + }} + + + , + { wrapper: swrWrapper } + ) + await waitFor(() => expect(capturedIds).toContain('my-list'), { timeout: 2000 }) + }) +}) diff --git a/packages/react-components/specs/utils/context.ts b/packages/react-components/specs/utils/context.ts index a8739b89..04efc2e5 100644 --- a/packages/react-components/specs/utils/context.ts +++ b/packages/react-components/specs/utils/context.ts @@ -14,3 +14,11 @@ export interface SkusContext extends LocalContext { skus: string[] skuId: string } + +export interface SkuListsContext extends LocalContext { + skuListId: string +} + +export interface AvailabilityContext extends LocalContext { + skuCode: string +} diff --git a/packages/react-components/specs/utils/use-custom-context.spec.tsx b/packages/react-components/specs/utils/use-custom-context.spec.tsx new file mode 100644 index 00000000..108ccaed --- /dev/null +++ b/packages/react-components/specs/utils/use-custom-context.spec.tsx @@ -0,0 +1,51 @@ +import { render } from "@testing-library/react" +import { vi } from "vitest" +import AvailabilityContext from "#context/AvailabilityContext" +import useCustomContext from "#utils/hooks/useCustomContext" + +function ContextChecker({ keyProp }: { keyProp: string | null }) { + useCustomContext({ + context: AvailabilityContext, + contextComponentName: "AvailabilityContainer", + currentComponentName: "TestComponent", + key: keyProp as string, + }) + return null +} + +describe("useCustomContext hook", () => { + it("returns context when key is present in context object", () => { + render( + + + , + ) + }) + + it("returns context when key is null and context is non-null", () => { + render( + + + , + ) + }) + + it("throws when key is not found in context (used outside provider)", () => { + const consoleError = vi.spyOn(console, "error").mockImplementation(() => {}) + expect(() => render()).toThrow( + "Cannot use outside of ", + ) + consoleError.mockRestore() + }) + + it("logs console.error in production when key is not found", () => { + vi.stubEnv("NODE_ENV", "production") + const consoleError = vi.spyOn(console, "error").mockImplementation(() => {}) + render() + expect(consoleError).toHaveBeenCalledWith( + expect.stringContaining("Cannot use "), + ) + consoleError.mockRestore() + vi.unstubAllEnvs() + }) +}) diff --git a/packages/react-components/src/components/SubmitButton.tsx b/packages/react-components/src/components/SubmitButton.tsx index f5092cd2..35072a90 100644 --- a/packages/react-components/src/components/SubmitButton.tsx +++ b/packages/react-components/src/components/SubmitButton.tsx @@ -1,13 +1,12 @@ import type { ReactNode, JSX } from 'react'; import Parent from '#components/utils/Parent' import type { ChildrenFunction } from '#typings/index' -import isFunction from 'lodash/isFunction' interface ChildrenProps extends Omit {} interface Props extends Omit { children?: ChildrenFunction - label?: string | ReactNode + label?: string | ReactNode | (() => ReactNode) } export function SubmitButton(props: Props): JSX.Element { @@ -20,7 +19,7 @@ export function SubmitButton(props: Props): JSX.Element { {children} ) : ( ) } diff --git a/packages/react-components/src/components/addresses/Address.tsx b/packages/react-components/src/components/addresses/Address.tsx index 071f0205..4f2d5351 100644 --- a/packages/react-components/src/components/addresses/Address.tsx +++ b/packages/react-components/src/components/addresses/Address.tsx @@ -1,5 +1,5 @@ import type { Address as AddressType } from "@commercelayer/sdk" -import isEmpty from "lodash/isEmpty" +import { isEmpty } from "#utils/isEmpty" import { type JSX, useContext, useEffect, useState } from "react" import AddressCardsTemplate, { type AddressCardsTemplateChildren, @@ -66,6 +66,7 @@ export function Address(props: Props): JSX.Element { const items = !isEmpty(addresses) ? addresses : (addressesContext && addressesContext) || [] + // biome-ignore lint/correctness/useExhaustiveDependencies: intentional effect with stable context refs useEffect(() => { if (items && !deselect) { items.forEach((address, k) => { @@ -162,7 +163,10 @@ export function Address(props: Props): JSX.Element { ? `${className || ""} ${disabledClassName}` : addressSelectedClass return ( + // biome-ignore lint/suspicious/noArrayIndexKey: address list has no stable unique key other than index + {/* biome-ignore lint/a11y/noStaticElementInteractions: address card uses div for flexible layout */} + {/* biome-ignore lint/a11y/useKeyWithClickEvents: address card uses div for flexible layout */}
{ diff --git a/packages/react-components/src/components/addresses/AddressCountrySelector.tsx b/packages/react-components/src/components/addresses/AddressCountrySelector.tsx index 699c42db..8e635a75 100644 --- a/packages/react-components/src/components/addresses/AddressCountrySelector.tsx +++ b/packages/react-components/src/components/addresses/AddressCountrySelector.tsx @@ -58,7 +58,7 @@ export function AddressCountrySelector(props: Props): JSX.Element { if (value && customerAddress?.setValue) { customerAddress.setValue(name, value) } - }, [value]) + }, [value, billingAddress.setValue, customerAddress.setValue, name, shippingAddress.setValue]) const hasError = useMemo(() => { if (billingAddress?.errors?.[name]?.error) { @@ -72,10 +72,9 @@ export function AddressCountrySelector(props: Props): JSX.Element { } return false }, [ - value, - billingAddress?.errors, - shippingAddress?.errors, - customerAddress?.errors + billingAddress?.errors, + shippingAddress?.errors, + customerAddress?.errors, name ]) const errorClassName = billingAddress?.errorClassName || @@ -88,6 +87,7 @@ export function AddressCountrySelector(props: Props): JSX.Element { ) : ( + // biome-ignore lint/a11y/noStaticElementInteractions: anchor used as action trigger per existing API + // biome-ignore lint/a11y/useValidAnchor: href intentionally omitted for action-only anchor {label} diff --git a/packages/react-components/src/components/addresses/AddressInputSelect.tsx b/packages/react-components/src/components/addresses/AddressInputSelect.tsx index c2db7549..d578fb59 100644 --- a/packages/react-components/src/components/addresses/AddressInputSelect.tsx +++ b/packages/react-components/src/components/addresses/AddressInputSelect.tsx @@ -47,7 +47,7 @@ export function AddressInputSelect(props: Props): JSX.Element { if (value && shippingAddress?.setValue) { shippingAddress.setValue(name, value) } - }, [value]) + }, [value, billingAddress.setValue, name, shippingAddress.setValue]) const hasError = useMemo(() => { if (billingAddress?.errors?.[name]?.error) { @@ -57,7 +57,7 @@ export function AddressInputSelect(props: Props): JSX.Element { return true } return false - }, [value, billingAddress?.errors, shippingAddress?.errors]) + }, [billingAddress?.errors, shippingAddress?.errors, name]) const errorClassName = billingAddress?.errorClassName || shippingAddress?.errorClassName const classNameComputed = `${className ?? ''} ${ @@ -66,6 +66,7 @@ export function AddressInputSelect(props: Props): JSX.Element { return ( { if (!include?.includes('billing_address')) { addResourceToInclude({ diff --git a/packages/react-components/src/components/addresses/BillingAddressForm.tsx b/packages/react-components/src/components/addresses/BillingAddressForm.tsx index a9ab9237..8c7ddfd8 100644 --- a/packages/react-components/src/components/addresses/BillingAddressForm.tsx +++ b/packages/react-components/src/components/addresses/BillingAddressForm.tsx @@ -58,7 +58,7 @@ export function BillingAddressForm(props: Props): JSX.Element { reset: resetForm, setValue: setValueForm, setError: setErrorForm, - } = useRapidForm({ fieldEvent }) + } = (useRapidForm as any)({ fieldEvent }) const { setAddressErrors, setAddress, isBusiness } = useContext(AddressesContext) const { @@ -102,7 +102,6 @@ export function BillingAddressForm(props: Props): JSX.Element { if (inError) { const errorMsg = errors[fieldName]?.message if (errorMsg != null && errorMsg !== customMessage) { - // @ts-expect-error no type errors[fieldName].message = customMessage } } else { @@ -121,7 +120,6 @@ export function BillingAddressForm(props: Props): JSX.Element { if (fieldInError) { const errorMsg = errors[field]?.message if (errorMsg != null && errorMsg !== message) { - // @ts-expect-error no type errors[field].message = message setValueForm(field, value ?? "") } @@ -179,7 +177,6 @@ export function BillingAddressForm(props: Props): JSX.Element { } } setAddress({ - // @ts-expect-error no type values: { ...values, ...(isBusiness && { business: isBusiness }), @@ -215,11 +212,9 @@ export function BillingAddressForm(props: Props): JSX.Element { } if (ref) { ref.current?.reset() - // @ts-expect-error no type resetForm({ target: ref.current }) setAddressErrors([], "billing_address") - // @ts-expect-error no type - setAddress({ values: {}, resource: "billing_address" }) + setAddress({ values: {} as any, resource: "billing_address" }) } } }, [errors, values, reset, include, includeLoaded, isBusiness]) @@ -249,7 +244,6 @@ export function BillingAddressForm(props: Props): JSX.Element { requiresBillingInfo: order?.requires_billing_info || false, errors: errors as any, resetField: (name: string) => { - // @ts-expect-error no type resetForm({ currentTarget: ref.current }, name) }, } diff --git a/packages/react-components/src/components/addresses/SaveAddressesButton.tsx b/packages/react-components/src/components/addresses/SaveAddressesButton.tsx index 4a5fcbe3..6e5da80c 100644 --- a/packages/react-components/src/components/addresses/SaveAddressesButton.tsx +++ b/packages/react-components/src/components/addresses/SaveAddressesButton.tsx @@ -1,5 +1,4 @@ import type { Order } from "@commercelayer/sdk" -import isFunction from "lodash/isFunction" import { type JSX, type ReactNode, useContext, useState } from "react" import Parent from "#components/utils/Parent" import AddressContext from "#context/AddressContext" @@ -24,7 +23,7 @@ interface ChildrenProps extends Omit {} interface Props extends Omit { children?: ChildrenFunction - label?: string | ReactNode + label?: string | ReactNode | (() => ReactNode) onClick?: (params: TOnClick) => void addressId?: string requiredMetadataFields?: string[] @@ -179,7 +178,7 @@ export function SaveAddressesButton(props: Props): JSX.Element { }} {...p} > - {isFunction(label) ? label() : label} + {typeof label === 'function' ? label() : label} ) } diff --git a/packages/react-components/src/components/addresses/ShippingAddressContainer.tsx b/packages/react-components/src/components/addresses/ShippingAddressContainer.tsx index 2e1c0aaa..e57a5c84 100644 --- a/packages/react-components/src/components/addresses/ShippingAddressContainer.tsx +++ b/packages/react-components/src/components/addresses/ShippingAddressContainer.tsx @@ -23,6 +23,7 @@ export function ShippingAddressContainer(props: Props): JSX.Element { const config = useContext(CommerceLayerContext) const { order } = useContext(OrderContext) const { setCloneAddress } = useContext(AddressContext) + // biome-ignore lint/correctness/useExhaustiveDependencies: intentional effect with stable context refs useEffect(() => { if (order && config) { setShippingCustomerAddressId({ diff --git a/packages/react-components/src/components/addresses/ShippingAddressForm.tsx b/packages/react-components/src/components/addresses/ShippingAddressForm.tsx index 5248c99f..724e830f 100644 --- a/packages/react-components/src/components/addresses/ShippingAddressForm.tsx +++ b/packages/react-components/src/components/addresses/ShippingAddressForm.tsx @@ -53,7 +53,7 @@ export function ShippingAddressForm(props: Props): JSX.Element { reset: resetForm, setValue: setValueForm, setError: setErrorForm, - } = useRapidForm({ fieldEvent }) + } = (useRapidForm as any)({ fieldEvent }) const { setAddressErrors, setAddress, @@ -101,7 +101,6 @@ export function ShippingAddressForm(props: Props): JSX.Element { if (inError) { const errorMsg = errors[fieldName]?.message if (errorMsg != null && errorMsg !== customMessage) { - // @ts-expect-error no type errors[fieldName].message = customMessage } } else { @@ -120,7 +119,6 @@ export function ShippingAddressForm(props: Props): JSX.Element { if (fieldInError) { const errorMsg = errors[field]?.message if (errorMsg != null && errorMsg !== message) { - // @ts-expect-error no type errors[field].message = message setValueForm(field, value ?? "") } @@ -183,7 +181,6 @@ export function ShippingAddressForm(props: Props): JSX.Element { } } setAddress({ - // @ts-expect-error no type values: { ...values, ...(isBusiness && { business: isBusiness }), @@ -215,11 +212,9 @@ export function ShippingAddressForm(props: Props): JSX.Element { } if (ref) { ref.current?.reset() - // @ts-expect-error no type resetForm({ target: ref.current }) setAddressErrors([], "shipping_address") - // @ts-expect-error no type - setAddress({ values: {}, resource: "shipping_address" }) + setAddress({ values: {} as any, resource: "shipping_address" }) } } }, [ @@ -255,7 +250,6 @@ export function ShippingAddressForm(props: Props): JSX.Element { errorClassName, errors: errors as any, resetField: (name: string) => { - // @ts-expect-error no type resetForm({ currentTarget: ref.current }, name) }, } as any diff --git a/packages/react-components/src/components/customers/CustomerAddressForm.tsx b/packages/react-components/src/components/customers/CustomerAddressForm.tsx index d6aa2030..0d31ae9b 100644 --- a/packages/react-components/src/components/customers/CustomerAddressForm.tsx +++ b/packages/react-components/src/components/customers/CustomerAddressForm.tsx @@ -7,7 +7,6 @@ import type { AddressField } from '#reducers/AddressReducer' import type { AddressCountrySelectName, AddressInputName } from '#typings' import OrderContext from '#context/OrderContext' import { isEmptyStates } from '#utils/countryStateCity' -import type { TCustomerAddress } from '#reducers/CustomerReducer' interface Props extends Omit { children: ReactNode @@ -29,7 +28,7 @@ export function CustomerAddressForm(props: Props): JSX.Element { countriesWithPredefinedStateOptions, ...p } = props - const { validation, values, errors, reset: resetForm } = useRapidForm() + const { validation, values, errors, reset: resetForm } = (useRapidForm as any)() const { setAddressErrors, setAddress } = useContext(AddressesContext) const { order } = useContext(OrderContext) const ref = useRef(null) @@ -86,7 +85,7 @@ export function CustomerAddressForm(props: Props): JSX.Element { } } setAddress({ - values: values as TCustomerAddress, + values: values as any, resource: 'billing_address' }) } @@ -96,11 +95,9 @@ export function CustomerAddressForm(props: Props): JSX.Element { ) { if (ref) { ref.current?.reset() - // @ts-expect-error no type resetForm({ target: ref.current }) setAddressErrors([], 'billing_address') - // @ts-expect-error Check this type - setAddress({ values: {}, resource: 'billing_address' }) + setAddress({ values: {} as any, resource: 'billing_address' }) } } }, [errors, values, reset]) @@ -112,7 +109,7 @@ export function CustomerAddressForm(props: Props): JSX.Element { [name.replace('billing_address_', '')]: value } setAddress({ - values: { ...(values as TCustomerAddress), ...field }, + values: { ...(values as any), ...field }, resource: 'billing_address' }) } @@ -124,7 +121,6 @@ export function CustomerAddressForm(props: Props): JSX.Element { requiresBillingInfo: order?.requires_billing_info || false, errors: errors as any, resetField: (name: string) => { - // @ts-expect-error no type resetForm({ currentTarget: ref.current }, name) } } diff --git a/packages/react-components/src/components/customers/CustomerInput.tsx b/packages/react-components/src/components/customers/CustomerInput.tsx index 77c2eabd..b7d76fb7 100644 --- a/packages/react-components/src/components/customers/CustomerInput.tsx +++ b/packages/react-components/src/components/customers/CustomerInput.tsx @@ -31,7 +31,7 @@ export function CustomerInput(props: Props): JSX.Element { errorClassName, ...p } = props - const { validation, values, errors, setError } = useRapidForm({ + const { validation, values, errors, setError } = (useRapidForm as any)({ fieldEvent: 'blur' }) const { saveCustomerUser, setCustomerErrors, setCustomerEmail } = diff --git a/packages/react-components/src/components/customers/MyIdentityLink.tsx b/packages/react-components/src/components/customers/MyIdentityLink.tsx index 3b4e0312..bd79f80e 100644 --- a/packages/react-components/src/components/customers/MyIdentityLink.tsx +++ b/packages/react-components/src/components/customers/MyIdentityLink.tsx @@ -1,19 +1,19 @@ -import { useContext, useEffect, useState, type JSX } from 'react'; -import Parent from '../utils/Parent' -import type { ChildrenFunction } from '#typings/index' -import CommerceLayerContext from '#context/CommerceLayerContext' -import { getApplicationLink } from '#utils/getApplicationLink' -import { getDomain } from '#utils/getDomain' -import { getOrganizationConfig } from '#utils/organization' +import { type JSX, useContext, useEffect, useState } from "react" +import CommerceLayerContext from "#context/CommerceLayerContext" +import type { ChildrenFunction } from "#typings/index" +import { getApplicationLink } from "#utils/getApplicationLink" +import { getDomain } from "#utils/getDomain" +import { getOrganizationConfig } from "#utils/organization" +import Parent from "../utils/Parent" -interface ChildrenProps extends Omit { +interface ChildrenProps extends Omit { /** * The link href */ href: string } -interface Props extends Omit { +interface Props extends Omit { /** * A render function to render your own custom component */ @@ -25,7 +25,7 @@ interface Props extends Omit { /** * The type of the link */ - type: 'login' | 'signup' + type: "login" | "signup" /** * The client id of the Commerce Layer application */ @@ -78,17 +78,20 @@ export function MyIdentityLink(props: Props): JSX.Element { const { accessToken, endpoint } = useContext(CommerceLayerContext) const [href, setHref] = useState(undefined) if (accessToken == null || endpoint == null) - throw new Error('Cannot use `MyIdentityLink` outside of `CommerceLayer`') - const { domain, slug } = getDomain(endpoint) + throw new Error("Cannot use `MyIdentityLink` outside of `CommerceLayer`") useEffect(() => { if (accessToken && endpoint) { + const { domain, slug } = getDomain(endpoint) getOrganizationConfig({ accessToken, endpoint, params: { accessToken, - slug - } + slug, identityType: type, + clientId, + scope, + returnUrl: returnUrl ?? window.location.href, + resetPasswordUrl, }, }).then((config) => { if (config?.links?.identity) { setHref(config.links.identity) @@ -96,14 +99,14 @@ export function MyIdentityLink(props: Props): JSX.Element { const link = getApplicationLink({ slug, accessToken, - applicationType: 'identity', + applicationType: "identity", domain, modeType: type, clientId, scope, returnUrl: returnUrl ?? window.location.href, resetPasswordUrl, - customDomain + customDomain, }) setHref(link) } @@ -112,14 +115,14 @@ export function MyIdentityLink(props: Props): JSX.Element { return () => { setHref(undefined) } - }, [accessToken, endpoint]) + }, [accessToken, endpoint, type, clientId, scope, returnUrl, resetPasswordUrl, customDomain]) const parentProps = { label, href, clientId, scope, - ...p + ...p, } return children ? ( {children} diff --git a/packages/react-components/src/components/customers/SaveCustomerButton.tsx b/packages/react-components/src/components/customers/SaveCustomerButton.tsx index a47d68a8..d4a1ecce 100644 --- a/packages/react-components/src/components/customers/SaveCustomerButton.tsx +++ b/packages/react-components/src/components/customers/SaveCustomerButton.tsx @@ -1,7 +1,7 @@ import { type ReactNode, useContext, type JSX } from 'react'; import Parent from '#components/utils/Parent' import type { ChildrenFunction } from '#typings/index' -import isEmpty from 'lodash/isEmpty' +import { isEmpty } from '#utils/isEmpty' import CustomerContext from '#context/CustomerContext' interface ChildrenProps extends Omit { diff --git a/packages/react-components/src/components/gift_cards/GiftCard.tsx b/packages/react-components/src/components/gift_cards/GiftCard.tsx index 1d1cbe1b..ef71faff 100644 --- a/packages/react-components/src/components/gift_cards/GiftCard.tsx +++ b/packages/react-components/src/components/gift_cards/GiftCard.tsx @@ -1,6 +1,6 @@ import { useRef, useContext, type RefObject, type JSX } from 'react'; import validateFormFields from '#utils/validateFormFields' -import isEmpty from 'lodash/isEmpty' +import { isEmpty } from '#utils/isEmpty' import GiftCardContext from '#context/GiftCardContext' import type { GiftCardI } from '#reducers/GiftCardReducer' import type { BaseState } from '#typings/index' diff --git a/packages/react-components/src/components/gift_cards/GiftCardOrCouponForm.tsx b/packages/react-components/src/components/gift_cards/GiftCardOrCouponForm.tsx index 9ff0ea0e..3bfb5ca5 100644 --- a/packages/react-components/src/components/gift_cards/GiftCardOrCouponForm.tsx +++ b/packages/react-components/src/components/gift_cards/GiftCardOrCouponForm.tsx @@ -18,7 +18,7 @@ interface Props extends Omit { export function GiftCardOrCouponForm(props: Props): JSX.Element | null { const { children, codeType, autoComplete = 'on', onSubmit, ...p } = props - const { validation, values, reset } = useRapidForm() + const { validation, values, reset } = (useRapidForm as any)() const { setGiftCardOrCouponCode, order, errors, setOrderErrors } = useContext(OrderContext) const ref = useRef(null) diff --git a/packages/react-components/src/components/line_items/LineItemOption.tsx b/packages/react-components/src/components/line_items/LineItemOption.tsx index f52dca20..dae84f95 100644 --- a/packages/react-components/src/components/line_items/LineItemOption.tsx +++ b/packages/react-components/src/components/line_items/LineItemOption.tsx @@ -1,6 +1,5 @@ import { useContext, type CSSProperties, type JSX } from 'react'; import LineItemOptionChildrenContext from '#context/LineItemOptionChildrenContext' -import map from 'lodash/map' import Parent from '#components/utils/Parent' import type { LineItemOption as LineItemOptionType } from '@commercelayer/sdk' import type { ChildrenFunction } from '#typings/index' @@ -42,11 +41,11 @@ export function LineItemOption(props: Props): JSX.Element { const label = name != null ? lineItemOption?.options?.[name] : '' const components = showAll && isJSON(JSON.stringify(lineItemOption?.options)) ? ( - map(lineItemOption?.options, (value: string, key) => { + Object.entries(lineItemOption?.options ?? {}).map(([key, value]) => { return ( {`${key}:`} - {`${value}`} + {`${value as string}`} ) }) diff --git a/packages/react-components/src/components/orders/AddToCartButton.tsx b/packages/react-components/src/components/orders/AddToCartButton.tsx index 709e0f06..2a8b7372 100644 --- a/packages/react-components/src/components/orders/AddToCartButton.tsx +++ b/packages/react-components/src/components/orders/AddToCartButton.tsx @@ -165,9 +165,9 @@ export function AddToCartButton(props: Props): JSX.Element { const qty: number = quantity != null ? Number.parseInt(quantity) : 1 if (skuLists != null && skuListId && url) { if (skuListId in skuLists) { - const lineItems = skuLists?.[skuListId]?.map((skuCode: string) => { + const lineItems = skuLists?.[skuListId]?.map((sku) => { return { - skuCode, + skuCode: sku.code, quantity: qty, _update_quantity: 1, } @@ -220,6 +220,8 @@ export function AddToCartButton(props: Props): JSX.Element { orderId, accessToken, slug, + skuListId, + skuId: sku?.id, }, }) location.href = diff --git a/packages/react-components/src/components/orders/HostedCart.tsx b/packages/react-components/src/components/orders/HostedCart.tsx index 0511bff4..9ebe2549 100644 --- a/packages/react-components/src/components/orders/HostedCart.tsx +++ b/packages/react-components/src/components/orders/HostedCart.tsx @@ -1,26 +1,33 @@ -import CommerceLayerContext from '#context/CommerceLayerContext' -import OrderContext from '#context/OrderContext' -import OrderStorageContext from '#context/OrderStorageContext' -import { getApplicationLink } from '#utils/getApplicationLink' -import { getDomain } from '#utils/getDomain' -import useCustomContext from '#utils/hooks/useCustomContext' -import { type CSSProperties, useContext, useEffect, useState, useRef, type JSX } from 'react'; -import { iframeResizer } from 'iframe-resizer' -import type { Order } from '@commercelayer/sdk' -import { subscribe, unsubscribe } from '#utils/events' -import { getOrganizationConfig } from '#utils/organization' +import type { Order } from "@commercelayer/sdk" +import { iframeResizer } from "iframe-resizer" +import { + type CSSProperties, + type JSX, + useContext, + useEffect, + useRef, + useState, +} from "react" +import CommerceLayerContext from "#context/CommerceLayerContext" +import OrderContext from "#context/OrderContext" +import OrderStorageContext from "#context/OrderStorageContext" +import { subscribe, unsubscribe } from "#utils/events" +import { getApplicationLink } from "#utils/getApplicationLink" +import { getDomain } from "#utils/getDomain" +import useCustomContext from "#utils/hooks/useCustomContext" +import { getOrganizationConfig } from "#utils/organization" interface IframeData { message: | { - type: 'update' + type: "update" payload?: Order } | { - type: 'close' + type: "close" } | { - type: 'blur' + type: "blur" } } @@ -33,50 +40,50 @@ interface Styles { } const defaultIframeStyle = { - width: '1px', - minWidth: '100%', - minHeight: '100%', - border: 'none', - paddingLeft: '20px', - paddingRight: '20px' + width: "1px", + minWidth: "100%", + minHeight: "100%", + border: "none", + paddingLeft: "20px", + paddingRight: "20px", } satisfies CSSProperties const defaultContainerStyle = { - position: 'fixed', - top: '0', - right: '-25rem', - height: '100%', - width: '23rem', - transition: 'right 0.5s ease-in-out', + position: "fixed", + top: "0", + right: "-25rem", + height: "100%", + width: "23rem", + transition: "right 0.5s ease-in-out", // zIndex: '0', - pointerEvents: 'none', - overflow: 'auto' + pointerEvents: "none", + overflow: "auto", } satisfies CSSProperties const defaultBackgroundStyle = { - opacity: '0', - position: 'fixed', - top: '0', - left: '0', - height: '100%', - width: '100vw', - transition: 'opacity 0.5s ease-in-out', + opacity: "0", + position: "fixed", + top: "0", + left: "0", + height: "100%", + width: "100vw", + transition: "opacity 0.5s ease-in-out", // zIndex: '-10', - pointerEvents: 'none', - backgroundColor: 'black' + pointerEvents: "none", + backgroundColor: "black", } satisfies CSSProperties const defaultIconStyle = { - width: '1.25rem', - height: '1.25rem' + width: "1.25rem", + height: "1.25rem", } satisfies CSSProperties const defaultIconContainer = { - textAlign: 'left', - paddingLeft: '20px', - paddingTop: '20px', - background: '#ffffff', - color: '#686E6E' + textAlign: "left", + paddingLeft: "20px", + paddingTop: "20px", + background: "#ffffff", + color: "#686E6E", } satisfies CSSProperties const defaultStyle = { @@ -84,11 +91,11 @@ const defaultStyle = { container: defaultContainerStyle, background: defaultBackgroundStyle, icon: defaultIconStyle, - iconContainer: defaultIconContainer + iconContainer: defaultIconContainer, } satisfies Styles interface Props - extends Omit { + extends Omit { /** * The style of the cart. */ @@ -100,7 +107,7 @@ interface Props /** * The type of the cart. Defaults to undefined. */ - type?: 'mini' + type?: "mini" /** * If true, the cart will open when a line item is added to the order clicking the add to cart button. Defaults to false. * Works only with the `type` prop set to `mini`. @@ -148,11 +155,12 @@ export function HostedCart({ }: Props): JSX.Element | null { const [isOpen, setOpen] = useState(false) const ref = useRef(null) + const loadedOrderIdRef = useRef(null) const { accessToken, endpoint } = useCustomContext({ context: CommerceLayerContext, - contextComponentName: 'CommerceLayer', - currentComponentName: 'HostedCart', - key: 'accessToken' + contextComponentName: "CommerceLayer", + currentComponentName: "HostedCart", + key: "accessToken", }) const [src, setSrc] = useState() if (accessToken == null || endpoint == null) return null @@ -166,11 +174,12 @@ export function HostedCart({ accessToken, endpoint, params: { - orderId: order?.id, + orderId: order?.id ?? orderId, accessToken, - slug - } + slug, + }, }) + loadedOrderIdRef.current = orderId setSrc( config?.links?.cart ?? getApplicationLink({ @@ -178,9 +187,9 @@ export function HostedCart({ orderId, accessToken, domain, - applicationType: 'cart', - customDomain - }) + applicationType: "cart", + customDomain, + }), ) if (openCart) { setTimeout(() => { @@ -192,35 +201,35 @@ export function HostedCart({ } function onMessage(data: IframeData): void { switch (data.message.type) { - case 'update': + case "update": if (data.message.payload != null) { getOrder(data.message.payload.id) } break - case 'close': - if (type === 'mini') { + case "close": + if (type === "mini") { if (handleOpen != null) handleOpen() else setOpen(false) } break - case 'blur': - if (type === 'mini' && isOpen) { + case "blur": + if (type === "mini" && isOpen) { ref.current?.focus() } break } } useEffect(() => { - const orderId = localStorage.getItem(persistKey) + const resolvedOrderId = order?.id ?? localStorage.getItem(persistKey) let ignore = false if (open != null && open !== isOpen) { setOpen(open) } - if (openAdd && type === 'mini') { - subscribe('open-cart', () => { - window.document.body.style.overflow = 'hidden' - if (src == null && order?.id == null && orderId == null) { + if (openAdd && type === "mini") { + subscribe("open-cart", () => { + window.document.body.style.overflow = "hidden" + if (src == null && resolvedOrderId == null) { setOrder(true) } else { if (src != null && ref.current != null) { @@ -235,36 +244,36 @@ export function HostedCart({ } if ( src == null && - order?.id == null && - orderId == null && + resolvedOrderId == null && accessToken != null && !ignore && isOpen ) { setOrder() } else if ( - src == null && - (order?.id != null || orderId != null) && - accessToken + resolvedOrderId != null && + accessToken && + (src == null || loadedOrderIdRef.current !== resolvedOrderId) ) { getOrganizationConfig({ accessToken, endpoint, params: { - orderId: order?.id, + orderId: resolvedOrderId, accessToken, - slug - } + slug, + }, }).then((config) => { + loadedOrderIdRef.current = resolvedOrderId setSrc( config?.links?.cart ?? getApplicationLink({ slug, - orderId: order?.id ?? orderId ?? '', + orderId: resolvedOrderId, accessToken, domain, - applicationType: 'cart' - }) + applicationType: "cart", + }), ) }) } @@ -273,42 +282,41 @@ export function HostedCart({ } return (): void => { ignore = true - if (openAdd && type === 'mini') { - // biome-ignore lint/suspicious/noEmptyBlockStatements: - unsubscribe('open-cart', () => {}) + if (openAdd && type === "mini") { + unsubscribe("open-cart", () => {}) } } - }, [src, open, order?.id, accessToken]) + }, [src, open, order?.id, accessToken, persistKey]) useEffect(() => { if (ref.current == null) return iframeResizer( { checkOrigin: false, // @ts-expect-error No types available - onMessage + onMessage, }, - ref.current + ref.current, ) }, [ref.current != null]) /** * Close the cart. */ function onCloseCart(): void { - window.document.body.style.removeProperty('overflow') + window.document.body.style.removeProperty("overflow") if (handleOpen != null) handleOpen() else setOpen(false) } - return src == null ? null : type === 'mini' ? ( + return src == null ? null : type === "mini" ? ( <>