fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(demos): pass the plugin explicitly when discovering gateways - #174

Merged
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch
Aug 24, 2026
Merged

fix(demos): pass the plugin explicitly when discovering gateways#174
farhan-t-ottu merged 2 commits into
devfrom
fix/159191-checkout-demo-plugin-mismatch

Conversation

@farhan-t-ottu

Copy link
Copy Markdown
Contributor

Fixes the interactive Checkout SDK demo failing at the Creating session step.

Refs #159191

Root cause

CheckoutDemoInner.tsx called callPaymentMethods without a plugin, which callPaymentMethods silently defaulted to payment_request, and then created the session with type: "e_commerce". Gateways are enabled per plugin, so discovery returned gateways the session type does not accept.

Reproduced live against sandbox.ottu.net:

Payment Methods callgateways returned
plugin: "payment_request" (what the demo sent)14 — includes cbk-private
plugin: "e_commerce"13cbk-private dropped
POST /b/checkout/v1/pymt-txn/ (type: e_commerce, 14 codes) → 400
["pg code `cbk-private` is not enabled for `e_commerce` plugin."]

With the 13-code set → 201.

Changes

  • src/utils/sandbox.ts — added PaymentPlugin ("e_commerce" | "payment_request", matching the Checkout API type enum). callPaymentMethods now takes plugin as a required argument, removing the ?? "payment_request" default that hid this. CreateSessionOptions.type narrowed from string to PaymentPlugin.
  • All four demos — each declares const PLUGIN once and uses it for both the Payment Methods call and the session type, so the two can no longer drift apart.
  • RecurringDemo / PaymentJourney — now send the type they already displayed in their request panels. RecurringDemo was rendering type: "e_commerce" while actually sending payment_request.
  • WalletDemo — dropped a || ({} as any) that widened the filter to any and would have silently defeated the new required-plugin check.

Verification

Each demo driven in the browser with a fetch recorder capturing the real request bodies:

DemoPayment MethodsSession
CheckoutDemoplugin: "e_commerce" → 200201, cbk-private absent from the 13 codes
RecurringDemoplugin: "e_commerce" → 200201, type: "e_commerce"
PaymentJourneyplugin: "payment_request" → 200201
WalletDemo (KSA + webhook:local)wallet filter → 200seed 201, session 201

npm run typecheck and npm run build both pass. Omitting plugin, a misspelled plugin, and a misspelled session type are all now compile errors.

CheckoutDemo's SDK form then shows the SDK's own "Website doesn't run on non https pages" — that is the Ottu SDK refusing plain http://localhost, raised only after both API steps succeeded. It does not occur on the deployed https site.

Note for reviewers

This will not be visible on docs.ottu.dev yet. ACTIVE_CONNECT = KSA, and that merchant has a separate failure tracked in #159192: its demo gateways span KWD/USD/SAR default currencies and Connect rejects a session mixing two foreign defaults (a missing Currency Exchange entry). This PR takes effect on main/SANDBOX, or on dev once #159192 is resolved.

🤖 Generated with Claude Code

CheckoutDemo called callPaymentMethods without a plugin, which silently
defaulted to payment_request, then created the session with
type: "e_commerce". On sandbox.ottu.net that discovery returns cbk-private,
which is not enabled for the e-commerce plugin, so the Checkout API rejected
the session:
400 ["pg code `cbk-private` is not enabled for `e_commerce` plugin."]
Each demo now declares its plugin once and uses it for both the Payment
Methods call and the session type, so the two can no longer drift apart.
callPaymentMethods takes plugin as a required PaymentPlugin, removing the
default that hid this; RecurringDemo and PaymentJourney also send the type
they already display in their request panels.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 2m 14s —— View job


Claude finished reviewing this PR

  • Gather context (PR body, changed files list)
  • Fetch branch diff against origin/dev
  • Review src/utils/sandbox.ts changes
  • Review each demo component's changes (CheckoutDemo, PaymentJourney, RecurringDemo, WalletDemo)
  • Post review feedback

Summary

The fix itself is sound: callPaymentMethods's plugin is now required (no more ?? "payment_request" default), each demo declares one PLUGIN/filter.plugin source of truth, and I traced every createSandboxSession/callPaymentMethods call site in CheckoutDemo, RecurringDemo, WalletDemo — all pass a type/plugin that's derived from the single constant. RecurringDemoInner.tsx's citRequest/mitRequest display objects (used for the ApiPanel preview) are also correctly wired to PLUGIN now, matching what's actually sent — the drift called out in the PR description is fixed there.

Finding

src/components/PaymentJourney/PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview for POST /b/checkout/v1/pymt-txn/ still hardcodes type: "payment_request" instead of PLUGIN:

<ApiPanellabel="POST /b/checkout/v1/pymt-txn/"data={{type: "payment_request",// ← should be `type: PLUGIN`pg_codes: state.pgCodes,
...

The actual request at line 349 correctly sends type: PLUGIN, and the Step 1 panel at line 523 was updated to plugin: PLUGIN, but this one spot was missed. It's harmless today only because PLUGIN = "payment_request" for this file, but it's exactly the class of bug this PR is fixing — the displayed request silently drifting from what's actually sent if PLUGIN is ever changed here. Worth a one-line fix for consistency: `Fix this →

Minor observation (non-blocking)

CreateSessionOptions.type in src/utils/sandbox.ts is still optional with a silent fallback (options.type ?? "payment_request" at sandbox.ts:92), while callPaymentMethods's plugin was deliberately made required to prevent exactly this kind of silent-default bug. Every current call site does pass type explicitly, so there's no live bug, but the same footgun that caused #159191 remains available to a future demo that forgets to pass it. Consider making type required on CreateSessionOptions too, for symmetry with plugin.

Not verified

I wasn't able to run npm run typecheck / npm run build in this sandboxed environment (command execution required approval that wasn't available). The PR description states both pass; the static review above didn't surface any type errors, but this wasn't independently confirmed.

Addresses PR review on #174.
PaymentJourney's Step 3 request panel still hardcoded
type: "payment_request" while the real call used PLUGIN. Harmless today
because they happen to match, but it is the exact drift this branch
removes, so the panel now reads PLUGIN as well.
CreateSessionOptions.type is now required, dropping its
?? "payment_request" fallback. That silent default is the same footgun
that caused #159191 on the discovery side; every call site already passes
type explicitly, so this is a type-level change only.
Refs #159191
@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

Both points addressed in 2c56101.

Finding — PaymentJourneyInner.tsx:554. Fixed; the Step 3 panel now reads type: PLUGIN. You were right that it's the exact drift this branch removes. I swept the rest of the demos for the same pattern and this was the last hardcoded occurrence — the only remaining literals are walletDemoConfig.ts (the intended single source for WalletDemo) and the sandbox.ts fallback, which is gone as of this commit.

Verified in the browser against sandbox.ottu.net: the rendered Step 3 panel shows "type": "payment_request", identical output, now sourced from the constant.

Minor observation — CreateSessionOptions.type. Taken; type is now required and the ?? "payment_request" fallback is removed. Agreed that leaving it optional kept the exact footgun that caused #159191 available on the session side. Every call site already passed type explicitly, so it was type-level only — npm run typecheck passed without touching a single call site, and omitting type is now a compile error:

error TS2345: Argument of type '{ pg_codes: string[]; }' is not assignable to parameter of type 'CreateSessionOptions'.

npm run typecheck and npm run build both pass.

@farhan-t-ottu

Copy link
Copy Markdown
ContributorAuthor

@claude review again

@claude

claudeBot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @farhan-t-ottu's task in 1m 12s —— View job


Re-review complete ✅

  • Gather context (fetch latest diff since previous review, incl. commit 2c56101)
  • Re-review sandbox.ts (now-required type)
  • Re-review PaymentJourneyInner.tsx fix (Step 3 panel)
  • Sweep all changed files for remaining issues
  • Post final review feedback

Both items from the first pass are correctly resolved in 2c56101:

  1. PaymentJourneyInner.tsx:554 — the Step 3 ApiPanel preview now reads type: PLUGIN instead of the hardcoded "payment_request". Confirmed the live call at line 349 and the displayed preview now share the same source.
  2. sandbox.tsCreateSessionOptions.type is now required (type: PaymentPlugin, no more ?: ), and createSandboxSession uses options.type directly with the ?? "payment_request" fallback removed. sandbox.ts:92 no longer has a silent default.

Additional verification this pass:

  • Traced every createSandboxSession() call site in the repo (Grep) — exactly the 4 demo components + sandbox.ts itself, all passing type from a single PLUGIN/filter.plugin constant. No orphaned call site was left without an explicit type.
  • Swept for remaining "payment_request"/"e_commerce" string literals — only the intended single-source declarations remain: PLUGIN const in CheckoutDemoInner.tsx, RecurringDemoInner.tsx, PaymentJourneyInner.tsx, and pgFilter.plugin in walletDemoConfig.ts. No drift.
  • Re-checked RecurringDemoInner.tsx's citRequest/mitRequest (one-step and two-step) display objects against their actual request bodies — both still match, sourced from PLUGIN.
  • Re-checked WalletDemoInner.tsx — no || ({} as any) reintroduced; filter.plugin from WALLET_DEMO.pgFilter is used consistently for both the Payment Methods call and the session type.

No new findings. The fix is complete and internally consistent.

Not verified (same limitation as last pass): I don't have permission to execute npm run typecheck / npm run build in this sandboxed environment (command execution requires approval that isn't available here). Static review found no type errors, and the author reported both passing after this commit, including the new compile-time enforcement (error TS2345 when type is omitted) — that wasn't independently re-run.
·

@junaidsarfrazdevjunaidsarfrazdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@farhan-t-ottu
farhan-t-ottu merged commit f5d156e into devAug 24, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@farhan-t-ottu@junaidsarfrazdev