الشحن والمدفوعات من Playbooks
تشرح هذه الدليل كيفية تنفيذ معالجة الشحن والمدفوعات داخل Playbooks الخاصة بـ Ansible لعمليات توفير OmniCRM.
📘 مرجع API الكامل: للحصول على تفاصيل كاملة حول واجهات برمجة التطبيقات الخاصة بالمدفوعات، وتوجيه المحفظة، والمبالغ المستردة، والهندسة المعمارية المستقلة عن البائع، راجع دليل نظام المدفوعات API
نظرة عامة
يمكن لـ Playbooks الخاصة بـ OmniCRM Ansible التعامل مع معالجة المدفوعات بعدة طرق مختلفة، ولكن جميعها تقوم فقط باستدعاء واجهات برمجة التطبيقات على CRM للشحن.
- تدفق دفع الالتزام ذو المرحلتين - للخدمات المدفوعة التي تتطلب تفويضًا فوريًا للدفع والاحتفاظ (أي الخدمات المدفوعة مسبقًا)
- إنشاء معاملة مباشرة - لإضافة رسوم / ائتمانات دون معالجة الدفع الفورية (مثل رسوم الإعداد، الائتمانات اليدوية) والتي يمكن معالجتها لاحقًا (أي خدمات 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
}
}
ماذا يحدث:
- تم التقاط البطاقة للمبلغ الناقص (إذا تم تفويض البطاقة)
- تم ائتمان المحفظة بمبلغ البطاقة الملتقطة
- تم خصم المحفظة للمبلغ الكامل للخدمة
- إذا كانت
invoice: trueفي بيانات التعريف الخاصة بالتفويض:- تم إنشا�� معاملة الخصم (مبلغ إيجابي = شحن)
- تم إنشاء فاتورة مرتبطة بمعاملة الخصم
- تم إنشاء معاملة ائتمان (مبلغ سالب = المدفوعات المستلمة)
- تم وضع علامة على الفاتورة كمدفوعة (تكون الخصومات والائتمانات صافية إلى صفر)
- تظهر المعاملة في فواتير العميل
- يتم إنشاء سجل الدفع في قاعدة البيانات
- إذا كانت
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": "تم إطلاق التفويض"
}
ماذا يحدث:
- تم إطلاق تفويض البطاقة (إذا كانت البطاقة قد تم تفويضها)
- تم إرجاع الأموال المحتجزة إلى رصيد العميل المتاح
- لم يتم خصم المحفظة (لأن الخصم يحدث فقط عند الالتقاط)
- لم يتم شحن العميل
- لم يتم إنشاء أي معاملة
ملاحظة: مع توجيه المحفظة أولاً، لا حاجة لاسترداد المحفظة حيث لا يتم خصم المحفظة حتى وقت الالتقاط.
إنشاء معاملة مباشرة
بالنسبة للرسوم التي لا تتطلب معالجة الدفع (رسوم الإعداد، الائتمانات اليدوية، التعديلات)، يمكنك إنشاء معاملات مباشرة عبر API.
إضافة معاملة عبر 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: توفير خدمة مدفوعة
بالنسبة للخدمات التي تتطلب دفعًا فوريًا:
- الحصول على طريقة الدفع الخاصة بالعميل
- تفويض الدفع
- توفير الخدمة في OCS / CGRateS
- إنشاء سجل الخدمة في CRM
- التقاط الدفع
- في حالة الفشل: إطلاق التفويض والتراجع
راجع play_topup_charge_then_action.yaml للحصول على مثال كامل.
النمط 2: خدمة مجانية مع رسوم إعداد
بالنسبة للخدمات التي تكون مجانية ولكن لديها تكلفة إعداد لمرة واحدة:
- توفير الخدمة
- إنشاء سجل الخدمة
- إضافة معاملة رسوم الإعداد مباشرة (دون معالجة الدفع)
- تظهر رسوم الإعداد في الفاتورة التالية
راجع play_simple_service.yaml في الأسطر 202-232 للحصول على مثال كامل.
النمط 3: إضافة / ملحق مجاني
بالنسبة للإضافات المجانية التي لا تتطلب دفعًا:
- الحصول على معلومات الخدمة
- تنفيذ إجراء CGRateS
- تحديث تواريخ الخدمة
- لا حاجة للدفع أو إنشاء المعاملة
النمط 4: رسوم متكررة عبر ActionPlan
بالنسبة للرسوم التلقائية المتكررة:
- إنشاء إجراء مع
*http_postإلى نقطة النهاية الخاصة بالتوفير - إنشاء ActionPlan مع توقيت
*monthly - تعيين ActionPlan إلى الحساب
- ستقوم CGRateS تلقائيًا باستدعاء نقطة النهاية شهريًا
- تتعامل 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.
انظر أيضًا:
- الهندسة المعمارية المودولية - كيف تعمل تجريد البائع
- تكوين البائع - إعداد Stripe / PayPal ومفاتيح API
الوثائق ذات الصلة
أدلة محددة لـ Playbook
concepts_ansible.md- أنماط Playbook العامة والبنيةconcepts_provisioning.md- نظرة عامة على نظام التوفير
وثائق نظام المدفوعات
- دليل نظام المدفوعات API - مرجع API كامل، توجيه المحفظة، المبالغ المستردة
payments_transaction.md- إدارة المعاملات والرسوم اليدويةpayments_process.md- معالجة المدفوعات والفواتيرbasics_payment.md- طرق الدفع وفوترة العملاء