Skip to content
Last updated

Introduction

This documentation describes how to integrate with a unified and secure payment system for initiating payments or tokenizing payment instruments. The approach follows a two-step flow:

  1. Obtain a concludable payment request token – This token represents either an authorized payment (ready for capture) or an authorized payment instrument for future use. To obtain it, the frontend mounts the Universal Payment Component (UPC) using a short-lived user payment session token securely obtained from the backend.

  2. Post a usage with the obtained payment request token – The token can be used to create a payment instrument in the customer account, attach it to a contract, or post a usage (e.g., credit top-up, ticket purchase). If the usage involves an amount greater than zero, capture is deferred until the related good or service is booked, minimizing refunds. If unused within its validity period, the authorization is cancelled or refunded.

Key Concepts

  • Universal Payment Component (UPC) – Embeddable JavaScript component handling the authorization of a payment request token.
  • Universal Payment Gateway (UPG) – Public API the UPC communicates with, authorized via a short-lived session token.
  • Payment Instrument – A user-specific object used to attempt a payment.
  • Payment Method – The type of payment instrument (e.g., credit card, direct debit, PayPal).
  • Scope – Defines the payment context (MEMBER_ACCOUNT or ECOM) to determine available payment methods.

Important Usage Notes

  • Always define either finionPayCustomerId or customerId for an existing customer session. When assigning a paymentRequestToken to a usage, the system checks that the token belongs to the correct customer. If this check fails, the operation will not succeed.
  • Example: Selling a contract online and collecting a payment method for the upfront fee requires two paymentRequestTokens — one for the payment instrument and one for the actual upfront payment. This means creating two separate user payment sessions. If the flow is for a new customer, create the second session using the finionPayCustomerId returned by the first.
  • While the paymentRequestToken is unused for posting a usage, no funds are captured (if the payment method allows). Authorizing a token with amount > 0 only authorizes the payment; capture happens when posting the usage. This prevents unnecessary collections or refunds in case of process errors or user cancellation.
  • Saving payment methods: If the scope is ECOM and the payment method supports saving, the user can choose to store the method for future use.
  • Authorizing saved payment methods: Stored payment methods are already authorized, so they are not re-authorized when selected via the component. The payment result is returned upon posting the usage.
  • Any unused paymentRequestToken is automatically cancelled when the related user payment session expires.
  • A user payment session is automatically invalidated once one paymentRequestToken from that session is used — only one token per session can be used.

Endpoints Using paymentRequestToken

The paymentRequestToken returned by the UPC can be used in the following scenarios:

Creating a Payment Instrument in the Customer

  • Create a payment instrument and link it to the customer so it can be used in future payment runs (e.g., membership fees).
  • Applies to:
    • Creating a new customer and contract (work in progress)
    • Adding a contract to an existing customer (work in progress)
    • Offering self-service payment method updates (work in progress)
    • Adding a secondary payment method (planned)

Posting a Sellable Entity

If the paymentRequestToken is authorized with a payment amount, it can be used for purchasing any sellable entity:

  • Upfront payment in contract creation (joining fee or total contract value) (work in progress)
  • Account balancing for open fees (work in progress)
  • Purchasing a day ticket (work in progress)
  • Purchasing a value voucher (planned)
  • Purchasing a contract voucher (planned)
  • Purchasing a course contingent (planned)
  • Purchasing an appointment (e.g., personal training contingent) (planned)

Creating a User Payment Session

To initiate a payment process or capture a payment instrument, you must first create a user payment session.

Endpoint: POST /v1/payments/user-session

Required Scope: PAYMENT_WRITE

Description: This request generates a short-lived token used by the UPC to authenticate payment flows. It can be for immediate transactions or for storing payment instruments for future recurring payments.

Request Body Parameters

amountnumberrequired

Specifies the payment amount for the initiated transaction. Should equal 0 to capture a payment instrument for future recurring payments. The currency is defined by the studio.

Example: 19.99
scopestringrequired

Specifies where the created payment instruments will be used, as the available payment methods differ by scope.

Enum ValueDescription
MEMBER_ACCOUNT

Use when initiating a payment user session to collect a payment instrument intended for future payment runs (e.g., BACS, credit card).

ECOM

Use when the user is making a purchase or when the payment instrument will be used for future user-initiated payments (e.g., saving a credit card for later purchases).

Example: "MEMBER_ACCOUNT"
customerIdinteger(int64)

