Powered by Coinflow
Payments · Documentation
Operational

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

  1. Customer submits subscription purchase
  2. First payment is processed immediately
  3. If payment succeeds: Subscription is created with Active status
  4. 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