CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

This is a Val Town project — a Klarna payment integration prototype demonstrating iframe-based payment flows for the JPM (J.P. Morgan) partnership. It runs on the Val Town platform (Deno serverless runtime, not Node.js).

Running the Project

This project is deployed and run via Val Town. Locally, use the vt CLI:

vt dev # Start local dev server vt push # Deploy to Val Town vt pull # Pull latest from Val Town

The project has a .vt/state.json that tracks the Val Town project ID and branch.

Architecture

The app uses a single-iframe architecture for Klarna payment integration:

  1. main.ts — HTTP request handler (Val Town entry point). Serves static files from frontend/ and shared/ using Val Town's serveFile/readFile utilities.

  2. frontend/index.html — First-party merchant page. Embeds a single <iframe> pointing to klarna-iframe.html. Listens for postMessage events to resize the iframe between button-size and fullscreen, and to display payment results.

  3. frontend/klarna-iframe.html — Third-party iframe containing both the Klarna SDK payment button and the payment flow. Has a transparent background so the merchant page shows through the SDK's overlay. On button click, tells the parent to expand to fullscreen, then initiates Payment.initiate() with ON_PAGE mode. On completion/abort, tells the parent to shrink back to button size.

  4. shared/types.ts — TypeScript type definitions for all postMessage payloads (ButtonReadyMessage, ExpandIframeMessage, ShrinkIframeMessage, PaymentCompleteMessage, PaymentAbortMessage) plus type guard functions.

PostMessage Flow (all iframe → parent)

klarna-iframe  →  button-ready       →  index.html (parent sizes iframe)
klarna-iframe  →  expand-iframe      →  index.html (parent goes fullscreen)
klarna-iframe  →  shrink-iframe      →  index.html (parent restores button size)
klarna-iframe  →  payment-complete   →  index.html (parent shows result)
klarna-iframe  →  payment-abort      →  index.html (parent shows result)

Val Town Platform Constraints

  • Runtime is Deno, not Node.js — use Deno.env.get() for env vars, not process.env
  • Use https://esm.sh for npm imports (works in both server and browser)
  • Code in shared/ must work in both frontend and backend — cannot use Deno namespace there
  • Entry point exports a default async function: export default async function(req: Request)
  • Use Response.redirect workaround: new Response(null, { status: 302, headers: { Location: url } })
  • Do NOT use Deno KV, alert(), prompt(), or confirm()
  • Imports use URL-based specifiers (e.g., https://esm.town/v/std/utils@85-main/index.ts)

Klarna SDK

The iframe initializes the Klarna Web SDK v2 from https://js.playground.klarna.com/web-sdk/v2/klarna.mjs using test credentials. The SDK is configured with products: ["PAYMENT"]. Payment is initiated with initiationMode: "ON_PAGE" so the SDK renders its overlay inside the fullscreen iframe rather than trying to open a popup (which would be blocked from within an iframe).

Styling

Uses TailwindCSS via twind CDN (https://cdn.twind.style). When using React, pin all dependencies to react@18.2.0.