Get a quote

بناء محرك التسعير الديناميكي والخصومات في Go لمنصات SaaS

كل منصة SaaS تحتاج في نهاية المطاف إلى أكثر من قائمة أسعار ثابتة. بناء محرك تسعير قابل للتكوين في Go يعني فصل تخزين القواعد عن تقييمها، وتحديد سياسات التكديس، وجعل الحساب قابلاً للتدقيق.

بناء محرك التسعير الديناميكي والخصومات في Go لمنصات SaaS

كل منصة SaaS تحتاج في نهاية المطاف إلى أكثر من قائمة أسعار ثابتة. خصومات ساعات الذروة، ومستويات الحجم، والعروض الموسمية، وأكواد الخصم، كلها متطلبات منتج مشروعة. ترميز هذه القواعد في منطق الخدمة يخلق كابوساً للصيانة: كل عرض ترويجي جديد يتطلب تغيير كود ونشراً جديداً.

محرك التسعير القابل للتكوين يفصل تخزين القواعد عن تقييمها. القواعد تعيش في قاعدة البيانات. المحرك يُقيِّم القواعد التي تنطبق على سياق طلب معين ويحسب السعر النهائي.

نموذج البيانات

CREATE TABLE pricing_rules (
    id             UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id      UUID NOT NULL,
    name           TEXT NOT NULL,
    rule_type      TEXT NOT NULL,
    discount_type  TEXT NOT NULL,
    discount_value NUMERIC(10, 4) NOT NULL,
    max_discount   NUMERIC(15, 2),
    min_order_total NUMERIC(15, 2),
    conditions     JSONB NOT NULL DEFAULT '{}',
    priority       INT NOT NULL DEFAULT 100,
    stacking       TEXT NOT NULL DEFAULT 'exclusive',
    active         BOOLEAN NOT NULL DEFAULT true,
    starts_at      TIMESTAMPTZ,
    ends_at        TIMESTAMPTZ
);

حقل conditions بنوع JSONB يخزن شروط القاعدة في مخطط مرن:

{
  "time_ranges": [
    {"days": ["monday","tuesday","wednesday","thursday","friday"],
     "start": "17:00", "end": "19:00"}
  ],
  "categories": ["beverages"],
  "min_items": 2
}

تقييم القواعد

المحرك يستقبل سياق الطلب ويُرجع الخصم المنطبق:

func (e *PricingEngine) Evaluate(ctx context.Context, order OrderContext) (*DiscountResult, error) {
    rules, err := e.rules.LoadActive(ctx, order.TenantID)
    if err != nil {
        return nil, err
    }

    var applicable []PricingRule
    for _, rule := range rules {
        if e.ruleApplies(rule, order) {
            applicable = append(applicable, rule)
        }
    }

    if len(applicable) == 0 {
        return &DiscountResult{FinalPrice: order.Total}, nil
    }

    return e.applyRules(applicable, order), nil
}

فحص الشروط

الفحص الزمني الأكثر تعقيداً هو الفترات اليومية في منطقة زمنية المستأجر:

func (e *PricingEngine) inAnyTimeRange(t time.Time, ranges []TimeRange) bool {
    dayName := strings.ToLower(t.Weekday().String())
    timeStr := t.Format("15:04")

    for _, tr := range ranges {
        for _, d := range tr.Days {
            if d == dayName && timeStr >= tr.Start && timeStr < tr.End {
                return true
            }
        }
    }
    return false
}

سياسة التكديس

قد تنطبق قواعد متعددة في آنٍ واحد. سياسة التكديس تحدد كيفية دمجها:

  • exclusive: طبِّق فقط القاعدة ذات الأولوية الأعلى
  • additive: كدِّس فوق خصومات أخرى
  • override: استبدل جميع القواعد المطبقة سابقاً
switch rule.Stacking {
case "exclusive":
    if len(applied) == 0 {
        applied = append(applied, AppliedRule{Rule: rule, Discount: discount})
        totalDiscount = discount
    }
case "additive":
    applied = append(applied, AppliedRule{Rule: rule, Discount: discount})
    totalDiscount = totalDiscount.Add(discount)
case "override":
    applied = []AppliedRule{{Rule: rule, Discount: discount}}
    totalDiscount = discount
}

الحساب

استخدام حزمة decimal لجميع حسابات الأسعار أمر بالغ الأهمية. الأرقام العشرية العائمة تتراكم فيها أخطاء التقريب عند الخانة الرابعة:

func (e *PricingEngine) calculateDiscount(rule PricingRule, total decimal.Decimal) decimal.Decimal {
    switch rule.DiscountType {
    case DiscountPercentage:
        return total.Mul(rule.DiscountValue).Div(decimal.NewFromInt(100))
    case DiscountFixedAmount:
        return rule.DiscountValue
    }
    return decimal.Zero
}

جعل الحسابات قابلة للتدقيق

كل طلب يجب أن يخزن نتيجة حساب التسعير الكاملة:

CREATE TABLE order_pricing_log (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    order_id      UUID NOT NULL,
    base_total    NUMERIC(15, 2) NOT NULL,
    discount_amt  NUMERIC(15, 2) NOT NULL,
    final_total   NUMERIC(15, 2) NOT NULL,
    applied_rules JSONB NOT NULL
);

هذا يمنح موظفي العمليات القدرة على تدقيق سبب حصول طلب معين على سعر معين.

دروس من بيئات الإنتاج

  • خزِّن القواعد في قاعدة البيانات. العروض المرمَّزة تتطلب نشرات لتغييرها.
  • استخدم decimal.Decimal لجميع حسابات الأسعار، لا أرقاماً عائمة أبداً.
  • حدِّد سياسات تكديس صريحة: exclusive وadditive وoverride تغطي معظم الحالات الواقعية.
  • حمِّل قواعد التسعير مع TTL قصير للتخزين المؤقت لتجنب ضربات قاعدة البيانات على كل تقييم طلب.
  • سجِّل حساب التسعير الكامل لكل طلب لتمكين التدقيق والتصحيح.

نبني منصات SaaS لمطاعم وتجار التجزئة في لبنان وعبر الشرق الأوسط. إذا كنت تحتاج محرك تسعير وخصم مرناً يمكن للمشغلين تكوينه بدون تغييرات في الكود، تواصل معنا عبر https://voxire.com/get-a-quote/

العودة إلى المدونة
Chat on WhatsApp