This field represents the unique identifier for an existing customer within ERP. Providing this ID ensures the payment session is linked to the correct customer record. It is a mutually exclusive field with finionPayCustomerId, meaning you can only provide one or the other.

  • Conditions for use: This ID is required for payment sessions involving existing customers.

  • Behavior when omitted: If this field is left empty, a new customer will be treated as a “potential customer” and a finionPayCustomerId will be automatically generated and returned in the response. If omitted, it will not be possible to use it for existing customers.

  • Mutually exclusive with: finionPayCustomerId

Example: 1234567890
finionPayCustomerIdstring(uuid)

This field is the identifier for a customer within the Finion Pay payment service, typically used for customers who are not yet registered in ERP. Use this ID to track repeat payment sessions for a potential customer.

  • Conditions for use: This ID should only be provided for subsequent payment sessions for a customer who has been previously identified by Finion Pay but doesn’t have an ERP customerId yet.

  • Behavior when omitted: In the absence of a customerId, a new finionPayCustomerId will be automatically created and assigned to the user for the current session.

  • Mutually exclusive with: customerId

Example: "753ea8ec-c2ec-4761-824b-bc46eda3f644"
permittedPaymentChoicesArray of stringsunique

List of permitted payment choices, i.e. obtained by the contract offer. Acts as a filter for the available payment methods defined by the scope

Items Enum ValueDescription
BACS

Payment by BACS direct debit

IDEAL

Payment by ideal

PAYPAL

Payment by paypal

TWINT

Payment by Twint

SEPA

Payment by SEPA direct debit

BANK_TRANSFER

Payment by bank transfer

CH_DD

Payment by CH_DD direct debit

CASH

Payment by cash

BANCONTACT

Payment by bancontact

CREDIT_CARD

Payment by credit card

Example: ["CASH"]
referenceTextstringrequired

Allows the definition of the reference text shown on the bank statement of the customer.

Example: "Gym Joining Fee 01.07.2025"
requireDirectDebitSignatureboolean

When set to true the direct debit form will show a signature field to the user that is required to proceed. This applies to the payment methods SEPA, CH_DD and LSV.

Example: false

Example Request

curl -i -X POST \
  https://open-api-demo.open-api.magicline.com/v1/payments/user-session \
  -H 'Content-Type: application/json' \
  -H 'X-API-KEY: YOUR_API_KEY_HERE' \
  -d '{
    "amount": 19.99,
    "scope": "MEMBER_ACCOUNT",
    "customerId": 1234567890,
    "finionPayCustomerId": "753ea8ec-c2ec-4761-824b-bc46eda3f644",
    "permittedPaymentChoices": [
      "CASH"
    ],
    "referenceText": "Gym Joining Fee 01.07.2025",
    "requireDirectDebitSignature": false
  }'

Response body

tokenstringrequired

The token for the user session.

Example: "CllClFmVlSCs3oe0ND0JloLWlNzdb3QseU4507gf1mSVAHqRTwzKWU"
tokenValidUntilstring(date-time)required

The date and time until the token is valid.

Example: "2025-01-07T16:25:09.416924Z"
finionPayCustomerIdstring(uuid)required

Identifies a customer in Finion Pay, i.e. to retreive existing payment instruments.

Example: "753ea8ec-c2ec-4761-824b-bc46eda3f644"

The token returned is the userSessionToken required to initialize the UPC in your frontend integration.


Payment Widget Integration Guide

An embeddable payment interface that can be integrated into any web application.

The following URIs are available:

Quick Start

<script src="INSERT_WIDGET_URI_HERE"></script>
<div id="payment-widget"></div>

<script>
    const widget = window.paymentWidget.init({
        userSessionToken: 'your-session-token',
        environment: 'live',
        countryCode: 'US',
        locale: 'en',
        container: 'payment-widget'
    });

    // Clean up when done
    widget.destroy();
</script>

Configuration

ParameterTypeDescription
userSessionTokenstringUser session token
environment'test' | 'sandbox' | 'live'Payment environment
countryCodestringISO country code (e.g., 'US')
localestringLocale (e.g., 'en')
containerstring | HTMLElementElement ID or element reference

Optional:

  • styling - Custom theme colors and styling
  • i18n - Translation overrides
  • featureFlags - Enable experimental or alternative features
  • onSuccess - Success callback function that receives the payment request token, payment instrument details, and payment instrument token
  • devMode - Show i18n keys instead of translated text (development only)
  • hidePaymentButton - Hide widget's internal payment buttons for custom button control
  • onPaymentStateChange - Callback for payment button state changes (processing, canSubmit)
  • customerData - Pre-fill payment forms with customer information (name, email, address)

