Order Fulfillment
Automate provisioning and delivery of telecommunications services after order completion using Connect API workflows
Automate the complete order fulfillment process from order submission to service activation. This use case covers the workflows needed to provision telecommunications services, handle payments, and activate subscriptions for customers.
Prerequisites
Before you begin, ensure you have:
- Order Management: Understanding of order creation and submission flows
- Payment Processing: Integration with payment collection systems
- Service Provisioning: Access to subscription and license management endpoints
- Inventory Management: Phone number inventory for mobile services
Overview
Order fulfillment encompasses the complete process after order creation:
- Submit orders for processing
- Handle payment collection and validation
- Provision services and create subscriptions
- Activate services and manage inventory
- Monitor fulfillment status and handle exceptions
Step-by-Step Implementation
Step 1: Submit Order for Fulfillment
Transform draft orders into submitted orders ready for processing:
# Submit order for fulfillment
curl -X POST "https://apiv2.example.com/api/v2/orders/{orderId}/submit" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json"
# Monitor order status
curl -X GET "https://apiv2.example.com/api/v2/orders/{orderId}" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json"// Submit an order for fulfillment
const submitOrder = async (orderId) => {
const submitResponse = await fetch(`https://apiv2.example.com/api/v2/orders/${orderId}/submit`, {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
});
if (!submitResponse.ok) {
const error = await submitResponse.json();
throw new Error(`Order submission failed: ${error.message}`);
}
const submittedOrder = await submitResponse.json();
console.log('Order submitted:', submittedOrder.orderId);
return submittedOrder;
};
// Monitor order status after submission
const monitorOrderStatus = async (orderId) => {
const orderResponse = await fetch(`https://apiv2.example.com/api/v2/orders/${orderId}`, {
method: 'GET',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
});
const order = await orderResponse.json();
switch (order.state) {
case 'SUBMITTED':
console.log('Order submitted - awaiting processing');
break;
case 'PROCESSING':
console.log('Order processing - services being provisioned');
break;
case 'COMPLETED':
console.log('Order fulfilled - services active');
break;
case 'FAILED':
console.error('Order fulfillment failed:', order.failureReason);
break;
}
return order;
};Step 2: Handle Payment Collection
Create payment sessions to collect payment for orders:
# Create payment session
curl -X POST "https://apiv2.example.com/api/v2/payment-sessions" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderId": "296af5b6-f3b3-4128-b307-5ddc9190502f",
"paymentProvider": "STRIPE",
"hosted": true,
"returnUrl": "https://yourstore.com/success",
"cancelUrl": "https://yourstore.com/cancel"
}'
# Check payment status
curl -X GET "https://apiv2.example.com/api/v2/payment-sessions/{paymentSessionId}" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json"// Create payment session for order
const createPaymentSession = async (orderId) => {
const paymentSessionData = {
orderId: orderId,
paymentProvider: 'STRIPE',
hosted: true,
returnUrl: 'https://yourstore.com/success',
cancelUrl: 'https://yourstore.com/cancel',
metadata: {
orderReference: orderId,
channel: 'web',
},
};
const sessionResponse = await fetch('https://apiv2.example.com/api/v2/payment-sessions', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify(paymentSessionData),
});
const paymentSession = await sessionResponse.json();
// Redirect customer to the hosted checkout page
console.log('Payment URL:', paymentSession.hostedUrl);
return paymentSession;
};
// Check payment session status
const checkPaymentStatus = async (paymentSessionId) => {
const statusResponse = await fetch(
`https://apiv2.example.com/api/v2/payment-sessions/${paymentSessionId}`,
{
method: 'GET',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
},
);
const session = await statusResponse.json();
switch (session.status) {
case 'PENDING':
case 'REQUIRES_ACTION':
console.log('Payment pending - waiting for customer');
break;
case 'COMPLETED':
console.log('Payment completed successfully');
break;
case 'FAILED':
console.error('Payment failed');
break;
case 'CANCELED':
console.log('Payment canceled by customer');
break;
case 'EXPIRED':
console.log('Payment session expired');
break;
}
return session;
};Step 3: Reserve Phone Numbers (For Mobile Services)
Reserve phone numbers from inventory before service activation:
# Lease phone numbers
curl -X POST "https://apiv2.example.com/api/v2/inventory/lease-numbers" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"quantity": 1,
"region": "SE",
"numberType": "mobile",
"preferences": {
"areaCode": "08"
}
}'// Lease phone numbers for mobile services
const leasePhoneNumbers = async (requirements) => {
const leaseData = {
quantity: requirements.quantity,
region: requirements.region,
numberType: requirements.numberType || 'mobile',
preferences: {
areaCode: requirements.areaCode,
pattern: requirements.pattern,
},
};
const leaseResponse = await fetch('https://apiv2.example.com/api/v2/inventory/lease-numbers', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify(leaseData),
});
const lease = await leaseResponse.json();
console.log('Numbers leased:', lease.numbers.length);
lease.numbers.forEach((number) => {
console.log('Reserved number:', number.number);
});
return lease;
};
// Example usage for mobile subscription
const prepareMobileService = async (customer) => {
const requirements = {
quantity: 1,
region: customer.region,
areaCode: customer.preferredAreaCode,
};
const numberLease = await leasePhoneNumbers(requirements);
return numberLease.numbers[0]; // Use first available number
};Step 4: Activate Subscriptions
Activate subscription services after successful payment:
# List subscriptions for order
curl -X GET "https://apiv2.example.com/api/v2/subscriptions?orderId={orderId}" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json"
# Activate subscription
curl -X POST "https://apiv2.example.com/api/v2/subscriptions/{subscriptionId}/activate" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"activationDate": "2024-01-15T10:00:00Z"
}'// List subscriptions for an order
const getOrderSubscriptions = async (orderId) => {
const subscriptionsResponse = await fetch(
`https://apiv2.example.com/api/v2/subscriptions?orderId=${orderId}`,
{
method: 'GET',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
},
);
const subscriptions = await subscriptionsResponse.json();
return subscriptions.items;
};
// Activate a subscription
const activateSubscription = async (subscriptionId) => {
const activationData = {
activationDate: new Date().toISOString(),
notes: 'Activated via fulfillment process',
};
const activationResponse = await fetch(
`https://apiv2.example.com/api/v2/subscriptions/${subscriptionId}/activate`,
{
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify(activationData),
},
);
if (!activationResponse.ok) {
const error = await activationResponse.json();
throw new Error(`Activation failed: ${error.message}`);
}
const activation = await activationResponse.json();
console.log('Subscription activated:', subscriptionId);
return activation;
};
// Complete fulfillment workflow
const completeOrderFulfillment = async (orderId) => {
// Get all subscriptions for the order
const subscriptions = await getOrderSubscriptions(orderId);
// Activate each subscription
const activationPromises = subscriptions.map((sub) => {
if (sub.status === 'pending_activation') {
return activateSubscription(sub.subscriptionId);
}
return Promise.resolve(sub);
});
const activatedSubscriptions = await Promise.all(activationPromises);
console.log('Order fulfillment completed:', orderId);
console.log('Active subscriptions:', activatedSubscriptions.length);
return activatedSubscriptions;
};Error Handling and Retry Logic
Handle common fulfillment scenarios with proper error handling:
// Comprehensive error handling for fulfillment
const handleFulfillmentError = async (error, orderId, step) => {
console.error(`Fulfillment error at step ${step}:`, error.message);
const errorHandling = {
payment_failed: {
action: 'retry_payment',
message: 'Payment collection failed - customer needs to retry payment',
},
inventory_unavailable: {
action: 'wait_inventory',
message: 'Phone numbers unavailable - waiting for inventory replenishment',
},
activation_failed: {
action: 'manual_review',
message: 'Service activation failed - requires manual intervention',
},
network_error: {
action: 'retry_with_backoff',
message: 'Network connectivity issue - will retry automatically',
},
};
const handling = errorHandling[error.code] || {
action: 'escalate',
message: 'Unknown error - escalating to support',
};
// Log error for monitoring
await logFulfillmentError(orderId, step, error, handling);
// Take appropriate action
switch (handling.action) {
case 'retry_payment':
return await createPaymentSession(orderId);
case 'wait_inventory':
return await retryWithBackoff(() => processStep(orderId, step));
case 'retry_with_backoff':
return await retryWithBackoff(() => processStep(orderId, step));
case 'manual_review':
return await escalateToSupport(orderId, error);
default:
throw error;
}
};
// Retry with exponential backoff
const retryWithBackoff = async (operation, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
console.log(`Retry attempt ${attempt} failed, waiting ${delay}ms`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
};
Webhook Integration
Set up webhooks to handle asynchronous fulfillment events:
// Webhook handler for fulfillment events
const handleFulfillmentWebhook = (event) => {
switch (event.type) {
case 'order.submitted':
console.log('Order submitted:', event.data.orderId);
// Start fulfillment process
return initiateFulfillment(event.data.orderId);
case 'payment.completed':
console.log('Payment completed:', event.data.paymentId);
// Proceed with service activation
return processServiceActivation(event.data.orderId);
case 'subscription.activated':
console.log('Subscription activated:', event.data.subscriptionId);
// Send welcome notifications
return sendActivationNotification(event.data);
case 'fulfillment.completed':
console.log('Fulfillment completed:', event.data.orderId);
// Final cleanup and customer notification
return completeFulfillmentNotification(event.data);
case 'fulfillment.failed':
console.error('Fulfillment failed:', event.data);
// Handle fulfillment failure
return handleFulfillmentFailure(event.data);
default:
console.log('Unknown webhook event:', event.type);
}
};
// Express webhook endpoint example
app.post('/webhooks/connect', express.raw({ type: 'application/json' }), (req, res) => {
const event = JSON.parse(req.body);
try {
handleFulfillmentWebhook(event);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook handling failed:', error);
res.status(500).send('Internal Server Error');
}
});
Complete Fulfillment Workflow
Put it all together in a comprehensive fulfillment orchestrator:
// Complete fulfillment orchestrator
class OrderFulfillmentOrchestrator {
async processOrder(orderId) {
try {
console.log(`Starting fulfillment for order: ${orderId}`);
// Step 1: Submit order
const submittedOrder = await this.submitOrder(orderId);
// Step 2: Handle payment
const paymentSession = await this.createPaymentSession(orderId);
await this.waitForPaymentCompletion(paymentSession.paymentSessionId);
// Step 3: Reserve resources
const resources = await this.reserveResources(submittedOrder);
// Step 4: Activate services
const subscriptions = await this.activateServices(orderId);
// Step 5: Complete fulfillment
await this.completeFulfillment(orderId, subscriptions);
console.log(`Fulfillment completed successfully for order: ${orderId}`);
return { success: true, subscriptions };
} catch (error) {
console.error(`Fulfillment failed for order ${orderId}:`, error);
await this.handleFulfillmentError(error, orderId);
throw error;
}
}
async waitForPaymentCompletion(paymentSessionId, timeout = 300000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const session = await this.checkPaymentStatus(paymentSessionId);
if (session.status === 'completed') {
return session;
} else if (session.status === 'failed' || session.status === 'cancelled') {
throw new Error(`Payment ${session.status}: ${session.failureReason}`);
}
// Poll every 5 seconds
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error('Payment completion timeout');
}
async reserveResources(order) {
const resources = {};
// Reserve phone numbers for mobile services
for (const lineItem of order.lineItems) {
if (lineItem.productType === 'mobile') {
const phoneNumber = await this.leasePhoneNumbers({
quantity: 1,
region: order.customer.region,
});
resources[lineItem.lineItemId] = phoneNumber;
}
}
return resources;
}
}
// Usage example
const fulfillmentOrchestrator = new OrderFulfillmentOrchestrator();
// Process order fulfillment
fulfillmentOrchestrator
.processOrder('296af5b6-f3b3-4128-b307-5ddc9190502fe4567-e89b-12d3-a456-426614174000')
.then((result) => console.log('Fulfillment successful:', result))
.catch((error) => console.error('Fulfillment failed:', error));
Best Practices
Fulfillment Monitoring
- Implement comprehensive logging for all fulfillment steps
- Set up alerts for failed fulfillments or unusual delays
- Track fulfillment metrics (completion time, success rate, failure reasons)
- Use correlation IDs to trace orders through the entire process
Resource Management
- Reserve inventory (phone numbers) early in the process
- Implement inventory validation before order submission
- Handle inventory exhaustion gracefully with customer communication
- Clean up reserved resources if fulfillment fails
Payment Handling
- Validate payment completion before service activation
- Implement payment retry mechanisms for failed transactions
- Handle partial payments appropriately
- Secure payment session data and comply with PCI standards
Next Steps
After implementing order fulfillment:
Customer self-service
Enable customers to manage their active subscriptions and services
Payment processing
Set up recurring billing and ongoing payment management
Common Questions
Q: How long does typical fulfillment take? A: Fulfillment times vary by service type. Mobile services typically activate within 15-30 minutes, while specialized services may take longer.
Q: What happens if payment fails during fulfillment? A: The order remains in a pending state. Payment sessions can be retried, or new payment methods can be collected.
Q: Can I customize the fulfillment workflow? A: Yes, you can implement custom fulfillment logic using webhooks and the various API endpoints to match your business requirements.