تخطَّ إلى المحتوى

المال بالوحدات الصغرى

أعداد صحيحة دائماً، وجدول العملات، وتنسيق العرض، ومطابقة الدفاتر، وتعدد العملات.

آخر تحديث 2 أيلول 2026قراءة 3 د
في هذه الصفحة

هذه الصفحة لمن يعرض مبلغاً أو يطابق دفتراً. قاعدة واحدة لا استثناء لها: المال أعداد صحيحة دائماً. لا كسور عشرية، لا float، أبداً.

شكل المبلغ#

JSON
{ "amount_minor": 250000, "currency": "SYP", "decimals": 0 }
  • amount_minor عدد صحيح بأصغر وحدة للعملة.
  • decimals عدد الخانات اللازم للعرض، من الاستجابة نفسها لا من جدول ISO: تعتمد دكان الليرة السورية بلا خانات (SYP = 0) بخلاف ISO 4217. القسمة على 10^n من عندك تنتج أخطاء عرض بمئة ضعف.
  • USD بخانتين: 4000 تعني $40.00.

جدول العملات#

العملة decimals مثال amount_minor يُعرض
SYP 0 250000 250,000 ل.س
USD 2 4000 $40.00
EUR 2 1999 €19.99
JOD 3 12500 JOD 12.500
PYG 0 150000 Gs. 150,000

أي عملة خارج الجدول تُعامل بخانتين. عملات الدفع في واجهات المتاجر اليوم هي SYP وUSD وEUR. اقرأ decimals من كل مبلغ على حدة حتى لو حفظت هذا الجدول.

تنسيق العرض#

TypeScript
import { formatMoney, money } from "@dukkan.one/app-sdk/commerce";

formatMoney(money(250000, "SYP", 0), "ar", { numberingSystem: "arab" }); // Arabic digits
formatMoney(money(250000, "SYP", 0), "en"); // "SYP 250,000"
formatMoney(money(4000, "USD", 2), "en");   // "$40.00"

استخدم Decimal أو الأعداد الصحيحة في الحسابات؛ الاستدعاء الوحيد الذي يحوّل إلى كسر هو العرض النهائي.

معادلة الطلب#

Text
subtotal - discount + shipping + tax = total

tax محجوز ويساوي صفراً حالياً؛ أبقِه في معادلاتك كي لا تنكسر عند تفعيل الضرائب. يحمل الطلب أيضاً paid وrefunded كمبالغ مستقلة من دفتر المدفوعات.

line_pricing: صافٍ أم إجمالي؟#

نمطان يتعايشان بتصميم مقصود، والحقل line_pricing على كل طلب يحسم:

  • "gross" (طلبات واجهة المتجر ولوحة التاجر): مجموع items[].total يساوي subtotal، والخصم على مستوى الطلب.
  • "net" (طلبات نقاط البيع): الأسطر صافية بعد العرض، أي مجموع الأسطر + discount = subtotal.

فرّع على line_pricing حصراً، لا على source. تطبيق محاسبة يفترض نمطاً واحداً سيطابق دفاتر خاطئة على نصف الطلبات. المتجر التجريبي يحوي طلباً من كل نمط.

النسب المئوية والتقريب#

عند حساب نسبة من مبلغ (عمولة، ضريبة مستقبلية، تقسيم) احسب بالأعداد الصحيحة وقرّب مرة واحدة في النهاية إلى أقرب وحدة صغرى؛ لا تقرّب كل سطر على حدة ثم تجمع. حاصل الجمع يجب أن يساوي الأصل: وزّع الفارق الناتج عن التقريب على السطر الأخير.

تعدد العملات#

يحمل الطلب عملة الدفع ولقطة سعر الصرف المجمّدة وقت الشراء:

JSON
{ "exchange_rate": { "scaled": 1450000000000, "scale": 8 } }

السعر الفعلي = scaled / 10^scale (هنا 14,500 ليرة للدولار). احسبه بأعداد صحيحة أو عشرية دقيقة، لا بـfloat. اللقطة حقيقة تاريخية: لا تعيد التسعير بأسعار اليوم. exchange_rate يكون null عندما يطابق الطلب عملة المتجر الأساسية.

حمولات Webhooks#

تحمل أحداث الطلب المبلغ كـ total_minor (أو amount_minor في order.paid وrefund.created) مع currency دون decimals. إن احتجت إلى الأس للعرض، اجلب الطلب عبر GET /orders/{id} أو استخدم جدول العملات كملاذ أخير مع الحذر من التغيّر.

الاستردادات#

  • الاسترداد سجل مالي مستقل بتفصيل أسطر اختياري، ولا يعدّل أسطر الطلب الأصلية أبداً؛ تاريخ الطلب ثابت.
  • قابلية الاسترداد مقيّدة بالمبالغ المحصَّلة المتبقية، لا بحالة الطلب.
  • إرجاع البضاعة إلى المخزون قرار منفصل عن إرجاع المال: خيار restock يكتب حركة مخزون بسبب return ويتطلب inventory:write.
  • البنود المخصصة (kind: "custom"، المنشأة عبر POST /orders بلا خيار) لا شيء فيها يُرجَع إلى المخزون: استردّها بالمبلغ فقط. راجع الطلبات التي تنشئها التطبيقات.
  • payment_status انعكاس لدفتر المدفوعات: paid وpartially_refunded وrefunded تُحتسب من مجموع الحركات، ولا تُقلب يدوياً.
  • الاسترداد مال يخرج من التاجر، فيجب أن يأتي Idempotency-Key الخاص به من هوية الاسترداد نفسها في نظامك (معرّف إرجاع، معرّف تذكرة)، لا من المحاولة أبداً. فالعملية المنهارة التي تعيد تشغيل المهمة تعيد الإجابة المسجّلة بدلاً من الاسترداد مرتين؛ وتشتقّ الحزمة مفتاحاً كهذا من idempotency: { ref: [orderId, returnId] }.
  • احسب المبلغ مما ما زال قابلاً للاسترداد (تعكس refundableAmount في مساعدات التجارة في الحزمة قاعدة المنصّة) ودع المنصّة ترفض الرقم القديم بـ 409 بدلاً من تقليمه بنفسك.