Online Payment & Card Registration
Resamania provides a universal payment widget that abstracts the complexity of different PSPs into a single interface. This widget supports both one-shot payment (settling a sale) and card registration (webcard).
NOTE
A legacy integration (PSP-specific) is available for partners already in production with that approach. See the legacy documentation if needed.
Supported PSPs
| PSP | One-shot | Wallet |
|---|---|---|
| Stripe | ✅ | ✅ |
| Payline | ✅ | ✅ |
| GlobalPayment | ✅ | ✅ |
| XplorPay / Snap | ✅ | ❌ |
Integration Modes
Three approaches are available depending on your technical stack:
| Mode | Description | Recommended for |
|---|---|---|
| Native iframe | The widgetUrl returned by the API can be embedded directly in an HTML <iframe>. Communication (success/failure) goes through window.postMessage. | Any web project with a bit of JS |
| Direct redirect | HTTP redirect to the PSP's payment page — your backend redirects the user. | Stacks without client-side JS (PHP, etc.) |
| React component | <WidgetIframe /> via @resamania/payment-react — wraps the iframe and exposes onActionSuccess / onActionFailure callbacks. The package is installed via HTTP URL. | React / Next.js applications |
IMPORTANT
The mode is determined by the presence or absence of validUrl / cancelUrl in the POST /payable_objects request body:
- Without
validUrlorcancelUrl→ the API generates awidgetUrlto embed as an iframe - With
validUrlandcancelUrl→ the API returns aredirectUrlto the PSP
Required Headers
All requests to the Resamania API must include these headers:
Accept: application/json, text/plain, */*
Content-Type: application/ld+json
Authorization: Bearer {your_bearer_token}NOTE
Content-Type: application/ld+json is required for the server to process the request correctly. Accept: application/json is needed to receive the response as flat JSON, including all business fields (such as widgetUrl).
Case 1: Online Payment (one-shot)
Flow — Widget mode (iframe / React)
1. Your backend → POST /{clientToken}/payable_objects (without validUrl or cancelUrl)
← { id, widgetUrl, ... }
2. Your frontend → <iframe src="{widgetUrl}" />
or <WidgetIframe src={widgetUrl} />
3. The user pays in the iframe
4. ← window.postMessage / onActionSuccess(payload, { provider })
or onActionFailure(payload, { provider })Flow — Direct redirect (PHP, etc.)
1. Your backend → POST /{clientToken}/payable_objects (with validUrl and cancelUrl)
← { id, redirectUrl, ... }
2. Your backend → HTTP 302 to redirectUrl
3. The user pays on the PSP page
4. The PSP → Redirects to validUrl (success or failure) or cancelUrl (abandoned)
5. Your backend → PUT /{clientToken}/payable_objects/{id}/check
← { state: 'validated' | 'refused' | ... }1. Create a payment object
POST /{clientToken}/payable_objects
Widget mode — do not send validUrl or cancelUrl:
{
"contactId": "/demoapi/contacts/2972926",
"amount": 2500,
"currency": "EUR",
"clubCode": "CFF",
"clubId": "/demoapi/clubs/1398",
"checkout": "/demoapi/checkouts/1361",
"payableObjects": {
"sales": ["/demoapi/sales/3558464"],
"invoices": [],
"incidents": []
},
"useWebWallet": true
}Redirect mode — send validUrl and cancelUrl:
{
"contactId": "/demoapi/contacts/2972926",
"amount": 2500,
"currency": "EUR",
"clubCode": "CFF",
"clubId": "/demoapi/clubs/1398",
"checkout": "/demoapi/checkouts/1361",
"payableObjects": {
"sales": ["/demoapi/sales/3558464"],
"invoices": [],
"incidents": []
},
"validUrl": "https://your-site.com/payment/return",
"cancelUrl": "https://your-site.com/payment/cancel"
}NOTE
useWebWallet: true instructs the API to save the card as a reusable payment method (webcard) at the time of payment. Only use this if the club has this feature enabled in its PSP configuration.
WARNING
The validUrl is called for both success and failure. Embed your own session identifier in this URL (e.g. ?sessionId=xxx) to retrieve the transaction on return — Resamania does not add any parameters automatically.
Fields returned depending on the mode:
| Field | Widget mode | Redirect mode | Description |
|---|---|---|---|
id | ✅ | ✅ | IRI of the payableObject (e.g. /demoapi/payable_objects/12345) |
widgetUrl | ✅ | ❌ | URL to embed as an iframe — contains the authentication token |
redirectUrl | ✅ | ✅ | URL to the PSP's payment page |
token | ✅ | ✅ | Transaction reference on the PSP side |
2a. Native iframe
Embed the widgetUrl directly in an <iframe> and listen for postMessage events:
<iframe
src="{widgetUrl}"
style="width: 100%; min-height: 500px; border: none;"
></iframe>
<script>
window.addEventListener('message', (event) => {
if (event.data?.type === 'ACTION_SUCCESS') {
console.log('Payment accepted', event.data.payload)
} else if (event.data?.type === 'ACTION_FAILURE') {
console.error('Payment refused', event.data.payload)
}
})
</script>NOTE
The widgetUrl returned by the API already contains the authentication token (autologintoken) as a query param. Use it directly without modification.
2b. Direct redirect (PHP / server-side)
// Retrieve the redirectUrl from the response and redirect
header('Location: ' . $payableObject['redirectUrl']);
exit;2c. React component
Install the package:
npm install "https://payment.resamania.com/pkg/resamania-payment-react-0.0.0.tgz"import { WidgetIframe } from '@resamania/payment-react'
function PaymentPage({ widgetUrl }) {
const handleSuccess = (payload, { provider }) => {
// Payment was accepted by the PSP.
// Always verify the status server-side before confirming the order.
console.log('Payment accepted via', provider, payload)
}
const handleFailure = (payload, { provider }) => {
console.error('Payment refused via', provider, payload)
}
return (
<WidgetIframe
src={widgetUrl}
style={{ width: '100%', minHeight: 500, border: 'none' }}
onActionSuccess={handleSuccess}
onActionFailure={handleFailure}
/>
)
}3. Check payment status
PUT /{clientToken}/payable_objects/{id}/check
Call server-side after a payment return (redirect) or to confirm a success reported by the widget. Queries the PSP in real time and updates the object's state.
WARNING
{id} is the numeric identifier of the payableObject (e.g. 12345), extracted from the IRI /demoapi/payable_objects/12345.
| State | Description |
|---|---|
created | Created, not yet presented to the user |
pending | Awaiting confirmation from the PSP |
requested | Payment being processed |
validated | Payment accepted ✅ |
refused | Payment refused ❌ |
canceled | Transaction canceled by the user |
Case 2: Card Registration (Webcard)
The widget can register a payment card without an immediate charge — useful for SEPA mandates, recurring subscriptions, or pre-registration before purchase.
Flow
1. Your backend → POST /{clientToken}/web_wallets
← { widgetUrl, ... }
2. Your frontend → <WidgetIframe src={widgetUrl} />
3. The user enters their card
4. ← onActionSuccess(WebCard[], { provider })
or onActionFailure(payload, { provider })1. Create a wallet
POST /{clientToken}/web_wallets
{
"contactId": "/demoapi/contacts/2972926",
"clubId": "/demoapi/clubs/1398",
"checkout": "/demoapi/checkouts/1361"
}The response contains a widgetUrl to pass directly to the <WidgetIframe /> component.
2. Display the registration form
import { WidgetIframe } from '@resamania/payment-react'
function WebCardPage({ widgetUrl }) {
const handleSuccess = (webCards, { provider }) => {
// webCards is an array of registered WebCards
// [{ id, cardReference, state, createdAt, ... }]
}
return (
<WidgetIframe
src={widgetUrl}
style={{ width: '100%', minHeight: 400, border: 'none' }}
onActionSuccess={handleSuccess}
onActionFailure={(payload, { provider }) => console.error(payload)}
/>
)
}WidgetIframe Component Reference
| Prop | Type | Description |
|---|---|---|
src | string | widgetUrl returned by the API (required) |
style | CSSProperties | Inline styles for the iframe |
className | string | CSS class for the iframe |
onActionSuccess | (payload, context) => void | Success callback |
onActionFailure | (payload, context) => void | Failure callback |
context contains: { provider: string } — the PSP used by the club.
Full Examples
Complete examples are available in the resamania2-partners repository:
Migrating from the Legacy Integration
If you are using the old PSP-specific integration (Stripe Elements, Payline redirect…), here are the changes:
| Before (legacy integration) | Now (Resamania Widget) |
|---|---|
| PSP-specific integration | One widget, all PSPs |
Managing clientSecret + publishableKey (Stripe) | A single widgetUrl field returned by the API |
@stripe/react-stripe-js | @resamania/payment-react |
| Native PSP callbacks | onActionSuccess / onActionFailure |
| No unified verification | PUT /payable_objects/{id}/check |