# Shop FX Rate Automation Debt TODO

Date: 2026-06-18

Status: deferred technical debt. Do not implement until the exchange-rate provider and approval workflow are explicitly chosen.

Scope: plan future automation where admins enter IDR product prices and the system uses an exchange-rate API or scheduled rate snapshot to generate USD, EUR, SGD, and MYR product/variant prices while preserving Payload ecommerce plugin behavior.

Related documents:

- `docs/shop-multicurrency-public-selector-todo.md`
- `docs/shop-idr-currency-migration-todo.md`
- `docs/shop-commerce-data-readiness-todo.md`

## Intended Direction

- IDR remains the product pricing source of truth.
- Admins should be able to enter or maintain `priceInIDR`.
- A future exchange-rate integration can generate the other explicit Payload ecommerce price fields:
- `priceInUSD`
- `priceInEUR`
- `priceInSGD`
- `priceInMYR`
- Storefront, cart, filters, sort, and order creation must continue reading explicit database price fields.
- The exchange-rate API must not become a request-time storefront dependency.
- Exchange rates may sync automatically on a schedule, but public storefront prices must still come from stable database snapshots.

## Dynamic Rate Consideration

Currency rates are dynamic and can change frequently. The system should distinguish between rate freshness and storefront price stability.

Important distinction:

- Rate data can be refreshed automatically and frequently.
- Product price fields should not necessarily be rewritten every minute.
- Storefront prices should remain stable enough for cart, quote, customer expectation, and historical order consistency.

Recommended principle:

- Auto-sync exchange-rate snapshots.
- Use those snapshots to generate price proposals or controlled product price updates.
- Avoid live request-time conversion on the storefront.

Possible sync modes:

- Scheduled rate sync only: fetch and store latest rates every configured interval, but do not update product prices automatically.
- Scheduled dry-run: fetch rates and generate proposed price changes for admin review.
- Scheduled approved auto-write: automatically write foreign price fields using the latest approved rules, only if business accepts frequent price movement.
- Manual emergency refresh: admin can force a rate fetch when needed.

Recommended first production-safe mode:

- Scheduled rate sync plus scheduled dry-run.
- Admin approval required before foreign product prices are rewritten.
- Later, selected product groups can opt into scheduled approved auto-write if the business wants fully automatic foreign-price updates.

## Provider Decision Pending

Provider has not been chosen.

Candidates to research later:

- Bank Indonesia or another official Indonesian source, if it provides a suitable API, availability, terms, and currency coverage.
- Reputable third-party FX providers with clear SLA, historical rate support, rate limits, pricing, and legal terms.
- A manual admin-entered rate fallback if API access is unavailable or too fragile.

Research criteria:

- Supports `IDR` as base or reliable conversion to `USD`, `EUR`, `SGD`, and `MYR`.
- Provides stable API documentation and predictable availability.
- Allows commercial website usage under its terms.
- Has sane rate limits for admin/batch usage.
- Can be cached safely.
- Exposes rate timestamp/effective date.
- Handles API failure without impacting public storefront rendering.
- Has predictable pricing, including free-tier limits if used during development.

## Non-Negotiable Constraints

- Do not patch `@payloadcms/plugin-ecommerce`.
- Do not calculate product prices live on every storefront request.
- Do not rewrite historical orders, transactions, or carts.
- Do not silently change prices without admin visibility.
- Do not depend on an external API for cart totals or checkout correctness.
- Do not change the `/shop` SEO URL contract.
- Do not enable checkout/payment as part of this debt.

## Recommended Future Model

Use the API as a generator, not as storefront runtime pricing.

Recommended flow:

1. Admin enters or updates `priceInIDR`.
2. System fetches exchange-rate snapshots on a schedule, or admin triggers an emergency refresh.
3. System fetches rates from the chosen provider.
4. System calculates proposed prices for USD, EUR, SGD, and MYR.
5. Admin reviews old price, proposed price, rate source, and rounding.
6. Admin approves.
7. System writes explicit Payload price fields and enabled flags.
8. Storefront reads the updated database fields exactly like it does now.

## Rounding Policy Needed

This must be decided before implementation.

Open decisions:

- Should IDR be rounded to whole Rupiah only, or business-friendly steps such as nearest 1,000 / 5,000 / 10,000?
- Should USD/EUR/SGD/MYR use exact cents or charm pricing rules?
- Should generated foreign prices be rounded up to preserve margin?
- Should each currency support a configurable rounding increment?
- Should some products be excluded from auto-conversion?

## Possible Schema Later

Do not add these yet. They are candidates only.

Possible `currencyRates` collection or global:

- `baseCurrency`
- `targetCurrency`
- `rate`
- `provider`
- `providerRateId`
- `sourceType`
- `effectiveAt`
- `fetchedAt`
- `expiresAt`
- `status`
- `notes`

Possible `currencyPriceUpdateRuns` collection:

- `provider`
- `baseCurrency`
- `targetCurrencies`
- `rateSnapshot`
- `triggerType`
- `approvalMode`
- `dryRun`
- `affectedProducts`
- `affectedVariants`
- `skippedDocuments`
- `approvedBy`
- `approvedAt`
- `completedAt`
- `status`
- `errorLog`

## Execution Phases Later

### Phase 0 - Provider Research

- Verify official provider documentation.
- Compare BI or official-source option with reputable third-party APIs.
- Confirm terms, rate limits, price, coverage, and reliability.
- Decide fallback behavior when API fails.
- Decide acceptable rate freshness window.
- Decide whether provider supports frequent scheduled polling without violating terms or cost limits.

Acceptance:

- Provider decision is documented.
- API terms and rate limits are known.
- Failure policy is documented.
- Rate freshness policy is documented.

### Phase 1 - Pricing Policy

- Decide rounding rules.
- Decide rate sync interval.
- Decide product price update mode: manual trigger, scheduled dry-run, scheduled approved auto-write, or on-save preview.
- Decide whether admin approval is required before writing prices.
- Decide whether some product groups can auto-write while others require approval.

Recommended first policy:

- Scheduled rate sync.
- Scheduled dry-run.
- Admin approval required.
- No automatic silent product price writes.

### Phase 2 - Data Model

- Add minimal admin-only rate/runs model if needed.
- Generate Payload types after schema changes.
- Keep access admin-only.

### Phase 3 - Dry Run

- Build dry-run conversion.
- Read current Product and Variant `priceInIDR`.
- Calculate proposed target-currency prices.
- Show old and proposed values.
- Do not write product data yet.

### Phase 4 - Approved Write

- Require explicit admin confirmation.
- Write `priceIn*` and `priceIn*Enabled` fields.
- Log affected records.
- Preserve historical orders/carts/transactions.

### Phase 5 - Verification

- TypeScript.
- Targeted integration tests.
- Admin access test.
- Product and variant price update smoke.
- `/shop` price sort/filter smoke for non-IDR currency.
- Cart/order historical display smoke.

## Recommended Next Action

Do not implement now.

Next real action should be provider research and policy decision:

- BI or another official-source API.
- Third-party FX provider.
- Manual rate fallback.

Only after that should schema, endpoints, or admin UI be designed.
