amazon-seller-mcp
Version:
Model Context Protocol (MCP) client for Amazon Selling Partner API
319 lines • 11.8 kB
JavaScript
/**
* Order notifications handler
*
* This file implements the order status change notification handler
*/
import { getLogger, info, error, warn } from '../utils/logger.js';
/**
* Order status monitor class
*/
export class OrderStatusMonitor {
ordersClient;
notificationManager;
config;
monitoringInterval = null;
orderStatusCache = new Map();
isMonitoring = false;
constructor(ordersClient, notificationManager, config = {}) {
this.ordersClient = ordersClient;
this.notificationManager = notificationManager;
this.config = {
enablePeriodicMonitoring: config.enablePeriodicMonitoring ?? false,
monitoringInterval: config.monitoringInterval ?? 5 * 60 * 1000, // 5 minutes
maxOrdersPerCheck: config.maxOrdersPerCheck ?? 100,
monitoringWindowHours: config.monitoringWindowHours ?? 24,
};
}
/**
* Starts monitoring order status changes
*/
startMonitoring() {
if (this.isMonitoring) {
getLogger().info('Order status monitoring is already running');
return;
}
if (!this.config.enablePeriodicMonitoring) {
getLogger().info('Periodic order monitoring is disabled');
return;
}
getLogger().info(`Starting order status monitoring (interval: ${this.config.monitoringInterval}ms)`);
this.isMonitoring = true;
// Initial check
this.checkOrderStatusChanges().catch((error) => {
getLogger().error('Error in initial order status check:', {
error: error.message,
});
});
// Set up periodic monitoring
this.monitoringInterval = setInterval(() => {
this.checkOrderStatusChanges().catch((error) => {
getLogger().error('Error in periodic order status check:', {
error: error.message,
});
});
}, this.config.monitoringInterval);
}
/**
* Stops monitoring order status changes
*/
stopMonitoring() {
if (!this.isMonitoring) {
getLogger().info('Order status monitoring is not running');
return;
}
getLogger().info('Stopping order status monitoring');
this.isMonitoring = false;
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = null;
}
}
/**
* Checks for order status changes
*/
async checkOrderStatusChanges() {
try {
// Calculate the time window for monitoring
const now = new Date();
const windowStart = new Date(now.getTime() - this.config.monitoringWindowHours * 60 * 60 * 1000);
// Get recent orders
const ordersResult = await this.ordersClient.getOrders({
lastUpdatedAfter: windowStart.toISOString(),
maxResultsPerPage: this.config.maxOrdersPerCheck,
orderStatuses: [
'PENDING',
'UNSHIPPED',
'PARTIALLY_SHIPPED',
'SHIPPED',
'CANCELED',
'UNFULFILLABLE',
],
});
// Check each order for status changes
for (const order of ordersResult.orders) {
await this.checkSingleOrderStatusChange(order);
}
info(`Checked ${ordersResult.orders.length} orders for status changes`);
}
catch (err) {
error('Error checking order status changes:', { error: err });
}
}
/**
* Checks a single order for status changes
*/
async checkSingleOrderStatusChange(order) {
const orderId = order.amazonOrderId;
const currentStatus = order.orderStatus;
const previousStatus = this.orderStatusCache.get(orderId);
// Update cache with current status
this.orderStatusCache.set(orderId, currentStatus);
// If we have a previous status and it's different, send notification
if (previousStatus && previousStatus !== currentStatus) {
info(`Order ${orderId} status changed from ${previousStatus} to ${currentStatus}`);
this.notificationManager.sendOrderStatusChangeNotification({
orderId,
previousStatus,
newStatus: currentStatus,
marketplaceId: order.marketplaceId,
orderDetails: {
purchaseDate: order.purchaseDate,
orderTotal: order.orderTotal,
fulfillmentChannel: order.fulfillmentChannel,
numberOfItems: (order.numberOfItemsShipped || 0) + (order.numberOfItemsUnshipped || 0),
},
});
}
}
/**
* Manually checks a specific order for status changes
*/
async checkOrderStatus(orderId) {
try {
const order = await this.ordersClient.getOrder({ amazonOrderId: orderId });
await this.checkSingleOrderStatusChange(order);
}
catch (err) {
error(`Error checking status for order ${orderId}:`, { error: err });
throw err;
}
}
/**
* Gets the current monitoring status
*/
isMonitoringActive() {
return this.isMonitoring;
}
/**
* Gets the monitoring configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Updates the monitoring configuration
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
// Restart monitoring if it's currently active and interval changed
if (this.isMonitoring && newConfig.monitoringInterval) {
this.stopMonitoring();
this.startMonitoring();
}
}
/**
* Clears the order status cache
*/
clearCache() {
this.orderStatusCache.clear();
info('Order status cache cleared');
}
/**
* Gets the current cache size
*/
getCacheSize() {
return this.orderStatusCache.size;
}
}
/**
* Enhanced order status change notification handler
*/
export class OrderStatusChangeHandler {
ordersClient;
notificationManager;
statusMonitor;
constructor(ordersClient, notificationManager, monitoringConfig = {}) {
this.ordersClient = ordersClient;
this.notificationManager = notificationManager;
this.statusMonitor = new OrderStatusMonitor(ordersClient, notificationManager, monitoringConfig);
}
/**
* Sets up order status change notifications
*/
setup() {
// Override updateOrderStatus method to add notification support
this.setupUpdateOrderStatusNotifications();
// Start monitoring if enabled
this.statusMonitor.startMonitoring();
info('Order status change notifications set up');
}
/**
* Sets up notifications for updateOrderStatus method calls
*/
setupUpdateOrderStatusNotifications() {
// Store original updateOrderStatus method
const originalUpdateOrderStatus = this.ordersClient.updateOrderStatus.bind(this.ordersClient);
// Override updateOrderStatus method to add notification support
this.ordersClient.updateOrderStatus = async (params, emitNotification = true) => {
const { amazonOrderId, action } = params;
// Get current order status if notification is enabled
let previousStatus = '';
let orderDetails;
let marketplaceId = '';
if (emitNotification) {
try {
const currentOrder = await this.ordersClient.getOrder({ amazonOrderId });
previousStatus = currentOrder.orderStatus;
marketplaceId = currentOrder.marketplaceId;
orderDetails = {
purchaseDate: currentOrder.purchaseDate,
orderTotal: currentOrder.orderTotal,
fulfillmentChannel: currentOrder.fulfillmentChannel,
numberOfItems: (currentOrder.numberOfItemsShipped || 0) + (currentOrder.numberOfItemsUnshipped || 0),
};
}
catch (err) {
// If we can't get the current order, just continue with the update
warn(`Could not get current order for ID ${amazonOrderId}: ${err.message}`);
}
}
// Call original method
const result = await originalUpdateOrderStatus(params);
// Send notification if successful and notification is enabled
if (emitNotification && result.success) {
// Determine new status based on action
const newStatus = this.mapActionToStatus(action, previousStatus);
// Only send notification if we have both previous and new status and they're different
if (previousStatus && newStatus && previousStatus !== newStatus) {
this.notificationManager.sendOrderStatusChangeNotification({
orderId: amazonOrderId,
previousStatus,
newStatus,
marketplaceId: marketplaceId,
orderDetails,
});
// Update the monitor's cache
this.statusMonitor['orderStatusCache'].set(amazonOrderId, newStatus);
}
}
return result;
};
}
/**
* Maps an action to the expected new status
*/
mapActionToStatus(action, currentStatus) {
switch (action) {
case 'CONFIRM':
// Confirmation typically moves from PENDING to UNSHIPPED
return currentStatus === 'PENDING' ? 'UNSHIPPED' : currentStatus;
case 'SHIP':
// Shipping moves to SHIPPED or PARTIALLY_SHIPPED
return currentStatus === 'UNSHIPPED'
? 'SHIPPED'
: currentStatus === 'PARTIALLY_SHIPPED'
? 'SHIPPED'
: 'SHIPPED';
case 'CANCEL':
// Cancellation moves to CANCELED
return 'CANCELED';
default:
warn(`Unknown action: ${action}`);
return currentStatus;
}
}
/**
* Gets the status monitor
*/
getStatusMonitor() {
return this.statusMonitor;
}
/**
* Manually checks an order for status changes
*/
async checkOrderStatus(orderId) {
return this.statusMonitor.checkOrderStatus(orderId);
}
/**
* Starts monitoring
*/
startMonitoring() {
this.statusMonitor.startMonitoring();
}
/**
* Stops monitoring
*/
stopMonitoring() {
this.statusMonitor.stopMonitoring();
}
/**
* Cleanup method
*/
cleanup() {
this.statusMonitor.stopMonitoring();
this.statusMonitor.clearCache();
}
}
/**
* Sets up order status change notifications (backward compatibility)
*
* @param ordersClient Orders client
* @param notificationManager Notification manager
* @param monitoringConfig Optional monitoring configuration
*/
export function setupOrderStatusChangeNotifications(ordersClient, notificationManager, monitoringConfig = {}) {
const handler = new OrderStatusChangeHandler(ordersClient, notificationManager, monitoringConfig);
handler.setup();
return handler;
}
//# sourceMappingURL=order-notifications.js.map