This guide explains how to integrate a membership signup flow using the Open API and the Universal Payment Component (UPC). This implementation requires careful orchestration of multiple API calls and payment sessions to handle both recurring and one-time payments.
For general information on working with the Payment API, see Working with Payment API.
- Flow Overview
- Step 1: Fetch Membership Offers
- Step 2: Get Offer Details
- Step 3: Collect Member Information
- Step 4: Create Contract Preview
- Step 5: Payment Session Logic
- Step 6: Mount Universal Payment Component
- Step 7: Session Persistence & Page Reload
- Step 8: Final Contract Submission
- Common Gotchas & Best Practices
The membership signup flow typically involves these stages:
- Fetch offers – Retrieve available membership options from API
- Select offer & term – Determine specific contract duration
- Collect member info – Gather personal details (name, email, address, DOB)
- Generate preview – Calculate pricing with discounts/vouchers
- Setup recurring payment – Create MEMBER_ACCOUNT session & mount widget
- Setup upfront payment – Create ECOM session & mount widget (if needed)
- Submit contract – Finalize membership creation
Critical: Two separate payment sessions are required:
- Session 1:
MEMBER_ACCOUNTscope – for recurring monthly payments (amount: 0) - Session 2:
ECOMscope – for upfront/setup fees (amount: actual due amount)
Each session must map to the same Finion Pay customer, which is achieved by passing the finionPayCustomerId from Session 1 into Session 2.
Endpoint: GET /v1/memberships/membership-offers
Purpose: Retrieve all available membership offers that are configured by the studio under Settings / Offer Configuration / Open API.
- Demo tenant
https://open-api-demo.open-api.magicline.com/v1/memberships/membership-offers
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
curl -i -X GET \
https://open-api-demo.open-api.magicline.com/v1/memberships/membership-offers \
-H 'X-API-KEY: YOUR_API_KEY_HERE'Unique identifier for the membership offer.
Description of the membership offer.
Name of the membership offer.
Sub description for the membership offer.
Temporary valid download link for rate bundle image. Expires after 2 hours.
Contractual or legal comments to be displayed below the offer.
Contract pre-use type information.
| Enum Value | Description |
|---|---|
| CHARGEABLE | Contract pre-use is chargeable |
| FREE | Contract pre-use is free |
| NOT_AVAILABLE | Contract is not available for pre-use |
Represents a date period.
Start of the interval
End of the interval
List of rate codes that are part of this membership offer.
The name of the rate code
Unique identifier for the rate code.
List of terms that are part of this membership offer.
Unique identifier of the membership offer term
Represents a term
Contains the total amount to be paid during the initial runtime of the contract and the average amounts per month and per the rate's payment frequency (e.g. 1 week)
Represents the payment frequency
The unique identifier of the payment frequency. (null possible for starter package)
Payment frequency type of a contract
| Enum Value | Description |
|---|---|
| TERM_BASED | Represents that the contract payment frequency is based on terms, with a possibly individual price per term. |
| NON_RECURRING | Represents that the contract payment frequency is non recurring, meaning only one payment is necessary here. |
| FREE | Represents that the contract payment frequency is free of charge. |
| RECURRING | Represents that the contract payment frequency is recurring, meaning that the payment will take place every term. |
| MONTH_DAY | Represents that the contract payment frequency is based on month days, with a possibly individual price per month day. |
Represents a term
Represents a financial data
Month day to prices list, used for contract payment frequency type MONTH_DAY
Terms to prices list, used for contract payment frequency type TERM_BASED. Note that the price will become active AFTER the respective term has passed
Whether the cycle of payments repeats indefinitely. Only relevant if type is TERM_BASED or MONTH_DAY.
List of age-based adjustments for the membership offer module.
The formatted payment frequency
Represents a term
Note: The response is an array of MembershipOffer objects.
Endpoint: GET /v1/memberships/membership-offers/{offerId}
Purpose: Fetch complete offer details including flatFees (setup/registration fees).
- Demo tenant
https://open-api-demo.open-api.magicline.com/v1/memberships/membership-offers/{membershipOfferId}
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
curl -i -X GET \
'https://open-api-demo.open-api.magicline.com/v1/memberships/membership-offers/{membershipOfferId}' \
-H 'X-API-KEY: YOUR_API_KEY_HERE'Unique identifier for the membership offer.
Description of the membership offer.
Name of the membership offer.
Sub description for the membership offer.
Temporary valid download link for rate bundle image. Expires after 2 hours.
Contractual or legal comments to be displayed below the offer.
Contract pre-use type information.
| Enum Value | Description |
|---|---|
| CHARGEABLE | Contract pre-use is chargeable |
| FREE | Contract pre-use is free |
| NOT_AVAILABLE | Contract is not available for pre-use |
Represents a date period.
Start of the interval
End of the interval
List of rate codes that are part of this membership offer.
The name of the rate code
Unique identifier for the rate code.
List of included modules of this membership offer.
Unique identifier for the membership offer module.
Name of the membership offer module.
Description of the membership offer module.
Image url of membership offer module. Expires after 5hours.
Represents the terms of a membership offer.
Module term extension type
| Enum Value | Description |
|---|---|
| SUBSEQUENT_RATE_DETAIL | Subsequent rate detail |
| TERM_EXTENSION | Term extension |
| NONE | No extension |
Represents a term
Represents a term
Represents a term
Represents a term
Represents the trial period of a membership offer.
Contract text block information.
Indicates if contract signatures are required for this membership offer.
Allowed payment choices for this membership offer.
| Items Enum Value | Description |
|---|---|
| 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 |
Maximum number of selectableModules for this membership offer. If 0, no modules can be selected.
List of contract text blocks that are part of this membership offer.
The unique identifier of the text block
The title of the text block
The text of the text block
The order of the text block in the contract
Text block configuration has a signature field. You have the option to show a signature input field and it will be added in the contract document. It is not a required field in magicline. It is up to your implementation if you add this field.
Text block contains information about the communication privacy options. You are advised to show a matrix with communication settings in combination with this text block.
Indicates the type of attachment for this block
| Enum Value | Description |
|---|---|
| CONTRACT_PDF_PREVIEW | Contract PDF preview |
| NONE | No text block |
| FILE | File information available in |
| URL | Url information available in |
Represents url information.
Represents document information.
The ID of the associated rate bundle module. Only set if this is a module consent text block.
List of selectable modules for this membership offer. Limited by maximumNumberOfSelectableModules.
List of terms that are part of this membership offer.
flatFees: Array of one-time fees (registration, setup, admin fees)starterPackage: true: Fee is due at signup (included in upfront payment)- Initial payment estimate: Sum of all
flatFeeswithstarterPackage: true
Note: The flatFees array provides an estimate of upfront costs. The authoritative source for the exact amount due at signup is the preview API response (dueOnSigningAmount). Always use the preview API before creating payment sessions.
Gather required personal details from the user:
Personal Info:
firstName(string) – RequiredlastName(string) – Requiredemail(string, valid email format) – RequireddateOfBirth(string, ISO date format:YYYY-MM-DD) – RequiredphoneNumberMobile(string, optional but recommended)
Address:
street(string, including house number) – Requiredcity(string) – RequiredzipCode(string) – RequiredcountryCode(string, ISO 3166-1 alpha-2, e.g., "DE") – Required
Language:
language.languageCode(string, ISO 639-1, e.g., "de") – Requiredlanguage.countryCode(string, ISO 3166-1, e.g., "DE") – Optional
Contract Details:
startDate(string, ISO date format:YYYY-MM-DD) – RequiredpreuseDate(string, ISO date, optional – allows gym access before contract start)voucherCode(string, optional – for discounts)
Endpoint: POST /v1/memberships/signup/preview
Purpose: Calculate exact pricing including discounts, age-based pricing, vouchers, and the authoritative dueOnSigningAmount.
The preview API should be called:
- Initially – After selecting a membership term (optional, can be conducted with dummy data)
- On voucher application – When voucher code is added or removed
- On date of birth change – For age-based pricing adjustments (debounced)
- On contract start date change – May affect promotional periods (debounced)
- On pre-use date change – May affect initial billing (debounced)
- Before payment step – Final pricing confirmation with complete member information
Debouncing: For fields that trigger preview updates (DOB, dates), implement debounce to avoid excessive API calls.
- Demo tenant
https://open-api-demo.open-api.magicline.com/v1/memberships/signup/preview
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
curl -i -X POST \
https://open-api-demo.open-api.magicline.com/v1/memberships/signup/preview \
-H 'Content-Type: application/json' \
-H 'X-API-KEY: YOUR_API_KEY_HERE' \
-d '{
"contract": {
"contractOfferTermId": 1000,
"startDate": "2026-02-01",
"preuseDate": "2026-01-01",
"notes": "Some notes",
"thirdPartyId": "1000a",
"employeeId": 1239812733,
"referralCode": "A500D",
"selectedSelectableModuleIds": [
0
],
"selectedOptionalModuleIds": [
0
],
"initialPaymentRequestToken": "3JtyH5sakfn2V22vB0napNC2zWMlpFwS9gPQawuk7Jw1F00atOD0BA",
"contractSignature": {
"base64SvgSignature": "string"
},
"textBlockSignatures": [
{
"base64SvgSignature": "string",
"textBlockId": 0
}
],
"voucherCode": 123
},
"customer": {
"thirdPartyId": "A1000",
"firstName": "Peter",
"secondFirstName": "Thomas",
"lastName": "Muller",
"secondLastName": "Meyer",
"dateOfBirth": "2019-08-24",
"email": "example@example.com",
"gender": "MALE",
"phoneNumberPrivate": "+44123456789",
"phoneNumberMobile": "+44987654321",
"street": "Raboisen Street",
"secondStreet": "Second Street",
"cityPart": "Tegel",
"district": "District 12",
"streetType": "Avenue",
"streetBlock": "5th block",
"portal": "Portal 1",
"stairway": "Right stairway",
"door": "Door 1",
"province": "Champagne",
"additionalAddressInformation": "Additional information",
"floor": "2nd floor",
"language": {
"languageCode": "de",
"countryCode": "DE"
},
"houseNumber": "3-4",
"buildingName": "Empire State Building",
"city": "Hamburg",
"zipCode": "220-99",
"countryCode": "DE",
"communicationPreferences": [
{
"messageCategory": "CONTRACT",
"channels": [
{
"communicationChannel": "EMAIL",
"customerOverridable": true,
"active": true
}
]
}
],
"documentIdentification": {
"documentNumber": "CX5432112345DS",
"documentType": "ID_CARD"
},
"paymentRequestToken": "string",
"additionalInformationFieldAssignments": [
{
"additionalInformationFieldId": 1234567890,
"value": "string"
}
]
}
}'The contract information used for the membership signup
Unique ID of the contract offer term
The start date of the contract
The pre use date of the contract. If not provided, it will be evaluated based on contract offer configuration.
The notes related to the contract
Unique ID of the third party contract in the third party system
Unique ID of the employee who created the membership
Referral code to link with recruiter
The selected modules from the available selectable modules
The selected modules from the available optional modules
This token identifies a pre-authorized payment request. It acts as a reference to the payment session initiated by the user. It's not needed for preview endpoints.
Representing customer signature
Signatures for text blocks
An optional code for a voucher. Only credit and discount vouchers are supported.
The customer information used for the membership signup
Unique ID of the third party customer in the third party system
First name of the customer
Second first name of the customer
Last name of the customer
Second last name of the customer
Date of birth of the customer
Email address of the customer
Gender of the customer
| Enum Value | Description |
|---|---|
| MALE | Male gender of the customer |
| FEMALE | Female gender of the customer |
| UNISEX | Unisex gender of the customer |
Private phone number of the customer
Mobile phone number of the customer
Street of the customer's address
Second street line of the customer's address
City part of the customer's address
District of the customer's address
Street type of the customer's address
Street block of the customer's address
Portal of the customer's address
Stairway of the customer's address
Door of the customer's address
Province of the customer's address
Additional address information of the customer's address
Floor of the customer's address
Basic language information
The language code
The country code
House number of the customer's address
Building name
City of the customer's address
Zip code of the customer's address
Country code of the customer's address
List of communication preferences for the customer
Information from an official document that identifies the customer
By assigning the paymentRequestToken to the customer, the payment method associated with the token will be used as payment method setting of the customer. Additionally, if the paymentRequestToken is associated with a payment instrument, i.e. a SEPA Mandate, BACS Mandate, Credit Card, or other, the payment instrument will be made available in the member account for future collection via payment runs. By leaving this field empty the payment method of the customer will be set to CASH. For reference check 'Create a user payment session'.
List of additional information field assignments of the customer
Represents a financial data
Amount of the finance data tuple
Currency of the finance data tuple
Represents a financial data
Type of a voucher, if a valid voucher code has been provided
| Enum Value | Description |
|---|---|
| CREDIT | Represents a credit voucher |
| CHECKIN | Represents a check-in voucher |
| COMPANY_FEE_SPLIT | Represents a company fee split voucher |
| CONTRACT | Represents a contract voucher |
| DISCOUNT | Represents a discount voucher |
Voucher remarks, if a valid voucher code has been provided
The type of period for which the voucher is applied, if a valid voucher code has been provided
| Enum Value | Description |
|---|---|
| TIME_BASED | Represents a voucher with a time based effective period |
| UNLIMITED | Represents a voucher with unlimited effective period |
| INITIAL_TERM | Represents a voucher effective during the initial term of the membership |
Represents a term
Represents a financial data
Type of discount (percentage or absolute), if a valid discount voucher code has been provided
| Enum Value | Description |
|---|---|
| ABSOLUTE | Represents an absolute discount |
| PERCENTAGE | Represents a percentage based discount |
Represents a financial data
Represents a financial data
Represents a financial data
Type of company fee split (percentage or absolute), if a valid company discount voucher code has been provided
| Enum Value | Description |
|---|---|
| ABSOLUTE | Represents an absolute discount |
| PERCENTAGE | Represents a percentage based discount |
Represents a financial data
Name of the company that will pay the company amount, if a valid company discount voucher code has been provided
Represents a financial data
Represents a financial data
Represents a financial data
Represents a financial data
Represents a financial data
Represents a financial data
Represents a membership offer rate bonus period.
Voucher related text blocks, if a valid voucher code has been provided
Flat fee previews, including discount if a valid discount voucher code has been provided
Textual representation of the voucher conditions, if a valid voucher code has been provided
An error code, if the voucher code is invalid
| Value | Description |
|---|---|
| INVALID_CODE | Represents an invalid voucher code |
Represents a financial data
Contains the total amount to be paid during the initial runtime of the contract and the average amounts per month and per the rate's payment frequency (e.g. 1 week)
Consent texts for modules that are part of the contract
Optional module contract preview including discount, company split or credit, if a valid voucher code has been provided
Represents a preview of the payment schedule for a signup process.
This is the authoritative amount for upfront payment.
Location:
preview.paymentPreview.dueOnSigningAmount.amountPurpose: Exact amount to charge immediately (setup fees + first payment if applicable)
Usage: Use this value when creating the ECOM payment session (Session 2)
Configuration in ERP: Go to a membership offer → Offer Options → Set
Require Initial Payment for Open API sign-upsCalculation: This amount is calculated based on the rate / term configuration. The amount contains all claims that are due on the contract start date. If a pre-use is configured at a price, this will be included. For more reference, refer to the support documentation.
Based on the preview API response, determine which payment sessions are needed:
function analyzePaymentNeeds(preview) {
const dueOnSigning = preview.paymentPreview?.dueOnSigningAmount?.amount || 0;
const totalContractVolume = preview.contractVolumeInformation?.totalContractVolume?.amount || 0;
let needsRecurring = true;
let needsUpfront = false;
// Scenario 1: Full payment upfront (no recurring payments)
if (dueOnSigning > 0 && dueOnSigning === totalContractVolume) {
needsRecurring = false;
needsUpfront = true;
}
// Scenario 2: No upfront payment (recurring only)
else if (dueOnSigning === 0) {
needsRecurring = true;
needsUpfront = false;
}
// Scenario 3: Both payments needed
else if (dueOnSigning > 0) {
needsRecurring = true;
needsUpfront = true;
}
return { needsRecurring, needsUpfront, upfrontAmount: dueOnSigning };
}Create this session FIRST to obtain the finionPayCustomerId.
For detailed information on creating payment sessions, see Creating a User Payment Session.
- Demo tenant
https://open-api-demo.open-api.magicline.com/v1/payments/user-session
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
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
}'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.
Specifies where the created payment instruments will be used, as the available payment methods differ by scope.
| Enum Value | Description |
|---|---|
| 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). |
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
finionPayCustomerIdwill 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
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
customerIdyet.Behavior when omitted: In the absence of a
customerId, a newfinionPayCustomerIdwill be automatically created and assigned to the user for the current session.Mutually exclusive with:
customerId
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 Value | Description |
|---|---|
| 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 |
Allows the definition of the reference text shown on the bank statement of the customer.
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.
For membership recurring payments:
- Set
amountto0(MEMBER_ACCOUNT sessions store payment method only) - Set
scopeto"MEMBER_ACCOUNT" - Set
referenceTextto something like"Membership Contract Recurring Payment" - Set
permittedPaymentChoicesto theallowedPaymentChoicesfrom the offer details - Do NOT include
finionPayCustomerIdfor a new customer
The token for the user session.
The date and time until the token is valid.
Identifies a customer in Finion Pay, i.e. to retreive existing payment instruments.
- Amount is always 0 – MEMBER_ACCOUNT sessions store payment method only
referenceTextis required – This appears on bank statements- No
finionPayCustomerIdin request – This is the first session for a new customer - For
permittedPaymentChoicesuseallowedPaymentChoiceslist from offer details endpoint - Store the
finionPayCustomerIdfrom response – Required for Session 2 - Store the
token– Required for widget remounting after page reload
Create this session SECOND using the finionPayCustomerId from Session 1.
- Demo tenant
https://open-api-demo.open-api.magicline.com/v1/payments/user-session
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
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
}'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.
Specifies where the created payment instruments will be used, as the available payment methods differ by scope.
| Enum Value | Description |
|---|---|
| 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). |
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
finionPayCustomerIdwill 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
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
customerIdyet.Behavior when omitted: In the absence of a
customerId, a newfinionPayCustomerIdwill be automatically created and assigned to the user for the current session.Mutually exclusive with:
customerId
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 Value | Description |
|---|---|
| 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 |
Allows the definition of the reference text shown on the bank statement of the customer.
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.
For membership upfront payments:
- Set
amountto thedueOnSigningAmountfrom the preview API response - Set
scopeto"ECOM" - Set
referenceTextto something like"Membership Setup Fee" - Omit
permittedPaymentChoicesto expose all studio-configured payment methods for ECOM scope - MUST include
finionPayCustomerIdfrom Session 1 response to link both sessions
The token for the user session.
The date and time until the token is valid.
Identifies a customer in Finion Pay, i.e. to retreive existing payment instruments.
- Amount is the actual due amount – Use
dueOnSigningAmountfrom preview referenceTextis required – This appears on bank statements- MUST include
finionPayCustomerId– Links both sessions to same customer - Omit
permittedPaymentChoices– Recommended to expose all studio-configured payment methods for ECOM scope - Store the
token– Required for widget remounting after page reload
For detailed widget integration instructions, see Payment Widget Integration Guide.
Use either the preview or stable version URI of the widget
<!-- Add to <head> -->
<script src="https://widget.dev.payment.sportalliance.com/widget.js"></script>Or dynamically:
function loadWidgetLibrary() {
return new Promise((resolve, reject) => {
if (window.paymentWidget) {
resolve();
return;
}
const script = document.createElement('script');
script.src = 'https://widget.dev.payment.sportalliance.com/widget.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}// Ensure DOM element exists
const containerEl = document.getElementById('recurring-payment-container');
if (!containerEl) {
throw new Error('Container element not found');
}
// Clear any existing content
containerEl.innerHTML = '';
// Initialize widget
const recurringWidget = window.paymentWidget.init({
userSessionToken: recurringSession.token,
container: containerEl, // Can be element or element ID
countryCode: 'DE',
locale: 'en-US',
environment: 'test', // or 'sandbox', 'live'
onSuccess: (paymentRequestToken, paymentInstrumentDetails) => {
console.log('Recurring payment setup successful');
console.log('Payment Request Token:', paymentRequestToken);
console.log('Payment Method:', paymentInstrumentDetails);
// Store token for final submission
recurringPaymentToken = paymentRequestToken;
// Proceed to upfront payment (if needed)
if (needsUpfront) {
mountUpfrontWidget();
}
},
onError: (error) => {
console.error('Recurring payment error:', error);
// Handle error appropriately
}
});const containerEl = document.getElementById('upfront-payment-container');
if (!containerEl) {
throw new Error('Container element not found');
}
containerEl.innerHTML = '';
const upfrontWidget = window.paymentWidget.init({
userSessionToken: upfrontSession.token,
container: containerEl,
countryCode: 'DE',
locale: 'en-US',
environment: 'test',
onSuccess: (paymentRequestToken, paymentInstrumentDetails) => {
console.log('Upfront payment successful');
console.log('Payment Request Token:', paymentRequestToken);
console.log('Transaction:', paymentInstrumentDetails);
// Store token for final submission
upfrontPaymentToken = paymentRequestToken;
// Mark upfront payment as complete
upfrontPaymentComplete = true;
},
onError: (error) => {
console.error('Upfront payment error:', error);
// Handle error appropriately
}
});Always destroy widgets before unmounting components:
// Before component unmounts or page navigation
function cleanup() {
if (recurringWidget && typeof recurringWidget.destroy === 'function') {
recurringWidget.destroy();
}
if (upfrontWidget && typeof upfrontWidget.destroy === 'function') {
upfrontWidget.destroy();
}
}
// In React/Svelte/Vue lifecycle
onDestroy(() => {
cleanup();
});Payment widgets render iframes with external payment forms (credit card inputs, bank redirects, etc.). If the user:
- Refreshes the page
- Navigates away and back
- Browser session times out
...the widget state is lost, forcing the user to re-enter payment details.
Store session tokens and remount widgets on page load. For more details, see Handling Redirects.
// After creating payment sessions, store tokens
const sessionData = {
// Session tokens for remounting widgets
recurringSessionToken: recurringSession.token,
upfrontSessionToken: upfrontSession.token,
// Payment tokens (if already completed)
recurringPaymentToken: recurringPaymentToken || null,
upfrontPaymentToken: upfrontPaymentToken || null,
// FinionPay customer mapping
finionPayCustomerId: recurringSession.finionPayCustomerId,
// Other state
preview: preview,
personalInfo: personalInfo,
selectedOfferId: offer.id,
selectedTermId: term.id,
// Timestamp for TTL
timestamp: Date.now()
};
// Store in sessionStorage (cleared on tab close)
sessionStorage.setItem('contractFlowState', JSON.stringify(sessionData));function restoreSession() {
const storedData = sessionStorage.getItem('contractFlowState');
if (!storedData) return null;
const data = JSON.parse(storedData);
// Check TTL (e.g., 1 hour)
const ONE_HOUR = 60 * 60 * 1000;
if (Date.now() - data.timestamp > ONE_HOUR) {
sessionStorage.removeItem('contractFlowState');
return null;
}
return data;
}async function remountWidgets() {
const session = restoreSession();
if (!session) return;
// Remount recurring widget if session exists
if (session.recurringSessionToken) {
const containerEl = document.getElementById('recurring-payment-container');
if (!containerEl) return;
containerEl.innerHTML = '';
recurringWidget = window.paymentWidget.init({
userSessionToken: session.recurringSessionToken,
container: containerEl,
countryCode: 'DE',
locale: 'en-US',
environment: 'test',
onSuccess: (paymentRequestToken, paymentInstrumentDetails) => {
recurringPaymentToken = paymentRequestToken;
updateSessionStorage({ recurringPaymentToken });
},
onError: (error) => {
console.error('Remount error:', error);
}
});
// If payment was already completed, restore token
if (session.recurringPaymentToken) {
recurringPaymentToken = session.recurringPaymentToken;
recurringPaymentComplete = true;
}
}
// Remount upfront widget if session exists
if (session.upfrontSessionToken) {
// Similar logic...
}
}
// Call on component mount
onMount(async () => {
await loadWidgetLibrary();
await remountWidgets();
});function updateSessionStorage(updates) {
const storedData = sessionStorage.getItem('contractFlowState');
if (!storedData) return;
const data = JSON.parse(storedData);
const updatedData = { ...data, ...updates, timestamp: Date.now() };
sessionStorage.setItem('contractFlowState', JSON.stringify(updatedData));
}- Token validity: Check
tokenValidUntilfrom API response - Typical duration: 15-30 minutes
- Handle expiry: If token expired, create new session and remount widget
function isTokenValid(tokenValidUntil) {
const expiryTime = new Date(tokenValidUntil).getTime();
return Date.now() < expiryTime;
}
// Before remounting
if (!isTokenValid(session.recurringSession.tokenValidUntil)) {
// Create new session
const newSession = await createPaymentSession({ /* ... */ });
updateSessionStorage({ recurringSessionToken: newSession.token });
}Endpoint: POST /v1/memberships/signup
- Demo tenant
https://open-api-demo.open-api.magicline.com/v1/memberships/signup
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
curl -i -X POST \
https://open-api-demo.open-api.magicline.com/v1/memberships/signup \
-H 'Content-Type: application/json' \
-H 'X-API-KEY: YOUR_API_KEY_HERE' \
-d '{
"contract": {
"contractOfferTermId": 1000,
"startDate": "2026-02-01",
"preuseDate": "2026-01-01",
"notes": "Some notes",
"thirdPartyId": "1000a",
"employeeId": 1239812733,
"referralCode": "A500D",
"selectedSelectableModuleIds": [
0
],
"selectedOptionalModuleIds": [
0
],
"initialPaymentRequestToken": "3JtyH5sakfn2V22vB0napNC2zWMlpFwS9gPQawuk7Jw1F00atOD0BA",
"contractSignature": {
"base64SvgSignature": "string"
},
"textBlockSignatures": [
{
"base64SvgSignature": "string",
"textBlockId": 0
}
],
"voucherCode": 123
},
"customer": {
"thirdPartyId": "A1000",
"firstName": "Peter",
"secondFirstName": "Thomas",
"lastName": "Muller",
"secondLastName": "Meyer",
"dateOfBirth": "2019-08-24",
"email": "example@example.com",
"gender": "MALE",
"phoneNumberPrivate": "+44123456789",
"phoneNumberMobile": "+44987654321",
"street": "Raboisen Street",
"secondStreet": "Second Street",
"cityPart": "Tegel",
"district": "District 12",
"streetType": "Avenue",
"streetBlock": "5th block",
"portal": "Portal 1",
"stairway": "Right stairway",
"door": "Door 1",
"province": "Champagne",
"additionalAddressInformation": "Additional information",
"floor": "2nd floor",
"language": {
"languageCode": "de",
"countryCode": "DE"
},
"houseNumber": "3-4",
"buildingName": "Empire State Building",
"city": "Hamburg",
"zipCode": "220-99",
"countryCode": "DE",
"communicationPreferences": [
{
"messageCategory": "CONTRACT",
"channels": [
{
"communicationChannel": "EMAIL",
"customerOverridable": true,
"active": true
}
]
}
],
"documentIdentification": {
"documentNumber": "CX5432112345DS",
"documentType": "ID_CARD"
},
"paymentRequestToken": "string",
"additionalInformationFieldAssignments": [
{
"additionalInformationFieldId": 1234567890,
"value": "string"
}
]
}
}'The contract information used for the membership signup
Unique ID of the contract offer term
The start date of the contract
The pre use date of the contract. If not provided, it will be evaluated based on contract offer configuration.
The notes related to the contract
Unique ID of the third party contract in the third party system
Unique ID of the employee who created the membership
Referral code to link with recruiter
The selected modules from the available selectable modules
The selected modules from the available optional modules
This token identifies a pre-authorized payment request. It acts as a reference to the payment session initiated by the user. It's not needed for preview endpoints.
Representing customer signature
Signatures for text blocks
An optional code for a voucher. Only credit and discount vouchers are supported.
The customer information used for the membership signup
Unique ID of the third party customer in the third party system
First name of the customer
Second first name of the customer
Last name of the customer
Second last name of the customer
Date of birth of the customer
Email address of the customer
Gender of the customer
| Enum Value | Description |
|---|---|
| MALE | Male gender of the customer |
| FEMALE | Female gender of the customer |
| UNISEX | Unisex gender of the customer |
Private phone number of the customer
Mobile phone number of the customer
Street of the customer's address
Second street line of the customer's address
City part of the customer's address
District of the customer's address
Street type of the customer's address
Street block of the customer's address
Portal of the customer's address
Stairway of the customer's address
Door of the customer's address
Province of the customer's address
Additional address information of the customer's address
Floor of the customer's address
Basic language information
The language code
The country code
House number of the customer's address
Building name
City of the customer's address
Zip code of the customer's address
Country code of the customer's address
List of communication preferences for the customer
Information from an official document that identifies the customer
By assigning the paymentRequestToken to the customer, the payment method associated with the token will be used as payment method setting of the customer. Additionally, if the paymentRequestToken is associated with a payment instrument, i.e. a SEPA Mandate, BACS Mandate, Credit Card, or other, the payment instrument will be made available in the member account for future collection via payment runs. By leaving this field empty the payment method of the customer will be set to CASH. For reference check 'Create a user payment session'.
List of additional information field assignments of the customer
The API expects two separate payment tokens in specific locations:
customer.paymentRequestToken: Recurring payment token (MEMBER_ACCOUNT)- Used for monthly membership fees
- From Session 1
onSuccesscallback
contract.initialPaymentRequestToken: Upfront payment token (ECOM)- Used for immediate payment (setup fees)
- From Session 2
onSuccesscallback
Never use the same token for both fields. Each widget session produces a unique token.
Unique ID of the customer
Note: The API returns the customerId of the newly created membership. You can use this ID to fetch additional customer details if needed via other endpoints.
try {
const result = await createMembership(signupRequest);
// Success - clear session storage
sessionStorage.removeItem('contractFlowState');
// Handle successful signup
console.log('Membership created successfully:', result.customerId);
// Navigate to next step or confirmation
return { success: true, customerId: result.customerId };
} catch (error) {
console.error('Membership signup failed:', error);
if (error.status === 400) {
// Validation error - check error.validationErrors for field-specific issues
return { success: false, type: 'validation', errors: error.validationErrors };
} else if (error.status === 409) {
// Duplicate customer - customer with this email may already exist
return { success: false, type: 'duplicate_customer', message: error.message };
} else {
// Generic error
return { success: false, type: 'generic', message: 'Failed to create membership' };
}
}Always use decimal format, NOT cents.
// ❌ WRONG
const request = { amount: Math.round(49.99 * 100) }; // 4999 - will charge €4999!
// ✅ CORRECT
const request = { amount: 49.99 }; // Decimal format// ❌ WRONG - Creating both sessions without customer ID
const session1 = await createSession({ scope: 'MEMBER_ACCOUNT' });
const session2 = await createSession({ scope: 'ECOM' }); // Missing finionPayCustomerId
// ✅ CORRECT - Linking sessions
const session1 = await createSession({ scope: 'MEMBER_ACCOUNT' });
const customerId = session1.finionPayCustomerId;
const session2 = await createSession({
scope: 'ECOM',
finionPayCustomerId: customerId
});// ❌ WRONG - Using offer flatFees for payment amount
const upfrontAmount = offer.terms[0].flatFees.reduce((sum, fee) =>
sum + fee.paymentFrequency.price.amount, 0
);
// ✅ CORRECT - Using preview API
const preview = await createContractPreview(/* ... */);
const upfrontAmount = preview.paymentPreview.dueOnSigningAmount.amount;// ❌ WRONG - Not clearing container before remount
const widget = window.paymentWidget.init({ container: containerEl });
// ✅ CORRECT - Clear container first
containerEl.innerHTML = '';
const widget = window.paymentWidget.init({ container: containerEl });// ❌ WRONG - Storing in localStorage (persists across sessions)
localStorage.setItem('paymentTokens', JSON.stringify(tokens));
// ✅ CORRECT - Use sessionStorage (cleared on tab close)
sessionStorage.setItem('contractFlowState', JSON.stringify(state));
// ✅ BETTER - Clear after successful submission
sessionStorage.removeItem('contractFlowState');// ❌ WRONG - Calling preview on every keystroke
inputField.addEventListener('input', () => {
fetchPreview(); // Excessive API calls
});
// ✅ CORRECT - Debounce API calls
let debounceTimer;
inputField.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => fetchPreview(), 500);
});// ❌ WRONG - Using same payment choices for both sessions
const paymentChoices = ['SEPA', 'CREDIT_CARD', 'CASH'];
createSession({ scope: 'MEMBER_ACCOUNT', permittedPaymentChoices: paymentChoices });
createSession({ scope: 'ECOM', permittedPaymentChoices: paymentChoices });
// ✅ CORRECT - Use offer choices for MEMBER_ACCOUNT, omit for ECOM
const recurringChoices = offer.allowedPaymentChoices || ['SEPA', 'BACS', 'CREDIT_CARD', 'CASH', 'BANK_TRANSFER'];
await createSession({
scope: 'MEMBER_ACCOUNT',
permittedPaymentChoices: recurringChoices
});
// For ECOM, omit permittedPaymentChoices to expose all studio-configured payment methods
await createSession({
scope: 'ECOM',
// No permittedPaymentChoices - allows all payment methods configured for ECOM in studio
});// ❌ WRONG - Not destroying widget before navigation
navigate('/next-page'); // Widget iframe still active in background
// ✅ CORRECT - Always cleanup
function cleanup() {
if (widget && typeof widget.destroy === 'function') {
widget.destroy();
}
}
// Before navigation
cleanup();
navigate('/next-page');
// In framework lifecycle hooks
onBeforeUnmount(() => cleanup());// ❌ WRONG - No recovery from failed session creation
const session = await createSession({ /* ... */ });
mountWidget(session.token);
// ✅ CORRECT - Handle errors and allow retry
try {
const session = await createSession({ /* ... */ });
mountWidget(session.token);
} catch (error) {
console.error('Failed to create payment session:', error);
// Store error state and provide retry mechanism
return { success: false, error: error.message, canRetry: true };
}// ❌ WRONG - Creating payment session without final preview
collectPersonalInfo();
createPaymentSessions(); // Amount might be wrong
// ✅ CORRECT - Always fetch final preview first
collectPersonalInfo();
const preview = await fetchPreview(); // Get final amount
const upfrontAmount = preview.paymentPreview.dueOnSigningAmount.amount;
createPaymentSessions({ upfrontAmount });// ❌ WRONG - Missing referenceText
const sessionRequest = {
amount: 0,
scope: "MEMBER_ACCOUNT"
};
// ✅ CORRECT - Include all required fields
const sessionRequest = {
amount: 0,
scope: "MEMBER_ACCOUNT",
referenceText: "Membership Contract Recurring Payment" // Required!
};This integration requires careful orchestration of multiple API calls and payment sessions. Key points:
- Preview API is authoritative – Always use
dueOnSigningAmountfor upfront payment - Two separate sessions – MEMBER_ACCOUNT (recurring) and ECOM (upfront)
- Customer ID mapping – Pass
finionPayCustomerIdfrom Session 1 to Session 2 - Session persistence – Store tokens for widget remounting after page reload
- Two payment tokens – Store both tokens and submit in correct fields:
customer.paymentRequestTokenfor recurring paymentscontract.initialPaymentRequestTokenfor upfront payments
- Amount format – Always use decimal format (not cents)
- Required fields – Include
referenceTextin all payment session requests - Widget cleanup – Always destroy widgets before unmounting
- Response structure – API returns only
{ customerId: number }
Following this guide ensures a robust membership signup flow that handles edge cases, maintains state across page reloads, and correctly processes both recurring and upfront payments.
- GET /v1/memberships/membership-offers
- GET /v1/memberships/membership-offers/{offerId}
- POST /v1/memberships/signup/preview
- POST /v1/memberships/signup
- POST /v1/payments/user-session
For payment-related endpoints and concepts, see Working with Payment API.