משלוח אקספרס
ארבע נקודות קצה בקבוצת shipping מכסות שירותי אותו יום ואקספרס: הצעת מחיר, shipment promise של Wolt Drive, ושתי בדיקות זכאות ל-ZigZag Express 24.
אלה אינן workflow
כל אחת מהארבע מנותבת למחלקת שירות חברת השילוח של החברה של הרישיון עצמו, וכל אחת מיושמת על ידי חברת שילוח אחת בדיוק:
| נקודת קצה | מיושמת על ידי | המתודה שהיא דורשת |
|---|---|---|
same_day_delivery_quote | Get Package | getSameDayDeliveryQuote() |
create_shipping_promise | Wolt Drive | createShipmentPromise() |
get_express_delivery_enabled | ZigZag | getExpressDeliveryenabled() |
get_express_delivery_status | ZigZag | getExpressDeliveryStatus() |
רישיון שמחלקת שירות חברת השילוח שלו אינה מגדירה את המתודה של נקודת הקצה שאתם קוראים לה מקבל HTTP 400 ו-message: "Not supported for this provider" — ה-controller בודק עם method_exists() לפני כל דבר אחר, כולל לפני $request->validate(). לכן אין רצף של "בדיקת זמינות → הצעת מחיר → promise → סטטוס" שיש לעקוב אחריו: שתי בדיקות ה-ZigZag שייכות יחד, והצעת המחיר של Get Package וה-promise של Wolt Drive עומדות כל אחת בנפרד והן של חברות שילוח שונות.
ארבע אלה הן GET
בשונה משאר קבוצת ה-shipping של v1, שהיא POST עם גוף JSON, אלה מקבלות את כל הפרמטרים שלהן — כולל פרטי ההזדהות — כערכים במחרוזת השאילתה. שלחו אותם במחרוזת השאילתה — זו הצורה הנתמכת. גוף JSON נקרא גם הוא בנתיבי ה-GET האלה: שליחת כל הפרמטרים, כולל פרטי ההזדהות, כ-JSON עם Content-Type: application/json ועם מחרוזת שאילתה ריקה עובדת. גוף form-encoded אינו נקרא — הוא מחזיר 422.
אימות
כל הארבע יושבות מאחורי אותו middleware hasLicense כמו שאר הקבוצה, ולכן license_key ו-domain נדרשים בכל קריאה, כפרמטרי שאילתה. מפתח רישיון שלא ניתן לפענח מחזיר HTTP 403 עם המעטפת הסטנדרטית ועם message: "Invalid License Key" עוד לפני שה-handler רץ. ראו אימות.
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | מפתח הרישיון של חשבון חברת השילוח. |
domain | string | כן | הדומיין של החנות הקוראת. נדרש על ידי הוולידציה; הדומיין השמור של הרישיון עצמו אינו מושווה אליו כרגע. |
token | string | לא | Sanctum personal access token. HasLicense קורא ל-PersonalAccessToken::findToken() בכל בקשה; הוא משנה את התוצאה רק עבור רישיונות שהחברה שלהם מסומנת כפרימיום — בלי משתמש עם פרימיום פעיל הרישיונות האלה נדחים עם 403. |
כשלים מגיעים תמיד בסדר קבוע: 403 של דחיית רישיון, אחר כך 400 של בדיקת הספק, ואחר כך 422 של ולידציית הפרמטרים. מכיוון שבדיקת ה-method_exists() רצה לפני $request->validate(), קריאה על חברת שילוח שאינה נתמכת לא תוכל להחזיר 422 לעולם — על רישיון של Cargo, בקשה שנושאת רק פרטי הזדהות ובלי שום פרמטר אחר עונה 400 עם "Not supported for this provider". ההשלכה מעשית: אי אפשר לגלות את חוזה הפרמטרים של נקודת קצה על ידי גישושים מרישיון שחברת השילוח שלו אינה מיישמת אותה.
תשובות 422 בעמוד הזה אינן מעטפת ארבעת המפתחות של v1. הן בצורת הוולידציה של Laravel, {"message": ..., "errors": {...}}, וההודעות שלהן מתורגמות — בעברית במופעים הישראליים. license_key או domain חסר הוא אחד מה-422 האלה, שנזרק על ידי hasLicense; רק מפתח שאינו נפתר לשום רישיון מחזיר 403 עם המעטפת ועם "Invalid License Key".
חברת פרימיום נדחית עם 403 ועם אחת משתי הודעות מדויקות: "Premium companies are not supported in this version of plugin" כאשר לא ניתן היה לפענח משתמש מה-token, ו-"You must be a premium member to use this company" כאשר משתמש כן נפענח אך אינו פרימיום פעיל.
כתובות איסוף אינן מטופלות באופן אחיד בארבע נקודות הקצה. רק same_day_delivery_quote משתמשת בכתובת איסוף בכלל, והיא קוראת collect_street, collect_street_number ו-collect_city מההגדרות השמורות של הרישיון — ולא collect_company, שאף אחת מהארבע אינה קוראת. create_shipping_promise משתמשת ב-venue_id של Wolt מהרישיון במקום בכתובת, ולשתי נקודות הקצה של ZigZag אין מושג של כתובת איסוף כלל. גם לא נכון שכתובת האיסוף היא תמיד זו של הרישיון: כאשר לרישיון אין הגדרות איסוף שמורות, License::getCollectionSettings() מחזירה null-ים וה-handler נופל אל shipment_settings[collect_*] שסופק על ידי הקורא בבקשה, וזה מה שקובע.
GET /shipping/same_day_delivery_quote
קבלת מחיר למשלוח באותו יום דרך Get Package, מכתובת האיסוף של הרישיון לכתובת שאתם מספקים. שום דבר אינו מוזמן.
פרמטרים
שאילתה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. |
domain | string | כן | ראו אימות. |
city | string | כן | עיר היעד. |
street | string | כן | רחוב היעד. |
street_number | integer | כן | מספר הרחוב ביעד. עובר ולידציה כ-integer כאן (שלוש נקודות הקצה האחרות מקבלות מחרוזת). |
name | string | כן | שם הנמען. נדרש על ידי הוולידציה. |
phone | string | כן | טלפון הנמען. נדרש על ידי הוולידציה. |
date | string | כן | התאריך המבוקש, Y-m-d. נדרש על ידי הוולידציה. |
package_size | string | כן | אחד מ-0, 1, 2, 3, ENVELOPE, SMALL, MEDIUM, LARGE. |
TIP
name, phone ו-date נאכפים על ידי הוולידטור אך אינם מועברים ל-Get Package במימוש הנוכחי, ששולח רק את שתי הכתובות ואת גודל החבילה. שלחו בכל זאת ערכים אמיתיים — הוולידציה היא מה שהיא, וה-payload עשוי להתרחב.
package_size הוא פרמטר אמיתי, אבל package_id אינו יכול להיקבע על ידי הקורא: ה-handler מעביר הלאה רק קלט שעבר ולידציה, ולכן $data['package_id'] ?? Str::random() בשכבת חברת השילוח בוחר תמיד בענף הרנדומלי. אין לנסות לספק אותו.
איך הצעת המחיר נדחית
לפני מתן ההצעה, השירות מביא את רשימת היישובים של Get Package לשירות אותו יום (נשלפת עבור תל אביב, נשמרת במטמון ל-24 שעות) ובודק מולה את עיר האיסוף של הרישיון. עיר איסוף שאינה ברשימה מחזירה success: false עם message: "City {city} is not in location list".
בקשה לדוגמה
curl --location --get 'https://app.shipos.co.il/api/shipping/same_day_delivery_quote' \
--header 'Accept: application/json' \
--data-urlencode 'license_key={license_key}' \
--data-urlencode 'domain=your-store.example.com' \
--data-urlencode 'city=תל אביב' \
--data-urlencode 'street=הרצל' \
--data-urlencode 'street_number=10' \
--data-urlencode 'name=ישראל ישראלי' \
--data-urlencode 'phone=0501234567' \
--data-urlencode 'date=2026-09-01' \
--data-urlencode 'package_size=SMALL'// Node.js 18+ / דפדפנים — בלי תלויות
const query = new URLSearchParams({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: process.env.SHIPOS_DOMAIN,
city: 'תל אביב',
street: 'הרצל',
street_number: '10',
name: 'ישראל ישראלי',
phone: '0501234567',
date: '2026-09-01',
package_size: 'SMALL',
})
const response = await fetch(
`https://app.shipos.co.il/api/shipping/same_day_delivery_quote?${query}`,
{ headers: { Accept: 'application/json' } },
)
const body = await response.json()
if (!body.success) {
throw new Error(`ShipOS v1: ${body.message}`)
}
console.log(body.data)<?php
use Illuminate\Support\Facades\Http;
$body = Http::acceptJson()
->get('https://app.shipos.co.il/api/shipping/same_day_delivery_quote', [
'license_key' => config('services.shipos.license_key'),
'domain' => config('services.shipos.domain'),
'city' => 'תל אביב',
'street' => 'הרצל',
'street_number' => 10,
'name' => 'ישראל ישראלי',
'phone' => '0501234567',
'date' => '2026-09-01',
'package_size' => 'SMALL',
])
->json();
if (! ($body['success'] ?? false)) {
throw new RuntimeException('ShipOS v1: '.$body['message']);
}תשובה לדוגמה
data הוא גוף ההצעה של Get Package עצמה, מועבר כפי שהוא — התיעוד הזה אינו חוזר על הסכמה של חברת השילוח.
{
"data": { "...": "..." },
"message": "",
"success": true,
"warning": ""
}עיר איסוף שמחוץ לרשימת אותו יום:
{
"data": [],
"message": "City חוות זוהר המדבר is not in location list",
"success": false,
"warning": ""
}העיר שמופיעה בהודעה הזו היא עיר האיסוף של הרישיון, ולא עיר היעד שהבקשה ביקשה — הדוגמה שלמעלה שולחת city=תל אביב ונדחית בגלל שם מקום אחר לגמרי.
שגיאות
| סטטוס | מתי |
|---|---|
| 400 | חברת השילוח של הרישיון אינה מיישמת הצעת מחיר לאותו יום (Not supported for this provider), עיר האיסוף אינה ברשימה של Get Package, או ש-Get Package ענתה בשגיאת HTTP. בשגיאת HTTP של חברת השילוח message הוא Error Retrieving Quote וגוף השגיאה של חברת השילוח נמצא ב-data; כאשר לא ניתן היה לבצע את הקריאה כלל (DNS, timeout) message הוא שגיאת התעבורה כפי שהיא. |
| 403 | לא ניתן לפענח את הרישיון, הוא הושבת, או שהוא חברת פרימיום ללא משתמש עם פרימיום פעיל. |
| 422 | ולידציית הפרמטרים נכשלה, או ש-license_key/domain חסר (זה מגיע מ-hasLicense). נגיש רק לאחר שבדיקת הספק עברה. בצורת {"message", "errors"} של Laravel, ולא במעטפת של v1. |
GET /shipping/create_shipping_promise
בקשת shipment promise מ-Wolt Drive — הצעה מתומחרת ומוגבלת בזמן למשלוח מהמקום (venue) של הרישיון לכתובת שאתם מספקים.
promise כאן הוא לידיעה בלבד
נקודת קצה זו מחזירה את ה-promise של Wolt כדי שחנות תוכל להציג מחיר וחלון זמן. הוא אינו מועבר ליצירת משלוח: open_new_order יוצרת promise משל עצמה בפנים ומשתמשת ב-shipment_promise_id שלו. שום דבר שאתם מקבלים כאן אינו צריך להישמר או להישלח מחדש.
פרמטרים
שאילתה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. |
domain | string | כן | ראו אימות. |
city | string | כן | עיר המסירה. |
street | string | כן | רחוב המסירה. |
street_number | string | לא | מספר הרחוב במסירה. משורשר ל-street לפני שהוא נשלח ל-Wolt. |
ה-venue של Wolt, אסטרטגיית זמן ההכנה ושעות הפעילות מגיעים כולם מההגדרות של הרישיון (venue_id, time_type, min_preparation_time_minutes או time_slot, opening_time, closing_time), ולא מהבקשה.
שעות סגירה
כאשר לרישיון מוגדרים גם opening_time וגם closing_time והשעה הנוכחית ב-Asia/Jerusalem נמצאת מחוץ לחלון הזה, לא נעשית קריאה ל-Wolt והתשובה היא כישלון שנושא data.details: "Delivery area will be closed at the requested delivery time.".
איך המחיר נכתב מחדש
Wolt מחזירה את price.amount ביחידות משנה. ה-handler מחלק אותו ב-100 ומעגל לשתי ספרות אחרי הנקודה, ואז מיישם את ה-price_type של הרישיון:
price_type | ההשפעה על data.price.amount |
|---|---|
| לא מוגדר / כל דבר אחר | הסכום המומר של חברת השילוח. |
fix | מוחלף ב-fix_price של הרישיון. |
markup | הסכום המומר של חברת השילוח בתוספת ה-markup_price של הרישיון, מעוגל לשתי ספרות אחרי הנקודה. |
בקשה לדוגמה
curl --location --get 'https://app.shipos.co.il/api/shipping/create_shipping_promise' \
--header 'Accept: application/json' \
--data-urlencode 'license_key={license_key}' \
--data-urlencode 'domain=your-store.example.com' \
--data-urlencode 'city=תל אביב' \
--data-urlencode 'street=הרצל' \
--data-urlencode 'street_number=10'// Node.js 18+ / דפדפנים — בלי תלויות
const query = new URLSearchParams({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: process.env.SHIPOS_DOMAIN,
city: 'תל אביב',
street: 'הרצל',
street_number: '10',
})
const response = await fetch(
`https://app.shipos.co.il/api/shipping/create_shipping_promise?${query}`,
{ headers: { Accept: 'application/json' } },
)
const body = await response.json()
if (!body.success) {
// למשל מחוץ לשעות הפעילות של ה-venue
throw new Error(`ShipOS v1: ${body.data.details ?? body.message}`)
}
console.log(body.data.price.amount)<?php
use Illuminate\Support\Facades\Http;
$body = Http::acceptJson()
->get('https://app.shipos.co.il/api/shipping/create_shipping_promise', [
'license_key' => config('services.shipos.license_key'),
'domain' => config('services.shipos.domain'),
'city' => 'תל אביב',
'street' => 'הרצל',
'street_number' => '10',
])
->json();
if (! ($body['success'] ?? false)) {
throw new RuntimeException('ShipOS v1: '.($body['data']['details'] ?? $body['message']));
}
logger()->info('Wolt promise', ['amount' => $body['data']['price']['amount']]);תשובה לדוגמה
data הוא אובייקט ה-shipment promise של Wolt כאשר price.amount נכתב מחדש כמתואר למעלה. המפתחות שהאפליקציה הזו עצמה קוראת ממנו הם id, pickup.options, dropoff.location.coordinates, dropoff.location.formatted_address, dropoff.options ו-price; שאר האובייקט הוא של Wolt ומועבר כפי שהוא.
{
"data": {
"id": "...",
"price": { "amount": 24.9, "...": "..." },
"pickup": { "options": [] },
"dropoff": { "location": { "coordinates": {}, "formatted_address": "..." }, "options": [] }
},
"message": "",
"success": true,
"warning": ""
}מחוץ לשעות הפעילות:
{
"data": { "details": "Delivery area will be closed at the requested delivery time." },
"message": "Error Retrieving Quote",
"success": false,
"warning": ""
}שגיאות
| סטטוס | מתי |
|---|---|
| 400 | חברת השילוח של הרישיון אינה Wolt Drive (Not supported for this provider), ה-venue מחוץ לשעות הפעילות שלו, או ש-Wolt לא ענתה 201. message נופל בברירת מחדל ל-Error Retrieving Quote. |
| 403 | לא ניתן לפענח את הרישיון, הוא הושבת, או שהוא חברת פרימיום ללא משתמש עם פרימיום פעיל. |
| 422 | ולידציית הפרמטרים נכשלה, או ש-license_key/domain חסר (זה מגיע מ-hasLicense). נגיש רק לאחר שבדיקת הספק עברה. בצורת {"message", "errors"} של Laravel, ולא במעטפת של v1. |
GET /shipping/get_express_delivery_enabled
דיווח אם ZigZag Express 24 מופעל עבור הרישיון הזה. זה קורא את הגדרת ה-enable_express_delivery של הרישיון עצמו; אין קריאה ל-ZigZag ואין שאלה לגבי כתובת.
פרמטרים
שאילתה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. |
domain | string | כן | ראו אימות. |
אין פרמטרים אחרים. ה-handler אינו מבצע ולידציה על שום דבר מעבר למה ש-hasLicense דורש.
בקשה לדוגמה
curl --location --get 'https://app.shipos.co.il/api/shipping/get_express_delivery_enabled' \
--header 'Accept: application/json' \
--data-urlencode 'license_key={license_key}' \
--data-urlencode 'domain=your-store.example.com'// Node.js 18+ / דפדפנים — בלי תלויות
const query = new URLSearchParams({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: process.env.SHIPOS_DOMAIN,
})
const response = await fetch(
`https://app.shipos.co.il/api/shipping/get_express_delivery_enabled?${query}`,
{ headers: { Accept: 'application/json' } },
)
const body = await response.json()
// "לא מופעל" מגיע כ-HTTP 400 עם success: false
console.log(body.success ? 'express on' : body.message)<?php
use Illuminate\Support\Facades\Http;
$body = Http::acceptJson()
->get('https://app.shipos.co.il/api/shipping/get_express_delivery_enabled', [
'license_key' => config('services.shipos.license_key'),
'domain' => config('services.shipos.domain'),
])
->json();
logger()->info($body['success'] ? 'express on' : $body['message']);תשובה לדוגמה
צמד ה-{ success, message } של שירות חברת השילוח עצמו ממוקם בתוך data, וה-message שלו מועתק ל-message של המעטפת, כך שהשניים מסכימים ביניהם.
{
"data": { "success": true, "message": "Express Delivery service is enabled" },
"message": "Express Delivery service is enabled",
"success": true,
"warning": ""
}לא מופעל — שימו לב שזה HTTP 400, ולא 200 שנושא false:
{
"data": { "success": false, "message": "Express Delivery service is not enabled" },
"message": "Express Delivery service is not enabled",
"success": false,
"warning": ""
}שגיאות
| סטטוס | מתי |
|---|---|
| 400 | חברת השילוח של הרישיון אינה ZigZag (Not supported for this provider), או שהגדרת ה-enable_express_delivery של הרישיון כבויה. |
| 403 | לא ניתן לפענח את הרישיון, הוא הושבת, או שהוא חברת פרימיום ללא משתמש עם פרימיום פעיל. |
| 422 | license_key או domain הושמט — נזרק על ידי hasLicense עוד לפני שה-handler רץ. בצורת {"message", "errors"} של Laravel, ולא במעטפת של v1. |
GET /shipping/get_express_delivery_status
בדיקה אם כתובת אחת זכאית ל-ZigZag Express 24. הכתובת מחוברת ל-"{city}, {street} {street_number}" ומועברת לפעולת IsRemoteArea של ZigZag, בשימוש ה-express_username / express_password מהגדרות הרישיון; אזור מרוחק אינו זכאי.
פרמטרים
שאילתה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. |
domain | string | כן | ראו אימות. |
city | string | כן | עיר היעד. |
street | string | כן | רחוב היעד. |
street_number | string | לא | מספר הרחוב ביעד. |
בקשה לדוגמה
curl --location --get 'https://app.shipos.co.il/api/shipping/get_express_delivery_status' \
--header 'Accept: application/json' \
--data-urlencode 'license_key={license_key}' \
--data-urlencode 'domain=your-store.example.com' \
--data-urlencode 'city=תל אביב' \
--data-urlencode 'street=הרצל' \
--data-urlencode 'street_number=10'// Node.js 18+ / דפדפנים — בלי תלויות
const query = new URLSearchParams({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: process.env.SHIPOS_DOMAIN,
city: 'תל אביב',
street: 'הרצל',
street_number: '10',
})
const response = await fetch(
`https://app.shipos.co.il/api/shipping/get_express_delivery_status?${query}`,
{ headers: { Accept: 'application/json' } },
)
const body = await response.json()
// כתובת שאינה זכאית היא 400, לא 200 עם דגל false
const eligible = body.success
console.log(eligible ? 'express available' : body.message)<?php
use Illuminate\Support\Facades\Http;
$body = Http::acceptJson()
->get('https://app.shipos.co.il/api/shipping/get_express_delivery_status', [
'license_key' => config('services.shipos.license_key'),
'domain' => config('services.shipos.domain'),
'city' => 'תל אביב',
'street' => 'הרצל',
'street_number' => '10',
])
->json();
logger()->info($body['success'] ? 'express available' : $body['message']);תשובה לדוגמה
זכאית:
{
"data": { "success": true, "message": "This area is eligible for Express Delivery" },
"message": "This area is eligible for Express Delivery",
"success": true,
"warning": ""
}אינה זכאית — HTTP 400:
{
"data": { "success": false, "message": "This area is not eligible for Express Delivery" },
"message": "This area is not eligible for Express Delivery",
"success": false,
"warning": ""
}שגיאות
| סטטוס | מתי |
|---|---|
| 400 | חברת השילוח של הרישיון אינה ZigZag (Not supported for this provider), הגדרת ה-enable_express_delivery של הרישיון כבויה, או שהכתובת נמצאת באזור מרוחק ולכן אינה זכאית. |
| 403 | לא ניתן לפענח את הרישיון, הוא הושבת, או שהוא חברת פרימיום ללא משתמש עם פרימיום פעיל. |
| 422 | ולידציית הפרמטרים נכשלה, או ש-license_key/domain חסר (זה מגיע מ-hasLicense). נגיש רק לאחר שבדיקת הספק עברה. בצורת {"message", "errors"} של Laravel, ולא במעטפת של v1. |
המקבילה ב-v2
אין. ל-API v2 אין נקודת קצה להצעת מחיר לאותו יום, ל-shipment promise או לזכאות אקספרס — המשטח שלו הוא משלוחים, אצוות, תוויות, הזמנות, מעקב, נקודות איסוף, רישיונות, נתוני עזר, webhooks והתראות. אם אתם צריכים את שירותי חברות השילוח האלה, אתם נשארים ב-v1 בשבילם.