Skip to main content

Revup Unified SDK 1.1

Reference release: 1.1.5 — the last patch of the 1.1 line. Status: maintained. New integrations should start on 1.3. Existing 1.1 integrations keep working; when you are ready to move, follow Migrating 1.1 → 1.2. Coming from 1.0? Start at Migrating from 1.0 to 1.1.

Everything on this page describes 1.1.x only, and it is complete on its own — the full option reference and the full event contract are here, not inherited from the 1.0 page.


Contents​

  1. What the SDK does
  2. Quick start
  3. Loading the SDK script
  4. The container
  5. RevupConfig — constructor
  6. RevupOptions — init()
  7. form — card form options
  8. apms — payment zones
  9. appearance — checkout styling
  10. userInfo — payer email
  11. Languages
  12. Lifecycle: cleanup and re-initialization
  13. Session API (Revup.mount())
  14. Event contract
  15. Environments, URLs and CSP
  16. Known limits of the 1.1 line

Migrating from 1.0 to 1.1​

1.1 is a breaking minor. Read this section before upgrading.

1. Change the loader path​

- src="https://test.hosted.revuppayments.com/payments-widget/sdk/1/0/loader.js"
+ src="https://test.hosted.revuppayments.com/payments-widget/sdk/1/1/loader.js"

The integrity hash changes with the minor. Request the 1.1 value from RevUp.

2. Rewrite apms from a flat list to zones — required​

The flat enabled / buttonTypes / capabilityDetection shape is gone. It is not deprecated-but-working: those keys are simply not read, so an unchanged 1.0 apms block renders no APM buttons at all.

  apms: {
- enabled: ['apple_pay', 'google_pay', 'paypal', 'amazon_pay'],
- buttonTypes: { apple_pay: 'book', google_pay: 'buy', paypal: 'checkout' },
- capabilityDetection: ['apple_pay', 'google_pay'],
+ primary: [
+ { name: 'apple_pay', type: 'book', capabilityDetection: true },
+ { name: 'google_pay', type: 'buy', capabilityDetection: true },
+ { name: 'paypal', type: 'checkout' },
+ ],
+ secondary: [
+ { name: 'amazon_pay' },
+ ],
}

Mapping, key by key:

1.01.1
apms.enabled: ['x', 'y']one ApmEntry per method, placed in primary, secondary or other
apms.buttonTypes: { x: 'buy' }type: 'buy' on that method's entry
apms.capabilityDetection: ['x']capabilityDetection: true on that method's entry
—new per-entry color

3. Move styling to appearance — required​

form.styles, form.theme and form.buttonStyles are removed. They are rejected at runtime and logged; they do not style anything.

  form: {
- theme: 'bootstrap',
- styles: { /* … */ },
- buttonStyles: {
- backgroundColor: '#306de5',
- textColor: '#FFFFFF',
- height: 44,
- borderRadius: 16,
- },
collapsed: false,
},
+ appearance: {
+ theme: 'default',
+ variables: {
+ submitButtonBackgroundColor: '#306de5',
+ submitButtonTextColor: '#FFFFFF',
+ submitButtonHeight: '44px',
+ submitButtonBorderRadius: '16px',
+ },
+ },

Note the type change: appearance variables are CSS strings ('44px'), where buttonStyles used numbers (44). The 44–55 px height clamp and the 0–60 px border-radius clamp still apply.

4. Behaviour change — form.collapsed​

In 1.0, collapsed: true only took effect if at least one APM button rendered; otherwise the form was forced open. In 1.1 collapsed is honoured as written, independently of how many APMs render. If you relied on the old fallback, set collapsed: false explicitly.

5. Behaviour change — three of your events changed shape​

  • payment.* and validation.changed payloads are unchanged.
  • event.detail is now deep-frozen. A listener that mutated the event object will now fail silently (or throw in strict mode). Copy before you modify.
  • A new event type exists: revup.updated, with the new source: 'sdk'. If you switch on event.type with an exhaustive check, add a branch or a default.

6. New capabilities you can adopt (all optional)​

  • orderId can now be passed to init(), so switching order no longer requires a new Revup instance.
  • revup.destroy() exists and is the correct teardown — replace container.innerHTML = ''.
  • form.enabled: false renders an APM-only checkout.
  • Three new payment methods: ideal, sepa_debit, pix.
  • The Session API (Revup.mount()) replaces the destroy-and-rebuild cycle with session.set(patch).

7. What did not change​

form.selfHostedDomain is still a supported option in 1.1 with the same meaning. (It is removed in 1.2.) RevupConfig, getApmsAvailable(), Revup.getAvailableLanguages(), onRevupMessage(), the 19 languages and the card-brand list are all unchanged.

Migration checklist​

  • [ ] Loader path 1/0 → 1/1, new integrity hash
  • [ ] apms rewritten as primary / secondary / other entries
  • [ ] form.styles / form.theme / form.buttonStyles → appearance
  • [ ] form.collapsed set explicitly if you relied on the 1.0 fallback
  • [ ] Event handlers no longer mutate event.detail
  • [ ] container.innerHTML = '' → revup.destroy()
  • [ ] Order switching moved to init({ orderId }) instead of a new instance

What's new in 1.1​

