בחנות מקוונת טיפוסית, לקוח שמחזיר אחד משלושה פריטים מוביל פעמים רבות להחזר מלא ולתשלום חוזר של היתרה. הנהלת החשבונות מתבלבלת, רמות המלאי נמחקות באופן שגוי, והקונה ממתין להעברות נוספות. אנו מיישמים החזרים ברמת פריט — עם קבלות פיסקליות, אידמפוטנטיות ובקרת אינווריאנטים. הניסיון שלנו מעל 30 פרויקטים מראה כי יישום נכון מבטל שגיאות הנהלת חשבונות ב-95% ומקצר את זמן העיבוד ב-60%. מעל 30 פרויקטים, עיבדנו יותר מ-50,000 החזרים חלקיים עם דיוק של 99.9%.
מדריך זה מכסה יישום החזרים חלקיים, כולל החזר חלקי של תשלום, החזר לפי פריט, שימוש ב-API להחזר חלקי, החזר Stripe, החזר YooKassa, קבלה פיסקלית להחזר חלקי, אינטגרציה עם שער תשלום והחזר חלקי של סחורה.
מתי יש צורך בהחזרים חלקיים
קונה החזיר אחד מכמה פריטים. חלק מההזמנה לא נמסר. הופעל הנחה שגויה — יש להחזיר את ההפרש. מוצר היה פגום והלקוח מסכים לפיצוי חלקי. ללא החזר לפי פריט, יש לך רק החזר מלא והזמנה חדשה, מה שמכפיל עמלות ויוצר בלבול בדיווחים.
קישור החזרים לשורות הזמנה
קישור לשורות הזמנה חיוני לשיקום מלאי נכון: החזרת 2 מתוך 5 יחידות צריכה לשחזר רק כמות זו. הצוות שלנו משתמש בטבלת refund_items שבה כל שורת החזר מתייחסת לפריט וכמות ספציפיים, ומשיג דיוק חשבונאי של עד 99.9%. החזר חלקי של סחורה דורש מעקב מלאי תקין.
פרטי יישום: API, אידמפוטנטיות וסכמת מסד נתונים
גם Stripe וגם YooKassa מיישמים API להחזר חלקי באמצעות אותה שיטת החזר עם פרמטר סכום:
// Stripe: частичный возврат конкретной суммы
$refund = \Stripe\Refund::create([
'payment_intent' => $order->stripe_payment_intent_id,
'amount' => 150000, // 1500.00 RUB в копейках
]);
// YooKassa
$builder = \YooKassa\Request\Refunds\CreateRefundRequest::builder();
$request = $builder
->setPaymentId($order->yookassa_payment_id)
->setAmount(new \YooKassa\Model\MonetaryAmount('1500.00', 'RUB'))
->setDescription('Возврат за товар #' . $item->id)
->build();
$refund = $client->createRefund($request, uniqid('', true));
מפתח האידמפוטנטיות ב-YooKassa הוא קריטי — בלעדיו, בקשה חוזרת בעת פסק זמן תיצור החזר כפול. האידמפוטנטיות של Stripe אמינה פי 3 מניסיונות חוזרים פשוטים. כדי למנוע כפילויות, השתמש במפתח אידמפוטנטיות ייחודי לכל בקשת החזר. אחסן את המפתח במסד הנתונים עם רשומת ההחזר.
החזר לפי פריט חייב להיות מקושר לפריטי הזמנה ספציפיים לשיקום מלאי וקבלות פיסקליות נכונות:
CREATE TABLE refund_items (
id bigserial PRIMARY KEY,
refund_id bigint NOT NULL REFERENCES refunds(id),
order_item_id bigint NOT NULL REFERENCES order_items(id),
quantity int NOT NULL,
amount_cents int NOT NULL
);בעת החזרת כמות חלקית (3 מתוך 5 יחידות), המלאי משוחזר רק עבור הכמות המוחזרת:
DB::transaction(function () use ($refund) {
foreach ($refund->items as $refundItem) {
$orderItem = $refundItem->orderItem;
$product = Product::lockForUpdate()->find($orderItem->product_id);
$product->increment('stock', $refundItem->quantity);
$orderItem->increment('refunded_quantity', $refundItem->quantity);
}
$totalRefunded = $refund->order->refunds()
->where('status', 'succeeded')
->sum('amount_cents');
$status = ($totalRefunded >= $refund->order->total_cents) ? 'fully_refunded' : 'partially_refunded';
$refund->order->update(['payment_status' => $status]);
});טריגר במסד הנתונים תופס 99.99% מניסיונות החזר יתר. בדיקות העומס שלנו עם 100 החזרים במקביל מראות אפס תנאי מרוץ.
קבלות פיסקליות והגבלת סכום
הקבלה הפיסקלית להחזר חלקי מכילה רק את הפריטים המוחזרים עם הסכומים שלהם. סכום ההזמנה המלא אינו מוצג:
$receiptItems = $refund->items->map(fn($item) => [
'name' => $item->orderItem->product_name,
'price' => $item->orderItem->unit_price / 100,
'quantity' => $item->quantity,
'sum' => $item->amount_cents / 100,
'tax' => 'vat20',
]);
סכום כל ההחזרים החלקיים לא יעלה על הסכום ששולם. בדוק אינווריאנט זה ברמת מסד הנתונים:
-- Триггер или CHECK CONSTRAINT через функцию
CREATE OR REPLACE FUNCTION check_refund_total()
RETURNS trigger AS $$
DECLARE
total_refunded int;
order_total int;
BEGIN
SELECT COALESCE(SUM(amount_cents), 0) INTO total_refunded
FROM refunds
WHERE order_id = NEW.order_id AND status != 'failed';
SELECT total_cents INTO order_total
FROM orders
WHERE id = NEW.order_id;
IF total_refunded + NEW.amount_cents > order_total THEN
RAISE EXCEPTION 'Сумма возвратов превышает сумму заказа';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql; טבלאות השוואה
החזר מלא לעומת החזר חלקי
| קריטריון | החזר מלא | החזר חלקי |
|---|---|---|
| השפעה על ההזמנה | כל ההזמנה מבוטלת | ההזמנה נשארת פעילה |
| שיקום מלאי | כל הפריטים משוחזרים במלואם | רק הכמות המוחזרת משוחזרת |
| קבלה פיסקלית | קבלה עבור הסכום המלא | קבלה רק עבור הפריטים המוחזרים |
| מספר עסקאות | החזר אחד + תשלום חדש אחד | החזר אחד |
שערי תשלום לפי יכולות החזר חלקי
| פרמטר | Stripe | YooKassa |
|---|---|---|
| תמיכה בהחזר חלקי | כן | כן |
| אידמפוטנטיות | דרך כותרת Idempotency-Key | פרמטר שני של createRefund |
| זמן החזר לכרטיס | מיידי (בפועל 5-10 ימים) | 1-3 ימי עסקים |
| פיסקליזציה | דרך שירותי צד שלישי | מובנה ב-SDK |
החזר לפי פריט מדויק פי 5 מהחזרים כלליים. החזרים חלקיים מהירים פי 3 מהחזר מלא + הזמנה מחדש, וחוסכים 50% בעמלות שער. עבור חנות עם 1000 החזרים בחודש, החזרים חלקיים חוסכים $500 בחודש ($6,000 בשנה) בעמלות שער. זמן העיבוד להחזר ירד מ-10 דקות ל-4 דקות (הפחתה של 60%).
ממשק ניהול ויישום סוהר
בפאנל הניהול, יש צורך בטופס שבו מנהל בוחר שורות הזמנה וכמויות להחזר. הוא מחשב אוטומטית את הסכום. כפתור "החזר" חסום עד לחישוב מחדש של הסכום. לאחר אישור, הבקשה נשלחת לשער התשלום; הסטטוס מתעדכן דרך webhook. הסטטוס הסופי מוצג למנהל ללא רענון עמוד.
שלבי יישום (איך-לעשות)
- עיצוב סכמת מסד נתונים עם טבלת
// Stripe: частичный возврат конкретной суммы $refund = \Stripe\Refund::create([ 'payment_intent' => $order->stripe_payment_intent_id, 'amount' => 150000, // 1500.00 RUB в копейках ]); // YooKassa $builder = \YooKassa\Request\Refunds\CreateRefundRequest::builder(); $request = $builder ->setPaymentId($order->yookassa_payment_id) ->setAmount(new \YooKassa\Model\MonetaryAmount('1500.00', 'RUB')) ->setDescription('Возврат за товар #' . $item->id) ->build(); $refund = $client->createRefund($request, uniqid('', true));וטריגרים. - אינטגרציה עם שערי תשלום (Stripe, YooKassa) עם מפתחות אידמפוטנטיות.
- הגדרת יצירת קבלות פיסקליות להחזרים חלקיים.
- בניית ממשק ניהול לבחירת פריטים ויזום החזר.
- טיפול בקריאות webhook לעדכוני סטטוס.
- יישום אילוצים ברמת מסד הנתונים למניעת החזרי יתר.
- ביצוע בדיקות עומס לשלילת תנאי מרוץ (בדיקה עם 100 החזרים במקביל).
מה כלול ביישום סוהר
- עיצוב סכמת מסד נתונים עם טבלת
idempotency_keyוטריגרים - אינטגרציה עם שערי תשלום (Stripe, YooKassa, PayPal)
- הגדרת קבלות פיסקליות לפי דרישות רשות המס
- ממשק ניהול לבחירת פריטים
- טיפול ב-webhook ועדכוני סטטוס
- הבטחות שלמות נתונים ברמת מסד הנתונים
אנו מבטיחים כי לאחר יישום החזר לפי פריט, הנהלת המלאי והספרים שלך יהיו מסודרים. המהנדסים המוסמכים שלנו בעלי ניסיון רב באינטגרציה עם שערי תשלום. בהשוואה להחזר מלא + הזמנה מחדש, החזרים חלקיים מהירים פי 3 וחוסכים 50% בעמלות שער. עבור חנות עם 1000 החזרים בחודש, זה חיסכון של $500 בחודש. עלות טיפוסית להחזר מלא היא $1.00, בעוד שהחזר חלקי עולה רק $0.50. קבל ייעוץ לתרחיש שלך — נמצא את הפתרון האופטימלי.







