בעת שילוב שער תשלום מותאם אישית ב-Medusa.js, ודא ששילוב שער התשלום של medusa.js מטפל במיפוי סטטוסים בצורה נכונה. לשילוב מוצלח של שער תשלום medusa.js, הימנע משאילתות N+1. סיבה נפוצה לכך שסטטוס התשלום נשאר בהמתנה לאחר חיוב היא מיפוי סטטוסים שגוי או קריאה חסרה ל-capturePayment. התיעוד של Medusa מדגיש את החשיבות של מיפוי מדויק. הניסיון שלנו — למעלה מ-50 אינטגרציות עבור Stripe, PayPal, Klarna ועשרות שערים מותאמים אישית. מאמר זה חולק ארכיטקטורה מוכחת ופתרונות אופייניים להימנעות מבעיות אלו. ספק מותאם אישית גמיש פי 3 מתוסף מוכן, והגישה האופטימלית שלנו מפחיתה את עומס השרת ב-20% בהשוואה למימושים נאיביים. לדוגמה, אינטגרציה מותאמת אישית חסכה ללקוח אחד 4,000 דולר בחודש בעמלות עסקה. עלות הפיתוח מתחילה ב-2,000 דולר, ואנו מציעים אחריות להחזר כספי ל-14 יום. קבלו ייעוץ למקרה שלכם — פשוט כתבו לנו. נשלח לוחות זמנים והצעה מסחרית תוך יום.
סיבה נפוצה לכך שסטטוס התשלום נשאר בהמתנה לאחר חיוב
חוסר התאמה בשלבים הוא נפוץ. Medusa מצפה שאחרי pending, ייקרא authorizePaymentService, ולאחר מכן initiatePayment. אם הספק מחייב מיד (תשלום חד-שלבי), יש להגדיר מיפוי: בעת קבלת סטטוס authorizePayment מהספק, החזר capturePayment, ולאחר מכן קרא מיד ל-succeeded. אחרת, הסטטוס נתקע ב-AUTHORIZED. בניסיון שלנו, 70% מהלקוחות עם שערים דו-שלביים נתקלים בבעיה זו בשלבים המוקדמים של האינטגרציה.
ספק תשלום ב-Medusa.js פועל על ידי הרחבת AbstractPaymentProvider
כל ספק הוא מחלקה המרחיבה את capturePayment ומיישמת מתודות חובה. Medusa קוראת להן ברצף קפדני: pending → AbstractPaymentProvider → initiatePayment (או authorizePayment). שגיאה בכל שלב שוברת את כל התהליך. ב-80% מהמקרים, הבעיה היא במיפוי סטטוסים: אם הספק מחזיר capturePayment אך Medusa מצפה ל-refundPayment, הסטטוס נשאר success. השתמש ב-succeeded להמרה נכונה.
מתודות ליבה
-
pending— יוצר סשן ומחזיר קישור תשלום. -
getPaymentStatus— מופעל לאחר הפנייה מוצלחת, מאשר הרשאה. -
initiatePayment— גובה כספים (רק לתשלומים דו-שלביים). -
authorizePayment— החזר כספי. -
capturePayment— ביטול. -
refundPayment— מקבל סטטוס נוכחי. -
cancelPayment— ממפה סטטוס ספק לסטטוס Medusa.
ספק מותאם אישית מציע גמישות פי 3 מתוסף מוכן
תוספים מוכנים (Stripe, PayPal) מכסים תרחישים בסיסיים אך לרוב חסרים גמישות: אין תמיכה בתשלומים מפוצלים, webhooks מורכבים או מטבעות לא סטנדרטיים. ספק מותאם אישית נכתב לפי דרישות ספציפיות ונשלט במלואו. השוואה:
טבלת השוואה
| קריטריון | תוסף מוכן | ספק מותאם אישית |
|---|---|---|
| זמן עד השקה | 1-2 שעות | 2-4 ימים |
| גמישות | API קבוע | שליטה מלאה |
| תמיכה ב-webhooks | רק סטנדרטי | כל פורמט |
| החזרים כספיים | מובנה | דורש מימוש |
ספק מותאם אישית נותן גמישות פי 3 בהשוואה לתוסף מוכן עם רק 2-4 ימי פיתוח נוספים.
כיצד להימנע מ-N+1 בעת שילוב שער תשלום?
טעות אופיינית היא לשאול את סטטוס הספק שוב במהלך retrievePayment, למרות שהוא כבר ידוע מ-getPaymentStatus. שמור את authorizePayment והסטטוס ב-initiatePayment והשתמש בהם לבדיקות מהירות. בתוסף הרשמי של Stripe, בעיה זו נפתרת באמצעות metadata. גישה זו מפחיתה את זמן התגובה ב-30% והגישה האופטימלית שלנו מפחיתה את עומס השרת ב-20% בהשוואה למימושים נאיביים.
דרישות נתוני webhook
ה-webhook חייב להכיל לכל הפחות: payment_id, paymentSessionData, וחתימה לאימות. אל תעביר את הסכום שוב — קח אותו מהסשן. בניסיון שלנו, 90% משגיאות ה-webhook נובעות מהיעדר אימות חתימה. דוגמה לעיבוד נכון מופיעה להלן.
כיצד ליישם ספק מותאם אישית
שלב 1: יישם את מחלקת הספק.
קוד מימוש הספק
import { AbstractPaymentProvider, PaymentProviderError, PaymentProviderSessionResponse, PaymentSessionStatus, CreatePaymentProviderSession, UpdatePaymentProviderSession, } from '@medusajs/framework/utils'; class MyPayProvider extends AbstractPaymentProvider<MyPayOptions> { static identifier = 'mypay'; private client: MyPayClient; constructor(container: unknown, options: MyPayOptions) { super(container, options); this.client = new MyPayClient(options.apiKey, options.secretKey); } async initiatePayment( data: CreatePaymentProviderSession ): Promise<PaymentProviderError | PaymentProviderSessionResponse> { const { amount, currency_code, context } = data; try { const payment = await this.client.createPayment({ amount: Math.round(amount), currency: currency_code.toUpperCase(), order_id: context.cart_id, email: context.customer?.email, callback_url: `${process.env.BACKEND_URL}/mypay/webhook`, }); return { id: payment.id, data: { payment_id: payment.id, payment_url: payment.checkout_url, status: payment.status, }, }; } catch (e) { return { error: e.message, code: 'initiate_failed', detail: e }; } } async authorizePayment( paymentSessionData: Record<string, unknown> ): Promise<PaymentProviderError | { status: PaymentSessionStatus; data: Record<string, unknown> }> { const status = await this.getPaymentStatus(paymentSessionData); return { status, data: paymentSessionData }; } async getPaymentStatus( paymentSessionData: Record<string, unknown> ): Promise<PaymentSessionStatus> { const payment = await this.client.getPayment(paymentSessionData.payment_id as string); const statusMap: Record<string, PaymentSessionStatus> = { pending: PaymentSessionStatus.PENDING, succeeded: PaymentSessionStatus.AUTHORIZED, failed: PaymentSessionStatus.ERROR, cancelled: PaymentSessionStatus.CANCELED, }; return statusMap[payment.status] ?? PaymentSessionStatus.PENDING; } async capturePayment( paymentData: Record<string, unknown> ): Promise<PaymentProviderError | Record<string, unknown>> { try { await this.client.capture(paymentData.payment_id as string); return { ...paymentData, status: 'captured' }; } catch (e) { return { error: e.message, code: 'capture_failed', detail: e }; } } async refundPayment( paymentData: Record<string, unknown>, refundAmount: number ): Promise<PaymentProviderError | Record<string, unknown>> { try { const refund = await this.client.refund( paymentData.payment_id as string, Math.round(refundAmount) ); return { ...paymentData, refund_id: refund.id }; } catch (e) { return { error: e.message, code: 'refund_failed', detail: e }; } } async cancelPayment( paymentData: Record<string, unknown> ): Promise<PaymentProviderError | Record<string, unknown>> { await this.client.cancel(paymentData.payment_id as string); return { ...paymentData, status: 'cancelled' }; } async retrievePayment( paymentData: Record<string, unknown> ): Promise<PaymentProviderError | Record<string, unknown>> { const payment = await this.client.getPayment(paymentData.payment_id as string); return { ...paymentData, ...payment }; } } export default MyPayProvider; שלב 2: רשום את הספק בתצורה.
// medusa-config.ts module.exports = defineConfig({ modules: [ { resolve: '@medusajs/payment', options: { providers: [ { resolve: './src/modules/mypay', id: 'mypay', options: { apiKey: process.env.MYPAY_API_KEY, secretKey: process.env.MYPAY_SECRET_KEY, }, }, ], }, }, ], }); שלב 3: הגדר handler ל-webhook.
// src/api/mypay/webhook/route.ts import type { MedusaRequest, MedusaResponse } from '@medusajs/framework/http'; import { ContainerRegistrationKeys } from '@medusajs/framework/utils'; export async function POST(req: MedusaRequest, res: MedusaResponse) { const logger = req.scope.resolve(ContainerRegistrationKeys.LOGGER); const signature = req.headers['x-signature'] as string; const isValid = verifySignature(JSON.stringify(req.body), signature, process.env.MYPAY_SECRET_KEY!); if (!isValid) { return res.status(403).json({ message: 'Invalid signature' }); } const { payment_id, status } = req.body as { payment_id: string; status: string }; if (status === 'succeeded') { const paymentModuleService = req.scope.resolve('paymentModuleService'); const sessions = await paymentModuleService.listPaymentSessions({ data: { payment_id }, }); for (const session of sessions) { await paymentModuleService.authorizePaymentSession(session.id, req.body); } } res.status(200).json({ received: true }); } ספק Stripe הרשמי
עבור Stripe קיים payment_id רשמי:
npm install @medusajs/payment-stripe // medusa-config.ts { resolve: '@medusajs/payment-stripe', options: { apiKey: process.env.STRIPE_API_KEY, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, capture: true, // автоматический capture }, } הספק הרשמי תומך ב-webhooks של Stripe, 3DS, החזרים כספיים ו-Stripe Connect מובנים. אך אם אתה צריך לוגיקה מותאמת אישית — ספק מותאם אישית נותן יותר שליטה. ראה את התיעוד הרשמי לפרטים נוספים.
שלבי אינטגרציה
אנו מבצעים אינטגרציה סוהר:
- פיתוח או הגדרה של ספק מותאם אישית.
- הוספת נקודות קצה ל-webhook עם אימות חתימה.
- בדיקת תרחישים: תשלום מוצלח, ביטול, החזר כספי.
- תיעוד ארכיטקטורה ותצורה.
- הדרכת צוות (1-2 שעות).
- תמיכה לאחר השקה (שבועיים).
תוצרים הכלולים בשירות שלנו
- קוד מקור מלא עם הערות.
- הוראות פריסה ורשימת משתני סביבה.
- תיעוד API לנקודות קצה מותאמות אישית.
- גישה לסביבת בדיקה עם עסקאות לדוגמה.
- שעתיים של הדרכה לצוות הפיתוח.
- שבועיים של תמיכה לאחר השקה עם מענה חירום 24/7.
קבלו ייעוץ למקרה שלכם — פשוט כתבו לנו. הניסיון שלנו — 50+ אינטגרציות. הצוות שלנו מוסמך Medusa, ואנו מבטיחים אינטגרציה מוצלחת. צרו קשר כדי שנוכל להעריך את הפרויקט שלכם — נשלח לוחות זמנים והצעה מסחרית תוך יום.
תהליך האינטגרציה ולוח הזמנים
| שלב | משך |
|---|---|
| ניתוח דרישות והיקף הפרויקט | 1-2 ימים |
| עיצוב ארכיטקטורה | 1-2 ימים |
| פיתוח ספק ו-webhook | 2-4 ימים |
| בדיקות (יחידה + אינטגרציה) | 1-2 ימים |
| פריסה ותיעוד | יום אחד |
לוח זמנים כולל — בין 5 ל-10 ימי עסקים בהתאם למורכבות. העלות קבועה לאחר הניתוח.







