@foxtware/mineral 0.1.29 → 0.1.31

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.
Files changed (35) hide show
  1. package/.creds.yml.sample +21 -0
  2. package/api/dpd/docs.md +6 -0
  3. package/api/dpd/dpd.constants.js +9 -0
  4. package/api/dpd/dpd.utils.js +145 -0
  5. package/api/dpd/dpdTrackingGet.js +64 -0
  6. package/api/fedex/docs.md +6 -0
  7. package/api/fedex/fedex.constants.js +11 -0
  8. package/api/fedex/fedex.utils.js +151 -0
  9. package/api/fedex/fedexTrackingGet.js +76 -0
  10. package/api/paypal/docs.md +14 -0
  11. package/api/paypal/paypal.constants.js +13 -0
  12. package/api/paypal/paypal.utils.js +166 -0
  13. package/api/paypal/paypalAccessTokenGet.js +81 -0
  14. package/api/paypal/paypalAuthorizationCapture.js +75 -0
  15. package/api/paypal/paypalAuthorizationGet.js +57 -0
  16. package/api/paypal/paypalBalancesGet.js +65 -0
  17. package/api/paypal/paypalCaptureGet.js +57 -0
  18. package/api/paypal/paypalCaptureRefund.js +78 -0
  19. package/api/paypal/paypalOrderAuthorize.js +69 -0
  20. package/api/paypal/paypalOrderCapture.js +69 -0
  21. package/api/paypal/paypalOrderCreate.js +81 -0
  22. package/api/paypal/paypalOrderGet.js +57 -0
  23. package/api/paypal/paypalRefundGet.js +57 -0
  24. package/api/paypal/paypalTransactionsGet.js +101 -0
  25. package/api/paypal/paypalUserInfoGet.js +62 -0
  26. package/api/royalmail/docs.md +6 -0
  27. package/api/royalmail/royalmail.constants.js +7 -0
  28. package/api/royalmail/royalmail.utils.js +55 -0
  29. package/api/royalmail/royalmailTrackingGet.js +70 -0
  30. package/api/shopify/shopify.utils.js +12 -0
  31. package/api/starshipit/starshipitProductUpdate.js +102 -0
  32. package/api/starshipit/starshipitProductsGet.js +2 -2
  33. package/api/stylearcade/stylearcadeGet.js +5 -2
  34. package/api/utils.js +44 -0
  35. package/package.json +1 -1