Customization

Styling:

styling: {
    primaryColor: '#007bff',
    textColorMain: '#333333',
    borderRadius: '4px'
}

Translations:

i18n: {
    'upc.my.payment.instruments': 'My Payment Methods',
    'upc.payment.methods.add.new': 'Add New Payment Method'
}

Feature Flags:

featureFlags: {
    useRubiksUI: true; // Enable Rubiks Styleguide components (default: false)
}

The useRubiksUI flag switches the payment forms to use the Rubiks design system instead of the default Tailwind-based UI. This provides a more modern and consistent look aligned with the Rubiks component library.

Development Mode:

devMode: true; // Shows i18n keys instead of translations for development

Success Callback:

onSuccess: (
    paymentRequestToken,
    paymentInstrumentDetails,
    paymentInstrumentToken
) => {
    // paymentRequestToken: string - The payment request token
    // paymentInstrumentDetails: object - Payment instrument details (card info, bank details, etc.)
    // paymentInstrumentToken: string - The payment instrument token for future use
};

Integration Examples

React Integration

import React, { useEffect, useRef } from 'react';

export const PaymentWidget = ({ userToken, onPaymentSuccess }) => {
    const containerRef = useRef(null);
    const widgetRef = useRef(null);

    useEffect(() => {
        if (containerRef.current && window.paymentWidget) {
            widgetRef.current = window.paymentWidget.init({
                userSessionToken: userToken,
                environment: 'live',
                countryCode: 'US',
                locale: 'en',
                container: containerRef.current,
                featureFlags: {
                    useRubiksUI: true
                },
                onSuccess: (token, details, instrumentToken) => {
                    onPaymentSuccess(token, details, instrumentToken);
                }
            });
        }

        return () => widgetRef.current?.destroy();
    }, [userToken, onPaymentSuccess]);

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

Angular Integration

import { Component, ElementRef, ViewChild, OnDestroy } from '@angular/core';

@Component({
    selector: 'app-payment-widget',
    template: '<div #paymentContainer></div>'
})
export class PaymentWidgetComponent implements OnDestroy {
    @ViewChild('paymentContainer', { static: true }) containerRef!: ElementRef;
    private widget: any;

    ngAfterViewInit() {
        const sessionToken =
            sessionStorage.getItem('paymentSessionToken') ||
            this.getUserToken();
        sessionStorage.setItem('paymentSessionToken', sessionToken);

        this.widget = window.paymentWidget.init({
            userSessionToken: sessionToken,
            environment: 'live',
            countryCode: 'US',
            locale: 'en',
            container: this.containerRef.nativeElement,
            onSuccess: (
                token,
                paymentInstrumentDetails,
                paymentInstrumentToken
            ) => {
                sessionStorage.removeItem('paymentSessionToken');
                this.handlePaymentSuccess(
                    token,
                    paymentInstrumentDetails,
                    paymentInstrumentToken
                );
            }
        });
    }

    ngOnDestroy() {
        this.widget?.destroy();
    }
}

Vue.js Integration

<template>
    <div ref="paymentContainer"></div>
</template>

<script>
export default {
    name: 'PaymentWidget',
    props: ['userToken'],
    mounted() {
        const sessionToken =
            sessionStorage.getItem('paymentSessionToken') || this.userToken;
        sessionStorage.setItem('paymentSessionToken', sessionToken);

        this.widget = window.paymentWidget.init({
            userSessionToken: sessionToken,
            environment: 'live',
            countryCode: 'US',
            locale: 'en',
            container: this.$refs.paymentContainer,
            onSuccess: (
                token,
                paymentInstrumentDetails,
                paymentInstrumentToken
            ) => {
                sessionStorage.removeItem('paymentSessionToken');
                this.$emit('paymentSuccess', {
                    token,
                    paymentInstrumentDetails,
                    paymentInstrumentToken
                });
            }
        });
    },
    beforeUnmount() {
        this.widget?.destroy();
    }
};
</script>

Custom Payment Button Control

The widget supports hiding its internal payment buttons, allowing host applications to use custom buttons while maintaining full control over styling and placement.

Multiple Widget Instances: The widget fully supports mounting multiple instances on the same page. Each instance maintains its own isolated state and API reference.

// Example: Multiple widget instances on the same page
const widget1 = window.paymentWidget.init({
    userSessionToken: 'token-1',
    container: 'payment-widget-1'
    // ... other config
});

const widget2 = window.paymentWidget.init({
    userSessionToken: 'token-2',
    container: 'payment-widget-2'
    // ... other config
});

// Each widget operates independently
await widget1.submitPayment(); // Only affects widget1
const state2 = widget2.getPaymentState(); // Only returns widget2 state

Basic Usage

const widget = window.paymentWidget.init({
    userSessionToken: 'user-session-token',
    environment: 'live',
    countryCode: 'US',
    locale: 'en',
    container: 'payment-widget',
    hidePaymentButton: true,
    onPaymentStateChange: (state) => {
        // Update custom button based on payment state
        const button = document.getElementById('custom-pay-button');
        button.disabled = !state.canSubmit;
        button.textContent = state.isProcessing ? 'Processing...' : 'Pay Now';
    }
});

// Custom button handler
document
    .getElementById('custom-pay-button')
    .addEventListener('click', async () => {
        try {
            await widget.submitPayment();
        } catch (error) {
            console.error('Payment failed:', error);
        }
    });

Widget API Methods

When you initialize the widget, it returns an instance with the following methods:

interface PaymentWidgetInstance {
    destroy(): void; // Clean up widget
    submitPayment(): Promise<void>; // Trigger payment submission
    getPaymentState(): PaymentButtonState; // Get current payment state
    updateCustomerData(data: CustomerData): void; // Update customer data dynamically
}

interface PaymentButtonState {
    isProcessing: boolean; // Payment is being processed
    canSubmit: boolean; // Payment can be submitted (see Payment Method Behavior below)
}

interface CustomerData {
    name?: string;
    email?: string;
    address?: CustomerAddress;
}

interface CustomerAddress {
    line1?: string;
    line2?: string;
    city?: string;
    postalCode?: string;
    state?: string;
    country?: string;
}

Payment Method Behavior

The canSubmit state varies by payment method when using custom buttons:

Provider-based payments (Stripe/Adyen - Credit Card, PayPal, etc.):

  • canSubmit becomes true immediately when the payment provider loads
  • The provider's internal validation will handle form completeness during submission

Form-based payments (SEPA, CH_DD, LSV Direct Debit):

  • canSubmit starts as false
  • Becomes true only when required checkboxes are checked
  • Ensures mandate acceptance before allowing submission
// Example: Handling different payment methods
onPaymentStateChange: (state) => {
    // For SEPA: Button disabled until checkbox checked
    // For Stripe/Adyen: Button enabled when provider loads
    customButton.disabled = !state.canSubmit || state.isProcessing;
};

React Example

import React, { useState, useEffect } from 'react';

export const CustomPaymentButton = ({ userToken }) => {
    const [widget, setWidget] = useState(null);
    const [buttonState, setButtonState] = useState({
        disabled: true,
        text: 'Pay Now'
    });

    useEffect(() => {
        const widgetInstance = window.paymentWidget.init({
            userSessionToken: userToken,
            environment: 'live',
            countryCode: 'US',
            locale: 'en',
            container: 'payment-widget',
            hidePaymentButton: true,
            onPaymentStateChange: (state) => {
                setButtonState({
                    disabled: !state.canSubmit,
                    text: state.isProcessing ? 'Processing...' : 'Pay Now'
                });
            }
        });

        setWidget(widgetInstance);
        return () => widgetInstance?.destroy();
    }, [userToken]);

    const handlePayment = async () => {
        try {
            await widget?.submitPayment();
        } catch (error) {
            console.error('Payment failed:', error);
        }
    };

    return (
        <div>
            <div id="payment-widget" />
            <button
                onClick={handlePayment}
                disabled={buttonState.disabled}
                className="custom-pay-button"
            >
                {buttonState.text}
            </button>
        </div>
    );
};

Angular Example

import { Component, ElementRef, ViewChild, OnDestroy } from '@angular/core';

@Component({
    selector: 'app-payment-with-custom-button',
    template: `
        <div #paymentContainer></div>
        <button
            (click)="handlePayment()"
            [disabled]="!canSubmit"
            class="custom-pay-button"
        >
            {{ buttonText }}
        </button>
    `
})
export class PaymentWithCustomButtonComponent implements OnDestroy {
    @ViewChild('paymentContainer', { static: true }) containerRef!: ElementRef;
    private widget: any;
    canSubmit = false;
    buttonText = 'Pay Now';

    ngAfterViewInit() {
        this.widget = window.paymentWidget.init({
            userSessionToken: this.getUserToken(),
            environment: 'live',
            countryCode: 'US',
            locale: 'en',
            container: this.containerRef.nativeElement,
            hidePaymentButton: true,
            onPaymentStateChange: (state) => {
                this.canSubmit = state.canSubmit;
                this.buttonText = state.isProcessing
                    ? 'Processing...'
                    : 'Pay Now';
            }
        });
    }

    async handlePayment() {
        try {
            await this.widget?.submitPayment();
        } catch (error) {
            console.error('Payment failed:', error);
        }
    }

    ngOnDestroy() {
        this.widget?.destroy();
    }
}

Customer Data Prefilling

The widget supports pre-filling payment forms with customer information to improve the user experience. Customer data can be provided during initialization or updated dynamically at any time.

Initial Configuration

Provide customer data when initializing the widget:

const widget = window.paymentWidget.init({
    userSessionToken: 'user-session-token',
    environment: 'live',
    countryCode: 'US',
    locale: 'en',
    container: 'payment-widget',
    customerData: {
        name: 'John Doe',
        email: 'john.doe@example.com',
        address: {
            line1: '123 Main Street',
            line2: 'Apt 4B',
            city: 'New York',
            postalCode: '10001',
            state: 'NY',
            country: 'US'
        }
    }
});

Dynamic Updates

Update customer data after the widget is initialized:

// Update customer data dynamically
widget.updateCustomerData({
    name: 'Jane Smith',
    email: 'jane.smith@example.com',
    address: {
        line1: '456 Oak Avenue',
        city: 'Los Angeles',
        postalCode: '90001',
        state: 'CA',
        country: 'US'
    }
});

React Example with State Management

import React, { useState, useEffect, useRef } from 'react';

export const PaymentWithCustomerData = () => {
    const widgetRef = useRef(null);
    const [customerData, setCustomerData] = useState({
        name: '',
        email: '',
        address: {
            line1: '',
            city: '',
            postalCode: '',
            country: 'US'
        }
    });

    useEffect(() => {
        widgetRef.current = window.paymentWidget.init({
            userSessionToken: 'user-session-token',
            environment: 'live',
            countryCode: 'US',
            locale: 'en',
            container: 'payment-widget',
            customerData: customerData
        });

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

    // Update customer data dynamically
    useEffect(() => {
        if (widgetRef.current && customerData.name) {
            widgetRef.current.updateCustomerData(customerData);
        }
    }, [customerData]);

    const handleFormChange = (field, value) => {
        setCustomerData(prev => ({
            ...prev,
            [field]: value
        }));
    };

    return (
        <div>
            <h3>Customer Information</h3>
            <input
                type="text"
                placeholder="Name"
                value={customerData.name}
                onChange={(e) => handleFormChange('name', e.target.value)}
            />
            <input
                type="email"
                placeholder="Email"
                value={customerData.email}
                onChange={(e) => handleFormChange('email', e.target.value)}
            />

            <div id="payment-widget" />
        </div>
    );
};

Angular Example with Form Binding

import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
    selector: 'app-payment-with-customer-data',
    template: `
        <form [formGroup]="customerForm">
            <input formControlName="name" placeholder="Name" />
            <input formControlName="email" placeholder="Email" />
            <input formControlName="line1" placeholder="Address Line 1" />
            <input formControlName="city" placeholder="City" />
            <input formControlName="postalCode" placeholder="Postal Code" />
        </form>
        <div #paymentContainer></div>
    `
})
export class PaymentWithCustomerDataComponent implements OnInit, OnDestroy {
    @ViewChild('paymentContainer', { static: true }) containerRef!: ElementRef;
    private widget: any;
    customerForm: FormGroup;

    constructor(private fb: FormBuilder) {
        this.customerForm = this.fb.group({
            name: [''],
            email: [''],
            line1: [''],
            city: [''],
            postalCode: [''],
            country: ['US']
        });
    }

    ngOnInit() {
        this.widget = window.paymentWidget.init({
            userSessionToken: this.getUserToken(),
            environment: 'live',
            countryCode: 'US',
            locale: 'en',
            container: this.containerRef.nativeElement,
            customerData: this.customerForm.value
        });

        // Update widget when form changes
        this.customerForm.valueChanges.subscribe(data => {
            this.widget.updateCustomerData({
                name: data.name,
                email: data.email,
                address: {
                    line1: data.line1,
                    city: data.city,
                    postalCode: data.postalCode,
                    country: data.country
                }
            });
        });
    }

    ngOnDestroy() {
        this.widget?.destroy();
    }
}

Supported Payment Methods

Customer data prefilling is supported by the following payment providers:

  • Stripe: Pre-fills name, email, and full billing address in payment forms
  • Adyen: Pre-fills cardholder name, email, and address fields for card payments

Note: All customer data fields are optional. The widget will pre-fill only the fields that are provided.

Handling Redirects

For 3D Secure authentication, users may be redirected to their bank. The widget automatically detects and resumes payment processing after redirect.

Store session token to ensure continuity:

function initializeWidget() {
    // Get token from storage or current session
    const userSessionToken =
        sessionStorage.getItem('paymentSessionToken') || getCurrentUserToken();

    // Store for redirect continuity
    if (!sessionStorage.getItem('paymentSessionToken')) {
        sessionStorage.setItem('paymentSessionToken', userSessionToken);
    }

    const widget = window.paymentWidget.init({
        userSessionToken: userSessionToken,
        environment: 'live',
        countryCode: 'US',
        locale: 'en',
        container: 'payment-widget',
        onSuccess: (
            token,
            paymentInstrumentDetails,
            paymentInstrumentToken
        ) => {
            sessionStorage.removeItem('paymentSessionToken');
            handlePaymentSuccess(
                token,
                paymentInstrumentDetails,
                paymentInstrumentToken
            );
        }
    });
}

// Initialize widget on page load
initializeWidget();

Error Handling

Common validation errors:

  • Container element not found
  • Missing required parameters
  • Invalid environment value
try {
    const widget = window.paymentWidget.init(config);
} catch (error) {
    console.error('Widget initialization failed:', error.message);
}

API Reference

interface PaymentWidget {
    init(config: PaymentConfig): PaymentWidgetInstance;
}

interface PaymentWidgetInstance {
    destroy(): void;
    submitPayment(): Promise<void>;
    getPaymentState(): PaymentButtonState;
    updateCustomerData(data: CustomerData): void;
}

interface PaymentButtonState {
    isProcessing: boolean;
    canSubmit: boolean;
}

interface PaymentConfig {
    userSessionToken: string;
    environment: 'test' | 'sandbox' | 'live';
    countryCode: string;
    locale: string;
    container: string | HTMLElement;
    styling?: {
        primaryColor?: string;
        textColorMain?: string;
        borderRadius?: string;
        // ... other style options
    };
    i18n?: Record<string, string>;
    featureFlags?: PaymentFeatureFlags;
    hidePaymentButton?: boolean;
    customerData?: CustomerData;
    onSuccess?: (
        paymentRequestToken: string,
        paymentInstrumentDetails?: PaymentInstrumentDetails,
        paymentInstrumentToken?: string
    ) => void;
    onPaymentStateChange?: (state: PaymentButtonState) => void;
    devMode?: boolean;
}

interface PaymentFeatureFlags {
    useRubiksUI?: boolean; // Enable Rubiks Styleguide components
}

interface CustomerData {
    name?: string;
    email?: string;
    address?: CustomerAddress;
}

interface CustomerAddress {
    line1?: string;
    line2?: string;
    city?: string;
    postalCode?: string;
    state?: string;
    country?: string;
}

interface BankAccountDetails {
    accountHolder: string;
    bankName: string;
    bic: string;
    iban: string;
    signature?: string;
}

interface PaymentInstrumentDetails {
    creditCard?: {
        brand?: string;
        cardHolder: string;
        cardNumber: string;
        expiry: string;
        issuerCountry?: string;
    };
    sepa?: {
        bankAccountDetails: BankAccountDetails;
    };
    bacs?: {
        accountHolder: string;
        bankAccountNumber: string;
        bankLocationId: string;
        directDebitPdfFormUrl: string;
        mandateId: string;
        shopperEmail: string;
    };
    chDD?: {
        bankAccountDetails: BankAccountDetails;
    };
    lsvDD?: {
        bankAccountDetails: BankAccountDetails;
    };
    ideal?: {
        issuer: string;
    };
    banContactCard?: {
        cardHolder: string;
        cardNumber: string;
        expiry: string;
    };
    paypal?: {};
    twint?: {};
    cash?: {};
    bankTransfer?: {};
}