Skip to content

TypeScript

Where the types come from, which ones exist, and the two that need narrowing.

@wajub/js ships its own definitions. There is no @types/ package to install and nothing to configure. The framework packages export the same types, so installing one of them is enough: import type { PaymentSuccess } from '@wajub/react' works.

npm install @wajub/js

Each entry point is typed.

ImportTypesSide effect
@wajub/jsIncludedLoads Wajub.js on import
@wajub/js/pureIncludedNone until you call loadWajub()
https://js.wajub.comGlobals only, see belowLoads Wajub.js

Everything worth importing

Config and instances
import type {
  EmbeddedConfig,
  PopupConfig,
  CheckoutInstance,
  PopupInstance,
  ComponentConfig,
  ComponentFieldsConfig,
  ComponentInstance,
  ComponentsFactory,
  ComponentType,
  ConfirmPaymentOptions,
} from '@wajub/js';
Styling, payloads and errors
import type {
  AppearanceConfig,
  AppearanceVariables,
  AppearanceRules,
  AppearanceThemePreset,
  AppearanceColorScheme,
  AppearanceLabels,
  CheckoutLayout,
  CheckoutState,
  EmbedBreakdown,
  PaymentSuccess,
  SessionPreview,
  ClientSignals,
  ComponentChangeEvent,
  ComponentRedirectEvent,
  ComponentStateChangeEvent,
  ComponentEventMap,
  InheritSource,
  ComponentAddressValue,
  AddressValue,
  AddressMode,
  CollectAddressMode,
  PhoneFieldMode,
  WajubError,
  WajubErrorType,
} from '@wajub/js';
The runtime and the client
import type {
  WajubRuntime,
  WajubSDK,
  WajubClient,
  WajubFactory,
  WajubInitOptions,
  CreatePaymentParams,
  CreatePaymentResult,
  InitCheckoutOptions,
  InitCheckoutResult,
} from '@wajub/js';

Two places where narrowing matters

The helpers can return null. Everything imported from @wajub/js resolves to null when there is no window, which is what makes a server render safe. TypeScript will make you handle it. Two exceptions, componentEstimatedHeight() and componentReserveHeight(), are synchronous and return a number on the server too.

Nothing throws on the server
import { loadWajub } from '@wajub/js/pure';

const runtime = await loadWajub();
if (!runtime) return;

const checkout = runtime.wajub.mount('#checkout', { sessionId });

Once you hold the runtime, its methods are synchronous and never return null, so this is the only check you need.

The error callbacks are not typed as WajubError. They receive WajubError | Record<string, unknown>. Narrow before reading code.

Narrowing an error
import { loadWajub, mount } from '@wajub/js';

const runtime = await loadWajub();

await mount('#checkout', {
  sessionId,
  onError: (error) => {
    if (runtime && error instanceof runtime.WajubError) {
      report(error.code, error.retryable);
      return;
    }
    report('unknown_error', false);
  },
});

Take the WajubError class from what loadWajub() resolves to, as above, not from an import of @wajub/js. Everything else on this page is a type only import.

Event payloads

on(event, fn) and off(event, fn) type the payload of every event a component emits, through ComponentEventMap: statechange hands a ComponentStateChangeEvent, redirect a ComponentRedirectEvent, resize a { height }. The matching ComponentConfig callbacks, onStateChange, onResize and onRedirect, are typed the same way.

Reading the redirect
component.on('redirect', ({ url, method }) => {
  // The page leaves right after: a beacon survives it, a fetch may not.
  navigator.sendBeacon('/analytics/left-for-provider', JSON.stringify({ host: new URL(url).host, method }));
});

Server rendering

With a framework package, there is nothing to do. The code below is for wiring @wajub/js yourself.

React, without @wajub/react
'use client';

import { useEffect, useRef } from 'react';
import { loadWajub } from '@wajub/js/pure';
import type { CheckoutInstance } from '@wajub/js';

export function Checkout({ sessionId }: { sessionId: string }) {
  const host = useRef<HTMLDivElement>(null);
  const instance = useRef<CheckoutInstance | null>(null);

  useEffect(() => {
    loadWajub().then((runtime) => {
      if (!runtime || !host.current) return;
      instance.current = runtime.wajub.mount(host.current, { sessionId });
    });

    return () => instance.current?.destroy();
  }, [sessionId]);

  return <div ref={host} />;
}

Import from /pure, so that importing the module does nothing on the server.

To keep a payment field's room in that server render, give its container componentEstimatedHeight(type, variant, fields) as its min-height, the same on the server and at hydration. In the browser, set that min-height back to 0 right before you mount the field with loader: 'skeleton'.

Typing the CDN globals

With the script tag and no npm package, wajub, Wajub and WajubError sit on window. To type them, install @wajub/js as a development dependency and, since your code never imports it, name it with /// <reference types="@wajub/js" /> at the top of one file or "types": ["@wajub/js"] in compilerOptions.

Already declared for you
declare global {
  interface Window {
    wajub: WajubSDK;
    Wajub: WajubFactory;
    WajubError: typeof WajubError;
  }
}

Framework types

Each framework package exports everything above, and adds its own types.

PackageAdds
@wajub/reactWajubProviderProps, CheckoutEmbedProps, ComponentEmbedProps, WajubContextValue
@wajub/vueThe same four, plus WajubRuntimeRef
@wajub/svelteWajubContextValue, WajubRuntimeStore, ComponentEmbedProps, TypedComponentProps and SessionIdSource
@wajub/angularWajubOptions, SessionIdSource and WajubConfirmPayment. The inputs and outputs are typed on the component classes, which Angular's template type checking reads

What did you think of this content?