diff --git a/frontend/src/components/ContributeModal.jsx b/frontend/src/components/ContributeModal.jsx index b54664f..4c82bf0 100644 --- a/frontend/src/components/ContributeModal.jsx +++ b/frontend/src/components/ContributeModal.jsx @@ -348,9 +348,39 @@ export default function ContributeModal({ campaign, onClose, onSuccess }) { ? await submitWithFreighter() : await submitWithCustodial(); if (paymentMethod === 'anchor') return; + setResult(data); - setPhase('success'); - onSuccess(); + setPhase('confirming'); + setLoadingLabel('Confirming on Stellar…'); + + // Poll finalization endpoint until status is 'finalized' or 'failed' + const pollFinalization = async (txHash, maxAttempts = 15) => { + for (let i = 0; i < maxAttempts; i++) { + await new Promise(r => setTimeout(r, 2000)); + try { + const finalizationResult = await api.getContributionFinalization(txHash, token); + if (finalizationResult.finalization_status === 'finalized') { + setPhase('success'); + onSuccess(); + return; + } + if (finalizationResult.finalization_status === 'failed') { + setPhase('success'); + setError('The contribution transaction failed on Stellar. Please try again.'); + onSuccess(); + return; + } + } catch (err) { + // Keep polling on error + } + } + // Timeout: show success screen with timeout message but allow user to view on Stellar Expert + setPhase('success'); + setError(null); + onSuccess(); + }; + + pollFinalization(data.tx_hash); } catch (err) { if (paymentMethod === 'anchor' && anchorPopupRef.current && !anchorPopupRef.current.closed) { anchorPopupRef.current.close(); @@ -683,6 +713,44 @@ export default function ContributeModal({ campaign, onClose, onSuccess }) { Close + ) : phase === 'confirming' ? ( +
+

+ Confirming on Stellar… +

+
+
+

+ Your payment was submitted. We're waiting for it to be confirmed on the Stellar ledger, which usually takes 3–5 seconds. +

+
+ {result?.tx_hash && ( +

+ + View transaction on Stellar Expert + +

+ )} + +
) : (

@@ -717,6 +785,12 @@ export default function ContributeModal({ campaign, onClose, onSuccess }) {

)} + {error && ( +

+ {error} +

+ )} +

Tell your friends!

diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index ae04670..41a49a8 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -224,6 +224,8 @@ export const api = { request('GET', '/contributions/quote', null, token, { query: { send_asset, dest_asset, dest_amount }, }), + getContributionFinalization: (txHash, token) => + request('GET', `/contributions/finalization/${txHash}`, null, token), failExpiredCampaigns: (token) => request('POST', '/campaigns/cron/fail-expired', null, token), triggerCampaignRefunds: (campaignId, token) => request('POST', `/campaigns/${campaignId}/trigger-refunds`, null, token),