@@ -0,0 +1,57 @@
1
+ // https://developer.paypal.com/docs/api/payments/v2/#refunds_get
2
+
3
+ const { credsValidator } = require('../validators');
4
+ const { ArgsWarden } = require('../utils');
5
+ const { paypalClient } = require('../paypal/paypal.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ['refundId'],
10
+ ]);
11
+
12
+ const paypalRefundGet = async (
13
+ credsPayload,
14
+ refundId,
15
+ {
16
+ fetchClient = paypalClient,
17
+ } = {},
18
+ ) => {
19
+
20
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
21
+ credsPayload,
22
+ refundId,
23
+ });
24
+ if (rejectResponse) {
25
+ return rejectResponse;
26
+ }
27
+
28
+ const response = await fetchClient.fetch({
29
+ requestPayload: {
30
+ method: 'get',
31
+ url: `/v2/payments/refunds/${ encodeURIComponent(refundId) }`,
32
+ },
33
+ context: {
34
+ credsPayload,
35
+ },
36
+ });
37
+
38
+ return response;
39
+ };
40
+
41
+ const funcApiConfig = {
42
+ argsWarden,
43
+ };
44
+
45
+ module.exports = {
46
+ paypalRefundGet,
47
+ funcApiConfig,
48
+ };
49
+
50
+ /*
51
+ curl -X POST "http://localhost:8000/paypalRefundGet" \
52
+ -H "Content-Type: application/json" \
53
+ -d '{
54
+ "credsPayload": { "credsPath": "paypal" },
55
+ "refundId": "1JU08902781691411"
56
+ }'
57
+ */
@@ -0,0 +1,101 @@
1
+ // https://developer.paypal.com/docs/api/transaction-search/v1/#transactions_get
2
+
3
+ const { credsValidator } = require('../validators');
4
+ const { ArgsWarden } = require('../utils');
5
+ const { paypalClient } = require('../paypal/paypal.utils');
6
+ const { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } = require('../paypal/paypal.constants');
7
+
8
+ const argsWarden = new ArgsWarden([
9
+ ['credsPayload', credsValidator],
10
+ ['startDate'],
11
+ ['endDate'],
12
+ ]);
13
+
14
+ /**
15
+ * List transactions for the account (Transaction Search API).
16
+ * startDate / endDate are ISO-8601 (e.g. 2024-01-01T00:00:00Z).
17
+ * Date range must be ≤ 31 days per PayPal rules.
18
+ */
19
+ const paypalTransactionsGet = async (
20
+ credsPayload,
21
+ startDate,
22
+ endDate,
23
+ {
24
+ transactionId,
25
+ transactionStatus,
26
+ transactionType,
27
+ paymentInstrumentType,
28
+ page = 1,
29
+ pageSize = DEFAULT_PAGE_SIZE,
30
+ fields,
31
+ balanceAffectingRecordsOnly,
32
+ storeId,
33
+ terminalId,
34
+ fetchClient = paypalClient,
35
+ } = {},
36
+ ) => {
37
+
38
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
39
+ credsPayload,
40
+ startDate,
41
+ endDate,
42
+ });
43
+ if (rejectResponse) {
44
+ return rejectResponse;
45
+ }
46
+
47
+ const size = Math.min(Number(pageSize) || DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
48
+
49
+ const response = await fetchClient.fetch({
50
+ requestPayload: {
51
+ method: 'get',
52
+ url: '/v1/reporting/transactions',
53
+ params: {
54
+ start_date: startDate,
55
+ end_date: endDate,
56
+ page: Number(page) || 1,
57
+ page_size: size,
58
+ ...(transactionId ? { transaction_id: transactionId } : {}),
59
+ ...(transactionStatus ? { transaction_status: transactionStatus } : {}),
60
+ ...(transactionType ? { transaction_type: transactionType } : {}),
61
+ ...(paymentInstrumentType
62
+ ? { payment_instrument_type: paymentInstrumentType }
63
+ : {}),
64
+ ...(fields ? { fields } : {}),
65
+ ...(balanceAffectingRecordsOnly !== undefined
66
+ ? { balance_affecting_records_only: balanceAffectingRecordsOnly }
67
+ : {}),
68
+ ...(storeId ? { store_id: storeId } : {}),
69
+ ...(terminalId ? { terminal_id: terminalId } : {}),
70
+ },
71
+ },
72
+ context: {
73
+ credsPayload,
74
+ },
75
+ });
76
+
77
+ return response;
78
+ };
79
+
80
+ const funcApiConfig = {
81
+ argsWarden,
82
+ };
83
+
84
+ module.exports = {
85
+ paypalTransactionsGet,
86
+ funcApiConfig,
87
+ };
88
+
89
+ /*
90
+ curl -X POST "http://localhost:8000/paypalTransactionsGet" \
91
+ -H "Content-Type: application/json" \
92
+ -d '{
93
+ "credsPayload": { "credsPath": "paypal" },
94
+ "startDate": "2026-01-01T00:00:00Z",
95
+ "endDate": "2026-01-31T23:59:59Z",
96
+ "options": {
97
+ "page": 1,
98
+ "pageSize": 100
99
+ }
100
+ }'
101
+ */
@@ -0,0 +1,62 @@
1
+ // https://developer.paypal.com/docs/api/identity/v1/#userinfo_get
2
+
3
+ const { credsValidator } = require('../validators');
4
+ const { ArgsWarden } = require('../utils');
5
+ const { paypalClient } = require('../paypal/paypal.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ]);
10
+
11
+ /**
12
+ * Show OpenID Connect user profile details for the token subject.
13
+ * Client-credentials tokens may return limited claims; user-consent
14
+ * tokens with openid scopes return the full profile.
15
+ */
16
+ const paypalUserInfoGet = async (
17
+ credsPayload,
18
+ {
19
+ schema = 'openid',
20
+ fetchClient = paypalClient,
21
+ } = {},
22
+ ) => {
23
+
24
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
25
+ credsPayload,
26
+ });
27
+ if (rejectResponse) {
28
+ return rejectResponse;
29
+ }
30
+
31
+ const response = await fetchClient.fetch({
32
+ requestPayload: {
33
+ method: 'get',
34
+ url: '/v1/identity/openidconnect/userinfo',
35
+ params: {
36
+ schema,
37
+ },
38
+ },
39
+ context: {
40
+ credsPayload,
41
+ },
42
+ });
43
+
44
+ return response;
45
+ };
46
+
47
+ const funcApiConfig = {
48
+ argsWarden,
49
+ };
50
+
51
+ module.exports = {
52
+ paypalUserInfoGet,
53
+ funcApiConfig,
54
+ };
55
+
56
+ /*
57
+ curl -X POST "http://localhost:8000/paypalUserInfoGet" \
58
+ -H "Content-Type: application/json" \
59
+ -d '{
60
+ "credsPayload": { "credsPath": "paypal" }
61
+ }'
62
+ */
@@ -0,0 +1,6 @@
1
+ # Royal Mail
2
+
3
+ - [Royal Mail APIs](https://www.royalmail.com/business/tools-services/apis)
4
+ - [Tracking API V2](https://developer.royalmail.net/product/175625/api/76888)
5
+
6
+ Royal Mail provides a REST Tracking API V2 at `https://api.royalmail.net`. Server-side tracking uses `X-IBM-Client-Id` and `X-IBM-Client-Secret` credentials issued through the Royal Mail API Developer Portal, plus the `X-Accept-RMG-Terms: yes` header. The API supports summary, history, and proof-of-delivery data for mailpieces.
@@ -0,0 +1,7 @@
1
+ const ROYALMAIL_API_BASE_URL = 'https://api.royalmail.net';
2
+ const ROYALMAIL_TRACKING_PATH = '/mailpieces/v2';
3
+
4
+ module.exports = {
5
+ ROYALMAIL_API_BASE_URL,
6
+ ROYALMAIL_TRACKING_PATH,
7
+ };
@@ -0,0 +1,55 @@
1
+ const { resolveCreds, useBaseUrl } = require('../pipelineSteps');
2
+ const {
3
+ FetchClient,
4
+ fetchClientCommonSteps,
5
+ } = require('../utils');
6
+ const { ROYALMAIL_API_BASE_URL } = require('./royalmail.constants');
7
+
8
+ const useAuthHeaders = async (state) => {
9
+ const { requestPayload, context } = state;
10
+ const { creds } = context;
11
+ const {
12
+ CLIENT_ID,
13
+ CLIENT_SECRET,
14
+ } = creds || {};
15
+
16
+ if (!CLIENT_ID || !CLIENT_SECRET) {
17
+ return {
18
+ breakChain: true,
19
+ response: {
20
+ ok: false,
21
+ error: {
22
+ code: 'ROYALMAIL_AUTH_CONFIG_ERROR',
23
+ message: 'Royal Mail creds require CLIENT_ID and CLIENT_SECRET',
24
+ },
25
+ },
26
+ };
27
+ }
28
+
29
+ return {
30
+ requestPayload: {
31
+ ...requestPayload,
32
+ headers: {
33
+ Accept: 'application/json',
34
+ 'X-Accept-RMG-Terms': 'yes',
35
+ 'X-IBM-Client-Id': CLIENT_ID,
36
+ 'X-IBM-Client-Secret': CLIENT_SECRET,
37
+ ...requestPayload.headers,
38
+ },
39
+ },
40
+ };
41
+ };
42
+
43
+ const royalmailClient = new FetchClient({
44
+ pipeline: [
45
+ resolveCreds,
46
+ useBaseUrl(ROYALMAIL_API_BASE_URL),
47
+ useAuthHeaders,
48
+ 'fetch',
49
+ fetchClientCommonSteps.exitEarlyOnNotOk,
50
+ ],
51
+ });
52
+
53
+ module.exports = {
54
+ royalmailClient,
55
+ };
@@ -0,0 +1,70 @@
1
+ // https://developer.royalmail.net/product/175625/api/76888
2
+
3
+ const { ArgsWarden, objHasAny } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { ROYALMAIL_TRACKING_PATH } = require('./royalmail.constants');
6
+ const { royalmailClient } = require('./royalmail.utils');
7
+
8
+ const trackingIdentifierValidator = (trackingIdentifier) => {
9
+ return objHasAny(trackingIdentifier, [
10
+ 'trackingNumber',
11
+ ]);
12
+ };
13
+
14
+ const argsWarden = new ArgsWarden([
15
+ ['credsPayload', credsValidator],
16
+ ['trackingIdentifier', trackingIdentifierValidator],
17
+ ]);
18
+
19
+ const royalmailTrackingGet = async (
20
+ credsPayload,
21
+ trackingIdentifier,
22
+ {
23
+ operation = 'summary',
24
+ fetchClient = royalmailClient,
25
+ } = {},
26
+ ) => {
27
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
28
+ credsPayload,
29
+ trackingIdentifier,
30
+ });
31
+ if (rejectResponse) {
32
+ return rejectResponse;
33
+ }
34
+
35
+ const {
36
+ trackingNumber,
37
+ } = trackingIdentifier;
38
+ const operationPath = operation === 'summary'
39
+ ? `${ ROYALMAIL_TRACKING_PATH }/summary?mailPieceId=${ encodeURIComponent(trackingNumber) }`
40
+ : `${ ROYALMAIL_TRACKING_PATH }/mailPieces/${ encodeURIComponent(trackingNumber) }/${ operation }`;
41
+
42
+ return fetchClient.fetch({
43
+ requestPayload: {
44
+ url: operationPath,
45
+ },
46
+ context: {
47
+ credsPayload,
48
+ },
49
+ });
50
+ };
51
+
52
+ const funcApiConfig = {
53
+ argsWarden,
54
+ };
55
+
56
+ module.exports = {
57
+ royalmailTrackingGet,
58
+ funcApiConfig,
59
+ };
60
+
61
+ /*
62
+ curl -X POST "http://localhost:8000/royalmailTrackingGet" \
63
+ -H "Content-Type: application/json" \
64
+ -d '{
65
+ "credsPayload": { "credsPath": "royalmail" },
66
+ "trackingIdentifier": {
67
+ "trackingNumber": "AA123456789GB"
68
+ }
69
+ }'
70
+ */
@@ -236,8 +236,20 @@ const shopifyStorefrontClient = new FetchClient({
236
236
  ],
237
237
  });
