انتقل إلى المحتوى الرئيسي

الشحن والمدفوعات من Playbooks

تشرح هذه الدليل كيفية تنفيذ الشحن ومعالجة المدفوعات داخل Playbooks الخاصة بـ Ansible لعمليات توفير OmniCRM.

📘 مرجع API كامل: للحصول على تفاصيل كاملة حول واجهات برمجة التطبيقات للدفع، وتوجيه المحفظة، والمبالغ المستردة، والهندسة المعمارية المستقلة عن البائع، راجع دليل نظام الدفع API

نظرة عامة

يمكن أن تتعامل Playbooks الخاصة بـ OmniCRM Ansible مع معالجة المدفوعات بعدة طرق مختلفة، لكن جميعها تقوم فقط باستدعاء واجهات برمجة التطبيقات على CRM للشحن.

  1. تدفق دفع الالتزام ذو المرحلتين - للخدمات المدفوعة التي تتطلب تفويض دفع فوري واحتجاز (أي الخدمات المدفوعة مسبقًا)
  2. إنشاء معاملة مباشرة - لإضافة رسوم/ائتمانات بدون معالجة دفع فورية (مثل رسوم الإعداد، الائتمانات اليدوية) والتي يمكن معالجتها لاحقًا (أي خدمات Postpad)

تضمن هذه الأساليب معاملات ذرية حيث يتم شحن العملاء فقط إذا نجحت عملية التوفير، مع التراجع التلقائي إذا فشل أي خطوة.

مرونة التسعير: Playbooks كأداة برمجة

مهم: القيم بالدولار المحددة في المنتجات والخدمات (مثل retail_cost، wholesale_cost، retail_setup_cost) هي ببساطة قيم افتراضية مخزنة في قاعدة البيانات. إنها لا تحدد ما يجب عليك شحنه - Playbook لديها السيطرة الكاملة على التسعير النهائي.

Playbook الخاصة بـ Ansible هي في الأساس محرك برمجة يمكنك تخصيصه لتلبية أي متطلبات منطق الأعمال. يمكنك:

  • استخدام الأسعار المخزنة كما هي - ببساطة الإشارة إلى api_response_product.json.retail_cost في تفويضك
  • تجاوز الأسعار تمامًا - شحن مبلغ مختلف بغض النظر عما هو موجود في تعريف المنتج
  • تطبيق خصومات ديناميكية - حساب النسب المئوية بناءً على مستوى العميل، العروض الترويجية، أو الولاء
  • تنفيذ تسعير متدرج - شحن معدلات مختلفة بناءً على الكمية، مستويات الاستخدام، أو شروط العقد
  • تسعير مجمع - دمج عدة منتجات مع خصومات مجمعة مخصصة
  • تسعير يعتمد على الوقت - ضبط الأسعار بناءً على الوقت من اليوم، الموسم، أو فترات العروض الترويجية
  • تسعير خاص بالعميل - البحث عن الأسعار المتفاوض عليها من عقود العملاء

مثال: تجاوز سعر المنتج

