import retry from 'async-retry';
import { checkoutByCartId, createCart, submitCart, sleep } from './helpers';
const TEN_MINUTES_IN_MS = 1_000 * 60 * 10;
async function createAndSubmitCartWithRetries(items, remainingRetries = 1) {
const cartId = await createCart(items);
const failedProductIds = [];
const { result: submitResult, status: submitStatus } = await retry(async () => {
const { result, status } = await submitCart(cartId);
if (result.extensions.retryable) {
throw new Error('Retry retryable error');
}
return { result, status };
}, { retries: 3 });
const didSubmitTimeout = submitStatus === 503 || submitStatus === 504;
if (!didSubmitTimeout) {
if (submitResult.errors.length) {
return { didSubmit: false, failedProductIds };
}
for (const store of submitResult.cart.stores) {
if (store.errors.length) {
return { didSubmit: false, failedProductIds };
}
}
}
const endPollingBy = Date.now() + TEN_MINUTES_IN_MS;
const lineItemsByOrderId = new Map();
const processedOrderIds = new Set();
const retryableOrderIds = new Set();
while (Date.now() < endPollingBy) {
const pollResult = await checkoutByCartId(cartId);
for (const order of pollResult.data.orders) {
lineItemsByOrderId.set(order.id, order.lineItems);
}
const didProcessAllOrders = processedOrderIds.size === pollResult.data.checkoutByCartID.orders.length;
if (didProcessAllOrders) {
break;
}
for (const order of pollResult.data.orders) {
if (processedOrderIds.has(order.id)) {
// Don't process the same order twice
continue;
}
const orderFailedEvent = order.events.find((event) => (
event.__typename === 'OrderFailedOrderEvent',
));
const orderSucceededEvent = order.events.find((event) => (
event.__typename === 'OrderSucceededOrderEvent',
));
if (!orderFailedEvent && !orderSucceededEvent) {
// Nothing to do yet
continue;
}
if (orderSucceededEvent) {
// Nothing to do -- all went OK
processedOrderIds.add(order.id);
} else if (orderFailedEvent) {
// Track items belonging to this order so we can either retry with a new
// cart or communicate with the user later.
processedOrderIds.add(order.id);
if (orderFailedEvent.retryable) {
retryableOrderIds.add(order.id);
} else {
// Track failed products we cannot retry
if (lineItem.__typename === 'AmazonLineItem') {
failedProductIds.push({
marketplace: 'AMAZON',
productId: lineItem.productId,
});
} else {
failedProductIds.push({
marketplace: 'SHOPIFY',
variantId: lineItem.variantId,
});
}
}
} else {
// No info about this order; chill.
}
}
// Sleep between polls to be a good citizen
await sleep(200);
}
for (const [orderId, lineItems] of lineItemsByOrderId.entries()) {
if (!processedOrderIds.has(orderId)) {
// This order got stuck. We'll add it to our list of retryable order IDs.
retryableOrderIds.add(orderId);
}
}
// Do we have retryable order IDs and remaining retries?
if (retryableOrderIds.size && remainingRetries > 0) {
const retryableCartItems = {
amazonCartItemsInput: [],
shopifyCartItemsInput: [],
};
// Assemble cart items for new cart
for (const [orderId, lineItems] of lineItemsByOrderId.entries()) {
if (!retryableOrderIds.has(orderId)) continue;
for (const lineItem of lineItems) {
if (lineItem.__typename === 'AmazonLineItem') {
retryableCartItems.amazonCartItemsInput.push({
productId: lineItem.productId,
quantity: lineItem.quantity,
});
} else {
retryableCartItems.shopifyCartItemsInput.push({
quantity: lineItem.quantity,
variantId: lineItem.variantId,
});
}
}
}
// Retry products w/ a new cart
const { failedProductIds: failedRetriedProductIds } = await createAndSubmitCartWithRetries(
retryableCartItems,
remainingRetries - 1,
);
failedProductIds.push(...failedRetriedProductIds);
}
return { didSubmit: true, failedProductIds };
}
const result = await createAndSubmitCartWithRetries({
shopifyCartItemsInput: [{
quantity: 1,
variantId: '44346795295022',
}],
});
if (!result.didSubmit) {
// TODO: Inform user checkout did not even start (e.g. payment error).
} else if (result.failedProductIds.length) {
// TODO: Inform user about failed checkout for some products
} else {
// TODO: Inform user about checkout success
}