المال بالوحدات الصغرى
أعداد صحيحة دائماً، وجدول العملات، وتنسيق العرض، ومطابقة الدفاتر، وتعدد العملات.
في هذه الصفحة
هذه الصفحة لمن يعرض مبلغاً أو يطابق دفتراً. قاعدة واحدة لا استثناء لها: المال أعداد صحيحة دائماً. لا كسور عشرية، لا float، أبداً.
شكل المبلغ#
{ "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 من كل مبلغ على حدة حتى لو حفظت هذا الجدول.
تنسيق العرض#
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"export function formatMoney(amountMinor, currency, decimals, locale = "en") {
const value = decimals === 0 ? amountMinor : amountMinor / 10 ** decimals;
return new Intl.NumberFormat(locale, {
style: "currency",
currency,
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(value);
}
formatMoney(250000, "SYP", 0); // "SYP 250,000"
formatMoney(4000, "USD", 2); // "$40.00"from decimal import Decimal
def format_money(amount_minor: int, currency: str, decimals: int) -> str:
value = Decimal(amount_minor).scaleb(-decimals)
return f"{value:,.{decimals}f} {currency}"
format_money(250000, "SYP", 0) # "250,000 SYP"
format_money(4000, "USD", 2) # "40.00 USD"استخدم Decimal أو الأعداد الصحيحة في الحسابات؛ الاستدعاء الوحيد الذي يحوّل إلى كسر هو العرض النهائي.
معادلة الطلب#
subtotal - discount + shipping + tax = totaltax محجوز ويساوي صفراً حالياً؛ أبقِه في معادلاتك كي لا تنكسر عند تفعيل الضرائب. يحمل الطلب أيضاً paid وrefunded كمبالغ مستقلة من دفتر المدفوعات.
line_pricing: صافٍ أم إجمالي؟#
نمطان يتعايشان بتصميم مقصود، والحقل line_pricing على كل طلب يحسم:
"gross"(طلبات واجهة المتجر ولوحة التاجر): مجموعitems[].totalيساويsubtotal، والخصم على مستوى الطلب."net"(طلبات نقاط البيع): الأسطر صافية بعد العرض، أي مجموع الأسطر +discount=subtotal.
فرّع على line_pricing حصراً، لا على source. تطبيق محاسبة يفترض نمطاً واحداً سيطابق دفاتر خاطئة على نصف الطلبات. المتجر التجريبي يحوي طلباً من كل نمط.
النسب المئوية والتقريب#
عند حساب نسبة من مبلغ (عمولة، ضريبة مستقبلية، تقسيم) احسب بالأعداد الصحيحة وقرّب مرة واحدة في النهاية إلى أقرب وحدة صغرى؛ لا تقرّب كل سطر على حدة ثم تجمع. حاصل الجمع يجب أن يساوي الأصل: وزّع الفارق الناتج عن التقريب على السطر الأخير.
تعدد العملات#
يحمل الطلب عملة الدفع ولقطة سعر الصرف المجمّدة وقت الشراء:
{ "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بدلاً من تقليمه بنفسك.