Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@
},
"dependencies": {
"@commercelayer/js-auth": "^8.0.0",
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@57f8b0a"
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@4d57c9f"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Order } from "@commercelayer/sdk"
import { describe, expect, it } from "vitest"
import { isCreatedWithVersion } from "./createdWithVersion"

// The version as the SDK returns it: under the resource's `meta`.
const order = (createdWithVersion?: string): Order =>
({
id: "order-1",
...(createdWithVersion != null ? { meta: { created_with_version: createdWithVersion } } : {}),
}) as unknown as Order

describe("isCreatedWithVersion", () => {
it("compares the version the API reports in the order's meta", () => {
expect(isCreatedWithVersion(order("2026-05"), "2026-05")).toBe(true)
expect(isCreatedWithVersion(order("2017-08"), "2026-05")).toBe(false)
})

it("matches the version exactly", () => {
expect(isCreatedWithVersion(order("2027-01"), "2026-05")).toBe(false)
})

it("takes only a version the SDK supports", () => {
// @ts-expect-error a mistyped version is a compile error, not a silent false
expect(isCreatedWithVersion(order("2026-05"), "2026-5")).toBe(false)
})

it("is undefined when the order does not say which version created it", () => {
expect(isCreatedWithVersion(order(), "2026-05")).toBeUndefined()
expect(isCreatedWithVersion(order(""), "2026-05")).toBeUndefined()
expect(isCreatedWithVersion(undefined, "2026-05")).toBeUndefined()
expect(isCreatedWithVersion(null, "2026-05")).toBeUndefined()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ApiVersion, Order } from "@commercelayer/sdk"

/**
* Whether the order was created with exactly this API version, e.g. `2026-05`.
*
* It matters because Core API fixes some of an order's behaviour once, at
* creation, whatever version later requests use: an order created with
* `2026-05` is on the payment engine where the older order-level gift card is
* neither validated nor applied, and one created with `2017-08` keeps it even
* when read with `2026-05`.
*
* The version is the one the API reports in the order's `meta`. `version` is
* one the SDK supports, so a mistyped one fails to compile rather than silently
* never matching.
*
* Undefined when the order does not say which version created it — the API did
* not report it, or the order was not fetched from the API — so callers can
* keep deciding the way they would without it.
*/
export function isCreatedWithVersion(
order: Order | null | undefined,
version: ApiVersion
): boolean | undefined {
const createdWith = order?.meta?.created_with_version
if (createdWith == null || createdWith === "") return undefined
return createdWith === version
}
1 change: 1 addition & 0 deletions packages/core-components/src/payment_sessions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { applyGiftCard } from "./applyGiftCard"
export type { AuthorizeGiftCardSessionsResult } from "./authorizeGiftCardSessions"
export { authorizeGiftCardSessions } from "./authorizeGiftCardSessions"
export { buildGatewayReturnUrl } from "./buildGatewayReturnUrl"
export { isCreatedWithVersion } from "./createdWithVersion"
export { createPaymentSession } from "./createPaymentSession"
export type { PaymentSessionsState } from "./derivePaymentSessionsState"
export { derivePaymentSessionsState } from "./derivePaymentSessionsState"
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@babel/core": "^7.29.7",
"@babel/preset-env": "^7.29.7",
"@commercelayer/js-auth": "^8.0.0",
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@57f8b0a",
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@4d57c9f",
"@mdx-js/react": "^3.1.1",
"@storybook/addon-actions": "^9.0.8",
"@storybook/addon-backgrounds": "^9.0.8",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@commercelayer/core-components": "workspace:*",
"@commercelayer/organization-config": "^2.8.4",
"@commercelayer/react-hooks-components": "workspace:*",
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@57f8b0a",
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@4d57c9f",
"@stripe/react-stripe-js": "^6.8.1",
"@stripe/stripe-js": "^9.10.0",
"@tanstack/react-table": "^8.21.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,50 @@ describe("GiftCardOrCouponForm on the payment_sessions model", () => {
expect(inputName()).toBe("gift_card_or_coupon_code")
})
})

describe("GiftCardOrCouponForm by the API version the order was created with", () => {
// The API fixes an order's payment engine when it is created. On one created
// with 2026-05 the order-level gift card is neither validated nor applied, so
// the form offers the coupon alone — even with the older payment methods on
// offer, which the same order still accepts.
it("offers only the coupon on an order created with 2026-05", () => {
renderForm({
id: "order-1",
available_payment_methods: [{ id: "pm-1" }],
meta: { created_with_version: "2026-05" },
} as never)
expect(inputName()).toBe("coupon_code")
})

it("overrides an explicit gift_card_code request on an order created with 2026-05", () => {
renderForm(
{
id: "order-1",
available_payment_methods: [{ id: "pm-1" }],
meta: { created_with_version: "2026-05" },
} as never,
"gift_card_code"
)
expect(inputName()).toBe("coupon_code")
})

it("offers the combined field on an order created with 2017-08", () => {
renderForm({
id: "order-1",
available_payment_methods: [{ id: "pm-1" }],
meta: { created_with_version: "2017-08" },
} as never)
expect(inputName()).toBe("gift_card_or_coupon_code")
})

// The version is what the API decides on, so it wins over the payments model
// the order is on.
it("follows the version rather than the payments model", () => {
renderForm({
id: "order-1",
available_payment_settings: [MANUAL],
meta: { created_with_version: "2017-08" },
} as never)
expect(inputName()).toBe("gift_card_or_coupon_code")
})
})
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isCreatedWithVersion } from "@commercelayer/core-components"
import type { Order } from "@commercelayer/sdk"
import { useRapidForm } from "rapid-form"
import { type JSX, useCallback, useContext, useEffect, useState } from "react"
Expand Down Expand Up @@ -41,13 +42,21 @@ export function GiftCardOrCouponForm(props: Props): JSX.Element | null {

// Derive the active code type from the current order state
useEffect(() => {
// On the `payment_sessions` model a gift card is not an order-level code:
// it is spent by creating a Payment Session against a gift-card Payment
// Setting, so it appears among the payment methods instead. Writing
// `gift_card_code` on the order there is meaningless, and letting it
// through would silently apply a gift card that no session reflects — so
// this overrides an explicit `codeType` too, rather than trusting it.
if (paymentsModel === "payment_sessions") {
// An order created with API version 2026-05 takes only the coupon here.
// Its gift cards are spent by creating a Payment Session against a
// gift-card Payment Setting, and the API no longer validates nor applies
// the order-level one: `gift_card_code` is stored unchecked, and so is
// anything sent as `gift_card_or_coupon_code`, which tries the gift card
// first — a coupon typed there is kept as a gift card code and never
// discounts. This holds even when the order also offers the older
// payment methods, and it overrides an explicit `codeType` too.
//
// What decides it is the version the order was created with, since the API
// fixes the payment engine then. When the order does not say — the API did
// not report it — the payments model stands in for it.
const couponOnly =
isCreatedWithVersion(order, "2026-05") ?? paymentsModel === "payment_sessions"
if (couponOnly) {
setType("coupon_code")
return
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-hooks-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.5",
"@babel/core": "^8.0.1",
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@57f8b0a",
"@commercelayer/sdk": "https://pkg.pr.new/@commercelayer/sdk@4d57c9f",
"@rolldown/plugin-babel": "^0.2.3",
"@testing-library/react": "^16.3.2",
"@types/react": "^19.2.17",
Expand Down
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading