fix(transak): open checkout in a new tab instead of an embedded iframe - #656
Conversation
Production reports show the embedded (containerId) Transak widget failing with "Access Denied. Error code: T-INF-102 ; 403 Forbidden" while the exact same Secure Widget URL loads fine as a top-level navigation (confirmed by copying the URL from devtools into a fresh tab). Transak's own docs (docs.transak.com/integration/web/iframe) confirm the Secure Widget URL is domain-validated via `referrerDomain` and is single-use with a 5-minute TTL. That the identical URL fails embedded but succeeds top-level -- and is still valid on the second attempt -- points to a domain/frame-context check that runs before the session is consumed: Transak's server likely rejects the request when it detects it is being loaded inside a third-party iframe (e.g. via `Sec-Fetch-Dest: iframe`) unless the parent origin is on a separate, explicit iframe-embedding allowlist -- distinct from the `referrerDomain` used for session pinning, which `vechainkit.vechain.org` evidently satisfies for top-level loads but not for iframe embedding. This is circumstantial (Transak doesn't publish per-error-code docs), but consistent with every signal gathered. Rather than chase per-domain iframe allowlisting with Transak, drop the `@transak/ui-js-sdk` containerId/iframe integration entirely and open the Secure Widget URL in a new tab via a plain anchor (target="_blank" rel="noopener noreferrer"), mirroring the pattern already shipped and working in vechain/agent-marketplace's TopUpModal. - useTransakCheckout: replaces the SDK instance + postMessage event wiring with widgetUrl + a manual markCompleted() the caller invokes once the user confirms they finished in the Transak tab. There is no postMessage/order event to detect completion from a separate browsing context, so status gains a `'ready'` step (URL fetched, waiting on the user) in place of the old SDK-driven success/failure auto-detection. - TransakCheckoutModal / TransakOnrampContent: swap the embedded iframe container + spinner-over-iframe for the 'ready' step's link + manual confirm button; drop the container sizing tuned for the iframe. - PayWithTransakButton: forwards widgetUrl/markCompleted instead of widgetReady. - Removes the now-unused `@transak/ui-js-sdk` dependency and its container DOM id. - Adds the new copy to all 17 locales via `yarn translate`. Known gap: no automatic order-success/failure detection now that the widget runs in its own tab -- same manual-confirm tradeoff TopUpModal already accepted in agent-marketplace. A future iteration could look at whether Transak's redirect-URL param can restore that signal without an iframe.
|
Warning Review limit reached
Next review available in: 49 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughTransak checkout now generates a Secure Widget URL without embedding the SDK. The modal opens Transak in a new tab and supports URL expiration and manual completion. Checkout consumers support the ChangesTransak checkout flow
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant PayWithTransakButton
participant useTransakCheckout
participant TransakCheckoutModal
participant SecureWidgetURLBuilder
PayWithTransakButton->>useTransakCheckout: Start checkout
useTransakCheckout->>SecureWidgetURLBuilder: Generate Secure Widget URL
SecureWidgetURLBuilder-->>useTransakCheckout: Return widgetUrl
useTransakCheckout-->>TransakCheckoutModal: Return ready status and widgetUrl
TransakCheckoutModal->>TransakCheckoutModal: Open widgetUrl in a new tab
TransakCheckoutModal->>useTransakCheckout: Confirm completion
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Size Change: +278 kB (+3.08%) Total Size: 9.31 MB
ℹ️ View Unchanged
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx (1)
84-87: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winThe back button abandons the checkout without cancelling it.
If the user presses back while
statusis'processing', this handler setsstepto'form'. It does not cancel the pendingwidgetUrlBuildercall. When the promise resolves, the hook setsstatusto'ready', the effect at Line 60 fires, and the component leaves the form and shows the ready panel without user action.This component never calls the hook's
closeorreset. DestructureresetfromuseTransakCheckoutand call it when the user returns to the form, both here and in the "Try again" handler at Line 247. This fix depends on the hook discarding stale results; see the related comment onpackages/vechain-kit/src/hooks/payments/useTransakCheckout.tsLines 134-137.🐛 Proposed fix
const { open: startCheckout, status, widgetUrl, markCompleted, + reset: resetCheckout, } = useTransakCheckout(+ const backToForm = () => { + resetCheckout(); + setStep('form'); + }; + const handleBack = () => { setCurrentContent('main'); };<ModalBackButton onClick={ - step === 'form' ? handleBack : () => setStep('form') + step === 'form' ? handleBack : backToForm } />Apply
backToFormto the "Try again" button at Line 247 as well.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx` around lines 84 - 87, Destructure reset from useTransakCheckout and update the back-to-form flow to call reset before returning to the form, including the onClick handler currently using setStep('form') and the “Try again” handler around the existing backToForm logic. Ensure both paths cancel the pending checkout and then transition to the form.
🧹 Nitpick comments (4)
packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx (2)
165-183: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMake the success action consistent with the other dismiss actions.
The "Done" button calls
onClosedirectly. The error footer and the overlay path callhandleClose, which also callsonReset.PayWithTransakButtonhides this difference because itscloseschedulesreset. A different consumer that passes anonClosewithout a reset keepsstatusat'success', and a visibility rule such asisOpen={status !== 'idle'}keeps the modal open.♻️ Proposed change
actions={ <Button variant="vechainKitPrimary" - onClick={onClose} + onClick={handleClose} w="full" > {t('Done')} </Button> }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx` around lines 165 - 183, Update the success StatusScreen action in TransakCheckoutModal to invoke handleClose instead of onClose, matching the error footer and overlay dismissal paths so the modal resets to idle before closing.
116-163: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe ready-state checkout panel is duplicated across two components. Both sites render the same explanatory comment block, the same
'Continue in the new tab to complete your purchase with Transak, then come back here to confirm.'copy, and the sameButton as="a"withhref={widgetUrl ?? undefined},target="_blank",rel="noopener noreferrer", andisDisabled={!widgetUrl}. The matching "I've completed my purchase" footers are duplicated as well. A future change to the external-tab handling must be applied twice, and the two panels can drift.Extract one shared component, for example
TransakReadyPanel, that acceptswidgetUrlandonMarkCompleted.
packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx#L116-L163: replace the inline ready block and the ready footer at Lines 229-239 with the shared component.packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx#L150-L180: replace the inline ready block and the ready footer at Lines 231-241 with the same shared component.Move the iframe-versus-new-tab explanation into the shared component. Replace the reference to "the PR description" with the Transak error code and a durable link, because a PR description is not reachable from the source tree.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx` around lines 116 - 163, Extract the duplicated ready-state panel and completion footer into a shared TransakReadyPanel accepting widgetUrl and onMarkCompleted, including the iframe-versus-new-tab explanation with the T-INF-102 code and a durable link instead of the PR reference. Replace the inline ready content and footer in packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx lines 116-163 and 229-239, and packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx lines 150-180 and 231-241, with the shared component.packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx (1)
57-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe effect duplicates transitions that the hook callbacks already perform.
useTransakCheckoutreceives() => setStep('success')and() => setStep('error')at Lines 53-54. The hook invokes those callbacks on the same transitions this effect mirrors. Only the'ready'transition needs the effect. Keeping all three creates two paths to the same state and makes future changes error prone.♻️ Proposed narrowing
- // Mirror the hook's status into this component's own step state (which - // additionally tracks the pre-checkout 'form' step the hook has no - // concept of). + // The hook drives 'success' and 'error' through the callbacks above. + // Only 'ready' has no callback, so mirror that one transition here. useEffect(() => { - if (status === 'ready') setStep('ready'); - else if (status === 'error') setStep('error'); - else if (status === 'success') setStep('success'); + if (status === 'ready') setStep('ready'); }, [status]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx` around lines 57 - 64, Update the status synchronization effect in TransakOnrampContent so it only handles the 'ready' status and calls setStep('ready'). Remove its 'error' and 'success' branches, preserving the existing hook callbacks as the sole handlers for those transitions.packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts (1)
106-112: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winValidate the URL scheme before you store
widgetUrl.
widgetUrlBuilderis supplied by the integrating application. Its return value is rendered directly into an anchorhrefinTransakCheckoutModal.tsx(Line 152) andTransakOnrampContent.tsx(Line 168). If a builder returns ajavascript:ordata:URL, the link executes that scheme on click. A scheme check here protects both render sites from one place.🛡️ Proposed scheme validation
if (genRef.current !== gen) { // A newer open() call has superseded this one. return; } + const parsed = new URL(url); + if (parsed.protocol !== 'https:') { + throw new Error( + 'transak.widgetUrlBuilder must return an https URL', + ); + } + setWidgetUrl(url); setStatus('ready');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts` around lines 106 - 112, Validate the URL returned by widgetUrlBuilder before setWidgetUrl in the current-generation branch, allowing only safe web URL schemes such as https: (and the existing supported scheme, if applicable). Reject unsafe javascript: and data: values without storing them or setting ready status, while preserving the generation guard and valid-URL flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/playground/src/app/`(playground)/payments/page.tsx:
- Around line 30-39: Update the HOOK_SNIPPET return block so the button and
explanatory comments form valid JSX: wrap the top-level content in a fragment
and convert the // comments into a JSX comment, or move the explanation before
return. Preserve the existing open call and Transak completion guidance.
In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts`:
- Around line 134-137: Update the close callback to increment genRef before
scheduling reset, invalidating any in-flight widgetUrlBuilder result when the
modal is dismissed. Preserve the existing setIsOpen(false) and delayed reset
behavior, and leave open’s generation handling unchanged.
- Around line 129-132: Update markCompleted to make completion idempotent under
rapid repeated invocations, ensuring the success state transition and
onSuccessRef callback occur only once. Use a functional setStatus update or a
completion ref, and reset that guard in reset and at the start of open if using
a ref.
---
Outside diff comments:
In
`@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx`:
- Around line 84-87: Destructure reset from useTransakCheckout and update the
back-to-form flow to call reset before returning to the form, including the
onClick handler currently using setStep('form') and the “Try again” handler
around the existing backToForm logic. Ensure both paths cancel the pending
checkout and then transition to the form.
---
Nitpick comments:
In
`@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx`:
- Around line 57-64: Update the status synchronization effect in
TransakOnrampContent so it only handles the 'ready' status and calls
setStep('ready'). Remove its 'error' and 'success' branches, preserving the
existing hook callbacks as the sole handlers for those transitions.
In
`@packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx`:
- Around line 165-183: Update the success StatusScreen action in
TransakCheckoutModal to invoke handleClose instead of onClose, matching the
error footer and overlay dismissal paths so the modal resets to idle before
closing.
- Around line 116-163: Extract the duplicated ready-state panel and completion
footer into a shared TransakReadyPanel accepting widgetUrl and onMarkCompleted,
including the iframe-versus-new-tab explanation with the T-INF-102 code and a
durable link instead of the PR reference. Replace the inline ready content and
footer in
packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx
lines 116-163 and 229-239, and
packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx
lines 150-180 and 231-241, with the shared component.
In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts`:
- Around line 106-112: Validate the URL returned by widgetUrlBuilder before
setWidgetUrl in the current-generation branch, allowing only safe web URL
schemes such as https: (and the existing supported scheme, if applicable).
Reject unsafe javascript: and data: values without storing them or setting ready
status, while preserving the generation guard and valid-URL flow.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9e76a2d5-b548-4fe9-8955-7b9c9eb6ada0
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (23)
examples/playground/src/app/(playground)/payments/page.tsxpackages/vechain-kit/package.jsonpackages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsxpackages/vechain-kit/src/components/PayWithTransakButton/PayWithTransakButton.tsxpackages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsxpackages/vechain-kit/src/hooks/payments/useTransakCheckout.tspackages/vechain-kit/src/languages/de.jsonpackages/vechain-kit/src/languages/en.jsonpackages/vechain-kit/src/languages/es.jsonpackages/vechain-kit/src/languages/fr.jsonpackages/vechain-kit/src/languages/hi.jsonpackages/vechain-kit/src/languages/it.jsonpackages/vechain-kit/src/languages/ja.jsonpackages/vechain-kit/src/languages/ko.jsonpackages/vechain-kit/src/languages/nl.jsonpackages/vechain-kit/src/languages/pt.jsonpackages/vechain-kit/src/languages/ro.jsonpackages/vechain-kit/src/languages/ru.jsonpackages/vechain-kit/src/languages/sv.jsonpackages/vechain-kit/src/languages/tr.jsonpackages/vechain-kit/src/languages/tw.jsonpackages/vechain-kit/src/languages/vi.jsonpackages/vechain-kit/src/languages/zh.json
💤 Files with no reviewable changes (1)
- packages/vechain-kit/package.json
There was a problem hiding this comment.
Pull request overview
Replaces Transak’s failing iframe integration with a new-tab checkout and manual completion flow.
Changes:
- Adds the
readycheckout state, widget URL, and manual completion callback. - Updates both checkout interfaces and playground documentation.
- Removes the Transak SDK dependency and localizes the new UI.
Reviewed changes
Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
yarn.lock |
Removes Transak SDK packages. |
packages/vechain-kit/package.json |
Removes the SDK dependency. |
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts |
Implements the new checkout state machine. |
packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx |
Adds new-tab checkout UI. |
packages/vechain-kit/src/components/PayWithTransakButton/PayWithTransakButton.tsx |
Forwards the new hook API. |
packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx |
Updates account on-ramp flow. |
examples/playground/src/app/(playground)/payments/page.tsx |
Updates examples and guidance. |
packages/vechain-kit/src/languages/en.json |
Adds English checkout copy. |
packages/vechain-kit/src/languages/de.json |
Adds German checkout copy. |
packages/vechain-kit/src/languages/es.json |
Adds Spanish checkout copy. |
packages/vechain-kit/src/languages/fr.json |
Adds French checkout copy. |
packages/vechain-kit/src/languages/hi.json |
Adds Hindi checkout copy. |
packages/vechain-kit/src/languages/it.json |
Adds Italian checkout copy. |
packages/vechain-kit/src/languages/ja.json |
Adds Japanese checkout copy. |
packages/vechain-kit/src/languages/ko.json |
Adds Korean checkout copy. |
packages/vechain-kit/src/languages/nl.json |
Adds Dutch checkout copy. |
packages/vechain-kit/src/languages/pt.json |
Adds Portuguese checkout copy. |
packages/vechain-kit/src/languages/ro.json |
Adds Romanian checkout copy. |
packages/vechain-kit/src/languages/ru.json |
Adds Russian checkout copy. |
packages/vechain-kit/src/languages/sv.json |
Adds Swedish checkout copy. |
packages/vechain-kit/src/languages/tr.json |
Adds Turkish checkout copy. |
packages/vechain-kit/src/languages/tw.json |
Adds Traditional Chinese checkout copy. |
packages/vechain-kit/src/languages/vi.json |
Adds Vietnamese checkout copy. |
packages/vechain-kit/src/languages/zh.json |
Adds Simplified Chinese checkout copy. |
Suppressed comments (1)
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts:137
- Closing the checkout does not invalidate an in-flight
widgetUrlBuildercall. If the modal is dismissed via Escape/mobile-sheet dismissal while processing, the old promise can still pass the generation check and setstatusback toready, reopening the status-driven modal after it was closed. Invalidate the generation synchronously when closing.
const close = useCallback(() => {
setIsOpen(false);
setTimeout(reset, 300);
}, [reset]);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ab checkout - useTransakCheckout: make markCompleted() idempotent against rapid repeat clicks (functional setStatus update, onSuccess fires once) [CodeRabbit]. - useTransakCheckout: bump genRef in close() so a widgetUrlBuilder() call still in flight when the user closes the modal can no longer resolve into 'ready' after the fact [CodeRabbit]. - useTransakCheckout: track widgetUrl's 5-minute TTL / single-use contract (docs/recipes/transak-onramp.md) via a new widgetUrlExpired flag + timer, and a markWidgetUrlOpened() callers call on click -- surfaces a "get a new link" prompt instead of letting the user follow a dead link [Copilot]. - TransakCheckoutModal / TransakOnrampContent: render the expired/used state and wire markWidgetUrlOpened into the "Continue with Transak" link's onClick. - TransakOnrampContent: stop mirroring hook status into `step` once the user has navigated back to the form, and cancel the in-flight checkout on Back -- previously a widgetUrlBuilder() promise resolving after Back was clicked could silently pull the user back into the 'ready' screen [Copilot]. - examples/playground: HOOK_SNIPPET was invalid JSX (a `//` comment as a sibling after `return`'s single child) [CodeRabbit]; CustomHookDemo never rendered a link or called markCompleted, so following it left the user stuck at 'ready' with no way to finish [Copilot]. Both now show the full, correct flow including the expired-link case. - New copy translated to all 17 locales via `yarn translate`.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts (1)
120-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
getConfig(network.type)for Transak network values.Retrieve the Transak environment and network identifier through
getConfig(network.type)instead of deriving them from hardcoded literals.As per coding guidelines, use
getConfig(networkType)to retrieve network-specific configurations instead of hardcoding network values.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts` around lines 120 - 130, Update the Transak checkout configuration in the widget URL builder to call getConfig(network.type), and use its environment and network identifier values instead of deriving the environment from network.type or hardcoding 'vechain'. Preserve the existing wallet, fiat, and cryptocurrency parameters.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/playground/src/app/`(playground)/payments/page.tsx:
- Around line 26-50: The payment example around useTransakCheckout must handle
the single-use widget URL lifecycle. Destructure widgetUrlExpired and
markWidgetUrlOpened, invoke markWidgetUrlOpened when the Transak link is
clicked, and replace the expired-URL display with a new open() action so stale
URLs are not presented as usable.
- Around line 113-121: Update the custom-trigger purchase button in the payment
page to be disabled when status === 'processing', preventing its onClick handler
from invoking open() during processing while preserving the existing behavior
for other statuses.
In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts`:
- Around line 78-84: Update reset and close in the checkout hook to invalidate
pending work by advancing genRef whenever reset() is called. Capture the current
generation when close() schedules its 300 ms delayed reset, and only perform
that reset if the generation is still unchanged; otherwise preserve the newer
checkout state.
---
Nitpick comments:
In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts`:
- Around line 120-130: Update the Transak checkout configuration in the widget
URL builder to call getConfig(network.type), and use its environment and network
identifier values instead of deriving the environment from network.type or
hardcoding 'vechain'. Preserve the existing wallet, fiat, and cryptocurrency
parameters.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0767550c-3aa8-442b-8a95-ecca390e9443
📒 Files selected for processing (22)
examples/playground/src/app/(playground)/payments/page.tsxpackages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsxpackages/vechain-kit/src/components/PayWithTransakButton/PayWithTransakButton.tsxpackages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsxpackages/vechain-kit/src/hooks/payments/useTransakCheckout.tspackages/vechain-kit/src/languages/de.jsonpackages/vechain-kit/src/languages/en.jsonpackages/vechain-kit/src/languages/es.jsonpackages/vechain-kit/src/languages/fr.jsonpackages/vechain-kit/src/languages/hi.jsonpackages/vechain-kit/src/languages/it.jsonpackages/vechain-kit/src/languages/ja.jsonpackages/vechain-kit/src/languages/ko.jsonpackages/vechain-kit/src/languages/nl.jsonpackages/vechain-kit/src/languages/pt.jsonpackages/vechain-kit/src/languages/ro.jsonpackages/vechain-kit/src/languages/ru.jsonpackages/vechain-kit/src/languages/sv.jsonpackages/vechain-kit/src/languages/tr.jsonpackages/vechain-kit/src/languages/tw.jsonpackages/vechain-kit/src/languages/vi.jsonpackages/vechain-kit/src/languages/zh.json
🚧 Files skipped from review as they are similar to previous changes (13)
- packages/vechain-kit/src/languages/it.json
- packages/vechain-kit/src/languages/ja.json
- packages/vechain-kit/src/languages/fr.json
- packages/vechain-kit/src/languages/en.json
- packages/vechain-kit/src/languages/sv.json
- packages/vechain-kit/src/languages/ko.json
- packages/vechain-kit/src/languages/nl.json
- packages/vechain-kit/src/languages/ro.json
- packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx
- packages/vechain-kit/src/languages/pt.json
- packages/vechain-kit/src/components/PayWithTransakButton/PayWithTransakButton.tsx
- packages/vechain-kit/src/languages/de.json
- packages/vechain-kit/src/languages/es.json
- useTransakCheckout: reset() now also bumps genRef, so a widgetUrlBuilder() call still in flight when reset() is called directly (not via close()) can no longer resolve into 'ready' afterwards. - useTransakCheckout: close()'s delayed reset() is now guarded by the generation captured at close() time -- if a new open() starts within the 300ms delay, the stale reset can no longer clobber the newer checkout's state. - examples/playground: HOOK_SNIPPET now shows the full widgetUrlExpired / markWidgetUrlOpened lifecycle (previously only CustomHookDemo had it, leaving the copyable snippet incomplete); the custom-trigger button is disabled while status === 'processing' in both the snippet and the live demo, preventing repeated open() calls from minting redundant Secure Widget URLs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.
Suppressed comments (2)
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts:173
- The state updater performs the externally visible
onSuccessside effect. React requires updater functions to be pure and may invoke them twice in Strict Mode, so consumers can receive duplicate success callbacks (and callbacks that set state can run during React's state calculation). Move the callback outside the updater and keep idempotency separately, such as with a ref that is reset for each checkout.
setStatus((current) => {
if (current === 'success') return current;
onSuccessRef.current?.();
return 'success';
packages/vechain-kit/src/languages/it.json:419
- The Italian sentence is grammatically incomplete: “Ottieni un nuovo” uses an adjective without the noun/pronoun for “link.” Use “Ottienine uno nuovo” so the expiry prompt reads naturally.
"This link has expired or was already opened. Get a new one to continue.": "Questo link è scaduto o è già stato aperto. Ottieni un nuovo per continuare.",
Problem
In production, the Transak checkout embedded via
containerId(iframe mode,@transak/ui-js-sdk) fails with:Copying the exact same Secure Widget URL from devtools' Network tab into a new browser tab loads it successfully.
Diagnosis
This is not the same code path #655 verified — that PR's end-to-end check used top-level navigation (
preview_startopening the widget URL directly), never the actualcontainerIdiframe integration the app ships. So the iframe path was never actually exercised until this report.Transak's own docs (
docs.transak.com/integration/web/iframe) confirm two relevant facts about the Secure Widget URL:referrerDomainpassed at creation time.That the identical URL fails when loaded in our iframe but succeeds when loaded top-level — and is still valid on that second, later attempt — points at a check that runs (and rejects) before the session gets consumed, and that check is context-sensitive (iframe vs. top-level), not just domain-string-sensitive. The likely mechanism: Transak's server inspects the frame context of the request (e.g.
Sec-Fetch-Dest: iframe) and rejects it unless the parent origin is on a separate, explicit iframe-embedding allowlist — distinct from thereferrerDomainused for session pinning, whichvechainkit.vechain.orgevidently satisfies for top-level loads but apparently not for iframe embedding.I want to be upfront that this is circumstantial — Transak doesn't publish a per-error-code reference for
T-INF-102, so I can't cite their side of it directly — but it's consistent with every signal gathered (the docs above, Transak's CSP headers, and the fact thatvechainkit.vechain.orgitself sends noX-Frame-Options/CSP that would explain a same-side block).Fix
Rather than chase a possibly separate iframe-allowlisting request with Transak, this drops the
@transak/ui-js-sdkcontainerIdintegration entirely and opens the Secure Widget URL in a new tab, via a plain anchor (target="_blank" rel="noopener noreferrer") — mirroring the pattern already shipped and working invechain/agent-marketplace'sTopUpModal.useTransakCheckout: drops the SDK instance + postMessage event wiring in favor ofwidgetUrl+ a newmarkCompleted()the caller invokes once the user confirms they finished in the Transak tab.statusgains a'ready'step (URL fetched, waiting on the user) in place of the old SDK-driven success/failure auto-detection.TransakCheckoutModal/TransakOnrampContent: swap the embedded iframe container + spinner-over-iframe for the'ready'step's link + manual confirm button; drop the container sizing that was tuned for the iframe (fix(homepage): route production onramp through vechain/onramp-proxy #655).PayWithTransakButton: forwardswidgetUrl/markCompletedinstead ofwidgetReady.@transak/ui-js-sdkdependency and its container DOM id.yarn translate.Known gap
There is no automatic order-success/failure detection anymore, since the widget now runs in its own browsing context with no
postMessagechannel back to us. The user confirms manually via "I've completed my purchase" — the same tradeoffTopUpModalalready made inagent-marketplace. A future iteration could look at whether Transak's redirect-URL param can restore an automatic signal without going back to an iframe.Verification
tsc --noEmit,eslint,prettier --checkclean on all touched files.yarn build(tsdown) succeeds./paymentsdemo, wallet-gate + hook state forced open for the check, fully reverted after):ready→ renders<a href={widgetUrl} target="_blank" rel="noopener noreferrer">(confirmed via computed DOM attributes) →markCompleted()→successscreen.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Localization