AreaChange
LayoutThree configurable payment zones — primary, secondary (behind an "Or pay with" divider), other (behind a "+ N more payment methods" toggle)
LayoutThe card form became an accordion with a "Credit or debit card" header and a "Popular" badge; only one accordion is open at a time across the checkout
LayoutA localized secure-payment footer is always rendered inside the container
Payment methodsiDEAL, SEPA Direct Debit and Pix added, served through Stripe Payment Element
StylingFull Appearance API — theme, variables, rules — replacing form.styles / form.buttonStyles
ConfigorderId accepted by init(); form.enabled added
Lifecyclerevup.destroy() added
LifecycleSession API: Revup.mount() → RevupSession with set(), on(), subscribeTo(), state, destroy()
EventsNew revup.updated event and new source: 'sdk'
Eventsevent.detail is deep-frozen before dispatch
EventsNew error names: amazon_pay_error, ideal_error, sepa_debit_error, pix_error
ReliabilityA method that fails after render removes its own slot and re-flows the layout

1. What the SDK does​

RevUp is a single hosted script that renders a complete payment block inside one element of your checkout page. It orchestrates:

  • A credit / debit card form, hosted in a sandboxed iframe on RevUp's domain, so card data never touches your page. In 1.1 it lives inside an accordion row.
  • Alternative payment methods (APMs) — Apple Pay, Google Pay, PayPal, Stripe Link, Amazon Pay, iDEAL, SEPA Direct Debit and Pix — arranged across three layout zones, limited to the methods enabled for your merchant account.

The script exposes one global constructor, Revup, and reports everything that happens through one native DOM event, onRevupMessage.

Checkout layout​

┌─ container ─────────────────────────────────┐
│ primary zone │
│ [ Apple Pay ] [ Google Pay ] [ PayPal ]│
│ ▸ Credit or debit card (POPULAR) │ ← card accordion
│ │
│ ──────────── Or pay with ───────────── │
│ secondary zone (2-column grid) │
│ [ Amazon Pay ] [ Stripe Link ] │
│ │
│ + 3 more payment methods ▾ │ ← other zone
│ │
│ 🔒 This is a secure 256-bit encrypted … │
└─────────────────────────────────────────────┘
ZonePositionMax entriesHidden when
primaryTop of the block, together with the card accordion row4Never — the card row can still render when form.enabled is true
secondaryBelow primary, after an "Or pay with" divider, in a responsive 2-column grid (single column on narrow containers)4Empty, or none of its methods are available
otherBelow secondary, behind a "+ N more payment methods" toggleunlimitedEmpty, or none of its methods are available

Zone caps are applied before availability filtering. Configuring 6 primary entries drops entries 5 and 6 outright — even if entries 1–4 later turn out to be unavailable.

De-duplication. A method may appear in only one zone. If the same name is listed in several zones, the highest-priority placement wins: primary > secondary > other.

Availability filtering. Every configured name is intersected with the methods your merchant account has enabled. A method that is not enabled is silently skipped and produces no event.

Reactive layout. If a method fails after the first render (vendor SDK load error, capability check, no methods reported), the SDK removes that slot, updates the "+ N more" count and hides secondary / other if they become empty. It also dispatches apm.unavailable. No merchant handling is required.

Height changes. Expanding the card accordion or an inline payment form changes the container's height. Make the surrounding layout tolerant of that.


2. Quick start​

<div id="revup-container"></div>

<script
type="text/javascript"
charset="utf-8"
src="https://test.hosted.revuppayments.com/payments-widget/sdk/1/1/loader.js"
crossorigin="anonymous"
integrity="sha512-…">
</script>
window.addEventListener('onRevupSDKLoaded', async () => {
const revup = new Revup({
merchantDomain: window.location.host,
apiKey: 'YOUR_PUBLIC_API_KEY',
orderId: 'ORDER_ID_FROM_YOUR_BACKEND',
});

const stopListening = revup.onRevupMessage((event) => {
console.log(event.type, event);
});

await revup.init({
containerId: 'revup-container',
orderId: 'ORDER_ID_FROM_YOUR_BACKEND',
language: 'en-US',
appearance: {
theme: 'default',
variables: {
submitButtonBackgroundColor: '#306de5',
submitButtonBorderRadius: '16px',
},
},
form: { collapsed: false, showZipCode: true },
apms: {
primary: [
{ name: 'apple_pay', capabilityDetection: true },
{ name: 'google_pay', capabilityDetection: true },
{ name: 'paypal', type: 'checkout' },
],
secondary: [{ name: 'amazon_pay' }, { name: 'stripe_link' }],
other: [{ name: 'ideal' }, { name: 'sepa_debit' }, { name: 'pix' }],
},
});
});

1.1 also offers a shorter, session-based entry point — see Session API. Both are fully supported.


3. Loading the SDK script​

https://<host>/payments-widget/sdk/<major>/<minor>/loader.js
EnvironmentLoader URL (1.1 line)
Sandboxhttps://test.hosted.revuppayments.com/payments-widget/sdk/1/1/loader.js
Livehttps://hosted.revuppayments.com/payments-widget/sdk/1/1/loader.js

The two path segments before loader.js are the SDK's major and minor. Patch releases inside a minor (1.1.4 → 1.1.5) do not change your script URL or your integrity hash — the loader resolves the current patch on every page load. You only edit your <script> tag when RevUp announces a new minor or major.

