✨ Support per-currency book price overrides (HKD/TWD)#1265
Merged
williamchong merged 1 commit intoMay 20, 2026
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for per-currency (HKD/TWD) price overrides on NFT book price tiers, so operations can charge bespoke local-currency amounts without modifying the shared USD-indexed price ladder. The changes centralize override resolution and Stripe currency_options generation to keep Stripe product creation, Stripe price updates, and inline checkout line items consistent.
Changes:
- Introduced
priceInDecimalByCurrencyon book price tiers and propagated it through types and cart/checkout data flows. - Added pricing helpers to resolve per-currency minor-unit amounts and to build Stripe
currency_optionsconsistently. - Updated Stripe product/price sync logic to recreate Stripe prices when overrides change and to allow “clearing” overrides.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/util/ValidationHelper.ts | Includes priceInDecimalByCurrency in filtered price payloads. |
| src/util/pricing.ts | Adds override-aware currency amount resolution + Stripe currency_options builder. |
| src/util/api/likernft/book/type.ts | Extends cart item info type to carry per-currency override data. |
| src/util/api/likernft/book/purchase.ts | Uses the new helper to compute Stripe unit_amount (incl. overrides). |
| src/util/api/likernft/book/index.ts | Formats/validates priceInDecimalByCurrency and centralizes Stripe currency options usage. |
| src/util/api/likernft/book/cart.ts | Carries priceInDecimalByCurrency from price tier into cart item info. |
| src/types/book.d.ts | Defines BookPriceInDecimalByCurrency and wires it into book price types. |
| src/routes/likernft/book/store.ts | Updates PUT price flow to sync Stripe when overrides change and attempts override clearing logic. |
Comment on lines
+318
to
+324
| // formatPriceInfo only sets priceInDecimalByCurrency when present, so the | ||
| // spread above would otherwise retain a stale override when the caller | ||
| // clears it. Reconcile against the validated value (undefined = cleared). | ||
| if (price.priceInDecimalByCurrency) { | ||
| newPriceInfo.priceInDecimalByCurrency = price.priceInDecimalByCurrency; | ||
| } else { | ||
| delete newPriceInfo.priceInDecimalByCurrency; |
Comment on lines
+583
to
+596
| if (priceInDecimalByCurrencyInput !== undefined) { | ||
| if (typeof priceInDecimalByCurrencyInput !== 'object' || priceInDecimalByCurrencyInput === null) { | ||
| throw new ValidationError('INVALID_PRICE_CURRENCY_OVERRIDE'); | ||
| } | ||
| const override: BookPriceInDecimalByCurrency = {}; | ||
| BOOK_PRICE_OVERRIDE_CURRENCIES.forEach((currency) => { | ||
| const value = priceInDecimalByCurrencyInput[currency]; | ||
| if (value === undefined) return; | ||
| if (!(typeof value === 'number' && Number.isInteger(value) && value >= 0)) { | ||
| throw new ValidationError('INVALID_PRICE_CURRENCY_OVERRIDE'); | ||
| } | ||
| override[currency] = value; | ||
| }); | ||
| if (Object.keys(override).length) priceInDecimalByCurrency = override; |
Comment on lines
+49
to
+55
| priceInDecimalByCurrency?: BookPriceInDecimalByCurrency, | ||
| ): number { | ||
| if (currency === 'usd') return usdPriceInDecimal; | ||
| const override = priceInDecimalByCurrency?.[currency]; | ||
| if (typeof override === 'number' && override > 0) return override; | ||
| return convertUSDPriceToCurrency(usdPriceInDecimal / 100, currency) * 100; | ||
| } |
Comment on lines
563
to
568
| isAllowCustomPrice, | ||
| isTippingEnabled: !priceInDecimal || isTippingEnabled, | ||
| order: order ?? index, | ||
| }; | ||
| if (priceInDecimalByCurrency) payload.priceInDecimalByCurrency = priceInDecimalByCurrency; | ||
| if (isOwner) { |
d2b8f90 to
1f970f8
Compare
Comment on lines
+318
to
+319
| if (!price.priceInDecimalByCurrency) { | ||
| delete newPriceInfo.priceInDecimalByCurrency; |
nwingt
approved these changes
May 20, 2026
Comment on lines
+336
to
+337
| if (oldPriceInfo.priceInDecimal !== newPriceInfo.priceInDecimal | ||
| || isCurrencyOverrideChanged) { |
Member
There was a problem hiding this comment.
Suggested change
| if (oldPriceInfo.priceInDecimal !== newPriceInfo.priceInDecimal | |
| || isCurrencyOverrideChanged) { | |
| if ( | |
| oldPriceInfo.priceInDecimal !== newPriceInfo.priceInDecimal | |
| || isCurrencyOverrideChanged | |
| ) { |
Let operations offer a bespoke off-ladder HKD/TWD price for a specific book tier without inserting a rung into the shared price ladder (which would retroactively reprice every existing book, since the ladder index is the price identity). The override lives on the price tier doc, so it stays invisible to the publisher dropdown and is set manually via PUT .../price/:priceIndex. USD remains the stored priceInDecimal and commission base; only the Stripe charge amount per currency changes. Centralised the override resolution and the Stripe currency_options block in one pricing helper so product creation, price updates and inline checkout stay consistent; clearing an override now also reverts the synced Stripe price.
1f970f8 to
02cda1c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Let operations offer a bespoke off-ladder HKD/TWD price for a specific book tier without inserting a rung into the shared price ladder (which would retroactively reprice every existing book, since the ladder index is the price identity).
The override lives on the price tier doc, so it stays invisible to the publisher dropdown and is set manually via PUT .../price/:priceIndex. USD remains the stored priceInDecimal and commission base; only the Stripe charge amount per currency changes. Centralised the override resolution and the Stripe currency_options block in one pricing helper so product creation, price updates and inline checkout stay consistent; clearing an override now also reverts the synced Stripe price.