# سعر المنتج retail_cost هو 99.00 دولار، لكننا نريد شحن 79.00 دولار كعرض ترويجي
- name: Set promotional price (ignoring product's retail_cost)
set_fact:
charge_amount: 79.00

- name: Authorize promotional payment
uri:
url: "http://localhost:5000/crm/payments/authorize/hold"
method: POST
body_format: json
body:
customer_id: "{{ customer_id | int }}"
amount: "{{ charge_amount | float }}" # باستخدام تجاوزنا، وليس retail_cost
# ...

مثال: خصم مستوى العميل

# تطبيق خصم بناءً على مستوى العميل
- name: Get customer tier
uri:
url: "http://localhost:5000/crm/customer/{{ customer_id }}"
# ...
register: customer_info

- name: Calculate tier discount
set_fact:
base_price: "{{ api_response_product.json.retail_cost | float }}"
discount_percent: >-
{% if customer_info.json.tier == 'gold' %}20
{% elif customer_info.json.tier == 'silver' %}10
{% else %}0{% endif %}

- name: Apply discount to final price
set_fact:
final_price: "{{ (base_price | float) * (1 - (discount_percent | float / 100)) | round(2) }}"

مثال: تسعير شريك الجملة

# يدفع الشركاء بالجملة wholesale_cost بدلاً من retail_cost
- name: Determine price based on customer type
set_fact:
charge_amount: >-
{% if customer_info.json.is_wholesale_partner %}
{{ api_response_product.json.wholesale_cost | float }}
{% else %}
{{ api_response_product.json.retail_cost | float }}
{% endif %}

النقطة الرئيسية هي أنك لست مقيدًا بأي نموذج تسعير. يمنحك Playbook السيطرة البرمجية الكاملة لتنفيذ أي قواعد أع��ال تتطلبها منظمتك. أسعار المنتجات/الخدمات في CRM هي مجرد قيم افتراضية ملائمة يمكن أن تستخدمها Playbooks أو تتجاهلها حسب الحاجة.

تدفق دفع الالتزام ذو المرحلتين

يتم استخدام نمط الالتزام ذو المرحلتين عندما تحتاج إلى شحن طريقة دفع العميل أثناء التوفير. يضمن ذلك أن يتم شحن العميل فقط إذا اكتملت عملية التوفير بنجاح.

نظرة عامة على التدفق

نمط التنفيذ

يتبع النمط هذه المراحل:

المرحلة 1: التفويض - احتجاز الأموال على طريقة الدفع المرحلة 2: التوفير - تنفيذ تحديثات رصيد/إجراءات OCS المرحلة 3: الالتقاط - إنهاء الدفع عند نجاح التوفير التراجع - تحرير التفويض إذا فشل التوفير

مثال كامل

إليك مثال كامل من play_topup_charge_then_action.yaml:

- name: Play Topup - Charge card then action the Topup
hosts: localhost
gather_facts: no
become: False

tasks:
- name: Include vars of crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config

# الحصول على معلومات المنتج والخدمة
- name: Get Product information from CRM API
uri:
url: "http://localhost:5000/crm/product/product_id//{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
validate_certs: no
register: api_response_product

- name: Get Service information from CRM API
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
validate_certs: no
register: api_response_service

- name: Set service and package facts
set_fact:
service_uuid: "{{ api_response_service.json.service_uuid }}"
customer_id: "{{ api_response_service.json.customer_id }}"
package_name: "{{ api_response_product.json.product_name }}"
monthly_cost: "{{ api_response_product.json.retail_cost }}"
wholesale_cost: "{{ api_response_product.json.wholesale_cost }}"

# الحصول على طريقة الدفع الافتراضية للعميل
- name: Get the Customer Payment Methods from Payment Controller
uri:
url: "http://localhost:5000/crm/payments/methods?customer_id={{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
validate_certs: no
register: api_response_payment_methods

- name: Get the default payment_method_id from the response
set_fact:
payment_method_id: "{{ api_response_payment_methods.json | json_query(query) }}"
vars:
query: "data[?is_default==`true`].payment_method_id | [0]"

# ============================================================
# تدفق دفع الالتزام ذو المرحلتين
# ============================================================

- name: "Phase 1: Authorize payment (hold funds)"
uri:
url: "http://localhost:5000/crm/payments/authorize/hold"
method: POST
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": "{{ customer_id | int }}",
"amount": "{{ monthly_cost | float }}",
"currency": "{{ crm_config.currency | default('AUD') }}",
"payment_method_id": "{{ payment_method_id | int }}",
"metadata": {
"description": "{{ package_name }} on {{ api_response_service.json.service_name }}",
"service_id": "{{ api_response_service.json.service_id | int }}",
"site_id": "{{ api_response_service.json.site_id | int }}",
"product_id": "{{ product_id }}",
"user_id": "{{ (initiating_user | int) if (initiating_user is defined and initiating_user is not none) else omit }}",
"title": "{{ package_name }}",
"wholesale_cost": "{{ wholesale_cost | float }}",
"invoice": true,
"contract_days": "{{ days | int }}",
"send_email": true
}
}
return_content: yes
register: api_response_authorization