Use integrity in production, paired with crossorigin="anonymous". The SRI hash is issued per environment and per minor line; request the current value from RevUp rather than copying it from an example.

Integrity hash

The integrity value is not published in this documentation. You may request it from RevUp by escalating a support ticket.

Dynamic loading (React, Vue, …)​

const script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.src = REVUP_SDK_URL;
script.crossOrigin = 'anonymous';
if (INTEGRITY_HASH) script.integrity = INTEGRITY_HASH;
document.head.appendChild(script);

The ready signal​

The loader fires onRevupSDKLoaded on window once Revup is available. Never touch Revup before it.


4. The container​

<div id="revup-container"></div>

The element must already exist in the DOM when you call init(). If it does not, init() throws:

Container with id: <containerId> not found, please check if the container is present in the DOM

init() takes ownership of the element: it clears the contents and writes its own class name, layout attributes and inline sizing. Do not target it by class name and do not put your own children inside it.

A localized secure-payment footer ("This is a secure 256-bit encrypted payment. You're safe.") is always appended inside the container. It is not configurable.


5. RevupConfig — constructor​

const revup = new Revup({
merchantDomain: window.location.host,
apiKey: 'YOUR_PUBLIC_API_KEY',
orderId: 'ORDER_ID',
// version: '2',
});
PropertyTypeRequiredDescription
merchantDomainstringYesYour merchant domain, e.g. window.location.host or your production domain. Must match a domain registered for your merchant account.
apiKeystringYesPublic API key for the environment (sandbox or live).
orderIdstringYesOrder identifier. In 1.1 it can also be passed to init(); when present in both, the init() value wins.
versionstringNoAPI version path segment. Defaults to the SDK's built-in version (currently "2"). Unrelated to the SDK version in the loader URL.

Public methods​

MethodReturnsDescription
revup.init(options)Promise<void>Builds the layout, initializes APMs, loads the card form. Throws if the container is missing.
revup.destroy()voidFull teardown. See Lifecycle.
revup.getApmsAvailable()Promise<string[]>Payment methods enabled for your merchant account. Safe to call before init().
revup.onRevupMessage(handler)() => voidSubscribes to SDK events; returns a cleanup function.
Revup.getAvailableLanguages()string[]Static. All supported language codes. Callable before init().
Revup.mount(options)Promise<RevupSession>Static. Session-based entry point — see Session API.

getApmsAvailable() adds a round-trip and is not needed for initialization; if you use it, call it in parallel with loading the SDK. It tells you which methods your account has enabled — not which of them will render on the current device.


6. RevupOptions — init()​

PropertyTypeRequiredDefaultDescription
containerIdstringYes—id of the element that will host the payment UI.
orderIdstringNo*value from the constructor*Required on the first init() unless it was supplied to the constructor. When present in both, this value wins. Passing a different value to a later init() re-fetches the order and re-initializes the payment methods against it, on the same instance. Subsequent calls may omit it to keep the previous value.
languagestringNobrowser language, then 'en-US'BCP 47 language code. See Languages.
appearanceobjectNo{ theme: 'default' }Checkout styling. See appearance.
formobjectNosee belowCard form options. See form.
apmsobjectNo{}Payment zones. See apms.
userInfoobjectNo{}Payer information. See userInfo.

init() is asynchronous and performs a full build: every payment method is destroyed and re-initialized, and the card iframe is recreated. Anything the payer had already typed is lost. Use the Session API when you need in-place updates.


7. form — card form options​

form: {
enabled: true,
collapsed: false,
showZipCode: true,
cardBrands: ['visa', 'mastercard', 'american_express'],
}
PropertyTypeDefaultDescription
enabledbooleantrueWhen false, the card accordion row and the card iframe are not rendered at all — an APM-only checkout.
collapsedbooleantruetrue = the card accordion starts closed; false = it starts expanded. Independent of how many APMs render (this differs from 1.0).
showZipCodebooleanfalseShows the ZIP / postal-code field.
cardBrandsCardBrand[]['visa', 'mastercard']Brand marks shown inside the card-number field before a brand is detected, and the set used to recognise the brand as the payer types. An empty array is ignored and the default is kept.
textDirectionstring'ltr'Accepted and stored, but has no visual effect in 1.1 — nothing reads it. Right-to-left mirroring is implemented from 1.2.
selfHostedDomainbooleantrueWhen true, 3-D Secure device-fingerprint (DDC) requests run inside the hosted card form iframe. Set to false when the SDK is embedded directly on your primary checkout page and your gateway requires a first-party browser context for those requests. Nested-iframe setups are not supported with false.
userInfoobject{}Same shape as top-level userInfo; the top-level form is preferred.

There is no form.styles, form.theme or form.buttonStyles in 1.1. All visual configuration — including the submit button's colour, height and border radius — goes through appearance.

Option validation​

Every key inside form is type-checked at runtime. A key with the wrong type — or a key the SDK does not recognise — is rejected, the default is used instead, and a message is written to the browser console:

<key> is not a valid option, default value was set instead.

This never throws and never blocks the checkout.

Supported card brands​

visa, mastercard, american_express, unionpay, jcb, discover, diners_club, maestro, visa_electron, vpay, rupay, elo, elo_new, hipercard, verve, troy, mir, cartes_bancaires, girocard, dankort, bancontact, interac, bc_card, dinacard, cabal, napas, mada, knet, benefit.

