This repository has been archived by the owner on May 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 338
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The synchronous version of /api/order/create added regarding vuestore…
…front/vue-storefront#2008 Orders are by default passed directly to Magento when sent from frontend
- Loading branch information
Showing
7 changed files
with
356 additions
and
296 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
class AbstractOrderProxy { | ||
create (orderData) { | ||
} | ||
} | ||
|
||
module.exports = AbstractOrderProxy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
|
||
|
||
const Magento2Client = require('magento2-rest-client').Magento2Client; | ||
|
||
const config = require('config') | ||
const Redis = require('redis'); | ||
let redisClient = Redis.createClient(config.redis); // redis client | ||
redisClient.on('error', function (err) { // workaround for https://github.com/NodeRedis/node_redis/issues/713 | ||
redisClient = Redis.createClient(config.redis); // redis client | ||
}); | ||
const countryMapper = require('../../lib/countrymapper') | ||
const Ajv = require('ajv'); // json validator | ||
const ajv = new Ajv(); // validator | ||
const merge = require('lodash/merge') | ||
const orderSchema = require('../../models/order.schema.json') | ||
const orderSchemaExtension = require('../../models/order.schema.extension.json') | ||
const validate = ajv.compile(merge(orderSchema, orderSchemaExtension)); | ||
|
||
function isNumeric(val) { | ||
return Number(parseFloat(val)).toString() === val; | ||
} | ||
|
||
/** | ||
* Send single order to Magento Instance | ||
* | ||
* The Magento2 API: https://magento.stackexchange.com/questions/136028/magento-2-create-order-using-rest-api | ||
* | ||
* @param {json} orderData order data in format as described in '../models/order.md' | ||
* @param {Object} config global CLI configuration | ||
* @param {Function} done callback - @example done(new Error()) - to acknowledge problems | ||
*/ | ||
function processSingleOrder(orderData, config, job, done, logger = console) { | ||
|
||
const TOTAL_STEPS = 4; | ||
const THREAD_ID = 'ORD:' + (job ? job.id : 1) + ' - '; // job id | ||
let currentStep = 1; | ||
|
||
if (!validate(orderData)) { // schema validation of upcoming order | ||
logger.error(THREAD_ID + " Order validation error!", validate.errors); | ||
done(new Error('Error while validating order object', validate.errors)); | ||
|
||
if(job) job.progress(currentStep++, TOTAL_STEPS); | ||
return; | ||
} | ||
let isThisAuthOrder = parseInt(orderData.user_id) > 0 | ||
const userId = orderData.user_id | ||
|
||
let apiConfig = config.magento2.api | ||
if (orderData.store_code) { | ||
if (config.availableStores.indexOf(orderData.store_code) >= 0) { | ||
apiConfig = Object.assign({}, apiConfig, { url: apiConfig.url + '/' + orderData.store_code }) | ||
console.log('> Store code', orderData.store_code) | ||
} else { | ||
logger.error('Invalid store code', orderData.store_code) | ||
} | ||
} | ||
const api = Magento2Client(apiConfig); | ||
|
||
logger.info('> Order Id', orderData.order_id) | ||
logger.info('> Is order authorized?', isThisAuthOrder) | ||
logger.info('> User Id', userId) | ||
|
||
let cartId = orderData.cart_id | ||
const cartIdPrepare = isThisAuthOrder ? api.cart.create(null, userId): ( cartId ? new Promise((resolve, reject) => { | ||
resolve (cartId) | ||
}): api.cart.create(null)) | ||
|
||
logger.info(THREAD_ID + '> Cart Id', cartId) | ||
|
||
const processCart = (result) => { | ||
cartId = result | ||
|
||
logger.info(THREAD_ID + '< Cart Id', cartId) | ||
|
||
// load current cart from the Magento to synchronize elements | ||
api.cart.pull(null, cartId, null, isThisAuthOrder).then((serverItems) => { | ||
|
||
const clientItems = orderData.products | ||
const syncPromises = [] | ||
|
||
logger.info(THREAD_ID + '> Sync between clientItems', clientItems.map((item) => { return { sku: item.sku, qty: item.qty, server_item_id: item.server_item_id, product_option: item.product_option }})) | ||
logger.info(THREAD_ID + '> ... and serverItems', serverItems) | ||
|
||
for (const clientItem of clientItems) { | ||
const serverItem = serverItems.find((itm) => { | ||
return itm.sku === clientItem.sku || itm.sku.indexOf(clientItem.sku + '-') >= 0 /* bundle products */ | ||
}) | ||
if (!serverItem) { | ||
logger.info(THREAD_ID + '< No server item for ' + clientItem.sku) | ||
syncPromises.push(api.cart.update(null, cartId, { // use magento API | ||
sku: clientItem.parentSku && config.cart.setConfigurableProductOptions ? clientItem.parentSku : clientItem.sku, | ||
qty: clientItem.qty, | ||
product_option: clientItem.product_option, | ||
quote_id: cartId | ||
}, isThisAuthOrder)) | ||
} else if (serverItem.qty !== clientItem.qty) { | ||
logger.info(THREAD_ID + '< Wrong qty for ' + clientItem.sku, clientItem.qty, serverItem.qty) | ||
syncPromises.push(api.cart.update(null, cartId, { // use magento API | ||
sku: clientItem.parentSku && config.cart.setConfigurableProductOptions ? clientItem.parentSku : clientItem.sku, | ||
qty: clientItem.qty, | ||
product_option: clientItem.product_option, | ||
item_id: serverItem.item_id, | ||
quote_id: cartId | ||
}, isThisAuthOrder)) | ||
} else { | ||
logger.info(THREAD_ID + '< Server and client items synced for ' + clientItem.sku) // here we need just update local item_id | ||
} | ||
} | ||
|
||
for (const serverItem of serverItems) { | ||
if (serverItem) { | ||
const clientItem = clientItems.find((itm) => { | ||
return itm.sku === serverItem.sku || serverItem.sku.indexOf(itm.sku + '-') >= 0 /* bundle products */ | ||
}) | ||
if (!clientItem) { | ||
logger.info(THREAD_ID + '< No client item for ' + serverItem.sku + ', removing from server cart') // use magento API | ||
syncPromises.push(api.cart.delete(null, cartId, { // delete server side item if not present if client's cart | ||
sku: serverItem.sku, | ||
item_id: serverItem.item_id | ||
}, isThisAuthOrder)) | ||
} | ||
} | ||
} | ||
|
||
Promise.all(syncPromises).then((results) => { | ||
if(job) job.progress(currentStep++, TOTAL_STEPS); | ||
logger.info(THREAD_ID + '< Server cart in sync') | ||
logger.debug(THREAD_ID + results) | ||
|
||
const billingAddr = orderData.addressInformation.billingAddress; | ||
const shippingAddr = orderData.addressInformation.shippingAddress; | ||
let mappedShippingRegion = 0 | ||
let mappedBillingRegion = 0 | ||
|
||
api.directory.countries().then((countryList) => { | ||
if (shippingAddr.region_id > 0) { | ||
mappedShippingRegion = { regionId: shippingAddr.region_id, regionCode: shippingAddr.region_code } | ||
} else { | ||
mappedShippingRegion = countryMapper.mapCountryRegion(countryList, shippingAddr.country_id, shippingAddr.region_code ? shippingAddr.region_code : shippingAddr.region) | ||
} | ||
|
||
if (billingAddr.region_id > 0) { | ||
mappedBillingRegion = { regionId: billingAddr.region_id, regionCode: billingAddr.region_code } | ||
} else { | ||
mappedBillingRegion = countryMapper.mapCountryRegion(countryList, billingAddr.country_id, billingAddr.region_code ? billingAddr.region_code : billingAddr.region) | ||
} | ||
|
||
const billingAddressInfo = { // sum up totals | ||
"address": { | ||
"countryId": billingAddr.country_id, | ||
"street": billingAddr.street, | ||
"telephone": billingAddr.telephone, | ||
"postcode": billingAddr.postcode, | ||
"city": billingAddr.city, | ||
"firstname": billingAddr.firstname, | ||
"lastname": billingAddr.lastname, | ||
"email": billingAddr.email, | ||
"regionCode": mappedBillingRegion.regionCode, | ||
"regionId": mappedBillingRegion.regionId, | ||
"company": billingAddr.company, | ||
"vatId": billingAddr.vat_id | ||
} | ||
} | ||
|
||
const shippingAddressInfo = { // sum up totals | ||
"addressInformation": { | ||
"shippingAddress": { | ||
"countryId": shippingAddr.country_id, | ||
"street": shippingAddr.street, | ||
"telephone": shippingAddr.telephone, | ||
"postcode": shippingAddr.postcode, | ||
"city": shippingAddr.city, | ||
"firstname": shippingAddr.firstname, | ||
"lastname": shippingAddr.lastname, | ||
"email": shippingAddr.email, | ||
"regionId": mappedShippingRegion.regionId, | ||
"regionCode": mappedShippingRegion.regionCode, | ||
"company": shippingAddr.company | ||
}, | ||
|
||
"billingAddress": { | ||
"countryId": billingAddr.country_id, | ||
"street": billingAddr.street, | ||
"telephone": billingAddr.telephone, | ||
"postcode": billingAddr.postcode, | ||
"city": billingAddr.city, | ||
"firstname": billingAddr.firstname, | ||
"lastname": billingAddr.lastname, | ||
"email": billingAddr.email, | ||
"regionId": mappedBillingRegion.regionId, | ||
"regionCode": mappedBillingRegion.regionCode, | ||
"company": billingAddr.company, | ||
"vatId": billingAddr.vat_id | ||
}, | ||
"shippingMethodCode": orderData.addressInformation.shipping_method_code, | ||
"shippingCarrierCode": orderData.addressInformation.shipping_carrier_code, | ||
"extensionAttributes": orderData.addressInformation.shippingExtraFields | ||
} | ||
} | ||
|
||
logger.info(THREAD_ID + '< Billing info', billingAddressInfo) | ||
api.cart.billingAddress(null, cartId, billingAddressInfo, isThisAuthOrder).then((result) => { | ||
logger.info(THREAD_ID + '< Billing address assigned', result) | ||
logger.info(THREAD_ID + '< Shipping info', shippingAddressInfo) | ||
api.cart.shippingInformation(null, cartId, shippingAddressInfo, isThisAuthOrder).then((result) => { | ||
logger.info(THREAD_ID + '< Shipping address assigned', result) | ||
|
||
if(job) job.progress(currentStep++, TOTAL_STEPS); | ||
|
||
api.cart.order(null, cartId, { | ||
"paymentMethod": { | ||
"method": orderData.addressInformation.payment_method_code, | ||
"additional_data": orderData.addressInformation.payment_method_additional | ||
} | ||
}, isThisAuthOrder).then(result => { | ||
logger.info(THREAD_ID, result) | ||
if(job) job.progress(currentStep++, TOTAL_STEPS); | ||
|
||
logger.info(THREAD_ID + '[OK] Order placed with ORDER ID', result); | ||
logger.debug(THREAD_ID + result) | ||
redisClient.set("order$$id$$" + orderData.order_id, JSON.stringify({ | ||
platform_order_id: result, | ||
transmited: true, | ||
transmited_at: new Date(), | ||
platform: 'magento2', | ||
order: orderData | ||
})); | ||
redisClient.set("order$$totals$$" + orderData.order_id, JSON.stringify(result[1])); | ||
|
||
if(job) job.progress(currentStep++, TOTAL_STEPS); | ||
return done(null, { magentoOrderId: result, transferedAt: new Date() }); | ||
}).catch(err => { | ||
logger.error('Error placing an order', err, typeof err) | ||
if (job) job.attempts(6).backoff({ delay: 30*1000, type:'fixed' }).save() | ||
return done(new Error('Error placing an order', err)); | ||
}) | ||
}).catch((errors) => { | ||
logger.error('Error while adding shipping address', errors) | ||
if (job) job.attempts(3).backoff({ delay: 60*1000, type:'fixed' }).save() | ||
return done(new Error('Error while adding shipping address', errors)); | ||
}) | ||
}).catch((errors) => { | ||
logger.error('Error while adding billing address', errors) | ||
if (job) job.attempts(3).backoff({ delay: 60*1000, type:'fixed' }).save() | ||
return done(new Error('Error while adding billing address', errors)); | ||
}) | ||
}).catch((errors) => { | ||
logger.error('Error while synchronizing country list', errors) | ||
if (job) job.attempts(3).backoff({ delay: 30*1000, type:'fixed' }).save() | ||
return done(new Error('Error while syncing country list', errors)); | ||
}) | ||
|
||
}).catch((errors) => { | ||
logger.error('Error while adding products', errors) | ||
if (job) job.attempts(3).backoff({ delay: 30*1000, type:'fixed' }).save() | ||
return done(new Error('Error while adding products', errors)); | ||
}) | ||
}) | ||
} | ||
|
||
cartIdPrepare.then(processCart).catch((error) => { // cannot create a quote for specific user, so bypass by placing anonymous order | ||
logger.error(THREAD_ID, error) | ||
logger.info('< Bypassing to anonymous order') | ||
isThisAuthOrder = false | ||
|
||
if (isNumeric(cartId)) { // we have numeric id - assigned to the user provided | ||
api.cart.create(null, null).then((result) => { | ||
processCart(result) | ||
// logger.info('< Assigning guest cart with the user') | ||
// api.cart.assign(cartId, userId).then((subres) =>{ | ||
// console.info(subres) | ||
// processCart(result) | ||
// }).catch((err) => { | ||
// logger.error(err) | ||
// }) | ||
}).catch(error => { | ||
logger.info(error) | ||
return done(new Error('Error while adding products', error)); | ||
}) // TODO: assign the guest cart with user at last? | ||
} else { | ||
logger.info(THREAD_ID + '< Using cartId provided with the order', cartId) | ||
processCart(cartId) | ||
} | ||
}) | ||
} | ||
|
||
module.exports.processSingleOrder = processSingleOrder |
Oops, something went wrong.