- name: Assert authorization was successful
assert:
that:
- api_response_authorization.status == 200
- api_response_authorization.json.success == true

- name: Store authorization_id for capture/release
set_fact:
authorization_id: "{{ api_response_authorization.json.data.authorization_id }}"

# المرحلة 2: توفير OCS (مغلفة في كتلة للتراجع المعاملاتي)
- block:
- name: "Phase 2: Execute CGRateS action"
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body:
{
"id": "{{ 999999999 | random }}",
"method": "APIerSv1.ExecuteAction",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionsId": "{{ cgr_action_name }}"
}]
}
status_code: 200
register: action_execute_response

- name: Assert that the response from the action is OK
assert:
that:
- action_execute_response.status == 200
- action_execute_response.json.result == "OK"

# المرحلة 3: التقاط الدفع - إنهاء المعاملة بعد التوفير الناجح
- name: "Phase 3: Capture authorized payment"
uri:
url: "http://localhost:5000/crm/payments/capture/{{ authorization_id }}"
method: POST
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"metadata": {
"provisioning_status": "success",
"cgr_action": "{{ cgr_action_name }}"
}
}
return_content: yes
register: api_response_capture

- name: Assert capture was successful
assert:
that:
- api_response_capture.status == 200
- api_response_capture.json.success == true

rescue:
# تراجع المعاملة: إلغاء التفويض لتحرير الأموال المحتجزة
- name: "Rollback: Release payment authorization"
uri:
url: "http://localhost:5000/crm/payments/release/{{ authorization_id }}"
method: POST
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"metadata": {
"release_reason": "provisioning_failed",
"cgr_action": "{{ cgr_action_name }}"
}
}
return_content: yes
register: api_response_release

- name: Terminate playbook execution after rollback
fail:
msg: "فشل توفير OCS. تم إلغاء تفويض الدفع {{ authorization_id }}. لم يتم شحن العميل."

نقاط نهاية API للدفع

💡 توجيه المحفظة أولاً: يقوم نظام الدفع تلقائيًا بتحسين رسوم البطاقة باستخدام رصيد المحفظة أولاً. إذا كان لدى العميل 1 دولار في محفظته واشترى إضافة بقيمة 10 دولارات، يتم شحن 9 دولارات فقط إلى بطاقته. راجع قسم توجيه المحفظة أولاً للحصول على التفاصيل.

1. تفويض/احتجاز الدفع

نقطة النهاية: POST /crm/payments/authorize/hold

تضع هذه النقطة نهاية احتجازًا على طريقة دفع العميل للمبلغ ا��محدد. يتم حجز الأموال ولكن لم يتم التقاطها بعد.

سلوك المحفظة أولاً: يقوم التفويض تلقائيًا بحساب العجز بعد التحقق من رصيد المحفظة. إذا كان رصيد المحفظة 150 دولارًا وكان التفويض بقيمة 500 دولار، يتم تفويض 350 دولارًا فقط على البطاقة. يتم خصم المحفظة عند وقت الالتقاط.

جسم الطلب:

{
"customer_id": 123,
"amount": 49.99,
"currency": "AUD",
"payment_method_id": 456,
"metadata": {
"description": "اسم الحزمة والوصف",
"service_id": 789,
"site_id": 12,
"product_id": "34",
"user_id": 5,
"title": "اسم الحزمة",
"wholesale_cost": 25.00,
"invoice": true,
"contract_days": 30,
"send_email": true
}
}

