TypeScript SDK
Package: @msv3/sdk
Das TypeScript SDK ist der primaere Weg, das MSV3 Smart Gateway in Node.js- und Browser-kompatiblen Umgebungen zu integrieren.
Installation
npm install @msv3/sdk Erfordert Node.js 18+ (nutzt die native fetch-API). Funktioniert mit TypeScript 5+ und JavaScript ESM/CommonJS.
Grundeinrichtung
import { Msv3Client } from '@msv3/sdk';
const client = new Msv3Client({
apiKey: process.env.MSV3_API_KEY!, // 'pk_live_...' oder 'pk_test_...'
}); | Option | Typ | Standard | Beschreibung |
|---|---|---|---|
apiKey | string | erforderlich | Ihr Gateway-API-Schluessel |
baseUrl | string | https://api.msv3gateway.example.com | Ueberschreibung fuer Tests |
timeout | number | 30000 | Anfrage-Timeout in Millisekunden |
Zugangsdaten
MSV3-Zugangsdaten (Grosshaendler-Benutzername + Passwort) werden pro Aufruf uebergeben, nicht im Client gespeichert.
import type { Msv3Credentials } from '@msv3/sdk';
const creds: Msv3Credentials = {
wholesaler: 'noweda',
username: process.env.MSV3_USERNAME!,
password: process.env.MSV3_PASSWORD!,
}; Ressourcen
| Ressource | Eigenschaft | Beschreibung |
|---|---|---|
ConnectionResource | client.connection | Grosshaendler-Konnektivitaet testen |
WholesalersResource | client.wholesalers | Grosshaendler auflisten und registrieren |
AvailabilityResource | client.availability | Echtzeit-Bestandsabfragen |
OrdersResource | client.orders | Bestellungen aufgeben und verfolgen |
ContractsResource | client.contracts | Vertragsdaten und Lieferfenster |
DeliveriesResource | client.deliveries | Lieferbenachrichtigungen |
ReturnsResource | client.returns | Retouren-Autorisierung und Ankuendigungen |
DocumentsResource | client.documents | PDF-Dokumente herunterladen |
WebhooksResource | client.webhooks | Webhook-Abonnement-Verwaltung |
Verbindung testen
const result = await client.connection.test(creds);
// result.connected → true
// result.wholesaler → 'noweda'
// result.latency_ms → 142 Verfuegbarkeit
client.availability.check(creds, body)
const result = await client.availability.check(creds, {
items: [
{ pzn: '761271', quantity: 5 },
{ pzn: '10203595', quantity: 1, demand_type: 'direct' },
],
});
for (const item of result.items) {
if (item.available) {
const d = item.deliveries[0];
console.log(`PZN ${item.pzn}: ${d.quantity} Stueck, ETA ${d.estimated_at}`);
}
if (item.substitution) {
console.log(` Ersetzt durch PZN ${item.substitution.replacement_pzn}`);
}
} client.availability.bulk(creds, body)
const { available_pzns } = await client.availability.bulk(creds, {
pzns: ['761271', '4211896', '10203595', '99999999'],
});
console.log('Auf Lager:', available_pzns); Bestellungen
client.orders.create(creds, body, options?)
const order = await client.orders.create(creds, {
items: [
{ pzn: '761271', quantity: 3, delivery_preference: 'backorder' },
{ pzn: '10203595', quantity: 1 },
],
});
console.log(`Status: ${order.status}`); // 'confirmed'
console.log(`Request ID: ${order.request_id}`);
for (const item of order.items) {
console.log(`PZN ${item.pzn}: ${item.quantity_confirmed}/${item.quantity_ordered}`);
} Liefervorgaben: 'normal' (Standard), 'backorder', 'grouped', 'disposition'
Dry Run
const order = await client.orders.create(
creds,
{ items: [...] },
{ dryRun: true },
);
// order.status === 'dry_run' Nachtmodus
if (order.status === 'queued_night_mode') {
console.log('Grosshaendler im Nachtmodus. Bestellung fuer naechstes Fenster vorgemerkt.');
} client.orders.status(creds, orderId)
const status = await client.orders.status(creds, order.request_id);
switch (status.status) {
case 'available':
console.log(`Bestellung ${status.order_id}: ${status.items.length} Artikel`);
break;
case 'expired':
console.log('Bestellantwort nicht mehr verfuegbar');
break;
case 'unknown':
console.log('Grosshaendler kennt diese Bestell-ID nicht');
break;
} Lieferungen
// Unbestaetigte Lieferavise abrufen
const { deliveries } = await client.deliveries.list(creds);
for (const delivery of deliveries) {
console.log(`Lieferung ${delivery.tracking_number} (${delivery.date})`);
for (const item of delivery.items) {
console.log(` PZN ${item.pzn}: ${item.quantity_delivered} geliefert`);
}
}
// Bestaetigen
const tracking_numbers = deliveries.map(d => d.tracking_number);
await client.deliveries.confirm(creds, { tracking_numbers }); Webhooks
// Webhook erstellen
const webhook = await client.webhooks.create({
url: 'https://example.com/hooks/msv3',
wholesaler: 'noweda',
events: ['delivery.received', 'order.status_changed'],
});
// WICHTIG: signing_secret jetzt speichern - wird nicht erneut angezeigt
console.log(`Webhook ID: ${webhook.id}`);
console.log(`Secret: ${webhook.signing_secret}`); Signatur-Verifizierung
import { verifyWebhookSignature } from '@msv3/sdk';
// Express-Beispiel
app.post('/hooks/msv3', (req, res) => {
const payload = req.body; // Roh-String - NICHT zuerst parsen
const signature = req.headers['x-msv3-signature'] ?? '';
const secret = process.env.MSV3_WEBHOOK_SECRET!;
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log(`Event: ${event.event} von ${event.wholesaler}`);
res.send('OK');
}); Fehlerbehandlung
import {
Msv3ApiError,
Msv3AuthError,
Msv3BadRequestError,
Msv3ProductUnavailableError,
Msv3RateLimitError,
Msv3WholesalerUnavailableError,
} from '@msv3/sdk';
try {
const order = await client.orders.create(creds, { items: [...] });
} catch (err) {
if (err instanceof Msv3AuthError) {
console.error('Authentifizierung fehlgeschlagen:', err.message);
} else if (err instanceof Msv3ProductUnavailableError) {
console.error(`Abgelehnt (Code ${err.code}):`, err.message);
} else if (err instanceof Msv3RateLimitError) {
const retryAfter = err.retryAfter ?? 60;
console.warn(`Rate-Limit. Erneut in ${retryAfter}s`);
} else if (err instanceof Msv3WholesalerUnavailableError) {
console.error(`Grosshaendler ${err.wholesaler} nicht erreichbar`);
} else if (err instanceof Msv3ApiError) {
console.error(`API-Fehler ${err.status}: ${err.message}`);
}
} | Eigenschaft | Typ | Beschreibung |
|---|---|---|
status | number | HTTP-Statuscode |
type | string | Maschinenlesbarer Fehlercode |
message | string | Menschenlesbare Beschreibung |
wholesaler | string | undefined | Grosshaendler-ID |
code | string | undefined | Roher MSV3-Fehlercode |
retryAfter | number | undefined | Wartezeit in Sekunden (nur 429) |
requestId | string | Request-ID aus dem X-Request-Id-Header |
Retry-Strategie
Das SDK wiederholt automatisch bei 503 mit exponentiellem Backoff (bis zu 3 Versuche, ab 500ms). Bei 4xx-Fehlern wird nicht wiederholt.
Fuer 502 (Grosshaendler nicht erreichbar) implementieren Sie eigene Retry-Logik:
async function createOrderWithRetry(
client: Msv3Client,
creds: Msv3Credentials,
items: any[],
maxAttempts = 3,
) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await client.orders.create(creds, { items });
} catch (err) {
if (err instanceof Msv3WholesalerUnavailableError && attempt < maxAttempts) {
const delay = 1000 * Math.pow(2, attempt - 1);
console.warn(`Versuch ${attempt} fehlgeschlagen, erneut in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
} else {
throw err;
}
}
}
} Vollstaendiges Beispiel
import { Msv3Client, Msv3ApiError } from '@msv3/sdk';
const client = new Msv3Client({ apiKey: process.env.MSV3_API_KEY! });
const creds = {
wholesaler: 'noweda',
username: process.env.MSV3_USERNAME!,
password: process.env.MSV3_PASSWORD!,
};
async function run() {
// 1. Verbindung testen
const connection = await client.connection.test(creds);
console.log(`Verbunden (${connection.latency_ms}ms)`);
// 2. Verfuegbarkeit pruefen
const availability = await client.availability.check(creds, {
items: [{ pzn: '761271', quantity: 3 }],
});
if (!availability.items[0].available) {
console.log('PZN 761271 nicht verfuegbar');
return;
}
// 3. Bestellung mit Nachlieferung aufgeben
const order = await client.orders.create(creds, {
items: [{ pzn: '761271', quantity: 3, delivery_preference: 'backorder' }],
});
console.log(`Bestellstatus: ${order.status}`);
console.log(`Request ID: ${order.request_id}`);
}
run().catch((err) => {
if (err instanceof Msv3ApiError) {
console.error(`MSV3-Fehler ${err.status}: ${err.message}`);
} else {
throw err;
}
});