@cloudcommerce/app-correios 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,470 +0,0 @@
1
- import correiosCalculate from './correios-ws.mjs';
2
-
3
- export default async ({ params, application }) => {
4
- const config = {
5
- ...application.data,
6
- ...application.hidden_data,
7
- };
8
-
9
- // Correios calculate params
10
- let nCdServico;
11
- let sCdMaoPropria;
12
- let sCdAvisoRecebimento;
13
- let nVlPeso = 0;
14
- let nVlValorDeclarado = 0;
15
- let nCdEmpresa = '';
16
- let sDsSenha = '';
17
- const contract = config.correios_contract;
18
- if (contract) {
19
- const code = typeof contract.code === 'string' && contract.code.trim();
20
- if (code) {
21
- nCdEmpresa = code;
22
- const password = typeof contract.password === 'string' && contract.password.trim();
23
- if (password) {
24
- sDsSenha = password;
25
- }
26
- }
27
- }
28
- const sCepDestino = params.to?.zip.replace(/\D/g, '') || '';
29
- const sCepOrigem = params.from?.zip.replace(/\D/g, '')
30
- || config.zip?.replace(/\D/g, '')
31
- || '';
32
-
33
- const checkZipCode = (rule) => {
34
- // validate rule zip range
35
- if (sCepDestino && rule.zip_range) {
36
- const { min, max } = rule.zip_range;
37
- return Boolean((!min || sCepDestino >= min) && (!max || sCepDestino <= max));
38
- }
39
- return true;
40
- };
41
-
42
- // https://apx-mods.e-com.plus/api/v1/calculate_shipping/response_schema.json?store_id=100
43
- const response = {
44
- shipping_services: [],
45
- };
46
- // search for configured free shipping rule
47
- if (Array.isArray(config.shipping_rules)) {
48
- for (let i = 0; i < config.shipping_rules.length; i++) {
49
- const rule = config.shipping_rules[i];
50
- if (rule.free_shipping && checkZipCode(rule)) {
51
- if (!rule.min_amount) {
52
- response.free_shipping_from_value = 0;
53
- break;
54
- } else if (!(response.free_shipping_from_value <= rule.min_amount)) {
55
- response.free_shipping_from_value = rule.min_amount;
56
- }
57
- }
58
- }
59
- }
60
-
61
- // params object follows calculate shipping request schema:
62
- // https://apx-mods.e-com.plus/api/v1/calculate_shipping/schema.json?store_id=100
63
- if (!params.to) {
64
- // respond only with free shipping option
65
- return response;
66
- }
67
-
68
- if (!sCepOrigem) {
69
- // must have configured origin zip code to continue
70
- return {
71
- error: 'CALCULATE_ERR',
72
- message: 'Zip code is unset on app hidden data (merchant must configure the app)',
73
- };
74
- }
75
-
76
- // optinal predefined or configured service codes
77
- if (params.service_code) {
78
- nCdServico = params.service_code;
79
- } else if (Array.isArray(config.services) && config.services[0]) {
80
- const firstServiceCode = config.services[0].service_code;
81
- if (firstServiceCode) {
82
- nCdServico = firstServiceCode;
83
- for (let i = 1; i < config.services.length; i++) {
84
- nCdServico += `,${config.services[i].service_code}`;
85
- }
86
- }
87
- }
88
-
89
- // optional params to Correios services
90
- if (params.subtotal && !config.no_declare_value) {
91
- nVlValorDeclarado = params.subtotal;
92
- }
93
- if (params.own_hand) {
94
- sCdMaoPropria = 's';
95
- }
96
- if (params.receipt) {
97
- sCdAvisoRecebimento = 's';
98
- }
99
-
100
- // calculate weight and pkg value from items list
101
- if (params.items) {
102
- const pkg = {
103
- dimensions: {
104
- width: {
105
- value: 0,
106
- unit: 'cm',
107
- },
108
- height: {
109
- value: 0,
110
- unit: 'cm',
111
- },
112
- length: {
113
- value: 0,
114
- unit: 'cm',
115
- },
116
- },
117
- weight: {
118
- value: 0,
119
- unit: 'kg',
120
- },
121
- };
122
-
123
- params.items.forEach(({
124
- price,
125
- quantity,
126
- dimensions,
127
- weight,
128
- }) => {
129
- let physicalWeight = 0;
130
- let cubicWeight = 0;
131
- if (!params.subtotal && !config.no_declare_value) {
132
- nVlValorDeclarado += price * quantity;
133
- }
134
-
135
- // sum physical weight
136
- if (weight && weight.value) {
137
- switch (weight.unit) {
138
- case 'kg':
139
- physicalWeight = weight.value;
140
- break;
141
- case 'g':
142
- physicalWeight = weight.value / 1000;
143
- break;
144
- case 'mg':
145
- physicalWeight = weight.value / 1000000;
146
- break;
147
- default:
148
- }
149
- pkg.weight.value += physicalWeight * quantity;
150
- }
151
-
152
- // sum total items dimensions to calculate cubic weight
153
- if (dimensions) {
154
- const sumDimensions = {};
155
- Object.keys(dimensions).forEach((side) => {
156
- const dimension = dimensions[side];
157
- if (dimension && dimension.value) {
158
- let dimensionValue;
159
- switch (dimension.unit) {
160
- case 'cm':
161
- dimensionValue = dimension.value;
162
- break;
163
- case 'm':
164
- dimensionValue = dimension.value * 100;
165
- break;
166
- case 'mm':
167
- dimensionValue = dimension.value / 10;
168
- break;
169
- default:
170
- }
171
- // add/sum current side to final dimensions object
172
- if (dimensionValue) {
173
- sumDimensions[side] = sumDimensions[side]
174
- ? sumDimensions[side] + dimensionValue
175
- : dimensionValue;
176
- }
177
- }
178
- });
179
-
180
- // calculate cubic weight
181
- // https://suporte.boxloja.pro/article/82-correios-calculo-frete
182
- // (C x L x A) / 6.000
183
- Object.keys(sumDimensions).forEach((side) => {
184
- if (sumDimensions[side]) {
185
- cubicWeight = cubicWeight > 0
186
- ? cubicWeight * sumDimensions[side]
187
- : sumDimensions[side];
188
- pkg.dimensions[side].value += sumDimensions[side] * quantity;
189
- }
190
- });
191
- if (cubicWeight > 0) {
192
- cubicWeight /= 6000;
193
- }
194
- }
195
- if (!config.free_no_weight_shipping || physicalWeight > 0) {
196
- const unitWeight = cubicWeight < 5 || physicalWeight > cubicWeight
197
- ? physicalWeight : cubicWeight;
198
- nVlPeso += (quantity * unitWeight);
199
- }
200
- });
201
-
202
- // pre check for maximum allowed declared value
203
- if (nVlValorDeclarado > 10000) {
204
- nVlValorDeclarado = 10000;
205
- }
206
-
207
- let result;
208
- try {
209
- result = await correiosCalculate({
210
- sCepOrigem,
211
- sCepDestino,
212
- nCdEmpresa,
213
- sDsSenha,
214
- nCdServico,
215
- sCdMaoPropria,
216
- sCdAvisoRecebimento,
217
- nVlPeso,
218
- nVlValorDeclarado,
219
- });
220
- } catch (err) {
221
- return {
222
- error: 'CALCULATE_FAILED',
223
- message: err.message,
224
- };
225
- }
226
-
227
- const { Servicos, cServico } = result;
228
- // set services array from `Servicos` or `cServico`
229
- let services;
230
- if (Servicos) {
231
- if (Array.isArray(Servicos)) {
232
- services = Servicos;
233
- } else if (Servicos.cServico) {
234
- services = Array.isArray(Servicos.cServico) ? Servicos.cServico : [Servicos.cServico];
235
- }
236
- }
237
- if (!services) {
238
- services = Array.isArray(cServico) ? cServico : [cServico];
239
- }
240
-
241
- if (services[0] && services[0].Codigo) {
242
- let errorMsg;
243
- services.forEach((service) => {
244
- // check error first
245
- let { Erro, PrazoEntrega } = service;
246
- const { MsgErro, url } = service;
247
- let notes;
248
- PrazoEntrega = parseInt(PrazoEntrega, 10);
249
- // known Correios errors
250
- switch (Erro) {
251
- case '010':
252
- case 10:
253
- Erro = false;
254
- notes = MsgErro;
255
- break;
256
- case '011':
257
- case 11:
258
- Erro = false;
259
- PrazoEntrega += 7;
260
- break;
261
- default:
262
- }
263
-
264
- if ((!Erro || Erro === '0') && PrazoEntrega >= 0) {
265
- // fix price strings to number
266
- [
267
- 'Valor',
268
- 'ValorSemAdicionais',
269
- 'ValorMaoPropria',
270
- 'ValorAvisoRecebimento',
271
- 'ValorValorDeclarado',
272
- ].forEach((field) => {
273
- switch (typeof service[field]) {
274
- case 'number':
275
- break;
276
- case 'string':
277
- service[field] = parseFloat(service[field].replace('.', '').replace(',', '.'));
278
- break;
279
- default:
280
- service[field] = 0;
281
- }
282
- });
283
- const {
284
- Codigo,
285
- Valor,
286
- ValorSemAdicionais,
287
- ValorMaoPropria,
288
- ValorAvisoRecebimento,
289
- ValorValorDeclarado,
290
- } = service;
291
-
292
- // find respective configured service label
293
- let serviceName;
294
- switch (Codigo) {
295
- case '04014':
296
- serviceName = 'SEDEX';
297
- break;
298
- case '04510':
299
- serviceName = 'PAC';
300
- break;
301
- default:
302
- }
303
- let label = serviceName || `Correios ${Codigo}`;
304
- if (Array.isArray(config.services)) {
305
- for (let i = 0; i < config.services.length; i++) {
306
- const _service = config.services[i];
307
- if (_service && _service.service_code === Codigo && _service.label) {
308
- label = _service.label;
309
- }
310
- }
311
- }
312
-
313
- // parse to E-Com Plus shipping line object
314
- const shippingLine = {
315
- from: {
316
- ...params.from,
317
- zip: sCepOrigem,
318
- },
319
- to: params.to,
320
- package: pkg,
321
- price: ValorSemAdicionais || Valor,
322
- declared_value: nVlValorDeclarado,
323
- declared_value_price: ValorValorDeclarado > 0 ? ValorValorDeclarado : 0,
324
- own_hand: Boolean(sCdMaoPropria),
325
- own_hand_price: ValorMaoPropria,
326
- receipt: Boolean(sCdAvisoRecebimento),
327
- receipt_price: ValorAvisoRecebimento,
328
- discount: 0,
329
- total_price: Valor,
330
- delivery_time: {
331
- days: PrazoEntrega,
332
- working_days: true,
333
- },
334
- posting_deadline: {
335
- days: 3,
336
- ...config.posting_deadline,
337
- },
338
- flags: ['correios-ws'],
339
- notes,
340
- };
341
-
342
- // check for default configured additional/discount price
343
- if (typeof config.additional_price === 'number' && config.additional_price) {
344
- if (config.additional_price > 0) {
345
- shippingLine.other_additionals = [{
346
- tag: 'additional_price',
347
- label: 'Adicional padrão',
348
- price: config.additional_price,
349
- }];
350
- } else {
351
- // negative additional price to apply discount
352
- shippingLine.discount -= config.additional_price;
353
- }
354
- // update total price
355
- shippingLine.total_price += config.additional_price;
356
- }
357
-
358
- // search for discount by shipping rule
359
- if (Array.isArray(config.shipping_rules)) {
360
- for (let i = 0; i < config.shipping_rules.length; i++) {
361
- const rule = config.shipping_rules[i];
362
- if (
363
- rule
364
- && (!rule.service_code || rule.service_code === Codigo)
365
- && checkZipCode(rule)
366
- && !(rule.min_amount > params.subtotal)
367
- ) {
368
- // valid shipping rule
369
- if (rule.free_shipping) {
370
- shippingLine.discount += shippingLine.total_price;
371
- shippingLine.total_price = 0;
372
- break;
373
- } else if (rule.discount) {
374
- let discountValue = rule.discount.value;
375
- if (rule.discount.percentage) {
376
- discountValue *= (shippingLine.total_price / 100);
377
- }
378
- if (discountValue) {
379
- shippingLine.discount += discountValue;
380
- shippingLine.total_price -= discountValue;
381
- if (shippingLine.total_price < 0) {
382
- shippingLine.total_price = 0;
383
- }
384
- }
385
- break;
386
- }
387
- }
388
- }
389
- }
390
-
391
- if (typeof url === 'string') {
392
- // add WS URL to custom fields to facilitate debugging
393
- shippingLine.custom_fields = [{
394
- field: 'correios_ws_params',
395
- value: (nCdEmpresa ? url.replace(nCdEmpresa, 'c').replace(sDsSenha, 's') : url)
396
- .replace(/[^?]+\?(.*)/, '$1')
397
- .replace(/(StrRetorno|nIndicaCalculo|nCdFormato|nVlDiametro)=[^&]+&?/g, '')
398
- .slice(0, 255),
399
- }];
400
- }
401
-
402
- // push shipping service object to response
403
- response.shipping_services.push({
404
- label,
405
- carrier: 'Correios',
406
- // https://informederendimentos.com/consulta/cnpj-correios/
407
- carrier_doc_number: '34028316000103',
408
- service_code: Codigo,
409
- service_name: serviceName || label,
410
- shipping_line: shippingLine,
411
- });
412
- } else {
413
- errorMsg = `Correios erro ${Erro}`;
414
- if (typeof MsgErro === 'string') {
415
- errorMsg += `: ${MsgErro}`;
416
- }
417
- if (typeof url === 'string') {
418
- errorMsg += `\n${url}`;
419
- }
420
- }
421
- });
422
-
423
- if (response.shipping_services.length) {
424
- // free shipping if all items has no weigth
425
- const freeNoWeightShipping = nVlPeso <= 0 && config.free_no_weight_shipping;
426
- if (freeNoWeightShipping) {
427
- let cheapestShippingLine;
428
- for (let i = 0; i < response.shipping_services.length; i++) {
429
- const shippingLine = response.shipping_services[i].shipping_line;
430
- if (!shippingLine.total_price) {
431
- // already free
432
- break;
433
- }
434
- if (
435
- !cheapestShippingLine
436
- || cheapestShippingLine.total_price > shippingLine.total_price
437
- ) {
438
- cheapestShippingLine = shippingLine;
439
- }
440
- }
441
- if (cheapestShippingLine) {
442
- // set the cheapest shipping line free
443
- cheapestShippingLine.discount = cheapestShippingLine.total_price;
444
- cheapestShippingLine.total_price = 0;
445
- cheapestShippingLine.flags.push('free_no_weight');
446
- }
447
- }
448
- } else if (errorMsg) {
449
- // pass Correios error message
450
- return {
451
- error: 'CALCULATE_ERR_MSG',
452
- message: errorMsg,
453
- };
454
- }
455
-
456
- // success response with available shipping services
457
- return response;
458
- }
459
-
460
- // unexpected result object
461
- return {
462
- error: 'CALCULATE_UNEXPECTED_RSP',
463
- message: 'Unexpected object from Correios response, please try again later',
464
- };
465
- }
466
- return {
467
- error: 'CALCULATE_EMPTY_CART',
468
- message: 'Cannot calculate shipping without cart items',
469
- };
470
- };
@@ -1,155 +0,0 @@
1
- /* eslint-disable no-console */
2
- import axios from 'axios';
3
- import * as xml2js from 'xml2js';
4
-
5
- const roundDecimal = (num) => Math.round(num * 100) / 100;
6
-
7
- // https://www.correios.com.br/precos-e-prazos/calculador-remoto-de-precos-e-prazos
8
- const baseUrl = 'http://ws.correios.com.br/calculador/CalcPrecoPrazo.aspx?StrRetorno=xml';
9
-
10
- const sendCalculateRequest = (params, timeout, isDebug = false) => {
11
- let url = baseUrl;
12
- Object.keys(params).forEach((param) => {
13
- if (params[param] !== undefined) {
14
- url += `&${param}=${params[param]}`;
15
- }
16
- });
17
- if (isDebug === true) {
18
- // eslint-disable-next-line no-console
19
- console.log(`[ws] ${url}`);
20
- }
21
-
22
- return axios({
23
- method: 'get',
24
- url,
25
- timeout,
26
- responseType: 'text',
27
- })
28
- .then(async (response) => {
29
- // parse XML to object
30
- // https://www.npmjs.com/package/xml2js#options/
31
- const result = await xml2js.parseStringPromise(response.data, { explicitArray: false });
32
- let services;
33
- if (Array.isArray(result.Servicos)) {
34
- services = result.Servicos;
35
- } else {
36
- services = Array.isArray(result.Servicos.cServico)
37
- ? result.Servicos.cServico
38
- : [result.Servicos.cServico];
39
- }
40
- const availableService = services.find(({ Erro, MsgErro }) => {
41
- return String(Erro) !== '-33' && !String(MsgErro).includes('Unavailable');
42
- });
43
- if (availableService) {
44
- return {
45
- ...result,
46
- services,
47
- url,
48
- };
49
- }
50
- const err = new Error('Correios WS unavailable');
51
- err.code = 'E503';
52
- err.config = response.config;
53
- err.response = response;
54
- throw err;
55
- })
56
-
57
- .catch((error) => {
58
- switch (error.code) {
59
- case 'ECONNRESET':
60
- case 'ECONNABORTED':
61
- case 'ETIMEDOUT':
62
- break;
63
- default:
64
- // Debug unknown error
65
- if (error.response) {
66
- const err = new Error('Correios WS error');
67
- err.code = error.code;
68
- err.config = error.config;
69
- err.response = {
70
- status: error.response.status,
71
- data: error.response.data,
72
- };
73
- console.error(err);
74
- } else {
75
- console.error(error);
76
- }
77
- }
78
- throw error;
79
- });
80
- };
81
-
82
- export default (params, timeout = 20000, isDebug = false) => {
83
- params.nIndicaCalculo = 3;
84
- params.nCdFormato = 1;
85
- params.nVlDiametro = 0;
86
- if (!params.nCdServico) {
87
- // PAC, SEDEX
88
- params.nCdServico = '04510,04014';
89
- }
90
- // Check minimum package dimensions
91
- if (!(params.nVlPeso >= 0.1)) {
92
- params.nVlPeso = 0.1;
93
- } else {
94
- params.nVlPeso = roundDecimal(params.nVlPeso);
95
- }
96
- if (!(params.nVlComprimento >= 16)) {
97
- params.nVlComprimento = 16;
98
- }
99
- if (!(params.nVlAltura >= 2)) {
100
- params.nVlAltura = 2;
101
- }
102
- if (!(params.nVlLargura >= 11)) {
103
- params.nVlLargura = 11;
104
- }
105
-
106
- // Optional additional services
107
- ['sCdMaoPropria', 'sCdAvisoRecebimento'].forEach((param) => {
108
- if (!params[param]) {
109
- params[param] = 'n';
110
- }
111
- });
112
- if (params.nVlValorDeclarado === undefined) {
113
- params.nVlValorDeclarado = 0;
114
- } else if (params.nVlValorDeclarado < 24) {
115
- params.nVlValorDeclarado = 24;
116
- } else if (params.nVlValorDeclarado > 10000) {
117
- params.nVlValorDeclarado = 10000;
118
- } else {
119
- params.nVlValorDeclarado = roundDecimal(params.nVlValorDeclarado);
120
- }
121
-
122
- const promises = [];
123
- if (!params.nCdEmpresa || !params.sDsSenha) {
124
- // Correios limit up to 1 service code per public request
125
- params.nCdServico.split(',').forEach((nCdServico) => {
126
- if (nCdServico) {
127
- promises.push(sendCalculateRequest({ ...params, nCdServico }, timeout, isDebug));
128
- }
129
- });
130
- } else {
131
- // Can send multiple service codes
132
- promises.push(sendCalculateRequest(params, timeout, isDebug));
133
- }
134
-
135
- return Promise.all(promises).then((results) => {
136
- if (results.length === 1) {
137
- // single request with multiple services
138
- const { Servicos, url } = results[0];
139
- const { cServico } = Servicos;
140
- return {
141
- Servicos: {
142
- cServico: (Array.isArray(cServico) ? cServico : [cServico])
143
- .map((_cServico) => ({ ..._cServico, url })),
144
- },
145
- };
146
- }
147
- return {
148
- Servicos: {
149
- cServico: results.map(({ Servicos, url }) => {
150
- return { ...Servicos.cServico, url };
151
- }),
152
- },
153
- };
154
- });
155
- };