وصف الحقول:

  • customer_id (مطلوب) - معرف العميل الذي يتم شحنه
  • amount (مطلوب) - المبلغ الذي سيتم تفويضه بتنسيق عشري
  • currency (اختياري) - رمز العملة (يكون افتراضيًا إلى الافتراضي للنظام)
  • payment_method_id (مطلوب) - طريقة الدفع التي سيتم شحنها
  • metadata (اختياري) - بيانات إضافية لإنشاء المعاملة:
    • description - وصف المعاملة
    • service_id - الخدمة التي يتم شحنها
    • site_id - الموقع المرتبط بالخدمة
    • product_id - المنتج الذي يتم توفيره
    • user_id - المستخدم الذي يبدأ الشحن (اختياري)
    • title - عنوان المعاملة
    • wholesale_cost - تكلفتك (لتتبع الهامش)
    • invoice - إذا كانت true، يتم إنشاء المعاملة تلقائيًا عند الالتقاط
    • contract_days - مدة العقد
    • send_email - إذا كانت true، يتم إرسال إشعار عبر البريد الإلكتروني

الاستجابة:

{
"success": true,
"message": "تم تفويض الدفع (تم إنشاء الاحتجاز)",
"data": {
"authorization_id": 301,
"vendor_authorization_id": "auth_xxxxx",
"amount": 500.00,
"currency": "USD",
"status": "authorized",
"wallet_balance": 150.00,
"wallet_to_use": 150.00,
"card_amount": 350.00,
"message": "تم تفويض البطاقة بمبلغ 350 دولارًا (تعبئة المحفظة). سيتم خصم 500 دولار من المحفظة عند الالتقاط."
}
}

مهم:

  • احفظ authorization_id لاستخدامه في مكالمات الالتقاط أو الإلغاء
  • لاحظ أن card_amount يظهر فقط أن العجز تم تفويضه
  • يتم التحقق من رصيد المحفظة ولكن لا يتم خصمه حتى وقت الالتقاط

2. التقاط الدفع

نقطة النهاية: POST /crm/payments/capture/{authorization_id}

تقوم هذه النقطة النهاية بإنهاء تفويض الدفع وتحصيل المبلغ من العميل. إذا تم تعيين invoice: true في بيانات التعريف الخاصة بالتفويض، يتم إنشاء معاملة تلقائيًا.

جسم الطلب:

{
"metadata": {
"provisioning_status": "success",
"cgr_action": "Action_Topup_Standard",
"additional_info": "أي بيانات ذات صلة أخرى"
}
}

الاستجابة:

{
"success": true,
"data": {
"payment_id": "pay_xyz789",
"transaction_id": 1234
}
}

ما يحدث:

  1. تم التقاط البطاقة للمبلغ العاجل (إذا تم تفويض البطاقة)
  2. تم ائتمان المحفظة بمبلغ البطاق�� الملتقطة
  3. تم خصم المحفظة للمبلغ الكامل للخدمة
  4. إذا كانت invoice: true في بيانات التعريف الخاصة بالتفويض:
    • تم إنشاء معاملة الخصم (مبلغ إيجابي = شحن)
    • تم إنشاء فاتورة مرتبطة بمعاملة الخصم
    • تم إنشاء معاملة ائتمان (مبلغ سالب = الدفع المستلم)
    • تم وضع علامة على الفاتورة كمدفوعة (الخصومات والائتمانات تتساوى مع الصفر)
    • تظهر المعاملة في فواتير العميل
  5. يتم إنشاء سجل الدفع في قاعدة البيانات
  6. إذا كانت send_email: true، يتلقى العميل بريدًا إلكترونيًا بالفاتورة

مثال: تفويض بقيمة 500 دولار مع رصيد محفظة بقيمة 150 دولار:

  • تم التقاط البطاقة: 350 دولار
  • تم ائتمان المحفظة: +350 دولار (الآن رصيد المحفظة 500 دولار)
  • تم خصم المحفظة: -500 دولار (رسوم الخدمة)
  • الرصيد النهائي للمحفظة: 0 دولار

راجع أمثلة توجيه المحفظة أولاً للحصول على تدفق مفصل.

3. إلغاء تفويض الدفع

نقطة النهاية: POST /crm/payments/release/{authorization_id}

