Swan — GraphQL Banking API
Swan est une neobanque B2B francaise fondee en 2019, offrant une API GraphQL moderne pour integrer des services bancaires complets dans votre plateforme.
Presentation
Swan permet a YaniPay d'integrer des comptes bancaires (IBAN francais), des cartes de paiement (VISA) et des virements SEPA directement dans la plateforme, sans devenir un etablissement bancaire. Swan detient la licence d'etablissement de paiement delivree par l'ACPR (Banque de France).
Fondee en 2019
Neobanque B2B francaise basee a Paris
HQ Paris
Siege social en France, equipe europeenne
Licence ACPR
Etablissement de paiement agree par la Banque de France
API GraphQL
API moderne avec types forts et introspection
$40M+ leves
Series A aupres de Lakestar, Accel et Creandum
Open-source
Librairies publiques : boxed, chicane, lake
Choix de Swan pour YaniPay
API GraphQL
Contrairement a Treezor qui expose une API REST, Swan utilise exclusivement GraphQL. Cela signifie que toutes les operations (queries, mutations, subscriptions) passent par un seul endpoint. Les avantages pour YaniPay sont :
- Types forts : le schema GraphQL genere automatiquement les types TypeScript via
graphql-codegen - Requetes optimisees : on ne recupere que les champs necessaires, pas de sur-fetching
- Introspection : exploration interactive du schema via GraphQL Playground
- Union types : les erreurs sont typees dans le schema (pas de codes HTTP ambigus)
1 mutation InitiateSepaCreditTransfer($input: InitiateCreditTransferInput!) { 2 initiateCreditTransfers(input: $input) { 3 ... on InitiateCreditTransfersSuccessPayload { 4 payment { 5 id 6 statusInfo { status } 7 } 8 } 9 ... on ForbiddenRejection { message } 10 ... on ValidationRejection { message fields { path message } } 11 } 12 }
L'endpoint GraphQL Swan est accessible a https://api.swan.io/sandbox-partner/graphql en sandbox et https://api.swan.io/live-partner/graphql en production. YaniPay utilise un client GraphQL centralise dans lib/baas/swan/client.tsqui gere l'authentification, le retry et le logging.
Authentification OAuth2
Swan utilise le protocole OAuth 2.0pour l'authentification. Deux types de tokens sont utilises :
- Project Access Token : obtenu via
client_credentials, utilise pour les operations serveur-a-serveur (creation de comptes, virements, etc.) - User Access Token : obtenu via
authorization_code, utilise pour les actions necessitant le consentement de l'utilisateur (SCA PSD2)
1 // Swan OAuth2 Token Exchange 2 const tokenResponse = await fetch('https://oauth.swan.io/oauth2/token', { 3 method: 'POST', 4 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 5 body: new URLSearchParams({ 6 grant_type: 'client_credentials', 7 client_id: process.env.SWAN_CLIENT_ID!, 8 client_secret: process.env.SWAN_CLIENT_SECRET!, 9 }), 10 }); 11 const { access_token } = await tokenResponse.json();
Securite des credentials
Le token obtenu a une duree de vie limitee (generalement 3600 secondes). YaniPay implemente un mecanisme de cache et de refresh automatique dans lib/baas/swan/auth.ts pour eviter les appels OAuth2 redondants.
Result Types
L'une des particularites majeures de Swan est l'utilisation de union types GraphQL pour les reponses. Au lieu de retourner des codes HTTP differents (400, 403, 422), Swan retourne toujours un 200 OK avec un payload type qui peut etre un succes ou un rejet specifique.
1 // Swan returns typed rejection unions instead of HTTP errors 2 type InitiateCreditTransfersPayload = 3 | InitiateCreditTransfersSuccessPayload 4 | ForbiddenRejection 5 | ValidationRejection 6 | InternalErrorRejection; 7 8 // YaniPay maps these to a BaaSError tagged union 9 import { Result } from '@swan-io/boxed'; 10 type BaaSResult<T> = Result<T, BaaSError>;
Ce pattern force une gestion exhaustive des erreurs a la compilation. TypeScript refuse de compiler si un cas de rejet n'est pas traite. YaniPay exploite la librairie @swan-io/boxed pour propager ces resultats dans toute la couche BaaS :
1 import { Result } from '@swan-io/boxed'; 2 import type { BaaSError } from '@/types/baas'; 3 4 export async function initiateTransfer( 5 input: TransferInput 6 ): Promise<Result<Payment, BaaSError>> { 7 const response = await swanClient.mutate(InitiateSepaCreditTransfer, { input }); 8 9 const payload = response.initiateCreditTransfers; 10 11 if (payload.__typename === 'InitiateCreditTransfersSuccessPayload') { 12 return Result.Ok(payload.payment); 13 } 14 15 // Typed rejection — no guessing 16 return Result.Error({ 17 code: payload.__typename, 18 message: payload.message, 19 provider: 'swan', 20 }); 21 }
Avantage des Result Types
try/catch ambigus et les verifications de codes HTTP. Chaque appel BaaS retourne un Result<T, BaaSError> que le code appelant doit explicitement decomposer.Librairies Open-Source
Swan maintient plusieurs librairies open-source que YaniPay utilise ou reference dans son architecture. Ces packages sont disponibles sur npm et GitHub.
| Package | Description | Usage YaniPay |
|---|---|---|
| @swan-io/boxed | Types fonctionnels Result, Option, Future et AsyncData pour TypeScript | Gestion des erreurs et etats asynchrones dans YaniPay |
| @swan-io/chicane | Router type-safe pour React avec parametres valides a la compilation | Non utilise (YaniPay utilise Next.js App Router) |
| @swan-io/lake | Bibliotheque de composants UI React utilisee dans le banking dashboard Swan | Reference pour les patterns UI bancaires |
| @swan-io/react-native-browser | Module React Native pour ouvrir un navigateur in-app (OAuth, consent PSD2) | Flux OAuth2 et consentement SCA dans l'app mobile YaniPay |
Sandbox & Tests
Swan fournit un environnement sandbox complet qui replique le comportement de la production. Toutes les operations (creation de compte, virements, cartes) peuvent etre testees sans impact reel.
Sandbox API
api.swan.io/sandbox-partner/graphql
Endpoint de test avec donnees simulees. Les virements SEPA sont traites instantanement.
Dashboard Sandbox
dashboard.swan.io
Interface web pour visualiser les comptes, transactions et simuler des evenements webhook.
# Swan Sandbox Configuration
SWAN_ENVIRONMENT=sandbox
SWAN_API_URL=https://api.swan.io/sandbox-partner/graphql
SWAN_OAUTH_URL=https://oauth.swan.io/oauth2/token
SWAN_CLIENT_ID=your-sandbox-client-id
SWAN_CLIENT_SECRET=your-sandbox-client-secret
SWAN_PROJECT_ID=your-sandbox-project-idLe dashboard Swan sandbox permet de simuler des evenements comme l'approbation d'un onboarding, la reception d'un virement SEPA entrant ou le changement de statut d'une carte. Ces evenements declenchent les webhooks configures dans YaniPay.
1 import { describe, it, expect } from 'vitest'; 2 import { swanClient } from '@/lib/baas/swan/client'; 3 4 describe('Swan BaaS Integration', () => { 5 it('should authenticate with sandbox credentials', async () => { 6 const token = await swanClient.getAccessToken(); 7 expect(token).toBeDefined(); 8 expect(typeof token).toBe('string'); 9 }); 10 11 it('should fetch account details', async () => { 12 const result = await swanClient.getAccount('sandbox-account-id'); 13 result.match({ 14 Ok: (account) => { 15 expect(account.currency).toBe('EUR'); 16 expect(account.country).toBe('FRA'); 17 }, 18 Error: (error) => { 19 throw new Error(`Unexpected error: ${error.message}`); 20 }, 21 }); 22 }); 23 24 it('should initiate a SEPA credit transfer', async () => { 25 const result = await swanClient.initiateTransfer({ 26 amount: { value: '10.00', currency: 'EUR' }, 27 targetIban: 'FR7630006000011234567890189', 28 label: 'Test transfer', 29 }); 30 expect(result.isOk()).toBe(true); 31 }); 32 });
Bonnes pratiques
Voici les recommandations cles pour une integration optimale de Swan dans YaniPay.
InstantWithFallback pour les virements
InstantWithFallbackpour les virements SEPA. Swan tente d'abord un virement instantane (SEPA Instant Credit Transfer) et bascule automatiquement en virement standard si la banque destinataire ne supporte pas l'instant. Cela garantit la meilleure experience utilisateur sans risque d'echec.excludeNonBooked pour l'historique
excludeNonBooked: truepour n'afficher que les transactions confirmees. Les transactions en attente (pending/reserved) peuvent changer de montant ou etre annulees, ce qui perturbe l'affichage du solde.consentRedirectUrl pour PSD2
consentRedirectUrlvers votre application mobile ou web. Swan redirigera l'utilisateur vers cette URL apres validation du consentement. Sur mobile, utilisez @swan-io/react-native-browser pour un flux OAuth in-app fluide.Gestion des webhooks
Implementez l'idempotence via Redis pour eviter le traitement en double des evenements Swan. Chaque webhook inclut un eventId unique.
Pagination cursor-based
Swan utilise la pagination Relay (cursor-based) et non la pagination offset. Utilisez first/after et last/before pour naviguer dans les resultats.
1 query ListTransactions($accountId: ID!, $after: String) { 2 account(accountId: $accountId) { 3 transactions(first: 20, after: $after, filters: { status: Booked }) { 4 edges { 5 node { 6 id 7 amount { value currency } 8 label 9 bookedDate 10 side 11 statusInfo { status } 12 } 13 cursor 14 } 15 pageInfo { 16 hasNextPage 17 endCursor 18 } 19 } 20 } 21 }