Handling Failed First Payments
Overview
When a customer attempts to subscribe to a plan, the first payment is processed immediately. If this initial payment fails, the subscription is not created. Understanding how to handle these failures is crucial for providing a smooth customer experience and maximizing successful conversions.
How First Payment Works
The Subscription Creation Flow
- Customer submits subscription purchase
- First payment is processed immediately
- If payment succeeds: Subscription is created with
Activestatus - If payment fails: Subscription is not created, and an error is returned
Important: Unlike recurring payments, if the first payment fails, no subscription record is created. The customer must retry the entire subscription purchase process.
Common Failure Reasons
Payment Method Issues
| Error | Description | Customer Action |
|---|---|---|
| Insufficient Funds | Not enough money in account | Add funds or try different payment method |
| Card Declined | Issuing bank declined the transaction | Contact bank or use different card |
| Invalid Card Details | Incorrect card number, CVV, or expiration | Re-enter correct card information |
| Expired Card | Card has passed expiration date | Use a different, valid card |
| AVS Mismatch | Address doesn’t match card on file | Enter correct billing address |
| 3DS Authentication Failed | Customer failed 3D Secure challenge | Retry 3DS authentication |
Account Issues
| Error | Description | Customer Action |
|---|---|---|
| Bank Account Not Verified | ACH account needs verification | Complete account verification via Plaid |
| Account Closed | Bank account has been closed | Use different account |
| ACH Not Supported | Bank doesn’t support ACH | Use card payment instead |
Technical Issues
| Error | Description | Action |
|---|---|---|
| Network Timeout | Connection to payment processor failed | Retry the subscription purchase |
| Invalid Session Key | Authentication token expired | Generate new session key and retry |
| Plan Not Found | Subscription plan doesn’t exist | Verify plan code is correct |
Error Response Format
When first payment fails, you’ll receive an error response:
{
"error": {
"code": "PAYMENT_FAILED",
"message": "Payment declined: Insufficient funds",
"details": {
"paymentMethod": "card",
"declineCode": "insufficient_funds",
"canRetry": true
}
}
}
Handling Failures in Your Application
Using Prebuilt UI
The CoinflowPurchase component automatically handles errors:
<CoinflowPurchase
sessionKey={sessionKey}
merchantId={merchantId}
planCode={planCode}
env="sandbox"
onSuccess={(data) => {
// Subscription created successfully
console.log('Subscription ID:', data.subscriptionId);
redirectToSuccessPage();
}}
onError={(error) => {
// First payment failed - subscription not created
console.error('Subscription failed:', error);
// Display user-friendly error message
if (error.details?.declineCode === 'insufficient_funds') {
showError('Payment declined due to insufficient funds. Please try a different payment method.');
} else if (error.details?.declineCode === 'card_declined') {
showError('Your card was declined. Please contact your bank or try a different card.');
} else {
showError('Unable to process payment. Please try again or use a different payment method.');
}
}}
/>
Using API Integration
Handle errors in your custom implementation:
async function createSubscription(planCode, paymentDetails) {
try {
const response = await fetch(
'https://api-sandbox.coinflow.cash/api/subscription/{merchantId}/subscribers/card',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-coinflow-auth-session-key': sessionKey
},
body: JSON.stringify({
planCode,
card: paymentDetails,
chargebackProtectionData: [/* ... */]
})
}
);
if (!response.ok) {
const error = await response.json();
throw error;
}
const subscriptionId = await response.text();
return { success: true, subscriptionId };
} catch (error) {
return {
success: false,
error: error.error || error,
canRetry: error.error?.details?.canRetry !== false
};
}
}
// Usage
const result = await createSubscription('premium-plan', cardDetails);
if (!result.success) {
// Handle failure
handleSubscriptionError(result.error);
} else {
// Success - subscription created
showSuccessMessage(result.subscriptionId);
}
User Experience Best Practices
Provide Clear, Actionable Error Messages
Always show specific error messages that help customers understand what went wrong and how to fix it:
function getErrorMessage(errorCode) {
const messages = {
insufficient_funds: {
title: 'Insufficient Funds',
message: 'Your payment method doesn\'t have enough funds to complete this purchase.',
action: 'Please add funds to your account or try a different payment method.'
},
card_declined: {
title: 'Card Declined',
message: 'Your card issuer declined this transaction.',
action: 'Please contact your bank for more information or use a different card.'
},
expired_card: {
title: 'Card Expired',
message: 'The card you entered has expired.',
action: 'Please use a different card with a valid expiration date.'
},
invalid_cvv: {
title: 'Invalid Security Code',
message: 'The CVV/security code you entered is incorrect.',
action: 'Please check your card and re-enter the 3-digit code on the back.'
},
authentication_failed: {
title: 'Authentication Failed',
message: 'Card authentication was not completed successfully.',
action: 'Please try again and complete the verification with your bank.'
},
default: {
title: 'Payment Failed',
message: 'We were unable to process your payment.',
action: 'Please try again or contact support if the problem persists.'
}
};
return messages[errorCode] || messages.default;
}
Implement a Smooth Retry Experience
Make it easy for customers to try again after a failed payment:
function SubscriptionPurchaseFlow() {
const [attempt, setAttempt] = useState(0);
const [error, setError] = useState(null);
const handlePurchase = async (paymentMethod) => {
try {
const result = await createSubscription(planCode, paymentMethod);
if (result.success) {
// Success!
onSubscriptionCreated(result.subscriptionId);
} else {
// Failed
setError(result.error);
setAttempt(prev => prev + 1);
}
} catch (err) {
setError(err);
}
};
return (
<div>
{error && (
<ErrorAlert error={error} attempt={attempt}>
<button onClick={() => setError(null)}>Try Again</button>
<button onClick={switchPaymentMethod}>Use Different Payment Method</button>
</ErrorAlert>
)}
<PaymentForm onSubmit={handlePurchase} />
</div>
);
}
Offer Multiple Payment Options
Give customers flexibility to choose their preferred payment method:
function PaymentMethodSelector({ onSelect }) {
return (
<div>
<h3>Choose Payment Method</h3>
<button onClick={() => onSelect('card')}>
Credit/Debit Card
</button>
<button onClick={() => onSelect('ach')}>
Bank Account (ACH)
</button>
<button onClick={() => onSelect('saved')}>
Use Saved Payment Method
</button>
</div>
);
}
Monitoring and Analytics
Track failed first payments to identify patterns:
// Log failed subscription attempts
analytics.track('Subscription Purchase Failed', {
planCode: 'premium-plan',
paymentMethod: 'card',
errorCode: error.code,
declineReason: error.details?.declineCode,
attemptNumber: attempt,
userId: customerId,
timestamp: new Date().toISOString()
});
Key Metrics to Track
- Failure Rate: Percentage of first payments that fail
- Failure Reasons: Most common decline codes
- Retry Success Rate: How often customers succeed on retry
- Payment Method Performance: Success rate by payment type
- Conversion After Failure: Do customers eventually subscribe?
Webhooks for Failed Attempts
While subscriptions aren’t created for failed first payments, you can still log these attempts:
// Your webhook handler
app.post('/webhooks/coinflow', (req, res) => {
const event = req.body;
if (event.event === 'subscription.payment_failed') {
const { customerId, planCode, errorCode } = event.data;
// Log the failed attempt
await db.failedSubscriptions.create({
customerId,
planCode,
errorCode,
attemptedAt: new Date()
});
// Send follow-up email
if (errorCode === 'insufficient_funds') {
await sendEmail(customerId, {
subject: 'Complete Your Subscription',
body: 'We noticed you tried to subscribe but the payment didn\'t go through...'
});
}
}
res.sendStatus(200);
});
Recovery Strategies
Send Targeted Follow-up Emails
After a failed payment attempt, send personalized emails to help customers complete their subscription:
async function sendRecoveryEmail(customerId, failureReason) {
const templates = {
insufficient_funds: {
subject: 'Complete Your Premium Subscription',
body: `
Hi there,
We noticed you tried to subscribe to our Premium plan, but the payment couldn't be processed due to insufficient funds.
We'd love to have you as a subscriber! Here's what you can do:
1. Add funds to your account
2. Try a different payment method
3. Use a credit card instead of bank account
[Complete Your Subscription]
If you have questions, reply to this email!
`
},
card_declined: {
subject: 'Let\'s Get Your Subscription Started',
body: `
Hi there,
Your card issuer declined the subscription payment. This can happen for various reasons:
- Daily spending limit reached
- International transaction restrictions
- Suspicious activity flags
Try these solutions:
1. Contact your bank to authorize the charge
2. Use a different card
3. Try again in a few hours
[Try Again]
`
}
};
const template = templates[failureReason] || templates.card_declined;
await sendEmail(customerId, template);
}
Retargeting Campaigns
Set up retargeting for failed subscription attempts:
- Show ads reminding them of the subscription benefits
- Offer a limited-time discount
- Highlight social proof and testimonials
- Make it easy to retry with one click