تقوم هذه النقطة النهاية بإلغاء تفويض الدفع وإطلاق الأموال المحتجزة. استخدم هذا في سيناريوهات الإنقاذ/التراجع عندما يفشل التوفير.

جسم الطلب:

{
"metadata": {
"release_reason": "provisioning_failed",
"error_details": "فشل إنشاء حساب OCS"
}
}

الاستجابة:

{
"success": true,
"message": "تم إطلاق التفويض"
}

ما يحدث:

  1. تم إطلاق تفويض البطاقة (إذا تم تفويض البطاقة)
  2. يتم إرجاع الأموال المحتجزة إلى رصيد العميل المتاح
  3. لم يتم خصم المحفظة (لأن الخصم يحدث فقط عند الالتقاط)
  4. لم يتم شحن العميل
  5. لم يتم إنشاء أي معاملة

ملاحظة: مع توجيه المحفظة أولاً، لا حاجة لاسترداد المحفظة لأن المحفظة لا يتم خصمها حتى وقت الالتقاط.

إنشاء معاملة مباشرة

بالنسبة للرسوم ��لتي لا تتطلب معالجة الدفع (رسوم الإعداد، الائتمانات اليدوية، التعديلات)، يمكنك إنشاء معاملات مباشرة عبر واجهة برمجة التطبيقات.

إضافة معاملة عبر API

نقطة النهاية: PUT /crm/transaction/

مثال من play_simple_service.yaml:

- name: Add Setup Cost Transaction via API
uri:
url: "http://localhost:5000/crm/transaction/"
method: PUT
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id | int }},
"service_id": {{ service_creation_response.json.service_id | int }},
"title": "{{ package_name }} - Setup Costs",
"description": "Setup Costs for {{ package_comment }}",
"invoice_id": null,
"wholesale_cost": {{ api_response_product.json.wholesale_setup_cost | float }},
"retail_cost": "{{ api_response_product.json.retail_setup_cost | float }}"
}
return_content: yes
register: api_response_transaction

- name: Assert that the response from the transaction is OK
assert:
that:
- api_response_transaction.status == 200

حقول جسم ال��لب:

  • customer_id (مطلوب) - معرف العميل
  • service_id (اختياري) - معرف الخدمة لربط المعاملة بها
  • title (مطلوب) - اسم قصير للمعاملة
  • description (اختياري) - وصف مفصل
  • invoice_id (اختياري) - معرف الفاتورة إذا تم إصدارها مسبقًا (عادةً null)
  • wholesale_cost (اختياري) - تكلفتك
  • retail_cost (مطلوب) - التكلفة الموجهة للعميل
  • site_id (اختياري) - معرف الموقع لربط المعاملة به
  • tax_percentage (اختياري) - نسبة الضريبة

حالات الاستخدام:

  • رسوم الإعداد أثناء توفير الخدمة
  • رسوم التركيب
  • الائتمانات أو التعديلات اليدوية
  • رسوم المعدات
  • الرسوم الإدارية

ملاحظة: إنشاء المعاملة مباشرة لا يعالج الدفع - إنه فقط ينشئ سجل فواتير. ستظهر المعاملة كغير مفوترة ويمكن تضمينها في الفواتير المستقبلية.

حساب الرسوم النسبية

يسمح الشحن النسبي لك بشحن العملاء بشكل نسبي بناءً على فترات الفواتير الجزئية. هذا شائع عندما:

  • يقوم العميل بالتسجيل في منتصف الشهر
  • يتم ترقية/خفض الخدمة في منتصف الدورة
  • يتم حساب الفواتير بناءً على الأيام المستخدمة

صيغة حساب النسبة

pro_rata_charge = (monthly_cost × days_remaining) / days_in_month

مثال التنفيذ

إليك كيفية حساب رسوم نسبية في Playbook:

# حساب الرسوم النسبية لشهر جزئي
# إذا قام العميل بالتسجيل في اليوم الخامس عشر والفوترة في اليوم الأول،
# يتم الشحن بشكل نسبي للأيام المتبقية

