Le verrou ne protège que l'app : si le POST part mais que la réponse se perd, le paiement reste pending et repart au prochain rejeu. Génère une clé d'idempotence à l'enregistrement, pas au rejeu, et envoie-la à chaque tentative :
// À l'enregistrement hors ligne
const key = Crypto.randomUUID();
await db.runAsync(
'INSERT INTO payments (id, idempotency_key, amount, status) VALUES (?, ?, ?, ?)',
id, key, amount, 'pending'
);
// Au rejeu
await api.post('/payments', p, {
headers: { 'Idempotency-Key': p.idempotency_key },
});
Côté serveur, un index unique sur la clé suffit à renvoyer la réponse déjà connue.