@shopickup/adapters-mpl 0.0.1
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.
- package/README.md +43 -0
- package/dist/capabilities/auth.d.ts +39 -0
- package/dist/capabilities/auth.d.ts.map +1 -0
- package/dist/capabilities/auth.js +130 -0
- package/dist/capabilities/close.d.ts +8 -0
- package/dist/capabilities/close.d.ts.map +1 -0
- package/dist/capabilities/close.js +70 -0
- package/dist/capabilities/get-shipment-details.d.ts +63 -0
- package/dist/capabilities/get-shipment-details.d.ts.map +1 -0
- package/dist/capabilities/get-shipment-details.js +97 -0
- package/dist/capabilities/index.d.ts +10 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +9 -0
- package/dist/capabilities/label.d.ts +33 -0
- package/dist/capabilities/label.d.ts.map +1 -0
- package/dist/capabilities/label.js +328 -0
- package/dist/capabilities/parcels.d.ts +33 -0
- package/dist/capabilities/parcels.d.ts.map +1 -0
- package/dist/capabilities/parcels.js +284 -0
- package/dist/capabilities/pickup-points.d.ts +41 -0
- package/dist/capabilities/pickup-points.d.ts.map +1 -0
- package/dist/capabilities/pickup-points.js +294 -0
- package/dist/capabilities/track.d.ts +72 -0
- package/dist/capabilities/track.d.ts.map +1 -0
- package/dist/capabilities/track.js +331 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +142 -0
- package/dist/mappers/label.d.ts +67 -0
- package/dist/mappers/label.d.ts.map +1 -0
- package/dist/mappers/label.js +83 -0
- package/dist/mappers/shipment.d.ts +110 -0
- package/dist/mappers/shipment.d.ts.map +1 -0
- package/dist/mappers/shipment.js +258 -0
- package/dist/mappers/tracking.d.ts +60 -0
- package/dist/mappers/tracking.d.ts.map +1 -0
- package/dist/mappers/tracking.js +187 -0
- package/dist/utils/httpUtils.d.ts +36 -0
- package/dist/utils/httpUtils.d.ts.map +1 -0
- package/dist/utils/httpUtils.js +76 -0
- package/dist/utils/oauthFallback.d.ts +47 -0
- package/dist/utils/oauthFallback.d.ts.map +1 -0
- package/dist/utils/oauthFallback.js +250 -0
- package/dist/utils/resolveBaseUrl.d.ts +75 -0
- package/dist/utils/resolveBaseUrl.d.ts.map +1 -0
- package/dist/utils/resolveBaseUrl.js +65 -0
- package/dist/validation.d.ts +1890 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +726 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @shopickup/adapters-mpl
|
|
2
|
+
|
|
3
|
+
MPL adapter for Shopickup.
|
|
4
|
+
|
|
5
|
+
[GitHub repo](https://github.com/shopickup/shopickup-integration-layer)
|
|
6
|
+
[Issues](https://github.com/shopickup/shopickup-integration-layer/issues)
|
|
7
|
+
|
|
8
|
+
## What it does
|
|
9
|
+
|
|
10
|
+
- `CREATE_PARCEL`
|
|
11
|
+
- `CREATE_PARCELS`
|
|
12
|
+
- `CREATE_LABEL`
|
|
13
|
+
- `CREATE_LABELS`
|
|
14
|
+
- `LIST_PICKUP_POINTS`
|
|
15
|
+
- OAuth/basic auth exchange helpers
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @shopickup/adapters-mpl @shopickup/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { MPLAdapter } from '@shopickup/adapters-mpl';
|
|
27
|
+
import { createAxiosHttpClient } from '@shopickup/core';
|
|
28
|
+
|
|
29
|
+
const adapter = new MPLAdapter();
|
|
30
|
+
const http = createAxiosHttpClient();
|
|
31
|
+
|
|
32
|
+
const result = await adapter.exchangeAuthToken(
|
|
33
|
+
{
|
|
34
|
+
credentials: { apiKey: 'your-api-key', apiSecret: 'your-api-secret' },
|
|
35
|
+
options: { useTestApi: true },
|
|
36
|
+
},
|
|
37
|
+
{ http, logger: console }
|
|
38
|
+
);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Status
|
|
42
|
+
|
|
43
|
+
Published as `0.x.x` while the adapter API is still evolving.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPL OAuth Token Exchange Capability
|
|
3
|
+
*
|
|
4
|
+
* Exchanges Basic auth credentials (apiKey + apiSecret) for an OAuth2 Bearer token.
|
|
5
|
+
*
|
|
6
|
+
* This capability allows integrators to:
|
|
7
|
+
* 1. Proactively exchange credentials for OAuth tokens
|
|
8
|
+
* 2. Cache tokens on their side (Redis, database, etc.)
|
|
9
|
+
* 3. Pass OAuth tokens on subsequent API calls instead of Basic auth
|
|
10
|
+
*
|
|
11
|
+
* Use cases:
|
|
12
|
+
* - Integrator wants to avoid Basic auth if disabled at account level
|
|
13
|
+
* - Integrator wants to cache tokens to reduce network calls
|
|
14
|
+
* - Integrator wants to manage token lifecycle explicitly
|
|
15
|
+
*/
|
|
16
|
+
import type { AdapterContext } from "@shopickup/core";
|
|
17
|
+
import type { ResolveOAuthUrl } from "../utils/resolveBaseUrl.js";
|
|
18
|
+
import { type ExchangeAuthTokenRequest, type ExchangeAuthTokenResponse } from "../validation.js";
|
|
19
|
+
/**
|
|
20
|
+
* Exchange Basic auth credentials for an OAuth2 Bearer token
|
|
21
|
+
*
|
|
22
|
+
* POST to /oauth2/token with:
|
|
23
|
+
* - Authorization: Basic <base64(apiKey:apiSecret)>
|
|
24
|
+
* - Content-Type: application/x-www-form-urlencoded
|
|
25
|
+
* - Body: grant_type=client_credentials
|
|
26
|
+
* - X-Accounting-Code header (required by MPL)
|
|
27
|
+
* - X-Request-ID header (for tracking)
|
|
28
|
+
*
|
|
29
|
+
* Returns token with expiry information (typically 1 hour / 3600 seconds)
|
|
30
|
+
*
|
|
31
|
+
* @param req Request with apiKey + apiSecret credentials
|
|
32
|
+
* @param ctx Adapter context with HTTP client and logger
|
|
33
|
+
* @param resolveOAuthUrl Function to resolve OAuth endpoint URL (test vs. production)
|
|
34
|
+
* @param accountingCode Customer accounting code from MPL
|
|
35
|
+
* @returns ExchangeAuthTokenResponse with access_token, expires_in, etc.
|
|
36
|
+
* @throws CarrierError on invalid credentials or network failure
|
|
37
|
+
*/
|
|
38
|
+
export declare function exchangeAuthToken(req: ExchangeAuthTokenRequest, ctx: AdapterContext, resolveOAuthUrl: ResolveOAuthUrl, accountingCode: string): Promise<ExchangeAuthTokenResponse>;
|
|
39
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/capabilities/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAG/B,MAAM,kBAAkB,CAAC;AAG1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,wBAAwB,EAC7B,GAAG,EAAE,cAAc,EACnB,eAAe,EAAE,eAAe,EAChC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,yBAAyB,CAAC,CAuKpC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPL OAuth Token Exchange Capability
|
|
3
|
+
*
|
|
4
|
+
* Exchanges Basic auth credentials (apiKey + apiSecret) for an OAuth2 Bearer token.
|
|
5
|
+
*
|
|
6
|
+
* This capability allows integrators to:
|
|
7
|
+
* 1. Proactively exchange credentials for OAuth tokens
|
|
8
|
+
* 2. Cache tokens on their side (Redis, database, etc.)
|
|
9
|
+
* 3. Pass OAuth tokens on subsequent API calls instead of Basic auth
|
|
10
|
+
*
|
|
11
|
+
* Use cases:
|
|
12
|
+
* - Integrator wants to avoid Basic auth if disabled at account level
|
|
13
|
+
* - Integrator wants to cache tokens to reduce network calls
|
|
14
|
+
* - Integrator wants to manage token lifecycle explicitly
|
|
15
|
+
*/
|
|
16
|
+
import { CarrierError, safeLog, createLogEntry, serializeForLog } from "@shopickup/core";
|
|
17
|
+
import { safeValidateExchangeAuthTokenRequest, isGatewayError, } from "../validation.js";
|
|
18
|
+
import { buildMPLHeaders } from "../utils/httpUtils.js";
|
|
19
|
+
/**
|
|
20
|
+
* Exchange Basic auth credentials for an OAuth2 Bearer token
|
|
21
|
+
*
|
|
22
|
+
* POST to /oauth2/token with:
|
|
23
|
+
* - Authorization: Basic <base64(apiKey:apiSecret)>
|
|
24
|
+
* - Content-Type: application/x-www-form-urlencoded
|
|
25
|
+
* - Body: grant_type=client_credentials
|
|
26
|
+
* - X-Accounting-Code header (required by MPL)
|
|
27
|
+
* - X-Request-ID header (for tracking)
|
|
28
|
+
*
|
|
29
|
+
* Returns token with expiry information (typically 1 hour / 3600 seconds)
|
|
30
|
+
*
|
|
31
|
+
* @param req Request with apiKey + apiSecret credentials
|
|
32
|
+
* @param ctx Adapter context with HTTP client and logger
|
|
33
|
+
* @param resolveOAuthUrl Function to resolve OAuth endpoint URL (test vs. production)
|
|
34
|
+
* @param accountingCode Customer accounting code from MPL
|
|
35
|
+
* @returns ExchangeAuthTokenResponse with access_token, expires_in, etc.
|
|
36
|
+
* @throws CarrierError on invalid credentials or network failure
|
|
37
|
+
*/
|
|
38
|
+
export async function exchangeAuthToken(req, ctx, resolveOAuthUrl, accountingCode) {
|
|
39
|
+
if (!ctx.http) {
|
|
40
|
+
throw new CarrierError("HTTP client not provided in adapter context", "Permanent", { raw: "Missing ctx.http" });
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
// Validate request format and credentials
|
|
44
|
+
const validated = safeValidateExchangeAuthTokenRequest(req);
|
|
45
|
+
if (!validated.success) {
|
|
46
|
+
// Extract detailed error messages from Zod validation
|
|
47
|
+
const errors = validated.error.issues.map((issue) => {
|
|
48
|
+
const path = issue.path.length > 0 ? `${issue.path.join('.')}` : 'root';
|
|
49
|
+
return `${path}: ${issue.message}`;
|
|
50
|
+
}).join('; ');
|
|
51
|
+
throw new CarrierError(`Invalid request: ${errors}`, "Validation", { raw: serializeForLog(validated.error) });
|
|
52
|
+
}
|
|
53
|
+
// Only apiKey credentials can be exchanged
|
|
54
|
+
if (validated.data.credentials.authType !== 'apiKey') {
|
|
55
|
+
throw new CarrierError("exchangeAuthToken requires apiKey credentials (apiKey + apiSecret), not oauth2 token", "Validation");
|
|
56
|
+
}
|
|
57
|
+
const useTestApi = validated.data.options?.useTestApi ?? false;
|
|
58
|
+
const oauthUrl = resolveOAuthUrl(validated.data.options);
|
|
59
|
+
safeLog(ctx.logger, 'debug', 'Exchanging Basic auth credentials for OAuth token', createLogEntry({ useTestApi, endpoint: '/oauth2/token' }, null, ctx, ['exchangeAuthToken']), ctx, ['exchangeAuthToken']);
|
|
60
|
+
// Make the OAuth token exchange request
|
|
61
|
+
// Body: application/x-www-form-urlencoded with grant_type=client_credentials
|
|
62
|
+
const httpResponse = await ctx.http.post(oauthUrl, new URLSearchParams({
|
|
63
|
+
grant_type: 'client_credentials',
|
|
64
|
+
}).toString(), {
|
|
65
|
+
headers: {
|
|
66
|
+
...buildMPLHeaders(validated.data.credentials, accountingCode),
|
|
67
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// Handle non-200 responses (auth failures)
|
|
71
|
+
if (httpResponse.status !== 200) {
|
|
72
|
+
const body = httpResponse.body;
|
|
73
|
+
if (isGatewayError(body)) {
|
|
74
|
+
const faultString = body.fault?.faultstring || 'Unknown error';
|
|
75
|
+
const errorCode = body.fault?.detail?.errorcode || 'UNKNOWN';
|
|
76
|
+
ctx.logger?.warn("OAuth token exchange failed", {
|
|
77
|
+
status: httpResponse.status,
|
|
78
|
+
errorCode,
|
|
79
|
+
faultString,
|
|
80
|
+
});
|
|
81
|
+
throw new CarrierError(`OAuth token exchange failed: ${faultString} (${errorCode})`, "Auth", {
|
|
82
|
+
carrierCode: errorCode,
|
|
83
|
+
raw: body,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Unexpected response format for error status
|
|
88
|
+
throw new CarrierError(`OAuth token exchange returned status ${httpResponse.status} with unexpected response format`, "Transient", { raw: body });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Validate 200 response structure
|
|
92
|
+
const body = httpResponse.body;
|
|
93
|
+
if (!body || typeof body !== 'object' || !('access_token' in body)) {
|
|
94
|
+
throw new CarrierError("Invalid OAuth token response: missing access_token", "Permanent", { raw: body });
|
|
95
|
+
}
|
|
96
|
+
const tokenResponse = body;
|
|
97
|
+
if (!tokenResponse.access_token || typeof tokenResponse.access_token !== 'string') {
|
|
98
|
+
throw new CarrierError("Invalid OAuth token response: access_token is not a string", "Permanent", { raw: tokenResponse });
|
|
99
|
+
}
|
|
100
|
+
if (!tokenResponse.expires_in || typeof tokenResponse.expires_in !== 'number') {
|
|
101
|
+
throw new CarrierError("Invalid OAuth token response: expires_in is not a number", "Permanent", { raw: tokenResponse });
|
|
102
|
+
}
|
|
103
|
+
const result = {
|
|
104
|
+
access_token: tokenResponse.access_token,
|
|
105
|
+
token_type: tokenResponse.token_type || 'Bearer',
|
|
106
|
+
expires_in: tokenResponse.expires_in,
|
|
107
|
+
issued_at: tokenResponse.issued_at,
|
|
108
|
+
raw: tokenResponse,
|
|
109
|
+
};
|
|
110
|
+
safeLog(ctx.logger, 'info', 'OAuth token exchanged successfully', {
|
|
111
|
+
expiresIn: result.expires_in,
|
|
112
|
+
tokenType: result.token_type,
|
|
113
|
+
useTestApi,
|
|
114
|
+
}, ctx, ['exchangeAuthToken']);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
// Handle caught CarrierErrors
|
|
119
|
+
if (err instanceof CarrierError) {
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
// Convert unknown errors to CarrierError
|
|
123
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
124
|
+
ctx.logger?.error("Failed to exchange OAuth token", {
|
|
125
|
+
error: errorMessage,
|
|
126
|
+
type: err instanceof Error ? err.constructor.name : typeof err,
|
|
127
|
+
});
|
|
128
|
+
throw new CarrierError(`Failed to exchange OAuth token: ${errorMessage}`, "Transient", { raw: err });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AdapterContext, CloseShipmentsRequest, CloseShipmentsResponse } from '@shopickup/core';
|
|
2
|
+
import { ResolveBaseUrl } from '../utils/resolveBaseUrl.js';
|
|
3
|
+
/**
|
|
4
|
+
* Close shipments for MPL
|
|
5
|
+
* Accepts core CloseShipmentsRequest-like shape but uses MPL-specific schema
|
|
6
|
+
*/
|
|
7
|
+
export declare function closeShipments(req: CloseShipmentsRequest | unknown, ctx: AdapterContext, resolveBaseUrl: ResolveBaseUrl): Promise<CloseShipmentsResponse>;
|
|
8
|
+
//# sourceMappingURL=close.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/capabilities/close.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAIrG,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,qBAAqB,GAAG,OAAO,EACpC,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,sBAAsB,CAAC,CAyEjC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CarrierError, serializeForLog } from '@shopickup/core';
|
|
2
|
+
import { safeValidateCloseShipmentsRequest } from '../validation.js';
|
|
3
|
+
import { buildMPLHeaders } from '../utils/httpUtils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Close shipments for MPL
|
|
6
|
+
* Accepts core CloseShipmentsRequest-like shape but uses MPL-specific schema
|
|
7
|
+
*/
|
|
8
|
+
export async function closeShipments(req, ctx, resolveBaseUrl) {
|
|
9
|
+
try {
|
|
10
|
+
// Validate full request envelope against MPL-shaped schema if possible
|
|
11
|
+
const validatedReq = safeValidateCloseShipmentsRequest(req);
|
|
12
|
+
if (!validatedReq.success) {
|
|
13
|
+
throw new CarrierError('Invalid close shipments request', 'Validation', { raw: validatedReq });
|
|
14
|
+
}
|
|
15
|
+
const validated = validatedReq.data;
|
|
16
|
+
// MPL expects trackingNumbers array + mpl accounting code under options
|
|
17
|
+
const body = {
|
|
18
|
+
fromDate: validated.close?.fromDate ?? undefined,
|
|
19
|
+
toDate: validated.close?.toDate ?? undefined,
|
|
20
|
+
trackingNumbers: validated.trackingNumbers ?? validated.close?.trackingNumbers,
|
|
21
|
+
checkList: validated.close?.checkList,
|
|
22
|
+
checkListWithPrice: validated.close?.checkListWithPrice,
|
|
23
|
+
tag: validated.close?.tag,
|
|
24
|
+
requestId: validated.close?.requestId,
|
|
25
|
+
summaryList: validated.close?.summaryList,
|
|
26
|
+
singleFile: validated.close?.singleFile,
|
|
27
|
+
};
|
|
28
|
+
if (!ctx.http) {
|
|
29
|
+
throw new CarrierError('HTTP client not provided in context', 'Permanent');
|
|
30
|
+
}
|
|
31
|
+
// Resolve base URL (supports useTestApi via options.useTestApi)
|
|
32
|
+
const baseUrl = resolveBaseUrl({ useTestApi: (validated.options?.useTestApi) ?? false });
|
|
33
|
+
const url = `${baseUrl}/shipments/close`;
|
|
34
|
+
const accountingCode = validated.options?.mpl?.accountingCode;
|
|
35
|
+
// Build headers (uses validated credentials + accounting code)
|
|
36
|
+
const headers = buildMPLHeaders(validated.credentials, accountingCode ?? '');
|
|
37
|
+
ctx.logger?.debug('MPL: Closing shipments', { trackingNumbers: (body.trackingNumbers || []).length });
|
|
38
|
+
const httpRes = await ctx.http.post(url, body, { headers });
|
|
39
|
+
const parsed = httpRes.body;
|
|
40
|
+
// If response is array of ShipmentCloseResult per OpenAPI, normalize
|
|
41
|
+
const results = Array.isArray(parsed) ? parsed : [parsed];
|
|
42
|
+
// Build core CloseShipmentsResponse
|
|
43
|
+
const closeResults = results.map((r) => ({
|
|
44
|
+
manifestId: r.dispatchId?.toString?.() ?? undefined,
|
|
45
|
+
manifest: r.manifest ?? r.manifestSUM ?? r.manifestRA ?? undefined,
|
|
46
|
+
errors: r.errors,
|
|
47
|
+
warnings: r.warnings,
|
|
48
|
+
raw: r,
|
|
49
|
+
}));
|
|
50
|
+
const successCount = closeResults.filter((c) => !c.errors || c.errors.length === 0).length;
|
|
51
|
+
const failureCount = closeResults.length - successCount;
|
|
52
|
+
return {
|
|
53
|
+
results: closeResults,
|
|
54
|
+
successCount,
|
|
55
|
+
failureCount,
|
|
56
|
+
totalCount: closeResults.length,
|
|
57
|
+
allSucceeded: failureCount === 0 && closeResults.length > 0,
|
|
58
|
+
allFailed: successCount === 0 && closeResults.length > 0,
|
|
59
|
+
someFailed: failureCount > 0 && successCount > 0,
|
|
60
|
+
summary: `${successCount} manifests generated, ${failureCount} failed`,
|
|
61
|
+
rawCarrierResponse: serializeForLog(httpRes),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof CarrierError)
|
|
66
|
+
throw err;
|
|
67
|
+
ctx.logger?.error('MPL: Close shipments failed', { error: err?.message });
|
|
68
|
+
throw new CarrierError(`Close shipments failed: ${err?.message ?? 'Unknown'}`, 'Transient', { raw: serializeForLog(err) });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPL Adapter: Get Shipment Details Capability
|
|
3
|
+
* Handles GET_SHIPMENT_DETAILS operation via GET /shipments/{trackingNumber}
|
|
4
|
+
*
|
|
5
|
+
* Retrieves shipment metadata including sender, recipient, items, and shipment state.
|
|
6
|
+
* Note: This returns shipment details/metadata, not tracking event history.
|
|
7
|
+
*/
|
|
8
|
+
import type { AdapterContext } from '@shopickup/core';
|
|
9
|
+
import type { ResolveBaseUrl } from '../utils/resolveBaseUrl.js';
|
|
10
|
+
/**
|
|
11
|
+
* Response type for GET_SHIPMENT_DETAILS operation
|
|
12
|
+
* Contains normalized shipment metadata from MPL API
|
|
13
|
+
*/
|
|
14
|
+
export interface ShipmentDetailsResponse {
|
|
15
|
+
/** Tracking/shipment number */
|
|
16
|
+
trackingNumber?: string;
|
|
17
|
+
/** Order ID if applicable */
|
|
18
|
+
orderId?: string;
|
|
19
|
+
/** Shipment date if available */
|
|
20
|
+
shipmentDate?: string;
|
|
21
|
+
/** Sender details */
|
|
22
|
+
sender?: {
|
|
23
|
+
name?: string;
|
|
24
|
+
street?: string;
|
|
25
|
+
city?: string;
|
|
26
|
+
postalCode?: string;
|
|
27
|
+
country?: string;
|
|
28
|
+
phone?: string;
|
|
29
|
+
};
|
|
30
|
+
/** Recipient details */
|
|
31
|
+
recipient?: {
|
|
32
|
+
name?: string;
|
|
33
|
+
street?: string;
|
|
34
|
+
city?: string;
|
|
35
|
+
postalCode?: string;
|
|
36
|
+
country?: string;
|
|
37
|
+
phone?: string;
|
|
38
|
+
};
|
|
39
|
+
/** Items in shipment */
|
|
40
|
+
items?: Array<{
|
|
41
|
+
id?: string;
|
|
42
|
+
weight?: number;
|
|
43
|
+
[key: string]: any;
|
|
44
|
+
}>;
|
|
45
|
+
/** Full raw response from MPL API */
|
|
46
|
+
raw: any;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get shipment details by tracking number via GET /shipments/{trackingNumber}
|
|
50
|
+
*
|
|
51
|
+
* Returns shipment metadata (sender, recipient, items, dates).
|
|
52
|
+
* This is NOT a tracking operation - it retrieves shipment state and details.
|
|
53
|
+
*
|
|
54
|
+
* To use test API, pass in request with options.useTestApi = true
|
|
55
|
+
*/
|
|
56
|
+
export declare function getShipmentDetails(req: {
|
|
57
|
+
trackingNumber: string;
|
|
58
|
+
credentials?: any;
|
|
59
|
+
options?: {
|
|
60
|
+
useTestApi?: boolean;
|
|
61
|
+
};
|
|
62
|
+
}, ctx: AdapterContext, resolveBaseUrl: ResolveBaseUrl): Promise<ShipmentDetailsResponse>;
|
|
63
|
+
//# sourceMappingURL=get-shipment-details.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-shipment-details.d.ts","sourceRoot":"","sources":["../../src/capabilities/get-shipment-details.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,+BAA+B;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,wBAAwB;IACxB,SAAS,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,wBAAwB;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC,CAAC;IACH,qCAAqC;IACrC,GAAG,EAAE,GAAG,CAAC;CACV;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACpC,EACD,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,uBAAuB,CAAC,CAmHlC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPL Adapter: Get Shipment Details Capability
|
|
3
|
+
* Handles GET_SHIPMENT_DETAILS operation via GET /shipments/{trackingNumber}
|
|
4
|
+
*
|
|
5
|
+
* Retrieves shipment metadata including sender, recipient, items, and shipment state.
|
|
6
|
+
* Note: This returns shipment details/metadata, not tracking event history.
|
|
7
|
+
*/
|
|
8
|
+
import { CarrierError, serializeForLog, errorToLog } from '@shopickup/core';
|
|
9
|
+
import { safeValidateGetShipmentDetailsRequest, safeValidateShipmentQueryResponse } from '../validation.js';
|
|
10
|
+
import { buildMPLHeaders } from '../utils/httpUtils.js';
|
|
11
|
+
/**
|
|
12
|
+
* Get shipment details by tracking number via GET /shipments/{trackingNumber}
|
|
13
|
+
*
|
|
14
|
+
* Returns shipment metadata (sender, recipient, items, dates).
|
|
15
|
+
* This is NOT a tracking operation - it retrieves shipment state and details.
|
|
16
|
+
*
|
|
17
|
+
* To use test API, pass in request with options.useTestApi = true
|
|
18
|
+
*/
|
|
19
|
+
export async function getShipmentDetails(req, ctx, resolveBaseUrl) {
|
|
20
|
+
try {
|
|
21
|
+
// Validate request format and credentials
|
|
22
|
+
const validated = safeValidateGetShipmentDetailsRequest(req);
|
|
23
|
+
if (!validated.success) {
|
|
24
|
+
throw new CarrierError(`Invalid request: ${validated.error.message}`, 'Validation', { raw: serializeForLog(validated.error) });
|
|
25
|
+
}
|
|
26
|
+
if (!ctx.http) {
|
|
27
|
+
throw new CarrierError('HTTP client not provided in context', 'Permanent');
|
|
28
|
+
}
|
|
29
|
+
// Extract accountingCode from credentials (required for MPL)
|
|
30
|
+
const accountingCode = validated.data.credentials?.accountingCode;
|
|
31
|
+
if (!accountingCode) {
|
|
32
|
+
throw new CarrierError('accountingCode is required in credentials', 'Validation');
|
|
33
|
+
}
|
|
34
|
+
// Extract useTestApi from validated request
|
|
35
|
+
const useTestApi = validated.data.options?.useTestApi ?? false;
|
|
36
|
+
const baseUrl = resolveBaseUrl(validated.data.options);
|
|
37
|
+
const trackingNumber = validated.data.trackingNumber;
|
|
38
|
+
ctx.logger?.debug('MPL: Getting shipment details', {
|
|
39
|
+
trackingNumber,
|
|
40
|
+
testMode: useTestApi,
|
|
41
|
+
});
|
|
42
|
+
// Get shipment details via GET /shipments/{trackingNumber} endpoint
|
|
43
|
+
const url = `${baseUrl}/shipments/${encodeURIComponent(trackingNumber)}`;
|
|
44
|
+
const httpResponse = await ctx.http.get(url, {
|
|
45
|
+
headers: buildMPLHeaders(validated.data.credentials, accountingCode),
|
|
46
|
+
});
|
|
47
|
+
// Extract body from normalized HttpResponse
|
|
48
|
+
const response = httpResponse.body;
|
|
49
|
+
// Validate response against Zod schema
|
|
50
|
+
const responseValidation = safeValidateShipmentQueryResponse(response);
|
|
51
|
+
if (!responseValidation.success) {
|
|
52
|
+
throw new CarrierError(`Invalid shipment response: ${responseValidation.error.message}`, 'Validation', { raw: serializeForLog(responseValidation.error) });
|
|
53
|
+
}
|
|
54
|
+
const validatedResponse = responseValidation.data;
|
|
55
|
+
// Check for errors in response
|
|
56
|
+
if (validatedResponse.errors && validatedResponse.errors.length > 0) {
|
|
57
|
+
const errorMsg = validatedResponse.errors
|
|
58
|
+
.map((e) => e.text || e.code)
|
|
59
|
+
.join('; ');
|
|
60
|
+
const notFound = validatedResponse.errors.some((e) => {
|
|
61
|
+
const code = String(e.code || '').toUpperCase();
|
|
62
|
+
const text = String(e.text || '').toLowerCase();
|
|
63
|
+
return code.includes('NOT_FOUND') || text.includes('not found');
|
|
64
|
+
});
|
|
65
|
+
throw new CarrierError(`MPL error: ${errorMsg}`, notFound ? 'NotFound' : 'Validation', { raw: validatedResponse.errors });
|
|
66
|
+
}
|
|
67
|
+
if (!validatedResponse.shipment) {
|
|
68
|
+
throw new CarrierError(`No shipment found for tracking number ${trackingNumber}`, 'NotFound');
|
|
69
|
+
}
|
|
70
|
+
const shipment = validatedResponse.shipment;
|
|
71
|
+
ctx.logger?.info('MPL: Shipment details retrieved', {
|
|
72
|
+
trackingNumber,
|
|
73
|
+
itemCount: shipment.items?.length || 0,
|
|
74
|
+
testMode: useTestApi,
|
|
75
|
+
});
|
|
76
|
+
// Return shipment details normalized to response type
|
|
77
|
+
return {
|
|
78
|
+
trackingNumber: shipment.trackingNumber,
|
|
79
|
+
orderId: shipment.orderId,
|
|
80
|
+
shipmentDate: shipment.shipmentDate,
|
|
81
|
+
sender: shipment.sender,
|
|
82
|
+
recipient: shipment.recipient,
|
|
83
|
+
items: shipment.items,
|
|
84
|
+
raw: validatedResponse,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error instanceof CarrierError) {
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
ctx.logger?.error('MPL: Error getting shipment details', {
|
|
92
|
+
trackingNumber: req.trackingNumber,
|
|
93
|
+
error: errorToLog(error),
|
|
94
|
+
});
|
|
95
|
+
throw new CarrierError(`Failed to get shipment details: ${error?.message || "Unknown error"}`, "Transient", { raw: serializeForLog(error) });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foxpost Adapter: Capabilities Export
|
|
3
|
+
* Re-exports all capability methods for clean imports
|
|
4
|
+
*/
|
|
5
|
+
export { createParcel, createParcels } from './parcels.js';
|
|
6
|
+
export { createLabel, createLabels } from './label.js';
|
|
7
|
+
export { fetchPickupPoints } from './pickup-points.js';
|
|
8
|
+
export { exchangeAuthToken } from './auth.js';
|
|
9
|
+
export { closeShipments } from './close.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capabilities/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foxpost Adapter: Capabilities Export
|
|
3
|
+
* Re-exports all capability methods for clean imports
|
|
4
|
+
*/
|
|
5
|
+
export { createParcel, createParcels } from './parcels.js';
|
|
6
|
+
export { createLabel, createLabels } from './label.js';
|
|
7
|
+
export { fetchPickupPoints } from './pickup-points.js';
|
|
8
|
+
export { exchangeAuthToken } from './auth.js';
|
|
9
|
+
export { closeShipments } from './close.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPL Adapter: Label Generation Capability
|
|
3
|
+
* Handles CREATE_LABEL and CREATE_LABELS operations
|
|
4
|
+
*
|
|
5
|
+
* Key differences from Foxpost:
|
|
6
|
+
* - Uses GET request instead of POST
|
|
7
|
+
* - Returns JSON array with base64-encoded label data
|
|
8
|
+
* - Multiple query parameters including labelType, labelFormat, orderBy, singleFile
|
|
9
|
+
* - Per-item error handling in the response array
|
|
10
|
+
*/
|
|
11
|
+
import type { AdapterContext, CreateLabelResponse, CreateLabelsResponse } from "@shopickup/core";
|
|
12
|
+
import type { CreateLabelMPLRequest, CreateLabelsMPLRequest } from '../validation.js';
|
|
13
|
+
import type { ResolveBaseUrl } from '../utils/resolveBaseUrl.js';
|
|
14
|
+
/**
|
|
15
|
+
* Create a label (generate PDF) for a single parcel
|
|
16
|
+
* Delegates to createLabels to reuse batching logic
|
|
17
|
+
*
|
|
18
|
+
* Returns Promise<LabelResult> with file mapping and metadata
|
|
19
|
+
*/
|
|
20
|
+
export declare function createLabel(req: CreateLabelMPLRequest, ctx: AdapterContext, resolveBaseUrl: ResolveBaseUrl): Promise<CreateLabelResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* Create labels for multiple parcels in one call
|
|
23
|
+
*
|
|
24
|
+
* MPL GET /shipments/label endpoint:
|
|
25
|
+
* - Takes array of tracking numbers (query params)
|
|
26
|
+
* - Optional labelType, labelFormat, orderBy, singleFile params
|
|
27
|
+
* - Returns JSON array of LabelQueryResult objects with base64-encoded label data
|
|
28
|
+
* - Each result has trackingNumber, label (base64), errors/warnings arrays
|
|
29
|
+
*
|
|
30
|
+
* Returns structured response with files array and per-item results
|
|
31
|
+
*/
|
|
32
|
+
export declare function createLabels(req: CreateLabelsMPLRequest, ctx: AdapterContext, resolveBaseUrl: ResolveBaseUrl): Promise<CreateLabelsResponse>;
|
|
33
|
+
//# sourceMappingURL=label.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../src/capabilities/label.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAEV,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EAGrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAkB,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACtG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAoEjE;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,qBAAqB,EAC1B,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAiD9B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,sBAAsB,EAC3B,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAmP/B"}
|