- name: Get current day of month
command: "date +%d"
register: current_day

- name: Get total days in current month
command: "date -d 'last day of this month' +%d"
register: days_in_month

- name: Get last day of month
command: "date -d 'last day of this month' +%Y-%m-%d"
register: last_day_of_month

- name: Calculate days remaining in month
set_fact:
days_remaining: "{{ (days_in_month.stdout | int) - (current_day.stdout | int) + 1 }}"

- name: Calculate pro-rata cost
set_fact:
pro_rata_cost: "{{ ((monthly_cost | float) * (days_remaining | float) / (days_in_month.stdout | float)) | round(2) }}"

- name: Display calculation details
debug:
msg:
- "Monthly cost: ${{ monthly_cost }}"
- "Days in month: {{ days_in_month.stdout }}"
- "Days remaining: {{ days_remaining }}"
- "Pro-rata charge: ${{ pro_rata_cost }}"

# استخدم pro_rata_cost في التفويض أو إنشاء المعاملة
- name: "Authorize pro-rata payment"
uri:
url: "http://localhost:5000/crm/payments/authorize/hold"
method: POST
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": "{{ customer_id | int }}",
"amount": "{{ pro_rata_cost | float }}",
"currency": "{{ crm_config.currency | default('AUD') }}",
"payment_method_id": "{{ payment_method_id | int }}",
"metadata": {
"title": "{{ package_name }} (Pro-rata {{ days_remaining }} days)",
"description": "Pro-rated charge for {{ days_remaining }}/{{ days_in_month.stdout }} days of {{ package_name }}",
"service_id": "{{ service_id | int }}",
"invoice": true
}
}

النسبة من تاريخ بدء مخصص

إذا كنت بحاجة إلى حساب النسبة من تاريخ بدء معين إلى تاريخ الفوترة:

- name: Set custom start date and billing date
set_fact:
service_start_date: "2024-01-15"
next_billing_date: "2024-02-01"

- name: Calculate days between dates
shell: |
echo $(( ( $(date -d "{{ next_billing_date }}" +%s) - $(date -d "{{ service_start_date }}" +%s) ) / 86400 ))
register: days_until_billing

- name: Get days in billing period (usually 30)
set_fact:
billing_period_days: 30

- name: Calculate pro-rata cost
set_fact:
pro_rata_cost: "{{ ((monthly_cost | float) * (days_until_billing.stdout | float) / (billing_period_days | float)) | round(2) }}"

- name: Display calculation
debug:
msg:
- "Start date: {{ service_start_date }}"
- "Next billing: {{ next_billing_date }}"
- "Days until billing: {{ days_until_billing.stdout }}"
- "Pro-rata charge: ${{ pro_rata_cost }}"

سيناريوهات النسبة

السيناريو 1: التسجيل في منتصف الشهر

  • يقوم العميل بالتسجيل في 15 يناير
  • التكلفة الشهرية: 60.00 دولار
  • الأيام في يناير: 31
  • الأيام المتبقية: 17 (15 إلى 31 شامل)
  • الرسوم النسبية: $60.00 × 17 ÷ 31 = $32.90

السيناريو 2: ترقية الخدمة

  • يقوم العميل بالترقية في اليوم العاشر من دورة الفوترة
  • الخطة القديمة: 30 دولارًا شهريًا
  • الخطة الجديدة: 50 دولارًا شهريًا
  • الأيام في الدورة: 30
  • الأيام المتبقية: 21
  • الفرق: 20 دولارًا شهريًا
  • الرسوم النسبية: $20.00 × 21 ÷ 30 = $14.00

أفضل الممارسات

1. استخدم دائمًا نمط الكتلة/الإنقاذ

قم بلف التقاط الدفع والتوفير في كتلة/إنقاذ لضمان التراجع:

- block:
# مهام التوفير
- name: Provision service
uri: ...

# التقاط الدفع فقط بعد النجاح
- name: Capture payment
uri: ...

