diff --git a/packages/medusa-plugin-brightpearl/src/api/index.js b/packages/medusa-plugin-brightpearl/src/api/index.js index 118785a3bc..b4e123186d 100644 --- a/packages/medusa-plugin-brightpearl/src/api/index.js +++ b/packages/medusa-plugin-brightpearl/src/api/index.js @@ -4,6 +4,20 @@ import bodyParser from "body-parser" export default (container) => { const app = Router() + app.post("/brightpearl/product", bodyParser.json(), async (req, res) => { + const { id, lifecycle_event } = req.body + + if (lifecycle_event === "created") { + const eventBus = req.scope.resolve("eventBusService") + const brightpearlService = req.scope.resolve("brightpearlService") + const bpProduct = await brightpearlService.retrieveProduct(id) + + eventBus.emit("brightpearl-product.created", bpProduct) + } + + res.sendStatus(200) + }) + app.post("/brightpearl/goods-out", bodyParser.json(), async (req, res) => { const { id, lifecycle_event } = req.body const brightpearlService = req.scope.resolve("brightpearlService") diff --git a/packages/medusa-plugin-brightpearl/src/services/brightpearl.js b/packages/medusa-plugin-brightpearl/src/services/brightpearl.js index 76cbd143d8..eacef7d301 100644 --- a/packages/medusa-plugin-brightpearl/src/services/brightpearl.js +++ b/packages/medusa-plugin-brightpearl/src/services/brightpearl.js @@ -40,12 +40,25 @@ class BrightpearlService extends BaseService { ) } - const client = new Brightpearl({ - account: this.options.account, - url: data.api_domain, - auth_type: data.token_type, - access_token: data.access_token, - }) + const client = new Brightpearl( + { + account: this.options.account, + url: data.api_domain, + auth_type: data.token_type, + access_token: data.access_token, + }, + async (client) => { + const newAuth = await this.oauthService_.refreshToken( + "brightpearl", + data.refresh_token + ) + console.log(newAuth) + client.updateAuth({ + auth_type: newAuth.token_type, + access_token: newAuth.access_token, + }) + } + ) this.authData_ = data this.brightpearlClient_ = client @@ -542,6 +555,11 @@ class BrightpearlService extends BaseService { }) } + async retrieveProduct(byId) { + const client = await this.getClient() + return client.products.retrieve(byId) + } + async retrieveProductBySKU(sku) { const client = await this.getClient() return client.products.retrieveBySKU(sku).then((products) => { @@ -559,8 +577,6 @@ class BrightpearlService extends BaseService { const goodsOut = await client.warehouses.retrieveGoodsOutNote(id) const order = await client.orders.retrieve(goodsOut.orderId) - console.log(order) - // Combine the line items that we are going to create a fulfillment for const lines = Object.keys(goodsOut.orderRows) .map((key) => { @@ -576,7 +592,7 @@ class BrightpearlService extends BaseService { }) .filter((i) => !!i) - return this.orderService_.createFulfillment(order.ref, lines, { + return this.orderService_.createFulfillment(order.externalRef, lines, { goods_out_note: id, }) } diff --git a/packages/medusa-plugin-brightpearl/src/services/oauth.js b/packages/medusa-plugin-brightpearl/src/services/oauth.js index 1041043c60..0f66f2188a 100644 --- a/packages/medusa-plugin-brightpearl/src/services/oauth.js +++ b/packages/medusa-plugin-brightpearl/src/services/oauth.js @@ -15,7 +15,8 @@ class BrightpearlOauth extends OauthService { const client_id = "medusa-dev" const client_secret = CLIENT_SECRET const state = randomize("A0", 16) - const redirect = "https://localhost:8000/a/oauth/brightpearl" + const redirect = + "https://staging-tekla-admin.medusa-commerce.com/a/oauth/brightpearl" return { application_name: "brightpearl", display_name: "Brightpearl", @@ -24,11 +25,23 @@ class BrightpearlOauth extends OauthService { } } + async refreshToken(refreshToken) { + const params = { + refresh_token: refreshToken, + client_id: "medusa-dev", + client_secret: CLIENT_SECRET, + } + + const data = await Brightpearl.refreshToken(this.account_, params) + return data + } + async generateToken(code) { const params = { client_id: "medusa-dev", client_secret: CLIENT_SECRET, - redirect: "https://localhost:8000/a/oauth/brightpearl", + redirect: + "https://staging-tekla-admin.medusa-commerce.com/a/oauth/brightpearl", code, } diff --git a/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js b/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js index de1e3e0c1d..a7f72cb66b 100644 --- a/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js +++ b/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js @@ -21,16 +21,35 @@ class BrightpearlClient { }).then(({ data }) => data) } - constructor(options) { + static refreshToken(account, data) { + const params = { + grant_type: "refresh_token", + refresh_token: data.refresh_token, + client_id: data.client_id, + client_secret: data.client_secret, + } + + return axios({ + url: `https://ws-eu1.brightpearl.com/${account}/oauth/token`, + method: "POST", + headers: { + "content-type": "application/x-www-form-urlencoded", + }, + data: qs.stringify(params), + }).then(({ data }) => data) + } + + constructor(options, onRefreshToken) { this.client_ = axios.create({ baseURL: `https://${options.url}/public-api/${options.account}`, headers: { "brightpearl-app-ref": "medusa-dev", "brightpearl-dev-ref": "sebrindom", - Authorization: `${options.auth_type} ${options.access_token}`, }, }) + this.authType_ = options.auth_type + this.token_ = options.access_token this.webhooks = this.buildWebhookEndpoints() this.payments = this.buildPaymentEndpoints() this.warehouses = this.buildWarehouseEndpoints() @@ -38,6 +57,57 @@ class BrightpearlClient { this.addresses = this.buildAddressEndpoints() this.customers = this.buildCustomerEndpoints() this.products = this.buildProductEndpoints() + + this.buildRefreshTokenInterceptor_(onRefreshToken) + } + + updateAuth(data) { + if (data.auth_type) { + this.authType_ = data.auth_type + } + + if (data.access_token) { + this.token_ = data.access_token + } + } + + buildRefreshTokenInterceptor_(onRefresh) { + this.client_.interceptors.request.use((request) => { + const authType = this.authType_ + const token = this.token_ + + if (token) { + request.headers["Authorization"] = `${authType} ${token}` + } + + return request + }) + + this.client_.interceptors.response.use(undefined, async (error) => { + const response = error.response + + if (response) { + if ( + response.status === 401 && + error.config && + !error.config.__isRetryRequest + ) { + try { + await onRefresh(this) + } catch (authError) { + console.log(authError) + // refreshing has failed, but report the original error, i.e. 401 + return Promise.reject(error) + } + + // retry the original request + error.config.__isRetryRequest = true + return this.client_(error.config) + } + } + + return Promise.reject(error) + }) } buildSearchResults_(response) { diff --git a/packages/medusa-plugin-brightpearl/utils/brightpearl.js b/packages/medusa-plugin-brightpearl/utils/brightpearl.js index 0d5d805bbb..26945a0f0e 100644 --- a/packages/medusa-plugin-brightpearl/utils/brightpearl.js +++ b/packages/medusa-plugin-brightpearl/utils/brightpearl.js @@ -13,6 +13,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "d function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } + +function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } + function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } @@ -44,9 +48,30 @@ var BrightpearlClient = /*#__PURE__*/function () { return data; }); } + }, { + key: "refreshToken", + value: function refreshToken(account, data) { + var params = { + grant_type: "refresh_token", + refresh_token: data.refresh_token, + client_id: data.client_id, + client_secret: data.client_secret + }; + return (0, _axios["default"])({ + url: "https://ws-eu1.brightpearl.com/".concat(account, "/oauth/token"), + method: "POST", + headers: { + "content-type": "application/x-www-form-urlencoded" + }, + data: _querystring["default"].stringify(params) + }).then(function (_ref2) { + var data = _ref2.data; + return data; + }); + } }]); - function BrightpearlClient(options) { + function BrightpearlClient(options, onRefreshToken) { var _this = this; _classCallCheck(this, BrightpearlClient); @@ -57,8 +82,8 @@ var BrightpearlClient = /*#__PURE__*/function () { return _this.client_.request({ url: "/integration-service/webhook", method: "GET" - }).then(function (_ref2) { - var data = _ref2.data; + }).then(function (_ref3) { + var data = _ref3.data; return data.response; }); }, @@ -79,8 +104,8 @@ var BrightpearlClient = /*#__PURE__*/function () { url: "/accounting-service/customer-payment", method: "POST", data: payment - }).then(function (_ref3) { - var data = _ref3.data; + }).then(function (_ref4) { + var data = _ref4.data; return data.response; }); } @@ -93,8 +118,8 @@ var BrightpearlClient = /*#__PURE__*/function () { return _this.client_.request({ url: "/warehouse-service/order/".concat(orderId, "/reservation"), method: "GET" - }).then(function (_ref4) { - var data = _ref4.data; + }).then(function (_ref5) { + var data = _ref5.data; return data.response; }); }, @@ -102,8 +127,8 @@ var BrightpearlClient = /*#__PURE__*/function () { return _this.client_.request({ url: "/warehouse-service/order/*/goods-note/goods-out/".concat(id), method: "GET" - }).then(function (_ref5) { - var data = _ref5.data; + }).then(function (_ref6) { + var data = _ref6.data; return data.response && data.response[id]; }); }, @@ -112,8 +137,8 @@ var BrightpearlClient = /*#__PURE__*/function () { url: "/warehouse-service/order/".concat(orderId, "/goods-note/goods-out"), method: "POST", data: data - }).then(function (_ref6) { - var data = _ref6.data; + }).then(function (_ref7) { + var data = _ref7.data; return data.response; }); }, @@ -146,8 +171,8 @@ var BrightpearlClient = /*#__PURE__*/function () { data: { products: data } - }).then(function (_ref7) { - var data = _ref7.data; + }).then(function (_ref8) { + var data = _ref8.data; return data.response; }); } @@ -160,8 +185,8 @@ var BrightpearlClient = /*#__PURE__*/function () { return _this.client_.request({ url: "/order-service/sales-order/".concat(orderId), method: "GET" - }).then(function (_ref8) { - var data = _ref8.data; + }).then(function (_ref9) { + var data = _ref9.data; return data.response.length && data.response[0]; })["catch"](function (err) { return console.log(err); @@ -172,8 +197,8 @@ var BrightpearlClient = /*#__PURE__*/function () { url: "/order-service/sales-order", method: "POST", data: order - }).then(function (_ref9) { - var data = _ref9.data; + }).then(function (_ref10) { + var data = _ref10.data; return data.response; }); }, @@ -182,8 +207,8 @@ var BrightpearlClient = /*#__PURE__*/function () { url: "/order-service/sales-credit", method: "POST", data: salesCredit - }).then(function (_ref10) { - var data = _ref10.data; + }).then(function (_ref11) { + var data = _ref11.data; return data.response; }); } @@ -197,8 +222,8 @@ var BrightpearlClient = /*#__PURE__*/function () { url: "/contact-service/postal-address", method: "POST", data: address - }).then(function (_ref11) { - var data = _ref11.data; + }).then(function (_ref12) { + var data = _ref12.data; return data.response; }); } @@ -210,24 +235,24 @@ var BrightpearlClient = /*#__PURE__*/function () { retrieveAvailability: function retrieveAvailability(productId) { return _this.client_.request({ url: "/warehouse-service/product-availability/".concat(productId) - }).then(function (_ref12) { - var data = _ref12.data; + }).then(function (_ref13) { + var data = _ref13.data; return data.response && data.response; }); }, retrieve: function retrieve(productId) { return _this.client_.request({ url: "/product-service/product/".concat(productId) - }).then(function (_ref13) { - var data = _ref13.data; + }).then(function (_ref14) { + var data = _ref14.data; return data.response && data.response[0]; }); }, retrieveBySKU: function retrieveBySKU(sku) { return _this.client_.request({ url: "/product-service/product-search?SKU=".concat(sku) - }).then(function (_ref14) { - var data = _ref14.data; + }).then(function (_ref15) { + var data = _ref15.data; return _this.buildSearchResults_(data.response); }); } @@ -239,8 +264,8 @@ var BrightpearlClient = /*#__PURE__*/function () { retrieveByEmail: function retrieveByEmail(email) { return _this.client_.request({ url: "/contact-service/contact-search?primaryEmail=".concat(email) - }).then(function (_ref15) { - var data = _ref15.data; + }).then(function (_ref16) { + var data = _ref16.data; return _this.buildSearchResults_(data.response); }); }, @@ -249,8 +274,8 @@ var BrightpearlClient = /*#__PURE__*/function () { url: "/contact-service/contact", method: "POST", data: customerData - }).then(function (_ref16) { - var data = _ref16.data; + }).then(function (_ref17) { + var data = _ref17.data; return data.response; }); } @@ -261,10 +286,11 @@ var BrightpearlClient = /*#__PURE__*/function () { baseURL: "https://".concat(options.url, "/public-api/").concat(options.account), headers: { "brightpearl-app-ref": "medusa-dev", - "brightpearl-dev-ref": "sebrindom", - Authorization: "".concat(options.auth_type, " ").concat(options.access_token) + "brightpearl-dev-ref": "sebrindom" } }); + this.authType_ = options.auth_type; + this.token_ = options.access_token; this.webhooks = this.buildWebhookEndpoints(); this.payments = this.buildPaymentEndpoints(); this.warehouses = this.buildWarehouseEndpoints(); @@ -272,9 +298,91 @@ var BrightpearlClient = /*#__PURE__*/function () { this.addresses = this.buildAddressEndpoints(); this.customers = this.buildCustomerEndpoints(); this.products = this.buildProductEndpoints(); + this.buildRefreshTokenInterceptor_(onRefreshToken); } _createClass(BrightpearlClient, [{ + key: "updateAuth", + value: function updateAuth(data) { + if (data.auth_type) { + this.authType_ = data.auth_type; + } + + if (data.access_token) { + this.token_ = data.access_token; + } + } + }, { + key: "buildRefreshTokenInterceptor_", + value: function buildRefreshTokenInterceptor_(onRefresh) { + var _this2 = this; + + this.client_.interceptors.request.use(function (request) { + var authType = _this2.authType_; + var token = _this2.token_; + + if (token) { + request.headers["Authorization"] = "".concat(authType, " ").concat(token); + } + + return request; + }); + this.client_.interceptors.response.use(undefined, /*#__PURE__*/function () { + var _ref18 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(error) { + var response; + return regeneratorRuntime.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + response = error.response; + + if (!response) { + _context.next = 14; + break; + } + + if (!(response.status === 401 && error.config && !error.config.__isRetryRequest)) { + _context.next = 14; + break; + } + + _context.prev = 3; + _context.next = 6; + return onRefresh(_this2); + + case 6: + _context.next = 12; + break; + + case 8: + _context.prev = 8; + _context.t0 = _context["catch"](3); + console.log(_context.t0); // refreshing has failed, but report the original error, i.e. 401 + + return _context.abrupt("return", Promise.reject(error)); + + case 12: + // retry the original request + error.config.__isRetryRequest = true; + return _context.abrupt("return", _this2.client_(error.config)); + + case 14: + return _context.abrupt("return", Promise.reject(error)); + + case 15: + case "end": + return _context.stop(); + } + } + }, _callee, null, [[3, 8]]); + })); + + return function (_x) { + return _ref18.apply(this, arguments); + }; + }()); + } + }, { key: "buildSearchResults_", value: function buildSearchResults_(response) { var results = response.results,