Payment Operations
Payment Operations provides comprehensive payment processing, subscription lifecycle management, and administrative payment retry capabilities within the DashClicks billing system.
API Endpoints Overview
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/admin/billing/subscription/:sub_id | Retry payment for past-due subscription |
DELETE | /v1/admin/billing/subscription/:sub_id | Cancel subscription |
PUT | /v1/admin/billing/subscription/:sub_id/resume | Resume canceled subscription |
POST | /v1/admin/billing/subscription/:sub_id/clear | Clear subscription pending tasks |
Service Methods & Business Logic
Payment Retry Operations
retrySub(subId) - Payment retry for past-due subscriptions with Stripe integration
- Retries payment processing for subscriptions in past-due status with comprehensive error handling
- Subscription Validation: Verifies subscription exists and is in valid state for retry:
- Confirms subscription exists in database with past-due status
- Validates subscription has associated Stripe subscription ID
- Ensures subscription is eligible for payment retry operation
- Stripe Payment Processing: Direct Stripe API integration for payment retry:
- Retrieves latest invoice from subscription data
- Attempts payment processing through Stripe Invoices API
- Validates payment success before updating subscription status
- Status Management: Updates subscription status based on payment outcome:
- Sets status to 'active' on successful payment processing
- Maintains 'past_due' status on payment failure with error details
- Provides detailed error messaging for failed payment attempts
- Data Aggregation: Returns complete subscription information:
- Aggregates partner and buyer account information
- Includes product details and pricing information
- Provides discount calculations and billing period data
- MongoDB Collections:
_store.subscriptionswith partner/buyer/product lookup - Returns: Complete subscription object with updated status and payment information
Subscription Cancellation Operations
cancelSub(subId, immediate) - Comprehensive subscription cancellation with Stripe synchronization
- Cancels subscriptions with flexible timing options and complete billing cleanup
- Cancellation Modes: Supports multiple cancellation approaches:
- Period-end cancellation (maintains service until billing period ends)
- Immediate cancellation (terminates service and billing immediately)
- Configurable cancellation timing based on administrative requirements
- Stripe Integration: Complete Stripe subscription lifecycle management:
- Updates Stripe subscription with period-end cancellation flag
- Processes immediate cancellation through Stripe API deletion
- Synchronizes cancellation status between internal and Stripe systems
- Task Management: Updates subscription administrative status:
- Sets
team_tasks_pendingflag for operational team notifications - Marks subscription for administrative cleanup and processing
- Provides workflow integration for post-cancellation operations
- Sets
- Invoice Management: Comprehensive invoice cleanup and verification:
- Voids open invoices to prevent future billing attempts
- Handles uncollectible invoices with appropriate status updates
- Processes multiple invoice statuses with pagination support
- Provides error handling for invoice operations that may fail
- Error Handling: Robust error management with user-friendly messaging:
- Translates Stripe error codes to user-understandable messages
- Handles incomplete_expired status translation to unpaid
- Provides detailed cancellation failure information
- MongoDB Collections:
_store.subscriptionswith complete relationship data - Returns: Updated subscription object with cancellation status and timing
Subscription Resume Operations
resumeCancelSub(subId) - Resume previously canceled subscriptions
- Resumes subscriptions that were scheduled for period-end cancellation
- Cancellation Reversal: Validates and reverses period-end cancellation:
- Confirms subscription exists with
cancel_at_period_endflag set - Validates subscription is eligible for cancellation reversal
- Ensures subscription is still within active billing period
- Confirms subscription exists with
- Stripe Synchronization: Updates Stripe subscription settings:
- Removes
cancel_at_period_endflag through Stripe API - Restores subscription to normal billing cycle operation
- Synchronizes subscription status between systems
- Removes
- Status Updates: Updates internal subscription status:
- Removes cancellation flags and pending task indicators
- Restores subscription to active operational status
- Updates subscription metadata for continued service
- Data Consistency: Ensures data consistency across systems:
- Validates Stripe and internal database synchronization
- Provides complete subscription information after resume
- Maintains audit trail of cancellation and resume operations
- MongoDB Collections:
_store.subscriptionswith relationship lookup - Returns: Complete subscription object with resumed status
Subscription Cleanup Operations
clearSub(subId) - Administrative subscription cleanup for canceled subscriptions
- Clears administrative flags and completes post-cancellation workflow
- Status Validation: Validates subscription is eligible for cleanup:
- Confirms subscription has 'canceled' status
- Verifies
team_tasks_pendingflag is set indicating pending work - Ensures subscription is ready for administrative cleanup
- Task Completion: Marks administrative tasks as completed:
- Removes
team_tasks_pendingflag to indicate work completion - Updates subscription status for operational team workflow
- Provides administrative closure for canceled subscriptions
- Removes
- Workflow Integration: Supports operational team workflow management:
- Enables tracking of post-cancellation task completion
- Provides clear status indicators for administrative oversight
- Supports subscription lifecycle management and reporting
- Data Integrity: Maintains complete subscription data integrity:
- Preserves subscription history and cancellation information
- Maintains relationship data for reporting and analysis
- Provides complete subscription information after cleanup
- MongoDB Collections:
_store.subscriptionswith aggregated data - Returns: Updated subscription object with cleared administrative flags
Technical Implementation Details
Payment Retry Implementation
const retrySubscriptionPayment = async subId => {
// Validate subscription exists and is past due
const subscription = await StoreSubscription.findOne({
_id: subId,
status: 'past_due',
});
if (!subscription) {
throw new Error('Subscription not found or not eligible for retry');
}
try {
// Attempt payment through Stripe
const invoice = await stripe.invoices.pay(subscription.latest_invoice);
if (invoice.paid) {
// Update subscription status on successful payment
subscription.status = 'active';
await subscription.save();
} else {
throw new Error('Payment processing failed - invoice not marked as paid');
}
} catch (stripeError) {
// Handle Stripe-specific errors
const errorMessage = `Payment failed: ${stripeError.message}`;
throw new Error(errorMessage);
}
// Return complete subscription data with updated status
const updatedSubscription = await StoreSubscription.aggregate(formatSubscriptionQuery(subId));
return updatedSubscription[0];
};
Subscription Cancellation Implementation
const cancelSubscriptionWithCleanup = async (subId, immediate = false) => {
const subscription = await StoreSubscription.findById(subId);
if (!subscription) {
throw new Error('Subscription not found');
}
try {
let updatedStripeSubscription;
if (!immediate) {
// Period-end cancellation
updatedStripeSubscription = await stripe.subscriptions.update(subscription.stripe_id, {
cancel_at_period_end: true,
});
} else {
// Immediate cancellation
updatedStripeSubscription = await stripe.subscriptions.del(subscription.stripe_id);
}
// Update internal subscription status
delete updatedStripeSubscription.id; // Remove Stripe ID to avoid conflicts
await StoreSubscription.updateOne(
{ _id: subId },
{
team_tasks_pending: true,
...updatedStripeSubscription,
},
);
// Cleanup existing invoices
await cleanupSubscriptionInvoices(subscription.stripe_id);
// Return updated subscription data
const result = await StoreSubscription.aggregate(formatSubscriptionQuery(subId));
return result[0];
} catch (error) {
const errorMessage = error.message.replace('incomplete_expired', 'unpaid');
throw new Error(`Cancellation failed: ${errorMessage}`);
}
};
const cleanupSubscriptionInvoices = async stripeSubscriptionId => {
const invoiceStatuses = ['open', 'uncollectible'];
for (const status of invoiceStatuses) {
try {
let invoiceList = { has_more: true };
while (invoiceList.has_more) {
invoiceList = await stripe.invoices.list({
subscription: stripeSubscriptionId,
status: status,
starting_after: invoiceList.data?.length
? invoiceList.data[invoiceList.data.length - 1].id
: undefined,
});
if (invoiceList.data.length) {
for (const invoice of invoiceList.data) {
await stripe.invoices.voidInvoice(invoice.id);
}
} else {
break;
}
}
} catch (error) {
logger.warn({
message: 'COULD NOT VOID ALL EXISTING INVOICES',
subscriptionId: stripeSubscriptionId,
status,
error: error.message,
});
}
}
};
Subscription Resume Implementation
const resumeCanceledSubscription = async subId => {
const subscription = await StoreSubscription.findOne({
_id: subId,
cancel_at_period_end: true,
});
if (!subscription) {
throw new Error('Cancelled subscription not found or not eligible for resume');
}
try {
// Remove cancellation flag from Stripe
const updatedStripeSubscription = await stripe.subscriptions.update(subscription.stripe_id, {
cancel_at_period_end: false,
});
// Update internal subscription status
delete updatedStripeSubscription.id;
await StoreSubscription.updateOne({ _id: subId }, { ...updatedStripeSubscription });
// Return complete subscription data
const result = await StoreSubscription.aggregate(formatSubscriptionQuery(subId));
return result[0];
} catch (error) {
throw new Error(`Failed to resume cancellation: ${error.message}`);
}
};
API Response Formats
Payment Retry Response
{
"success": true,
"data": {
"partner": {
"name": "DashClicks Agency",
"phone": "+1234567890",
"email": "agency@dashclicks.com",
"account": "partner_account_id"
},
"buyer": {
"name": "Client Business Inc",
"phone": "+1987654321",
"email": "client@business.com",
"account": "buyer_account_id"
},
"product": {
"name": "SEO Management",
"tier": "Professional",
"interval": "month",
"interval_count": 1,
"amount": 29999,
"amount_due": 25499,
"images": ["https://example.com/seo-product.jpg"]
},
"status": "active",
"team_tasks_pending": false,
"cancel_at_period_end": false,
"current_period_start": "2024-10-01T00:00:00.000Z",
"current_period_end": "2024-11-01T00:00:00.000Z",
"subscription_id": {
"stripe": "sub_stripe_id_123",
"internal": "subscription_internal_id"
},
"renew_date": "2024-11-01T00:00:00.000Z",
"created": "2024-10-01T00:00:00.000Z"
}
}
Subscription Cancellation Response
{
"success": true,
"data": {
"partner": {
"name": "DashClicks Agency",
"phone": "+1234567890",
"email": "agency@dashclicks.com",
"account": "partner_account_id"
},
"buyer": {
"name": "Client Business Inc",
"phone": "+1987654321",
"email": "client@business.com",
"account": "buyer_account_id"
},
"product": {
"name": "SEO Management",
"tier": "Professional",
"interval": "month",
"interval_count": 1,
"amount": 29999,
"amount_due": 25499
},
"status": "canceled",
"team_tasks_pending": true,
"cancel_at_period_end": false,
"current_period_start": "2024-10-01T00:00:00.000Z",
"current_period_end": "2024-11-01T00:00:00.000Z",
"cancel_at": "2024-10-15T14:30:00.000Z",
"ended_at": "2024-10-15T14:30:00.000Z",
"subscription_id": {
"stripe": "sub_stripe_id_123",
"internal": "subscription_internal_id"
}
}
}
Request Parameters
Payment Retry Parameters
sub_id(path parameter) - MongoDB ObjectId of subscription to retry payment
Subscription Cancellation Parameters
sub_id(path parameter) - MongoDB ObjectId of subscription to cancelimmediate(query parameter) - Boolean flag for immediate vs period-end cancellation
Resume Cancellation Parameters
sub_id(path parameter) - MongoDB ObjectId of subscription to resume
Clear Subscription Parameters
sub_id(path parameter) - MongoDB ObjectId of subscription to clear
Error Handling
Payment Operations Error Scenarios
- 404 Not Found: Subscription does not exist or not in valid state
- 400 Bad Request: Invalid subscription ID or operation not permitted
- 402 Payment Required: Payment processing failed due to payment method issues
- 500 Internal Server Error: Stripe API failures or database transaction errors
Payment Retry Error Response
{
"success": false,
"message": "Payment failed: Your card was declined. Your request was in test mode, but used a non test card. For a list of valid test cards, visit: https://stripe.com/docs/testing",
"errno": 402
}
Cancellation Error Response
{
"success": false,
"message": "Cancellation failed: No such subscription: sub_invalid_id",
"errno": 400
}
Resume Error Response
{
"success": false,
"message": "Cancelled subscription not found",
"errno": 404
}
Usage Examples
Retry Payment for Past-Due Subscription
POST /v1/admin/billing/subscription/507f1f77bcf86cd799439011
Authorization: Bearer {admin_token}
Cancel Subscription at Period End
DELETE /v1/admin/billing/subscription/507f1f77bcf86cd799439011
Authorization: Bearer {admin_token}
Cancel Subscription Immediately
DELETE /v1/admin/billing/subscription/507f1f77bcf86cd799439011?immediate=true
Authorization: Bearer {admin_token}
Resume Canceled Subscription
PUT /v1/admin/billing/subscription/507f1f77bcf86cd799439011/resume
Authorization: Bearer {admin_token}
Clear Subscription Tasks
POST /v1/admin/billing/subscription/507f1f77bcf86cd799439011/clear
Authorization: Bearer {admin_token}