rescue:
# تحرير التفويض إذا فشل أي شيء
- name: Release payment authorization
uri: ...

- name: Fail playbook
fail:
msg: "فشل التوفير، لم يتم شحن العميل"

2. تحقق من جميع استجابات API

تحقق دائمًا من أن العمليات الحرجة قد نجحت:

- name: Authorize payment
uri: ...
register: api_response_authorization

- name: Assert authorization was successful
assert:
that:
- api_response_authorization.status == 200
- api_response_authorization.json.success == true
fail_msg: "فشل تفويض الدفع: {{ api_response_authorization.json }}"

3. احفظ معرف التفويض

احفظ دائمًا authorization_id لاستخدامه في الالتقاط/الإلغاء:

- name: Store authorization_id for capture/release
set_fact:
authorization_id: "{{ api_response_authorization.json.data.authorization_id }}"

4. استخدم بيانات التعريف بفعالية

قم بتضمين بيانات تعريف شاملة في طلبات التفويض:

metadata:
description: "وصف واضح لما يتم شحن العميل من أجله"
service_id: "{{ service_id | int }}"
product_id: "{{ product_id }}"
user_id: "{{ initiating_user | int }}"
title: "عنوان قصير للمعاملة"
wholesale_cost: "{{ wholesale_cost | float }}"
invoice: true # إنشاء المعاملة تلقائيًا عند الالتقاط
send_email: true # إرسال إشعار للعميل

5. قم بتقريب قيم العملة

قم دائمًا بتقريب حسابات العملة إلى منزلتين عشريتين:

- name: Calculate cost with rounding
set_fact:
final_cost: "{{ (base_cost | float * multiplier | float) | round(2) }}"

6. التعامل مع طرق الدفع المفقودة

تحقق مما إذا كان لدى العميل طريقة دفع افتراضية قبل محاولة التفويض:

- name: Get default payment method
set_fact:
payment_method_id: "{{ api_response_payment_methods.json | json_query(query) }}"
vars:
query: "data[?is_default==`true`].payment_method_id | [0]"

- name: Verify payment method exists
assert:
that:
- payment_method_id is defined
- payment_method_id != ""
- payment_method_id != None
fail_msg: "لم يتم العثور على طريقة دفع افتراضية للعميل {{ customer_id }}"

الأنماط الشائعة

النمط 1: توفير خدمة مدفوعة

بالنسبة للخدمات التي تتطلب دفعًا فوريًا:

  1. الحصول على طريقة الدفع الخاصة بالعميل
  2. تفويض الدفع
  3. توفير الخدمة في OCS/CGRateS
  4. إنشاء سجل الخدمة في CRM
  5. التقاط الدفع
  6. في حالة الفشل: تحرير التفويض والتراجع

راجع play_topup_charge_then_action.yaml للحصول على مثال كامل.

النمط 2: خدمة مجانية مع رسوم إعداد

بالنسبة للخدمات التي تكون مجانية ولكن لديها تكلفة إعداد لمرة واحدة:

  1. توفير الخدمة
  2. إنشاء سجل الخدمة
  3. إضافة معاملة رسوم الإعداد مباشرة (بدون معالجة الدفع)
  4. تظهر رسوم الإعداد في الفاتورة التالية

راجع play_simple_service.yaml في الأسطر 202-232 للحصول على مثال كامل.

النمط 3: تعبئة/إضافة مجانية

بالنسبة للتعبئة المجانية التي لا تتطلب الدفع:

  1. الحصول على معلومات الخدمة
  2. تنفيذ إجراء CGRateS
  3. تحديث تواريخ الخدمة
  4. لا حاجة للدفع أو إنشاء المعاملة

النمط 4: رسوم متكررة عبر ActionPlan

لرسوم تلقائية متكررة:

  1. إنشاء إجراء مع *http_post إلى نقطة نهاية التوفير
  2. إنشاء ActionPlan مع توقيت *monthly
  3. تعيين ActionPlan إلى الحساب
  4. ستقوم CGRateS تلقائيًا باستدعاء نقطة النهاية شهريًا
  5. تتعامل Playbook نقطة النهاية مع معالجة الدفع

