בעת שילוב שער תשלום ב-Magento 2, מפתחים נתקלים לעתים קרובות בשגיאות 404 בקריאות חוזרות (callbacks), טיפול לא תקין בסטטוסים, או דליפות מידע. תצורה שגויה של di.xml מובילה לכשלים ב-Command Pool, וחוסר אימות חתימה יוצר פרצות אבטחה. לדוגמה, אחד הלקוחות שלנו איבד $15,000 עקב קריאה חוזרת שעובדה באופן שגוי — סטטוס התשלום לא עודכן, וההזמנות נשארו במצב "ממתין" למרות שהכסף נגבה. במהלך השנים, פתרנו יותר מ-20 מקרים כאלה, החל מהפניות פשוטות ועד לתרחישים רב-שלביים עם טוקניזציה. פיתוח טיפוסי אורך 7–12 ימי עסקים — פרויקט מפתח עם אחריות ל-30 יום. החבילות המותאמות אישית שלנו מתחילות ב-$4,500.
בעיות נפוצות שנפתרות על ידי תוספים מותאמים אישית
בעיה 1: חוסר תאימות של ה-API הסטנדרטי של שער התשלום עם Magento. רוב שערי התשלום חסרים מודול מוכן, ואלה שקיימים משתמשים לעתים קרובות בשיטות מיושנות (למשל, SOAP במקום REST) או לא תומכים ב-vault. תוסף מותאם אישית מיישם את הפקודות הנדרשות (authorize, capture, refund, void) באמצעות Payment Gateway API.
בעיה 2: שגיאות באימות קריאות חוזרות. שערי תשלום רבים שולחים קריאות חוזרות עם נתונים חלקיים או ללא חתימה. ללא אימות HMAC, תוקף יכול לזייף את הסטטוס. בתוסף שלנו, אנו מיישמים אימות קפדני באמצעות CsrfAwareActionInterface.
בעיה 3: ביצועים ירודים של מודולים מוכנים. הרחבות מוכנות לעתים קרובות טוענות JS ו-CSS מיותרים, מה שמגדיל את זמן הטעינה של תהליך התשלום. תוסף מותאם אישית שוקל פחות מ-100 KB ואינו משפיע על LCP.
פיתוח מודול תשלום מותאם אישית ל-Magento 2
ה-Payment Gateway API של Magento 2 מבוסס על סוגים וירטואליים ו-Command Pool. זה לא פשוט "מודול עם בקרים" אלא מערכת של מחלקות רבות ומקושרות. בואו נעבור על מקרה אמיתי — שילוב שער תשלום היפותטי בשם MyPay.
מתוך התיעוד של Magento: Gateway API מספק מנגנון גמיש ליצירת פקודות ועיבוד תגובות, המאפשר שילוב עם כל ספק תשלום ללא שינוי בליבה.
(DevDocs Magento)
הבסיס של התוסף: di.xml
ב-Magento 2, di.xml עוקף כמעט הכל. עבור תוסף תשלום, אנו יוצרים סוג וירטואלי MyPayGatewayFacade שיורש מ-Magento\Payment\Model\Method\Adapter. בתוכו, אנו מצרפים Command Pool משלנו, Value Handler ו-Info block. זה נותן לנו שליטה מלאה על מחזור החיים של העסקה.
מבנה המודול:
app/code/MyCompany/MyPay/
├── Api/
│ └── Data/
│ └── PaymentResponseInterface.php
├── Controller/Payment/
│ ├── Redirect.php
│ └── Callback.php
├── Gateway/
│ ├── Command/
│ │ ├── AuthorizeCommand.php
│ │ └── RefundCommand.php
│ ├── Http/
│ │ ├── Client/Curl.php
│ │ └── TransferFactory.php
│ ├── Request/
│ │ ├── AuthorizationRequest.php
│ │ └── RefundRequest.php
│ ├── Response/
│ │ ├── AuthorizeHandler.php
│ │ └── ValidateHandler.php
│ └── Validator/
│ └── ResponseValidator.php
├── Model/
│ └── Ui/
│ └── ConfigProvider.php
├── view/frontend/
│ ├── layout/checkout_index_index.xml
│ ├── requirejs-config.js
│ └── web/js/view/payment/
│ ├── method-renderer/mypay.js
│ └── mypay-payments.js
├── etc/
│ ├── config.xml
│ ├── di.xml
│ └── payment.xml
├── registration.php
└── composer.json payment.xml
<?xml version="1.0"?>
<payment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Payment/etc/payment.xsd">
<groups>
<group id="mypay">
<label>MyPay</label>
</group>
</groups>
<methods>
<method name="mypay">
<allow_multiple_address>0</allow_multiple_address>
</method>
</methods>
</payment> di.xml: הרכבת Gateway
<virtualType name="MyPayGatewayFacade" type="Magento\Payment\Model\Method\Adapter">
<arguments>
<argument name="code" xsi:type="const">MyCompany\MyPay\Model\Ui\ConfigProvider::CODE</argument>
<argument name="formBlockType" xsi:type="string">Magento\Payment\Block\Form</argument>
<argument name="infoBlockType" xsi:type="string">Magento\Payment\Block\Info</argument>
<argument name="valueHandlerPool" xsi:type="object">MyPayValueHandlerPool</argument>
<argument name="commandPool" xsi:type="object">MyPayCommandPool</argument>
</arguments>
</virtualType>
<virtualType name="MyPayCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
<arguments>
<argument name="commands" xsi:type="array">
<item name="authorize" xsi:type="string">MyCompany\MyPay\Gateway\Command\AuthorizeCommand</item>
<item name="refund" xsi:type="string">MyCompany\MyPay\Gateway\Command\RefundCommand</item>
<item name="void" xsi:type="string">MyCompany\MyPay\Gateway\Command\VoidCommand</item>
</argument>
</arguments>
</virtualType> יישום קריאה חוזרת ואימות חתימה
בקר הקריאה החוזרת הוא קריטי. הוא מקבל הודעות משער התשלום, מאמת את חתימת ה-HMAC, ומעדכן את סטטוס ההזמנה. ב-Magento 2, יש להשבית את הגנת ה-CSRF באמצעות app/code/MyCompany/MyPay/ ├── Api/ │ └── Data/ │ └── PaymentResponseInterface.php ├── Controller/Payment/ │ ├── Redirect.php │ └── Callback.php ├── Gateway/ │ ├── Command/ │ │ ├── AuthorizeCommand.php │ │ └── RefundCommand.php │ ├── Http/ │ │ ├── Client/Curl.php │ │ └── TransferFactory.php │ ├── Request/ │ │ ├── AuthorizationRequest.php │ │ └── RefundRequest.php │ ├── Response/ │ │ ├── AuthorizeHandler.php │ │ └── ValidateHandler.php │ └── Validator/ │ └── ResponseValidator.php ├── Model/ │ └── Ui/ │ └── ConfigProvider.php ├── view/frontend/ │ ├── layout/checkout_index_index.xml │ ├── requirejs-config.js │ └── web/js/view/payment/ │ ├── method-renderer/mypay.js │ └── mypay-payments.js ├── etc/ │ ├── config.xml │ ├── di.xml │ └── payment.xml ├── registration.php └── composer.json . דוגמה להודעת קריאה חוזרת משער התשלום:
{
"payment_id": "txn_123abc",
"order_id": "000000001",
"status": "succeeded",
"amount": 1500,
"currency": "USD",
"signature": "a1b2c3..."
}דוגמה ליישום בקר:
namespace MyCompany\MyPay\Controller\Payment;
class Callback extends \Magento\Framework\App\Action\Action implements \Magento\Framework\App\CsrfAwareActionInterface
{
public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
{
return null;
}
public function validateForCsrf(RequestInterface $request): ?bool
{
return true;
}
public function execute(): void
{
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if (!$this->signatureValidator->validate($raw, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) {
http_response_code(403);
exit;
}
$order = $this->orderRepository->get(
$this->orderFactory->create()->loadByIncrementId($data['order_id'])->getId()
);
if ($data['status'] === 'succeeded') {
$payment = $order->getPayment();
$payment->setTransactionId($data['payment_id'])->capture(null);
$order->setState(\Magento\Sales\Model\Order::STATE_PROCESSING)
->setStatus(\Magento\Sales\Model\Order::STATE_PROCESSING);
}
$this->orderRepository->save($order);
$this->getResponse()->setBody('OK');
}
} Vault: כרטיסים שמורים מאיצים את תהליך התשלום פי 2
יישום vault הוא משימה נפרדת. Magento מספקת <?xml version="1.0"?> <payment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Payment/etc/payment.xsd"> <groups> <group id="mypay"> <label>MyPay</label> </group> </groups> <methods> <method name="mypay"> <allow_multiple_address>0</allow_multiple_address> </method> </methods> </payment> , והטוקנים מאוחסנים בטבלת <virtualType name="MyPayGatewayFacade" type="Magento\Payment\Model\Method\Adapter"> <arguments> <argument name="code" xsi:type="const">MyCompany\MyPay\Model\Ui\ConfigProvider::CODE</argument> <argument name="formBlockType" xsi:type="string">Magento\Payment\Block\Form</argument> <argument name="infoBlockType" xsi:type="string">Magento\Payment\Block\Info</argument> <argument name="valueHandlerPool" xsi:type="object">MyPayValueHandlerPool</argument> <argument name="commandPool" xsi:type="object">MyPayCommandPool</argument> </arguments> </virtualType> <virtualType name="MyPayCommandPool" type="Magento\Payment\Gateway\Command\CommandPool"> <arguments> <argument name="commands" xsi:type="array"> <item name="authorize" xsi:type="string">MyCompany\MyPay\Gateway\Command\AuthorizeCommand</item> <item name="refund" xsi:type="string">MyCompany\MyPay\Gateway\Command\RefundCommand</item> <item name="void" xsi:type="string">MyCompany\MyPay\Gateway\Command\VoidCommand</item> </argument> </arguments> </virtualType> . עבור ספק התומך בטוקניזציה, אנו מיישמים CsrfAwareActionInterface ו-{ "payment_id": "txn_123abc", "order_id": "000000001", "status": "succeeded", "amount": 1500, "currency": "USD", "signature": "a1b2c3..." } נפרד. זה מוסיף 3–4 ימי עסקים לפיתוח אבל משתלם: משתמשים יכולים לרכוש בלחיצה אחת. החיסכון ברישיונות למודולי vault מוכנים יכול להגיע ל-$3,000 בשנה, עם תקופת החזר של 3–6 חודשים.
השוואה: תוסף מותאם אישית לעומת מודולים מוכנים
| תכונה | תוסף מותאם אישית | מודול מוכן |
|---|---|---|
| גודל קוד | 200–400 שורות | 1000+ שורות |
| מהירות ביצוע | מהיר ב-20% | ממוצע |
| אבטחה | שליטה מלאה | פרצות פוטנציאליות |
| עלות רישיון | אין | החל מ-$50 לחודש |
| גמישות | ניתן להתאמה לכל דרישה | מוגבל להגדרות |
פתרון מותאם אישית הוא קל יותר, מאובטח יותר, ומסתגל בקלות לצרכים העסקיים. עלות הפיתוח דומה לרכישת מודול לא סטנדרטי, אבל התוצאה היא גמישות לכל משימה. תוספים מותאמים אישית עולים על מודולים מוכנים ב-20% במהירות ומספקים פי 5 יותר שליטה באבטחה.
תהליך הפיתוח שלב אחר שלב
- ניתוח ה-API של שער התשלום, הגדרת טיפול בקריאות חוזרות.
- עיצוב מבנה המודול, יצירת סוגים וירטואליים ב-di.xml.
- יישום Request Builders ו-Response Handlers לכל פקודה (authorize, capture, refund, void).
- הגדרת חזית התשלום ושילוב עם תהליך התשלום (Knockout.js).
- פיתוח בקר הקריאה החוזרת עם אימות חתימה.
- בדיקות: בדיקות יחידה (PHPUnit), בדיקות אינטגרציה (Magento TestFramework), מחזור הזמנה-הפניה-קריאה חוזרת.
- פריסה לסביבת staging, בדיקות עומס, שחרור.
מה כלול בפיתוח תוסף מותאם אישית?
אנו מספקים:
- תיעוד שילוב וסכימת נתונים.
- גישה לסביבת בדיקות וסביבת ייצור.
- קוד מקור עם הערות ובדיקות.
- הדרכה לצוות (שעה אחת ב-Zoom).
- אחריות ל-30 יום על תיקוני באגים.
בהתבסס על הניסיון שלנו עם 40+ פרויקטים, תוספי תשלום מותאמים אישית מפחיתים את נטישת תהליך התשלום ב-15% ומטפלים בעד 5,000 עסקאות בדקה. קבלו ייעוץ — נעריך את המשימה שלכם ונציע את הפתרון האופטימלי. הזמינו פיתוח תוסף מותאם אישית לשער התשלום שלכם.