An unrecognised value renders the generic card mark rather than failing.


8. apms — payment zones​

apms: {
primary: [
{ name: 'apple_pay', color: '#111111', capabilityDetection: true },
{ name: 'google_pay', color: 'white', capabilityDetection: true },
{ name: 'paypal', type: 'checkout', color: 'blue' },
],
secondary: [
{ name: 'amazon_pay' },
{ name: 'stripe_link' },
],
other: [
{ name: 'ideal' },
{ name: 'sepa_debit' },
{ name: 'pix' },
],
}
PropertyTypeDescription
primaryApmEntry[]Rendered above / alongside the card accordion. Max 4.
secondaryApmEntry[]2-column grid below, after an "Or pay with" divider. Max 4. Hidden when empty. Light-surface default colours.
otherApmEntry[]Behind a "+ N more payment methods" toggle. Unlimited. Hidden when empty. Light-surface default colours.

ApmEntry​

FieldTypeRequiredDescription
namestringYesThe payment method identifier — see the list below.
typestringNoVendor button style variant. Ignored by ideal, pix and sepa_debit, which render an inline form rather than a vendor button.
colorstringNoVendor button colour — see Colours.
capabilityDetectionbooleanNo (default false)When true, run a device/browser support check before rendering. Only honoured by Apple Pay and Google Pay; ignored by everything else. If the check fails, the SDK dispatches apm.unavailable and does not render an unusable button.

Valid name values​

apple_pay, google_pay, paypal, stripe_link, amazon_pay, ideal, sepa_debit, pix.

  • credit_card is not valid here — the card flow is configured through form.
  • Use sepa_debit, not sepa, for SEPA Direct Debit.
  • getApmsAvailable() returns the methods your account has enabled.

How each method renders​

MethodRendering
apple_pay, google_payNative wallet button, opening the device payment sheet. When your merchant configuration routes them through Stripe, they open a Stripe Express Checkout sheet instead.
paypalPayPal button opening the PayPal popup. There is no page redirect — the SDK confirms in the background and dispatches payment.success.
stripe_link, amazon_payStripe Express Checkout button opening a Stripe payment sheet.
ideal, sepa_debit, pixAn inline Stripe Payment Element accordion row, collapsed by default, that expands to a form with its own "Pay Now" button. It participates in the same one-open-at-a-time behaviour as the card accordion; pressing Escape or clicking outside collapses it.

Button style variants (type)​

MethodAccepted values
apple_payadd-money, book, buy, check-out, continue, contribute, donate, order, pay, plain, reload, rent, set-up, subscribe, support, tip, top-up
google_paybook, buy, checkout, donate, order, pay, plain, subscribe
paypalpaypal, checkout, buynow, pay, installment
stripe_link, amazon_pay, ideal, pix, sepa_debitnot applicable — the value is ignored

An unrecognised value falls back to the vendor default and is reported; the button still renders.

Colours​