استكشاف الأخطاء وإصلاحها

فشل التفويض

الأعراض: ترجع نقطة نهاية التفويض خطأ

الأسباب الشائعة:

  • طريقة الدفع غير موجودة أو غير صالحة
  • أموال غير كافية
  • انتهاء صلاحية طريقة الدفع
  • عدم تطابق معرف العميل

الحل: تحقق من حالة طريقة الدفع ورصيد العميل.

فشل الالتقاط بعد التوفير الناجح

الأعراض: تم توفير الخدمة ولكن فشل التقاط الدفع

المشكلة: هذه حالة فشل حرجة - الخدمة نشطة ولكن لم يتم شحن العميل

الحل:

  • قد يكون التفويض قد انتهت صلاحيته (عادةً 7 أيام)
  • تحقق من أن التفويض لا يزال صالحًا قبل محاولة الالتقاط
  • تنفيذ المراقبة لفشل الالتقاط
  • قد تكون هناك حاجة لتدخل يدوي

لم يتم إنشاء معاملة بعد الالتقاط

الأعراض: تم التقاط الدفع ولكن لا توجد معاملة في الفواتير

السبب: لم يتم تعيين invoice: true في بيانات التعريف الخاصة بالتفويض

الحل: إما:

  • تعيين invoice: true في بيانات التعريف الخاصة بالتفويض، أو
  • إنشاء المعاملة يدويًا بعد الالتقاط الناجح

حساب النسبة غير صحيح

الأعراض: الرسوم النسبية لا تتطابق مع القيم المتوقعة

المشكلات الشائعة:

  • أخطاء في العد (شاملة/غير شاملة تواريخ البدء/النهاية)
  • شهر خاطئ مستخدم لحساب الأيام
  • أخطاء تقريب

الحل:

  • استخدم نطاقات تواريخ شاملة (شاملة كل من اليوم الأول والأخير)
  • قم دائمًا بالتقريب إلى منزلتين عشريتين
  • اختبر الحسابات بقيم معروفة
  • وثق أي تواريخ مشمولة في الحسابات

المبالغ المستردة والتعامل مع الأخطاء

خيارات المبالغ المستردة

يدعم نظام الدفع نوعين من المبالغ المستردة:

1. استرداد إلى مصدر الدفع - يتم إرجاع الأموال إلى البطاقة الأصلية/PayPal

- name: Refund payment to customer's card
uri:
url: "http://localhost:5000/crm/payments/refund"
method: POST
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"transaction_id": "{{ vendor_transaction_id }}",
"vendor": "stripe",
"amount": "{{ refund_amount | float }}",
"reason": "customer_request"
}

2. ائتمان إلى المحفظة - رصيد فوري للشراء في المستقبل (يتم التعامل معه تلقائيًا في سيناريوهات الخطأ)

بالنسبة لفشل التوفير، يقوم النظام تلقائيًا بإئتمان المحفظة بدلاً من استرداد البطاقة إلى:

  • تجنب رسوم الاسترداد
  • توفير توافر فوري لإعادة المحاولة
  • تحسين تجربة العميل

راجع خيارات المبالغ المستردة للحصول على تفاصيل كاملة.

دعم البائع

نظام الدفع هو مستقل عن البائع ويدعم حاليًا:

  • ✅ Stripe (البطاقات، ACH)
  • ✅ PayPal (حسابات PayPal، البطاقات)

يمكن إضافة بائعين جدد للدفع (Square، Adyen، Braintree، إلخ) دون تغيير Playbooks.

انظر أيضًا:

الوثائق ذات الصلة

أدلة محددة للـ Playbook

  • concepts_ansible.md - أنماط Playbook العامة والبنية
  • concepts_provisioning.md - نظرة عامة على نظام التوفير

وثائق نظام الدفع