Avant de mettre en place un traitement réel pour les paiements, les annulations ou toute autre opération, il est utile de comprendre le modèle unique qui régit toute communication avec l'application de paiement Market Pay. Chaque opération dans le SDK le suit. Une fois que cela est clair, toute l'API devient prévisible.
Ce guide explique trois choses : pourquoi les réponses ne reviennent pas directement de la méthode que vous appelez, comment distinguer les réponses lorsqu'elles arrivent toutes au même endroit, et pourquoi ce sont les super-types des réponses — et non leurs sous-types de succès — que vous devez gérer.
Les requêtes sont envoyées sans attente ; les réponses arrivent séparément
Lorsque vous appelez quelque chose comme sendPaymentRequest, vous pourriez vous attendre à ce qu'elle retourne le résultat du paiement. Ce n'est pas le cas. L'appel envoie la requête à l'application de paiement et retourne presque immédiatement — il ne bloque pas en attendant que le terminal traite la carte.
Le résultat réel arrive plus tard, de manière asynchrone, dans l'intercepteur que vous avez enregistré lors de l'initialisation du SDK. C'est la chose la plus importante à retenir : la méthode que vous appelez et la réponse que vous traitez se trouvent dans deux endroits différents de votre code.
// Vous envoyez ici…
viewModelScope.launch {
clientSDK.sendPaymentRequest(/* … */)
}
// …et le résultat arrive ici, plus tard, dans l'intercepteur :
.bindInterceptor(
interceptor = { message ->
// la réponse de paiement apparaît sous forme de `message`
Result.success(message)
}
)
Pourquoi est-ce conçu ainsi ? Un paiement par carte n'est pas instantané — le terminal invite le porteur de carte, communique avec l'acquéreur, et peut prendre plusieurs secondes. Bloquer votre appel pendant toute cette durée figerait votre application. Détacher la requête de la réponse permet au terminal de prendre le temps nécessaire tout en gardant votre application réactive, et permet à l'application de paiement de vous envoyer des mises à jour intermédiaires (voir les "demandes d'affichage" ci-dessous) avant le résultat final.
Chaque réponse est un DomainMessage
L'intercepteur reçoit un seul paramètre typé comme DomainMessage. Chaque message dans la communication App2App — réponses de connexion, réponses de paiement, erreurs, mises à jour de progression — est un sous-type de DomainMessage. C'est pourquoi un seul intercepteur peut recevoir tous ces messages.
Parce qu'ils arrivent tous sous le même type général, votre tâche dans l'intercepteur est de vérifier le type du message entrant pour décider ce que c'est et comment le gérer :
.bindInterceptor(
interceptor = { message ->
when (message) {
is SuccessRetailerLoginResponse -> { /* la session est maintenant ouverte */ }
is SuccessRetailerPaymentResponse -> { /* paiement approuvé */ }
is ErrorRetailerPaymentResponse -> { /* paiement échoué */ }
// …gérez les types de message qui intéressent votre application
}
Result.success(message)
}
)
Vous n'avez besoin de traiter que les types de message sur lesquels votre application agit réellement. Les messages non correspondants sont simplement ignorés — mais renvoyez toujours Result.success(message) à la fin pour que le SDK sache que le message a été accepté.
Chaque opération a son propre super-type de réponse
Les réponses ne sont pas une liste plate de classes sans lien. Chaque opération a un super-type scellé, sous lequel se trouvent les résultats concrets — généralement une variante de succès et une d'erreur. Correspondre au super-type attrape tous les résultats de cette opération ; correspondre à un seul sous-type attrape seulement un cas.
| Opération | Super-type de réponse | Sous-types que vous pouvez recevoir |
|---|---|---|
| Connexion | RetailerLoginResponse |
SuccessRetailerLoginResponse, ErrorRetailerLoginResponse
|
| Paiement | RetailerPaymentResponse |
SuccessRetailerPaymentResponse, ErrorRetailerPaymentResponse, PartialRetailerPaymentResponse
|
| Annulation | RetailerReversalResponse |
SuccessRetailerReversalResponse, ErrorRetailerReversalResponse
|
| Rapprochement | RetailerReconciliationResponse |
Success…, Error…
|
| Statut de transaction | RetailerTransactionStatusResponse |
Success…, Error…
|
| Acquisition de carte | RetailerAcquisitionResponse |
SuccessRetailerAcquisitionResponse, ErrorRetailerAcquisitionResponse
|
| Diagnostic (test de connexion) | RetailerDiagnosisResponse |
Success…, Error…
|
| Déconnexion | RetailerLogoutResponse |
Success…, Error…
|
Note. Le paiement a un troisième sous-type,
PartialRetailerPaymentResponse, que les autres opérations n'ont pas. C'est précisément pourquoi vous raisonnez en termes de super-type : un gestionnaire écrit seulement pour "succès ou erreur" manquerait le cas partiel. La liste complète et officielle des sous-types pour chaque opération se trouve dans la référence API (sdk-doc.zip) — en cas de doute, correspondez au super-type.
Le paiement envoie aussi des mises à jour de progression
Pendant un paiement, avant le résultat final, l'application de paiement peut vous envoyer des messages RetailerDisplayRequest. Ce ne sont pas le résultat du paiement — ils indiquent ce que fait le terminal en ce moment (attente de la carte, communication avec l'hôte, etc.), ce qui est utile si vous voulez afficher votre propre interface de progression. La même requête qui produit une RetailerPaymentResponse peut produire plusieurs messages RetailerDisplayRequest en cours de route. Gérez-les si vous souhaitez un suivi en direct ; ignorez-les sinon. Le résultat final est toujours l'un des sous-types de RetailerPaymentResponse.
Pourquoi vous attendez le super-type, pas le sous-type de succès
C'est l'erreur qui cause les bugs les plus déroutants, donc elle mérite sa propre section. Cela importe surtout quand vous passez du mode "fire-and-forget" au mode bloquant — attendre une réponse spécifique avant de continuer.
Le SDK fournit BlockingMessageGateway pour cela. Vous lui transmettez chaque message d'intercepteur, puis vous pouvez envoyer une requête et suspendre votre coroutine jusqu'à ce que la réponse correspondante arrive :
val gateway = BlockingMessageGateway()
// Dans l'intercepteur, transmettez chaque message à la gateway :
.bindInterceptor(
interceptor = { message ->
gateway.onNewMessage(message)
Result.success(message)
}
)
// Puis envoyez et attendez, en spécifiant le type de réponse attendu :
val response: RetailerLoginResponse = gateway.sendBlocking(
timeoutMillis = 30000
) {
clientSDK.sendLoginRequest()
}
when (response) {
is SuccessRetailerLoginResponse -> { /* connecté */ }
is ErrorRetailerLoginResponse -> { /* connexion refusée — gérez cela */ }
}
Le type que vous mettez entre crochets / auquel vous assignez est le type que sendBlocking attend. Voici le piège :
-
Attendre
RetailerLoginResponse(le super-type) → la coroutine reprend sur soit succès ou erreur. Vous gérez les deux. Correct. -
Attendre
SuccessRetailerLoginResponse(le sous-type de succès) → la coroutine reprend seulement si la connexion réussit. Si la connexion échoue, aucunSuccessRetailerLoginResponsen'arrive jamais, la gateway continue d'attendre, et votre coroutine reste bloquée jusqu'à ce qu'elle lance finalement uneTimeoutCancellationException. La réponse d'échec a bien été livrée — vous ne l'écoutiez simplement pas.
En d'autres termes : attendre le sous-type de succès transforme silencieusement chaque échec en un timeout. Attendez toujours le super-type pour que les deux résultats reprennent votre coroutine, puis distinguez-les avec un when comme montré ci-dessus.
sendBlocking a un timeout par défaut de 60 secondes (timeoutMillis = 60000). Passez une valeur plus courte pour les opérations qui doivent échouer rapidement. Quel que soit le timeout, encapsulez l'appel pour gérer la TimeoutCancellationException — un timeout signifie qu'aucune réponse n'est arrivée à temps, ce qui est lui-même une condition à laquelle votre application doit réagir.
Le modèle en un paragraphe
Vous envoyez une requête et elle retourne immédiatement. La réponse — ainsi que toute mise à jour de progression avant celle-ci — arrive plus tard dans votre intercepteur, tous typés comme DomainMessage. Vous vérifiez le type pour trouver ceux qui vous intéressent. Chaque opération a un super-type scellé de réponse couvrant tous ses résultats ; vous raisonnez et (en mode bloquant) attendez en termes de ce super-type, jamais d'un seul sous-type, pour que les échecs atteignent votre code au lieu de disparaître dans un timeout. Chaque opération du SDK fonctionne exactement ainsi — donc une fois que vous en avez géré une, vous les avez toutes gérées.
Articles connexes
- Démarrage rapide — le premier paiement de bout en bout que ce modèle soutient.
- Cycle de vie de la session et de la connexion — pourquoi la connexion doit réussir avant que toute réponse soit significative, et quand se reconnecter.
- Gestion de l'état et du premier plan — l'autre canal asynchrone (l'observateur d'événements), séparé de l'intercepteur décrit ici.
- Dépannage : sendBlocking bloque jusqu'au timeout — le mode d'échec que la dernière section de ce guide vise à éviter.
-
Référence API (
sdk-doc.zip) — la liste complète et officielle des sous-types pour chaque opération.