حزمة SDK لـ Python
anis-partners — لـ Python 3.11 إلى 3.14، مع عميل متزامن وآخر غير متزامن يتصرفان بالطريقة نفسها، مبنيَّين على
httpx ومكتوبَين بأنواع كاملة.
- الحزمة: pypi.org/project/anis-partners
- الشيفرة المصدرية: github.com/anis-ly/anis.partners-sdk-python
أداة مساعدة اختيارية، تُقدَّم كما هي
كل حزم SDK من أنيس مجانية وتُقدَّم بموجب رخصة MIT «كما هي»، دون أي ضمان من أي نوع. ولا تتحمّل أنيس (Aniscom for Technical Services) أي مسؤولية أو التزام عن استخدامها أو عن أي خسارة تنشأ عنه. وتبقى أنت المسؤول عن ربط نظامك — تسجيل طلبات الشراء قبل إرسالها، والاستئناف، وحفظ المفاتيح، والاختبار. الشيفرة المصدرية علنية: اقرأها لتفهم بدقة ما تفعله قبل أن تعتمد عليها. ولا تحتاج إلى حزمة SDK أصلًا — يمكنك استدعاء واجهة API لشركاء أنيس مباشرة باتباع دليل الاستخدام وصفحة مرجع API.
حزمة SDK بالإصدار 2.2.0. إصدارات 1.x مهجورة؛ راجع سجل التغييرات.
التثبيت
pip install anis-partners==2.2.0
الإعداد
from anis_partners import AcceptLanguage, AnisPartnersClient, ClientOptions, PemP256Signer
options = ClientOptions(
"https://<the address Anis gave you>",
signature_lifetime_seconds=60,
accept_language=AcceptLanguage.ARABIC,
signing_key_cache_seconds=300,
timeout_seconds=30,
)
signer = PemP256Signer.from_pem_file("/secure/partner-key.pem").for_key(key_id)
with AnisPartnersClient(options, signer) as anis:
...
مع شيفرة asyncio استخدم AsyncAnisPartnersClient بالطريقة نفسها، مع async with وawait. تُفحص الإعدادات عند بناء
الخيارات: فعنوان ليس HTTPS (يُقبل HTTP العادي على جهازك أنت فقط)، أو مدة صلاحية توقيع خارج النطاق من 1 إلى 60
ثانية، أو مدة حفظ للمفاتيح تتجاوز 5 دقائق، يوقفه عند تلك النقطة.
المُوقِّع خطوة منفصلة عن قصد. يوقّع PemP256Signer باستخدام ملف مفتاح. أما مع خزنة أسرار أو وحدة أمان عتادية،
فنفّذ RequestSigner — معرّف مفتاحك key_id ودالة sign(data) تعيد التوقيع ذا الـ 64 بايت — ولا يدخل المفتاح إلى
عمليتك أبدًا. ويقبل العميل غير المتزامن أيضًا مُوقِّعًا تكون sign فيه دالة coroutine، فلا تحجب رحلة الاتصال
بالخزنة حلقة الأحداث أبدًا.
عدة عمليات عاملة (gunicorn وCelery وOdoo): مرّر key_cache= — كائنًا بدالتي get وset — لكي تتشارك العمليات
وثيقة مفاتيح التوقيع العامة الخاصة بأنيس بدل أن تجلبها كل عملية بنفسها. استخدم ذاكرة مؤقتة لا يكتب فيها غير تطبيقك.
تحتفظ الحزمة بقائمة مفاتيح أنيس لمدة لا تتجاوز 5 دقائق من وقت جلبها، حتى حين تأتي من الذاكرة المؤقتة المشتركة.
استخدمها
profile = anis.profile.get()
for wallet in anis.wallets.list(): # every page, followed for you
print(wallet.name or wallet.id, wallet.balance)
for card in anis.catalogue.list_cards(wallet_id, subcategory_id):
print(card.id, card.unit_price)
credential = anis.owned_cards.reveal(wallet_id, sold_card_id)
المبالغ قيم من النوع Money تحمل Decimal بثلاث خانات عشرية بالضبط — وليست أعدادًا عشرية عائمة أبدًا.
يجري price.multiply(2) حسابًا دقيقًا.
ما الذي تتحقق منه
منذ الإصدار 2.0.1، تتحقق الحزمة من كل ردّ على الاستدعاءات التي توقّع أنيس ردودها — تقديم طلب شراء، وقراءة
طلب شراء، وكشف بطاقة، وكشف بطاقات فاتورة، واستدعاءات تسجيل المفتاح الأربعة، وتعليق التطبيق، وإلغاء المفتاح، والفحص الذاتي للتوقيع — نجاحًا كان الردّ
أم رفضًا. وتشترط وجود التوقيع هناك: فالردّ على أحد هذه الاستدعاءات الذي لا يحمل توقيعًا صحيحًا يُتخلَّص منه
(UnverifiableResponseError)، ويعود في طلب الشراء في صورة OrderOutcomeUnknown. ولا تتحقق من استدعاءات المعلومات — ملفك التعريفي
والمحافظ والكتالوج وبطاقاتك المملوكة (القائمة والقراءة): فردودها لا تحمل توقيعًا، وحماية TLS تكفيها. وما زالت توقّع كل
استدعاء.
راجع التحقق من الردود لمعرفة أي الردود موقَّعة.
طلبات الشراء
from uuid import uuid4
from anis_partners import (
CreateOrderRequest,
OrderCompleted,
OrderNotPlaced,
OrderOutcomeUnknown,
OrderProcessing,
OrderReplayed,
)
price = card.unit_price
if price is None:
raise ValueError("This card has no price for this wallet.")
operation_id = uuid4() # yours
request = CreateOrderRequest(card.id, quantity=2, expected_unit_price=price, expected_total=price.multiply(2))
save_order_intent(operation_id, wallet_id, request) # store it BEFORE sending
outcome = anis.orders.create(wallet_id=wallet_id, operation_id=operation_id, order=request)
if isinstance(outcome, OrderCompleted) and outcome.credentials:
store_credentials(outcome.credentials) # store FIRST
match outcome:
case OrderCompleted(codes_withheld=True):
report_codes_withheld(operation_id) # paid, codes withheld: do not buy again; write to support@anis.ly
case OrderCompleted():
mark_ready(operation_id)
case OrderReplayed(order=earlier):
ensure_stored(earlier) # sent earlier: reveal by invoice if you never stored them
case OrderProcessing(retry_after=delay) | OrderOutcomeUnknown(suggested_delay=delay):
resume_later(operation_id, delay) # may still complete: resume with the SAME id
case OrderNotPlaced(refusal=refusal):
close_not_placed(operation_id, refusal) # nothing bought: fix the cause, new order, new id
يرسل anis.orders.resume(wallet_id=..., operation_id=..., order=...) طلب الشراء نفسه مرة أخرى بالمعرّف نفسه — وهذه هي
طريقة الاستعادة — ويعيد النتائج الخمس نفسها. والاستعادة دائمًا هي إعادة الإرسال الموقَّعة هذه، وليست حلقة من
anis.orders.get() التي تقرأ الحالة فقط. وطلب الشراء الذي حالته RECOVERY_EXHAUSTED لم يفشل: واصل استئناف
المعرّف نفسه ببطء (بالدقائق لا بالثواني)، وأخبر support@anis.ly بمعرّف العملية. وفي وحدة Odoo
أو أي تطبيق مدعوم بقاعدة بيانات، ثبّت نيّة الطلب (commit) قبل أن تستدعي أنيس. راجع
طلبات الشراء والاستعادة للاطلاع على القواعد كاملةً.
لا ناقل لإعادة المحاولة
يعيد ناقل httpx الذي يكرّر المحاولة إرسال الاستدعاء الموقَّع نفسه بقيمة الاستخدام الواحد نفسها، فترفض
أنيس النسخة المكرَّرة. استعِد طلبات الشراء بـ orders.resume() التي توقّع استدعاءً جديدًا.
حالات الرفض
لا يرفع طلب الشراء الرفضَ استثناءً: يعود في صورة OrderNotPlaced أو OrderOutcomeUnknown. أما بقية الاستدعاءات فترفع
AnisApiError (أو صنفًا فرعيًا منه مثل PriceChangedError أو InsufficientBalanceError) يحمل code وrequest_id
وretry_after وis_replayed. وكل صفحة خطأ تذكر الرمز.
| الاستثناء | المعنى | في طلب الشراء |
|---|---|---|
UnverifiableResponseError |
ردّ على استدعاء توقّع أنيس ردوده تعذّر إثبات أنه صادر من أنيس؛ فجرى التخلّص منه | لا يُرفع — يعود OrderOutcomeUnknown: استأنف بالمعرّف نفسه |
LimitExceededError (spending_limit_exceeded، 429) |
كان الطلب سيتجاوز سقف إنفاق التطبيق أو المحفظة في النافذة الزمنية الحالية؛ لم يُشترَ شيء ولم يُخصم شيء | لا يُرفع — OrderNotPlaced. بعد retry_after قدّم طلبًا جديدًا بمعرّف جديد؛ ولا تستأنف هذا الطلب |
RequestSigningError |
أخفق المُوقِّع لديك؛ لم يُرسَل شيء | يُرفع — أرسله مرة أخرى بعد أن يعمل المُوقِّع |
ValueError، TypeError |
مدخلاتك خاطئة (معرّف ليس UUID، أو إجمالي لا يساوي سعر الوحدة × الكمية)؛ لم يُرسَل شيء | يُرفع قبل إرسال أي شيء |
كل خطأ في الحزمة يشتق من AnisPartnersError، فيكفي except واحد لتغطيتها كلها.
المراقبة
تذهب السجلات إلى وحدة logging القياسية تحت المسجّل anis_partners؛ وتمرّ التتبّعات والمقاييس عبر OpenTelemetry تحت
anis_partners. لا يُسجَّل أي مفتاح ولا توقيع ولا قيمة استخدام واحد ولا رمز بطاقة أبدًا. راجع
المراقبة لمعرفة ما يستحق التنبيه عليه.
الدوال وردود البطاقات
تستخدم استدعاءات البطاقات المملوكة /v1/wallets/{walletId}/owned-cards، وقراءة /{soldCardId}
وكشف /{soldCardId}/reveal، و/v1/wallets/{walletId}/invoices/{invoiceId}/owned-cards/reveal.
وتستخدم قراءة بطاقة الكتالوج GET /v1/wallets/{walletId}/catalog/cards/{cardId}.
تُستخدم CardSummary في البطاقات المملوكة والكشف وsoldCards في طلب الشراء: معرّف البطاقة واسمها المحلي،
والقيمة المطبوعة والشعار الاختياريان، والفئة الفرعية والفئة. تستخدم البطاقة المملوكة soldCardId وoperationId.
وتشمل البطاقة المكشوفة داخل طلب الشراء معرّف الفاتورة وملخص البطاقة ووقت الشراء أيضًا. تُحذف النصوص الفارغة
والنصوص المحلية الفارغة. المال نص بثلاث خانات عشرية ورمز عملة من ثلاثة أحرف كبيرة — ولا يكون سالبًا إلا رصيد المحفظة حين تستخدم الدين المسموح؛ اقرأ عملة المحفظة
من balance.currency.
تشترط الحزمة وسم التوقيع v2، وترويستي توقيع الاستدعاء في قائمة الأجزاء، وفحوص دورة حياة المفاتيح المنشورة. راجع التحقق من الردود. يسري الإيقاف فورًا؛ ولا يستأنف التطبيق إلا موظفو أنيس، وإلغاء مفتاحك الحالي يتطلب تسجيلًا جديدًا. راجع الأمان.
reason = "Suspected compromise"
catalogue_card = client.catalogue.get_card(wallet_id, card_id)
suspended = client.access.suspend_application(reason)
revoked = client.access.revoke_key(key_id, reason)
موارد المنتجات في SDK 2.2.0
يضيف الإصدار 2.2.0 من حزم SDK meterCharges وdirectRechargeProducts (المنتجات والمواقع وحساب السعر) وdirectRecharges (الإنشاء وقائمة عملياتك والقراءة وتعديل المستلم) وwebhookEndpoint وwebhookEvents وأداة للتحقق من توقيع الإشعارات وأنواع الأخطاء المحدّثة. حدّث من الإصدار 2.1.0 لاستخدامها. راجع أدلة العدادات والشحن المباشر والإشعارات لمعرفة سلوك HTTP. احفظ معرّف العملية ومحتواها كما سترسله قبل أي استدعاء ينقل مالًا؛ واستعد الردّ الضائع بالمعرّف نفسه. يذكر ملف README في كل حزمة وملف docs/webhooks.md فيها أسماء الدوال ومعاملات أداة التحقق.