MethodAccepted color
apple_payAny valid CSS colour (e.g. #1f6feb, rgb(20,20,20)), or the presets black, white, white-outline. When omitted in primary, the SDK picks black or white from the luminance of the backgroundColor appearance variable, defaulting to black when that variable is unset. It does not read your page's real background.
google_paydefault, black, white. An invalid value logs a warning and falls back to default.
paypalgold, blue, silver, white, black. An invalid value logs a warning and falls back to the default.
stripe_link, amazon_payNot customizable — the value is ignored.
ideal, pix, sepa_debitNot customizable — these render an inline form, styled by the Appearance API.

Zone defaults. Entries that appear only in secondary or other and have no explicit color receive light-surface defaults: Apple Pay white-outline, Google Pay white, PayPal white. primary entries never receive this default and keep their vendor look. An explicit color always wins, in every zone.


9. appearance — checkout styling​

appearance is the single styling surface in 1.1. It replaces form.styles and form.buttonStyles, and — unlike them — it styles both the hosted card form and the parent-page chrome (card accordion, payment zones, divider, footer).

appearance: {
theme: 'default',
variables: {
fontFamily: 'Inter, system-ui, sans-serif',
backgroundColor: '#ffffff',
submitButtonBackgroundColor: '#306de5',
submitButtonTextColor: '#FFFFFF',
submitButtonHeight: '44px',
submitButtonBorderRadius: '16px',
inputBorderRadius: '8px',
},
rules: {
'.InputWrapper--focused': { 'border-width': '2px' },
},
}
KeyTypeDescription
theme'default' | 'dark' | 'flat' | 'minimal'Preset used as the base. Defaults to 'default'.
variablesobjectHigh-level design tokens that override the preset.
rulesobjectFine-grained CSS overrides, keyed by an allowlisted element target.

Variables​

Variable names follow <domain><Sub?><Property><State?> in camelCase, e.g. cardButtonBorderColorHover, submitButtonOpacityDisabled. State suffixes are Hover, Focus, Active, Disabled, Invalid, Open; no suffix means the default state.

Grouped by domain:

DomainWhat it stylesExamples
GlobalTypography and motion across every surfacefontFamily, fontWeightRegular, fontWeightBold, disableAnimations, transitionDuration
ContainerThe RevUp block on your pagebackgroundColor, textColor, width, height, maxWidth, padding, borderRadius, justifyContent, alignItems
Form slotThe wrapper around the card iframeformBackgroundColor, formPadding, formBorderRadius
Payment methodsShared row surface for every methodpaymentMethodsContainerPadding, primaryGridGap, paymentMethodHeight, paymentMethodBorderWidth, paymentMethodBorderRadius, paymentMethodBackgroundColor, paymentMethodBorderColor, paymentMethodBoxShadow
Card accordionThe "Credit or debit card" headercardButtonBackgroundColor, cardButtonTextColor, cardButtonBorderColor, cardButtonBorderWidth, cardButtonBorderRadius, cardButtonFontSize, cardButtonFontWeight, cardButtonPadding, and the …Hover / …Open variants
Card badgeThe "POPULAR" pillcardBadgeBackgroundColor, cardBadgeTextColor, cardBadgeFontSize, cardBadgeFontWeight, cardBadgeLetterSpacing, cardBadgeBorderRadius, cardBadgePadding, and the …Open variants
Field / labelForm rows and labels inside the iframefieldMinHeight, expiryCvcRowGap, labelTextColor, labelFontSize, labelFontWeight, labelTextColorFocus, labelTextColorInvalid
InputCard inputsinputHeight, inputFontSize, inputPadding, inputBorderColor, inputBorderWidth, inputBorderRadius, inputBackgroundColor, inputTextColor, inputBoxShadow, inputPlaceholderColor, plus …Hover, …Focus, …Invalid, …Disabled variants, and inputAdornmentColor
ErrorField error texterrorTextColor, errorFontSize, errorFontWeight
Submit buttonThe card form's pay buttonsubmitButtonHeight, submitButtonBorderRadius, submitButtonBackgroundColor, submitButtonTextColor, submitButtonFontSize, submitButtonFontWeight, and the …Hover, …Active, …Disabled, submitButtonOpacityDisabled variants
Skeleton / overlayLoading placeholders and scrimsskeletonBackgroundColor, skeletonShimmerColor, skeletonAnimationDuration, overlayLoadingBackgroundColor, overlayDisabledBackgroundColor
Secondary zoneThe "Or pay with" divider and gridsecondaryGridGap, secondaryDividerColor, secondaryDividerThickness, secondaryDividerLabelTextColor, secondaryDividerLabelBackgroundColor, secondaryDividerLabelFontSize, secondaryDividerLabelFontWeight, secondaryDividerLabelPadding
Other zoneThe "+ N more payment methods" toggleotherGridGap, otherToggleTextColor, otherToggleTextColorHover, otherToggleFontSize, otherToggleFontWeight

Values are CSS strings, not numbers: '44px', '#306de5', '200ms'. Numeric weights (fontWeightRegular, …FontWeight) and submitButtonOpacityDisabled are numbers.

Clamping. Out-of-range values are clamped rather than rejected:

Token familyRange
Submit button height44–55 px
Input height30–60 px
Any border radius0–60 px
Any border width0–10 px
Any font size8–32 px
Any font weight1–1000
Any padding side0–40 px
Grid gaps0–40 px
submitButtonOpacityDisabled0–1

Rules​

rules accepts an allowlist of abstract element targets — not raw CSS selectors — mapped to real selectors in each render context. An unrecognised target is ignored.

Inside the card iframe: .Field, .Field--focused, .Field--invalid, .Label, .Label--focused, .Label--invalid, .InputWrapper, .InputWrapper--hover, .InputWrapper--focused, .InputWrapper--invalid, .InputWrapper--disabled, .Input, .Input::placeholder, .Select (+ --hover, --focused, --invalid, --disabled), .InputAdornment, .Error, .SubmitButton (+ --hover, --active, --disabled), .FormContainer.

On your page: .RevupContainer, .PaymentMethodsContainer, .FormContainerSlot, .PaymentMethod (+ --primary, --card, --secondary, --other), .PaymentMethodSkeleton, .CardButton (+ --hover, --open), .CardBadge (+ --open), .CardBrandSlot (the rounded chip behind the brand marks in the card-number field — it becomes a layout-only container in 1.2), .CardBrandIcon, .SecondarySection, .SecondaryDivider, .SecondaryDividerLabel, .SecondaryGrid, .OtherSection, .OtherToggle (+ --hover), .OtherPanel (+ --open), .LoaderOverlay, .LoaderSpinner, .DisabledOverlay.

The SDK sets --revup-* CSS custom properties on <html> to carry these values. They are removed by destroy().


10. userInfo — payer email​

userInfo: {
email: { required: true, value: 'buyer@example.com' },
}
PropertyTypeDefaultDescription
email.requiredbooleanfalseWhen true, the card form shows an email field above the card inputs and requires a valid address before the payment can be submitted.
email.valuestring''Pre-fills the email field when it is shown.

When the payer supplies an email, the payment request includes it (trimmed). When email.required is false or omitted, no email field is shown.


11. Languages​

language accepts a BCP 47 code. When omitted, the SDK detects the browser language and falls back to en-US.

19 supported languages: en-US, ar-SA, cs-CZ, de-DE, es-ES, fr-FR, it-IT, ja-JP, ko-KR, nl-NL, pl-PL, pt-PT, ro-RO, ru-RU, th-TH, tr-TR, uk-UA, zh-CN, zh-TW.

Read them at runtime with Revup.getAvailableLanguages().

language: 'ar-SA' translates the strings only. Right-to-left layout mirroring does not exist in 1.1 — it arrives in 1.2.


12. Lifecycle: cleanup and re-initialization​

Switching to a new order — no teardown needed​

await revup.init({ ...options, orderId: newOrderId });

Calling init() again on the same instance with a different orderId re-fetches the order, re-initializes the payment methods against it and rebuilds the UI. You do not need a new Revup instance for a new order.

Full teardown​

Required when you change merchantDomain or apiKey, or when the payment block leaves the page.

stopListening();   // 1. remove your event listener
revup.destroy(); // 2. tear the SDK down

// 3. later, with the new configuration
revup = new Revup(newConfig);
await revup.init({ containerId: 'revup-container', orderId, /* … */ });

destroy() does what clearing the container cannot: it tears down the payment-method handlers and the card iframe, resets the accordion coordinator, empties the container, clears the appearance CSS custom properties and parent-page rules, and removes the rel="preconnect" links the SDK injected into your <head>.

Do not use container.innerHTML = '' as teardown. It leaves appearance state, preconnect hints and document-level listeners behind.


13. Session API (Revup.mount())​

Available from 1.1.5. It is an alternative entry point, not a replacement — new Revup(config).init(options) remains fully supported and is not deprecated.

Instead of tearing the SDK down and rebuilding it whenever something changes, you mount once and apply patches:

const session = await Revup.mount({
merchantDomain: window.location.origin,
apiKey: 'YOUR_PUBLIC_API_KEY',
containerId: 'revup-container',
orderId: 'ORDER_ID',
language: 'en-US',
onMessage: (event) => console.log(event.type, event),
});

await session.set({ orderId: nextOrderId });
await session.set({ appearance: { theme: 'dark' } });

RevupSession​

MemberSignatureDescription
set(patch)Promise<UpdateReport>Applies a partial update. See routing below.
on(handler)() => voidSubscribes to SDK events; returns an unsubscribe function. Survives rebuilds.
subscribeTo(fields, handler)() => voidFires only when a successful update actually changed one of fields. noop updates never fire.
stateSessionStateDeep-frozen snapshot of the last committed { config, options }.
destroy()voidTears down the runtime and all listeners. Idempotent.

The patch​

A patch is a flat object. Its keys fall into two disjoint groups:

GroupKeysUpdate path
Merchant configmerchantDomain, apiKey, containerId, apiVersioncold — the runtime is destroyed and rebuilt internally
Checkout optionsorderId, language, appearance, form, apms, userInfohot — the same runtime is reused
  • undefined means "leave this field alone".
  • null resets an optional checkout field to its SDK default.
  • An unknown key, a symbol key, or a non-plain-object patch is rejected with a RevupSessionError.

What a hot update actually does​

hot means the SDK instance, its HTTP client and its merchant-level cache are preserved. How much of the UI is rebuilt depends on the field:

Changed fieldEffect
appearanceCSS variables and rules re-applied live. Nothing is rebuilt.
formThe accordion open/closed state is driven directly; the new options are pushed to the existing iframe. Payment methods are untouched and the iframe is not reloaded. Toggling form.enabled is structural and forces a full re-init.
apmsThe zones are diffed. Methods whose configuration did not change keep their DOM and vendor state. The card iframe is untouched.
orderIdThe order is re-fetched and every payment method is re-initialized against it (amount, currency and vendor session all change per order). The card iframe is reused, not reloaded, so anything already typed is preserved.
userInfoRe-synced without a rebuild.
languageFalls back to a full re-init on the same instance — labels and vendor buttons must re-render consistently. Still reported as path: 'hot'.
any merchant-config keypath: 'cold' — full internal rebuild.

UpdateReport​

type UpdateReport = {
path: 'hot' | 'cold' | 'noop';
changed: ReadonlyArray<string>; // fields actually re-applied; empty for noop
coalesced?: boolean; // true when several set() calls were merged
committed?: object; // final values for the changed fields
durationMs: number;
};

Concurrent set() calls made in the same tick are coalesced into one transition. Every coalesced caller resolves with the same report, and observers see exactly one revup.updated event for the merged batch.

Failure handling​

set() restores the previous state on failure and rejects with a RevupSessionError carrying a stable code:

codeMeaning
SESSION_DESTROYEDThe session was destroyed before the patch could apply.
INVALID_ARGUMENTMalformed patch (unknown key, not a plain object, empty subscribeTo field list).
INVALID_CONFIGThe merged state failed validation (e.g. empty orderId).
UPDATE_FAILEDThe update failed; the previous state was restored.
SESSION_CONFLICTAnother session is already active on this page.
UNRECOVERABLEThe update and the rollback both failed. The session is terminal.

Only one RevupSession may be active per page. Destroy the current one before mounting another.

Which entry point should I use?​

  • Existing integration on new Revup() — no action required. It keeps working exactly as documented in sections 5–12.
  • New integration — use Revup.mount(). Same result, less code, and you never call destroy() / new Revup() yourself except when the session ends.

14. Event contract​

Everything the SDK reports reaches your page as a single native CustomEvent named onRevupMessage, dispatched on document.

Listening​

const stop = revup.onRevupMessage((event) => {
console.log(event.type, event);
});

// or natively — read the SDK event from event.detail
document.addEventListener('onRevupMessage', (e) => {
const event = e.detail;
});

Guarantees​

  1. Events are dispatched on document with bubbles: false. A listener on window, or on your container element, will never fire. Listen on document.
  2. event.detail is deep-frozen — handlers cannot mutate it. Copy before editing.
  3. context, data and error are omitted when empty — always optional-chain.
  4. Payloads are sanitized before dispatch: card numbers, CVVs, API keys and other PCI/PII patterns are masked. Raw card data never appears in an event.
  5. Critical misconfiguration (missing container, invalid required fields) throws — it is not dispatched as an event.
  6. These events are advisory. Always confirm the final outcome server-side using transactionId / orderId.

Envelope​

{
id: string; // UUID v4, unique per event
type: SdkEventType;
source: 'form' | 'apm' | 'sdk';
timestamp: string; // ISO 8601

context?: {
orderId?: string;
transactionId?: string;
apm?: string; // APM events
transactionStatus?: string;
};

data?: unknown;
error?: {
name: string; // machine-readable — see Error names
message: string; // human-readable, sanitized
statusCode?: number; // HTTP status, when the failure came from an HTTP response
};
}

There is no error.code — the machine-readable field is error.name.

Event / source matrix​

typesource: 'form'source: 'apm'source: 'sdk'
initialized✅ card form——
validation.changed✅ card form——
payment.submitted✅✅ all methods—
payment.action_required✅ 3-D Secure✅ Stripe-served methods—
payment.success✅✅—
payment.failed✅✅—
payment.cancelled✅ gateway-reported✅ all except iDEAL / SEPA / Pix—
apm.unavailable—✅—
revup.updated——✅ Session API only

Only payment.success, payment.failed and payment.cancelled end a payment attempt.


source: 'form' — the card form​

initialized​

Fires once, when the card iframe has loaded and reported its size.

FieldValue
context.orderIdthe order id
data.message'initialized'

Use it to remove your loading placeholder — not as a signal that the payment can be submitted.

validation.changed​

FieldValue
context.orderIdthe order id
data.isValidboolean — all fields pass validation
data.isCallingboolean — a submission is in flight

This is the signal to enable or disable your own payment button.

1.1 does not report which individual fields are invalid. data.invalidFields arrives in 1.2.

payment.submitted​

Fires when the payer submits, before the gateway responds.

FieldValue
context.orderIdthe order id
data.isValidtrue
data.isCallingtrue

Terminal card events — one shared payload​

payment.action_required, payment.success, payment.failed (gateway decline) and payment.cancelled all come from the same gateway response, so they share one payload shape. The event type is derived from the gateway's transactionStatus:

Gateway transactionStatusEvent dispatched
successpayment.success
failedpayment.failed (error.name: 'PaymentFailed')
cancelled, cancelled_by_userpayment.cancelled
waiting_user_interaction, in_processpayment.action_required
anything elsepayment.submitted

Payload:

FieldValue
context.orderIdthe order id
context.transactionIdthe gateway transaction id, when one exists
data.actionpresent only when the gateway returned a challenge
data.transactionResult.statusthe raw status the form received
data.transactionResult.responsethe full gateway response body

transactionStatus and transactionId are deliberately not repeated at the top of data — they are represented by the event type and by context.transactionId. The gateway's own copy is still at data.transactionResult.response.transactionStatus.

payment.action_required — the 3-D Secure challenge​

data.action describes where to send the payer. The same object is also at data.transactionResult.response.action.

FieldValue
data.action.urlURL to redirect to or POST to
data.action.method'GET' or 'POST'
data.action.parametersarray of { key, value } pairs to submit
document.addEventListener('onRevupMessage', (e) => {
const { type, source, data } = e.detail;
if (type !== 'payment.action_required' || source !== 'form') return;

const { url, method, parameters } = data.action;

if (method === 'POST') {
const form = document.createElement('form');
form.method = 'POST';
form.action = url;
for (const { key, value } of parameters) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = value;
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
} else {
window.location.href = url;
}
});

payment.failed — two different causes​

Causeerror.nameWhere the detail is
The gateway declined (transactionStatus: 'failed')PaymentFaileddata.transactionResult.response
The form could not submit at all (network error, configuration/encryption failure)FormSubmitErrorerror.message

Read the decline reason from data, not from error.message. On a PaymentFailed event error.message is the generic string "Payment failed". The gateway's actual reason is in the response body at data.transactionResult.response.message. On a FormSubmitError event there is no gateway response, and error.message is the real description.

error.statusCode is never set on form events. When the payment call failed with an HTTP error, the status is at data.transactionResult.status.

Do not rethrow — the form re-enables itself and the payer can retry.


source: 'apm' — alternative payment methods​

Every APM event carries context.apm with the method identifier, except apm.unavailable raised by the SDK's own APM bootstrap.

EventWhenPayload
payment.submittedThe payer opened the wallet sheet, popup, or submitted an inline formcontext: { orderId, apm }, data.message
payment.action_requiredStripe-served methods only — the payment intent reports waiting_user_interaction. Informational: the confirm flow continues by itself, there is no data.action, and no merchant handling is requiredcontext: { transactionId, orderId, apm, transactionStatus }, data.message
payment.successApprovedcontext: { transactionId, orderId, apm, transactionStatus }, data.message
payment.failedDeclined, or the vendor flow erroredcontext: { transactionId?, orderId, apm }, error: { name, message, statusCode? }
payment.cancelledThe payer dismissed the sheet or popup. Not emitted by iDEAL, SEPA Direct Debit or Pixcontext: { orderId, apm, transactionId? }, data.message
apm.unavailableThe method cannot render or has just been removedcontext: { apm, orderId? }, error: { name, message, statusCode? }

Notes:

  • context.transactionStatus is set on APM payment.action_required and payment.success. It is not set on payment.failed.
  • context.transactionId is absent on payment.submitted — it fires before the transaction exists.
  • apm.unavailable from the SDK's own bootstrap (config validation failure, no methods configured, availability lookup failure) carries no context.apm, and the merchant-level lookup failure carries no context at all — always write event.context?.apm.
  • payment.cancelled is a normal outcome, not a failure.

Per-method triggers for apm.unavailable:

MethodTypical triggers
Apple PayVendor SDK fails to load; ApplePaySession missing; capability detection enabled and the device cannot pay or has no active card; merchant session or button init error
Google PayVendor SDK fails to load; configuration or order details unavailable; isReadyToPay reports unsupported; button container missing
PayPalContainer missing; clientId or order currency missing; vendor SDK fails to load; button not eligible or render fails
Stripe-served (stripe_link, amazon_pay, ideal, sepa_debit, pix)Stripe SDK fails to load; order details unavailable; public key missing; the element reports a load error; no available methods reported

source: 'sdk' — revup.updated​

Dispatched only when you use the Session API: after the initial mount() and after every set() that produced an observable change.

FieldValue
context.orderIdthe live order id at the time of the update
datathe UpdateReport

noop updates are returned on the set() promise but never dispatched as an event. Use this for telemetry and store synchronization — never to gate payment logic.

Error names​

error.nameOrigin
apms_initialization_errorThe SDK's APM bootstrap: configuration validation, or the availability lookup
apple_pay_errorApple Pay
google_pay_errorGoogle Pay
paypal_errorPayPal
stripe_link_errorStripe Link
amazon_pay_errorAmazon Pay
ideal_erroriDEAL
sepa_debit_errorSEPA Direct Debit
pix_errorPix
PaymentFailedCard form — gateway decline
FormSubmitErrorCard form — submission could not be sent

A complete handler​

const stop = revup.onRevupMessage((event) => {
const { type, source, context, data, error } = event;

switch (type) {
case 'initialized':
hideLoadingPlaceholder();
break;

case 'validation.changed':
myPayButton.disabled = !data.isValid || data.isCalling;
break;

case 'payment.submitted':
showSpinner();
break;

case 'payment.action_required':
// Only the card form carries an action to act on.
if (source === 'form' && data?.action) redirectToChallenge(data.action);
break;

case 'payment.success':
confirmOnServer(context.orderId, context.transactionId);
break;

case 'payment.failed':
showError(error.message);
break;

case 'payment.cancelled':
restoreCheckout();
break;

case 'apm.unavailable':
hideMethod(event.context?.apm);
break;

case 'revup.updated':
// Session API telemetry only
break;
}
});

15. Environments, URLs and CSP​

EnvironmentLoader host
Sandboxhttps://test.hosted.revuppayments.com
Livehttps://hosted.revuppayments.com

Use the API key that matches the environment; a sandbox key will not work against live.

Your Content Security Policy must allow:

  • script-src — the loader host above, and https://js.stripe.com when any Stripe-served method is enabled (Stripe Link, Amazon Pay, iDEAL, SEPA Direct Debit, Pix, or Apple/Google Pay routed through Stripe).
  • frame-src — the RevUp form origin, plus the wallet/vendor origins for the methods you enable.
  • connect-src — the RevUp API origin.

The exact origins for your account are provided with your API keys. The SDK's API base URL and form URL are fixed at build time; your integration only supplies merchantDomain, apiKey and orderId.


16. Known limits of the 1.1 line​

Limit in 1.1Resolved in
validation.changed does not say which fields are invalid1.2
form.textDirection: 'rtl' is accepted but has no visual effect1.2
Card-brand marks can only render inside the card-number field1.2
3-D Secure device-fingerprint behaviour depends on the selfHostedDomain toggle1.2 — always first-party, no toggle
The card form does not report a distinct terminal validation reset1.2

Patch history of the 1.1 line​

The loader always serves the newest patch of the minor, so a merchant pinned to /sdk/1/1/ is on 1.1.5.

PatchMerchant-visible change
1.1.0Payment zones and ApmEntry; Appearance API; form.enabled; revup.destroy(); card accordion with the "Popular" badge; "Or pay with" divider; "+ N more payment methods" toggle; secure-payment footer
1.1.3iDEAL, SEPA Direct Debit and Pix added, with their ideal_error / sepa_debit_error / pix_error names
1.1.5orderId accepted by init(); Session API (Revup.mount() / RevupSession), the revup.updated event and source: 'sdk'; event.detail deep-frozen before dispatch

If you are pinned to /sdk/1/1/ you are on 1.1.5 and everything in the table applies. The rows matter only if you have pinned a specific earlier patch by agreement with RevUp.