Iwf Guide

Iwf Guide

Integrations, I18n, and Payments

Iwf includes small integrations that cover common product needs while keeping provider-specific work inside the app.

1. Internationalization

Create a catalog in Idris:

catalog : I18nCatalog
catalog =
  MkI18nCatalog "en"
    [ translation "en" "nav.home" "Home"
    , translation "en" "article.publish" "Publish article"
    , translation "fr" "nav.home" "Accueil"
    , translation "fr" "article.publish" "Publier l'article"
    ]

Or load the canonical catalog format:

# config/i18n.catalog
en.nav.home = Home
fr.nav.home = Accueil
en.cart.items.one = %{count} item
en.cart.items.other = %{count} items
loaded : IO (Either String I18nCatalog)
loaded =
  loadI18nCatalog "en" "config/i18n.catalog"

Each non-comment line is locale.key = message. The locale is the segment before the first dot, and the rest is the translation key.

Choose a locale from the request:

locale : String
locale =
  selectLocale catalog request

Render copy:

<a href="/">{translate catalog locale "nav.home"}</a>

Lookup falls back from regional locales such as fr-CA to fr, then to the catalog default locale, then to the key.

Interpolated messages use %{name} placeholders:

translateWith catalog locale "greeting" [("name", "Alice")]

Plural messages use .one for count 1 and .other for all other counts. translatePlural automatically injects %{count}:

translatePlural catalog locale "cart.items" 3 []

Missing-key diagnostics are explicit:

case translateDiagnostic catalog locale "nav.admin" of
  Right message => message
  Left missing => missing.key

Locale persistence is session-based. Query parameter locale wins, then the persisted session locale, then Accept-Language, then the catalog default:

session1 : Session
session1 =
  persistRequestedLocale catalog request session0

locale : String
locale =
  selectLocaleWithPersistence catalog session1 request

Useful helpers:

  • translation
  • parseI18nCatalog
  • loadI18nCatalog
  • preferredLocales
  • availableLocales
  • translateIn
  • translate
  • translateWith
  • translatePlural
  • translateDiagnostic
  • missingTranslations
  • selectLocale
  • selectLocaleWithPersistence
  • persistRequestedLocale
  • lookupPersistedLocale
  • putPersistedLocale
  • deletePersistedLocale

2. Stripe Checkout

Iwf builds Stripe Checkout Session requests. It pins the API version to:

2026-02-25.clover

Build a request:

stripeConfig : StripeConfig
stripeConfig =
  MkStripeConfig stripeSecret webhookSecret

checkout : StripeCheckoutSession
checkout =
  MkStripeCheckoutSession
    CheckoutPayment
    "https://example.test/billing/success"
    "https://example.test/billing/cancel"
    [MkStripeLineItem "price_123" 1]
    (Just "user-42")
    [("plan", "pro")]

request : StripeApiRequest
request =
  checkoutSessionRequest stripeConfig checkout
    |> withStripeIdempotencyKey "checkout-user-42"

The app sends StripeApiRequest through a StripeTransport:

result : IO (Either String StripeApiResponse)
result =
  sendStripeRequest transport request

StripeTransport is the HTTP boundary. It lets the app use its HTTP client, timeouts, retries, observability, and secret-management policy while Iwf owns request construction. Use checkoutSessionResult to normalize the response into either a hosted Checkout URL or a Stripe error message.

3. Stripe Webhooks

Verify webhooks with an app-provided HMAC-SHA256 function:

handleWebhook : Request -> Response
handleWebhook request =
  case verifyStripeWebhookAt now 300 stripeConfig hmacSha256 request of
    Left response => response
    Right payload => okText "received"

Iwf parses Stripe's Stripe-Signature header, builds the signed timestamp.body payload, matches any v1 digest, and can enforce a timestamp tolerance through verifyStripeWebhookAt. The app supplies the HMAC-SHA256 primitive as StripeWebhookHmacSha256 so it can choose its crypto package and secret-management policy.

After verification, decode the event envelope:

case decodeStripeEvent payload of
  Right event =>
    case event.eventType of
      StripeCheckoutSessionCompleted => okText "checkout complete"
      _ => okText "ignored"
  Left error => badRequest error

Known event constructors cover Checkout completion/expiry, subscription updates, and invoice payment success. Unknown event names are preserved as StripeUnknownEvent.

4. OAuth and Passkeys

OAuth and passkey helpers live with auth because they affect identity:

  • OAuthProviderConfig
  • OAuthPkceChallenge
  • OAuthCallback
  • OAuthTokenRequest
  • oauthAuthorizationUrl
  • issueOAuthState
  • completeOAuthCallback
  • verifyOAuthState
  • completeOAuthLogin
  • issuePasskeyChallenge
  • completePasskeyRegistration
  • verifyPasskeyAssertion

The app owns provider credentials, callback routes, persistence, and cryptographic verifier implementations.

Next

Read HTMX, Auto Refresh, and Modals.