238
238
 
239
+ const shopifyProductFlattenMetafields = (product) => {
240
+ // If property begins with mf, try to flatten to property.value
241
+ const flattenedProduct = { ...product };
242
+ for (const [key, value] of Object.entries(product)) {
243
+ if (key.startsWith('mf')) {
244
+ flattenedProduct[key] = value?.value ?? value;
245
+ }
246
+ }
247
+ return flattenedProduct;
248
+ };
249
+
239
250
  module.exports = {
240
251
  parseShopifyJsonl,
241
252
  shopifyClient,
242
253
  shopifyStorefrontClient,
254
+ shopifyProductFlattenMetafields,
243
255
  };
@@ -0,0 +1,102 @@
1
+ // https://api-docs.starshipit.com/#9101a9d7-91b1-492c-b7ad-5f92f80bbfd7
2
+
3
+ const { ArgsWarden } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { starshipitClient } = require('../starshipit/starshipit.utils');
6
+ const { starshipitProductsGet } = require('../starshipit/starshipitProductsGet');
7
+
8
+ const argsWarden = new ArgsWarden([
9
+ ['credsPayload', credsValidator],
10
+ ['productId'],
11
+ ['sku'],
12
+ ['updatePayload'],
13
+ ]);
14
+
15
+ const starshipitProductUpdate = async (
16
+ credsPayload,
17
+ productId,
18
+ sku,
19
+ updatePayload,
20
+ {
21
+ setData = false,
22
+ } = {},
23
+ ) => {
24
+
25
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
26
+ credsPayload,
27
+ productId,
28
+ sku,
29
+ updatePayload,
30
+ });
31
+ if (rejectResponse) {
32
+ return rejectResponse;
33
+ }
34
+
35
+ if (!setData) {
36
+ const productsResponse = await starshipitProductsGet(
37
+ credsPayload,
38
+ {
39
+ searchTerm: sku,
40
+ },
41
+ );
42
+
43
+ if (!productsResponse?.ok) {
44
+ return productsResponse;
45
+ }
46
+
47
+ const currentProduct = productsResponse.data?.find(product => product?.sku === sku);
48
+ if (!currentProduct) {
49
+ return {
50
+ ok: false,
51
+ error: {
52
+ code: 'PRODUCT_NOT_FOUND',
53
+ message: `No Starshipit product found for SKU ${ sku }`,
54
+ },
55
+ };
56
+ }
57
+
58
+ updatePayload = {
59
+ ...currentProduct,
60
+ ...updatePayload,
61
+ };
62
+ }
63
+
64
+ return starshipitClient.fetch({
65
+ requestPayload: {
66
+ url: '/products/update',
67
+ method: 'put',
68
+ body: {
69
+ id: productId,
70
+ product: {
71
+ id: productId,
72
+ sku,
73
+ ...updatePayload,
74
+ },
75
+ },
76
+ },
77
+ context: {
78
+ credsPayload,
79
+ resultPath: 'result.product',
80
+ },
81
+ });
82
+ };
83
+
84
+ const funcApiConfig = {
85
+ argsWarden,
86
+ };
87
+
88
+ module.exports = {
89
+ starshipitProductUpdate,
90
+ funcApiConfig,
91
+ };
92
+
93
+ /*
94
+ curl -X POST "http://localhost:8000/starshipitProductUpdate" \
95
+ -H "Content-Type: application/json" \
96
+ -d '{
97
+ "credsPayload": { "credsPath": "starshipit.wf" },
98
+ "productId": 3015817,
99
+ "sku": "WFAL48-1-S",
100
+ "updatePayload": { "hs_code": "6104630011" }
101
+ }'
102
+ */
@@ -45,7 +45,7 @@ module.exports = {
45
45
  curl -X POST "http://localhost:8000/starshipitProductsGet" \
46
46
  -H "Content-Type: application/json" \
47
47
  -d '{
48
- "credsPayload": { "credsPath": "starshipit.acc" },
49
- "options": { "searchTerm": "WFAL48-1-S" }
48
+ "credsPayload": { "credsPath": "starshipit.wf" },
49
+ "options": { "limit": 20 }
50
50
  }'
