Vue 3
Provider, composables, embed props, and the Nuxt wiring that goes with them.
@wajub/vue wraps @wajub/js in Vue 3 syntax: a provider that loads the runtime once, five field
components, and four composables. The payment API itself does not change, so the behaviour of every
option is documented on Hosted checkout and
Payment fields. This page covers what is specific to Vue.
@wajub/vue
Stable · GAnpm
- Version
- 1.4.0
- Runtime
- Vue 3.3+
Covers
- Payments
- Payment Links
Not in this package: Billing, Transfers, Sync, Shield, Tax.
Install
@wajub/js is a peer dependency, not a bundled one, so it goes on the same line. The package is ESM
only and declares sideEffects: false.
npm install @wajub/vue @wajub/jsThe provider
WajubProvider calls loadWajub() from @wajub/js/pure and provides the runtime through Vue's
injection. One provider covers a whole subtree, so it belongs at the layout or page level rather
than around each component.
<script setup lang="ts">
import { WajubProvider } from '@wajub/vue';
</script>
<template>
<ClientOnly>
<WajubProvider>
<slot />
</WajubProvider>
</ClientOnly>
</template>| Prop | Type | Default | What it does |
|---|---|---|---|
defer | boolean | false | Skips the automatic load, leaving it to useLoadWajub() |
load-options | { jsOrigin?, jsUrl? } | CDN defaults | Overrides where the runtime is fetched from |
defer is for the case where a parent already loaded the runtime, or where you want the fetch to
wait for a user action. Without it, the provider starts loading as soon as it mounts.
Composables
useWajub throws while the runtime is loading
It throws on every call until the script has arrived, and again if the load failed. A component
that calls it in setup under the provider will crash before the first paint. Use
useWajubOptional(), which returns null instead, unless you already know the runtime is there.
<script setup lang="ts">
import { useWajubOptional } from '@wajub/vue';
const props = defineProps<{ sessionId: string }>();
const runtime = useWajubOptional();
function upgrade() {
runtime?.wajub.open({ sessionId: props.sessionId, onSuccess: onUpgraded });
}
</script>
<template>
<button :disabled="!runtime" @click="upgrade">Upgrade plan</button>
</template>| Composable | Returns | Notes |
|---|---|---|
useWajub() | { wajub, Wajub, WajubError } | Throws while loading, on failure, and with no runtime |
useWajubOptional() | The same, or null | Safe during SSR and before the script lands |
useLoadWajub() | { runtime, loading, error, loadWajub } | The first three are refs. Drives the load yourself, with defer |
useConfirmPayment(sessionId) | { confirm, isConfirming } | isConfirming is a ref. Submits a mounted field component |
useWajubContext() returns the same { runtime, loading, error } refs if you would rather build
your own composable on top, and WAJUB_INJECTION_KEY is the key the provider uses.
CheckoutEmbed
The hosted checkout inside your own layout. Unlike the other frameworks, the props are a closed
list: session-id, class, style, min-height, locale, embed-origin, theme, appearance,
layout, loading-text, show-loading, and the eleven callbacks.
<script setup lang="ts">
import { CheckoutEmbed } from '@wajub/vue';
defineProps<{ sessionId: string }>();
</script>
<template>
<CheckoutEmbed
:key="sessionId"
:session-id="sessionId"
layout="tabs"
:min-height="480"
@success="navigateTo('/order/complete')"
@error="(error) => console.error(error.code, error.message)"
/>
</template>Every callback is declared as a prop, so @success and :on-success are the same thing. Vue turns
the listener into the prop for you.
| Prop | Type | Default | What it does |
|---|---|---|---|
session-id | string | Required | The authorization_token from POST /payments |
class | string | none | Class on the container element |
style | Record<string, string> | none | Inline styles on the container |
min-height | number | 480 | Reserved height in pixels while the iframe loads |
The embed never reacts to a prop change
It mounts once and watches nothing, not even session-id. Changing appearance, locale or
layout afterwards does nothing, and a second session renders the first one. Bind :key to the
session so Vue rebuilds the component, and keep the instance from @ready for live changes.
<script setup lang="ts">
import { ref, watch } from 'vue';
import type { CheckoutInstance } from '@wajub/js';
const props = defineProps<{ sessionId: string; dark: boolean }>();
const checkout = ref<CheckoutInstance | null>(null);
watch(
() => props.dark,
(dark) => checkout.value?.update({ appearance: { colorScheme: dark ? 'dark' : 'light' } }),
);
</script>
<template>
<CheckoutEmbed
:key="sessionId"
:session-id="sessionId"
@ready="(instance) => (checkout = instance)"
/>
</template>Payment fields
Five components render one field group each: CardComponent, MobileMoneyComponent,
WalletComponent, PaymentComponent and AddressComponent. They share the props of
ComponentConfig plus the wrapper props below. ComponentEmbed
is the same component with an explicit type, when the choice is made at runtime.
<script setup lang="ts">
import { ref } from 'vue';
import type { ComponentInstance } from '@wajub/js';
import { AddressComponent, PaymentComponent, useConfirmPayment } from '@wajub/vue';
const props = defineProps<{ sessionId: string }>();
const { confirm, isConfirming } = useConfirmPayment(props.sessionId);
const field = ref<ComponentInstance | null>(null);
function pay() {
if (field.value) confirm(field.value);
}
</script>
<template>
<AddressComponent :session-id="sessionId" address-mode="shipping" />
<PaymentComponent
:session-id="sessionId"
collect-address="shipping"
collect-name
@instance="(instance) => (field = instance)"
/>
<button :disabled="isConfirming || !field" @click="pay">
{{ isConfirming ? 'Processing…' : 'Pay now' }}
</button>
</template>| Prop | Type | Default | What it does |
|---|---|---|---|
type | ComponentType | Set by the alias | Only on ComponentEmbed |
min-height | number | 200 | Reserved height while the field loads |
@instance | (instance | null) => void | none | Fires on mount and again with null on unmount |
@ready | (instance) => void | none | Fires once the field is interactive |
useConfirmPayment guards against a double submission, so a second click while isConfirming is
true is ignored. It resolves with { status } and rejects with a WajubError, including
confirmation_timeout after 60 seconds.
Field components do update in place
Unlike the embed, they watch appearance, locale and layout and call update() with all
three when any of them changes. Changing session-id or type remounts the field instead.
Nuxt
The session is created on your server. Nothing in this package needs a secret key, and nothing should ever receive one.
import { Wajub } from '@wajub/node';
const wajub = new Wajub({ secretKey: process.env.WAJUB_SECRET_KEY! });
export default defineEventHandler(async (event) => {
const { cartId } = await readBody(event);
const payment = await wajub.payments.create(
{
amount: 25000,
currency: 'XAF',
description: `Cart ${cartId}`,
callback: `${process.env.NUXT_PUBLIC_APP_URL}/order/return`,
metadata: { cart_id: cartId },
},
{ idempotencyKey: `cart-${cartId}` },
);
return { sessionId: payment.authorization_token };
});runtimeConfig.public is for the publishable key only
Anything under public is serialised into the page. pk. belongs there, sk. never does. A
secret key sent from a browser is answered 403 and its owner is emailed about it.
Three more things Nuxt asks for:
- Wrap the checkout in
<ClientOnly>. The runtime needs awindow, and the composables returnnullrather than throwing during the server render. - Put
WajubProviderin a layout so a navigation inside the checkout flow does not reload the runtime. - Fetch the session from
server/api/and pass the token down as a prop.
What the package exports
| Export | What it is |
|---|---|
WajubProvider | Loads the runtime once for the subtree |
CheckoutEmbed | The hosted checkout, inline |
CardComponent, MobileMoneyComponent, WalletComponent, PaymentComponent, AddressComponent | One field group each |
ComponentEmbed | The same, with the type given as a prop |
useWajub, useWajubOptional, useLoadWajub | Reach the runtime |
useConfirmPayment | Submit a field component |
WAJUB_INJECTION_KEY, useWajubContext | The raw injection, for your own composable |
Types come with the package: WajubProviderProps, CheckoutEmbedProps, ComponentEmbedProps and
WajubContextValue, alongside everything @wajub/js exports.