51
51
  */
@@ -1,4 +1,4 @@
1
- const { ArgsWarden, Getter } = require('../utils');
1
+ const { ArgsWarden, Getter, valueProvided } = require('../utils');
2
2
  const { credsValidator } = require('../validators');
3
3
  const { stylearcadeClient } = require('../stylearcade/stylearcade.utils');
4
4
  const { MAX_PER_PAGE } = require('../stylearcade/stylearcade.constants');
@@ -59,7 +59,10 @@ const stylearcadeGetDigester = (response, nodeName) => {
59
59
  return [];
60
60
  }
61
61
 
62
- return response?.data?.[nodeName] ?? [];
62
+ const items = response?.data?.[nodeName] ?? [];
63
+ // Filter out items that have data property, but it's null
64
+ const meaningfulItems = items.filter(item => Object.hasOwn(item, 'data') && valueProvided(item.data));
65
+ return meaningfulItems;
63
66
  };
64
67
 
65
68
  const stylearcadeGet = async (
package/api/utils.js CHANGED
@@ -1411,6 +1411,47 @@ const surveyObject = (
1411
1411
  );
1412
1412
  };
1413
1413
 
1414
+ const diffObjects = (
1415
+ sourceOfTruthObject,
1416
+ comparisonObject,
1417
+ {
1418
+ propMap = {},
1419
+ } = {},
1420
+ ) => {
1421
+ const diff = {};
1422
+ for (const [key, value] of Object.entries(sourceOfTruthObject)) {
1423
+ const comparisonKey = propMap[key] || key;
1424
+ // TODO: Consider deep equality
1425
+ if (comparisonObject[comparisonKey] !== value) {
1426
+ diff[key] = value;
1427
+ }
1428
+ }
1429
+ return diff;
1430
+ };
1431
+
1432
+ const objectPropsTranslate = (
1433
+ obj,
1434
+ propMap,
1435
+ {
1436
+ omitUnmappedProps = false,
1437
+ } = {},
1438
+ ) => {
1439
+ const translatedObj = {};
1440
+
1441
+ for (const [key, value] of Object.entries(obj)) {
1442
+ if (omitUnmappedProps && !propMap[key]) {
1443
+ continue;
1444
+ }
1445
+ const translatedKey = propMap[key] || key;
1446
+ translatedObj[translatedKey] = value;
1447
+ }
1448
+ return translatedObj;
1449
+ };
1450
+
1451
+ const randomArrayItem = (array) => {
1452
+ return array[Math.floor(Math.random() * array.length)];
1453
+ };
1454
+
1414
1455
  module.exports = {
1415
1456
  wait,
1416
1457
  timeMs,
@@ -1452,4 +1493,7 @@ module.exports = {
1452
1493
  objectMatchesPartial,
1453
1494
  oneFromManyInResponse,
1454
1495
  surveyObject,
1496
+ diffObjects,
1497
+ objectPropsTranslate,
1498
+ randomArrayItem,
1455
1499
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxtware/mineral",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "bin": {
5
5
  "mineral": "bin/mineral.js"
6
6
  },