أربعة نماذج Stripe التي نشغلها فعليًا: من الاشتراكات المرحلية إلى أسواق Connect

لقد قمت بإعداد تكاملات Stripe ربما عشر مرات على مدار السنوات القليلة الماضية، وإليك ما تعلمته: البرنامج التعليمي للبدء هو الجزء السهل. الجزء الصعب هو تشغيل أربعة نماذج تسعير مختلفة تمامًا في نفس المنظمة، والتعامل مع 30+ عملة دولة بدون أخطاء تقريب، والتأكد من أن معالجات webhook الخاصة بك لا تفشل بصمت الساعة 3 صباحًا يوم السبت.

هذا ليس منشورًا آخر بعنوان "إنشاء جلسة checkout في 5 دقائق". سنمر عبر أربعة نماذج تسعير إنتاجي بنيناها وشغلناها -- الاشتراكات المرحلية مع التسعير الإقليمي، وعمولات السوق عبر Stripe Connect، والتبرعات المتكررة المرتبطة بكيانات محددة، وتدفعات الخدمات لمرة واحدة. لكل منها مجموعتها الخاصة من المشاكل المحتملة، وسأشارك أنماط الكود المحددة وقرارات التكوين التي أنقذتنا من الأخطاء المؤلمة.

جدول المحتويات

أفضل إعداد Stripe لعمل الاشتراكات: 4 نماذج نشغلها

لماذا إعداد Stripe الواحد لا يناسب الجميع

توثيق Stripe ممتازة للشركات ذات النموذج الواحد. تختار الاشتراكات أو الدفعات لمرة واحدة، وتتبع الدليل، وأنت مباشر. لكن معظم الشركات الحقيقية لا تبقى بهذه البساطة لفترة طويلة.

نحن نعمل عبر منتجات متعددة: منصة SaaS مع اشتراكات مرحلية، وسوق يأخذ عمولات من المزودين، ومبادرة خيرية مع رعايات متكررة، ونظام حجز استشارات مع دفعات لمرة واحدة. كل واحد من هذه يعيش تحت نفس حساب Stripe لكن يتطلب تكوينات مختلفة بشكل أساسي للمنتجات والتسعير و webhooks وإدارة العملاء.

أكبر خطأ أراه الفريق يرتكبها هو محاولة فرض جميع الفواتير في نموذج واحد. معمارية موجهة للاشتراك تنهار عند الحاجة إلى دفعات لمرة واحدة. منهج قائم على جلسة checkout فقط ينهار عندما تحتاج إلى فواتير متكررة مع توزيع التكاليف. عليك أن تفكر في إعداد Stripe الخاص بك كمحفظة من أنماط الفواتير.

إذا كنت تبني شيئًا مشابهًا -- خاصة على معمارية بدون رأس مع Next.js أو Astro في الواجهة الأمامية -- فإن الأنماط هنا ستوفر لك أسابيع من التصحيح.

النموذج 1: الاشتراكات المرحلية مع التسعير الإقليمي

هذا هو النموذج الأكثر تعقيدًا الذي نشغله، وهو الذي علمنا أكثر الدروس المؤلمة. الإعداد: أربع مستويات (مجاني، أساسي، احترافي، بريميوم) مع تسعير يختلف عبر 30+ دول.

هيكل المنتج في Stripe

في Stripe، كل مستوى هو منتج منفصل. يحتوي كل منتج على أسعار متعددة -- واحد لكل مجموعة عملة/منطقة. هذا مهم: لا تحاول استخدام سعر واحد وقم بتحويل العملة بنفسك. تسعير Stripe متعدد العملات مصمم خصيصًا لهذا.

// تكوين التسعير الإقليمي
const REGIONAL_PRICING = {
  pro: {
    USD: { monthly: 2900, yearly: 29000 },  // $29/mo, $290/yr
    EUR: { monthly: 2700, yearly: 27000 },  // €27/mo, €270/yr
    GBP: { monthly: 2300, yearly: 23000 },  // £23/mo, £230/yr
    JPY: { monthly: 4200, yearly: 42000 },  // ¥4,200/mo -- ليس ¥42.00!
    KRW: { monthly: 38000, yearly: 380000 }, // ₩38,000/mo
    INR: { monthly: 190000, yearly: 1900000 }, // ₹1,900/mo
    BRL: { monthly: 14900, yearly: 149000 }, // R$149/mo
  },
  // ... كرر للمستويات الأساسية والبريميوم
};

لاحظ قيم JPY و KRW تلك؟ سأصل إلى هذا الخطأ بالتفصيل لاحقًا، لكن الإصدار القصير: هذه عملات بدون عشريات. عندما تمرر 4200 لـ JPY، تفسره Stripe على أنه ¥4,200 -- وليس ¥42.00. إذا ضربت في 100 كما تفعل مع USD، فقد فرضت رسومًا على شخص ما ¥420,000 ($2,800) بدلاً من ¥4,200 ($28). اسأل كيف أعرف.

منطق المحاولة الإقليمية

لا تحصل كل منطقة على محاولة مجانية. تعلمنا هذا بالطريقة الصعبة مع أسواق معينة في جنوب شرق آسيا حيث كان الاستخدام الاحتيالي للمحاولة أعلى بكثير من المناطق الأخرى. يبدو تكويننا كالتالي:

const TRIAL_CONFIG = {
  default_trial_days: 14,
  excluded_regions: ['VN', 'PH', 'ID', 'TH', 'MM', 'KH', 'LA'],
  reduced_trial_regions: {
    IN: 7,
    BR: 7,
  },
};

function getTrialDays(countryCode) {
  if (TRIAL_CONFIG.excluded_regions.includes(countryCode)) {
    return 0;
  }
  return TRIAL_CONFIG.reduced_trial_regions[countryCode] 
    ?? TRIAL_CONFIG.default_trial_days;
}

يتم تمرير هذا إلى إنشاء الاشتراك:

const subscription = await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: regionalPriceId }],
  trial_period_days: getTrialDays(customer.address.country),
  payment_behavior: 'default_incomplete',
  payment_settings: {
    save_default_payment_method: 'on_subscription',
  },
  expand: ['latest_invoice.payment_intent'],
});

سلوك التوزيع

عندما يقوم شخص ما بالترقية من الأساسي إلى الاحترافي في منتصف الدورة، عليك أن تقرر: هل يدفعون الفرق فورًا، أم في دورة الفواتير التالية؟ نحن نستخدم create_prorations مع الدفع الفوري:

const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
  items: [{
    id: existingItemId,
    price: newPriceId,
  }],
  proration_behavior: 'create_prorations',
  payment_behavior: 'pending_if_incomplete',
});

للترقية للأسفل، نجدول التغيير لنهاية فترة الفواتير. لا أحد يريد حساب ائتمان مفاجئ على فاتورته.

بوابة العميل

بوابة Stripe للعميل مقدرة بأقل من قيمتها. بدلاً من بناء واجهة مستخدم إدارة الاشتراك الخاصة بك، قم بتكوين البوابة وأعد توجيه المستخدمين إليها:

const portalSession = await stripe.billingPortal.sessions.create({
  customer: customerId,
  return_url: `${process.env.APP_URL}/settings/billing`,
});

قم بتكوينها في لوحة معلومات Stripe للسماح بتغيير الخطة والإلغاء (مع استطلاع أسباب الإلغاء -- البيانات ثمينة) وتحديثات طريقة الدفع. وحده هذا وفر لنا ربما 40 ساعة من تطوير الواجهة الأمامية.

النموذج 2: عمولات السوق مع Stripe Connect

يستخدم نموذج السوق الخاص بنا Stripe Connect لتسهيل الدفعات بين العملاء ومزودي الخدمات. تأخذ المنصة عمولة على كل معاملة. هذا هو إعداد Stripe Connect الذي تتجاهله معظم البرامج التعليمية.

إدراج المزود

يحتاج كل مزود في السوق إلى حساب Stripe Express. يقوم تدفق الإدراج بإنشاء الحساب وإعادة توجيهه إلى الإدراج المستضاف من Stripe:

const account = await stripe.accounts.create({
  type: 'express',
  country: provider.country,
  email: provider.email,
  capabilities: {
    card_payments: { requested: true },
    transfers: { requested: true },
  },
  business_type: 'individual',
  metadata: {
    provider_id: provider.id,
    platform: 'fme',
  },
});

const accountLink = await stripe.accountLinks.create({
  account: account.id,
  refresh_url: `${process.env.APP_URL}/provider/onboarding/refresh`,
  return_url: `${process.env.APP_URL}/provider/onboarding/complete`,
  type: 'account_onboarding',
});

التفصيل الرئيسي: refresh_url هو المكان الذي ترسل إليه Stripe المستخدمين إذا انتهت صلاحية الرابط. يحدث هذا بشكل متكرر أكثر مما تعتقد -- إذا بدأ شخص ما الإدراج على هاتفه وتشتت وعاد لاحقًا. تعامل مع هذا دائمًا برشاقة من خلال توليد رابط جديد.

هيكل العمولة

عندما يحجز عميل خدمة، نقوم بإنشاء PaymentIntent بـ application_fee_amount:

const paymentIntent = await stripe.paymentIntents.create({
  amount: bookingAmountInCents,
  currency: 'usd',
  application_fee_amount: Math.round(bookingAmountInCents * 0.15), // رسم منصة 15%
  transfer_data: {
    destination: providerStripeAccountId,
  },
  metadata: {
    booking_id: booking.id,
    provider_id: provider.id,
    customer_id: customer.id,
  },
});

تذهب عمولة 15% إلى المنصة. يذهب الباقي 85% (ناقص رسم معالجة Stripe) إلى حساب Express الخاص بالمزود.

جدولة السحب

افتراضيًا، تقوم Stripe بدفع حسابات Express على أساس متكرر. نحن نتجاوز هذا للسحب الأسبوعي، مما يعطينا مخزن مؤقت للاسترجاعات والنزاعات:

await stripe.accounts.update(providerStripeAccountId, {
  settings: {
    payouts: {
      schedule: {
        interval: 'weekly',
        weekly_anchor: 'friday',
      },
    },
  },
});

يعني السحب يوم الجمعة أن المزودين سيرون الأموال في حساباتهم المصرفية بحلول يوم الاثنين. إنه شيء صغير لكنه مهم بشكل كبير لرضا المزود والاحتفاظ به.

Webhooks خاصة بـ Connect

مع Stripe Connect، تتلقى webhooks لمنصتك والحسابات المتصلة بك. تحتاج إلى نقطة نهاية webhook منفصلة لأحداث Connect:

// نقطة نهاية webhook عادية
app.post('/webhooks/stripe', handlePlatformWebhooks);

// نقطة نهاية webhook Connect
app.post('/webhooks/stripe-connect', handleConnectWebhooks);

يحتاج معالج webhook Connect إلى التحقق من الحدث بشكل مختلف والتحقق من حقل account:

async function handleConnectWebhooks(req, res) {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers['stripe-signature'],
    process.env.STRIPE_CONNECT_WEBHOOK_SECRET // سر مختلف!
  );
  
  const connectedAccountId = event.account;
  // الآن تعامل مع الحدث في سياق الحساب المتصل
}

أفضل إعداد Stripe لعمل الاشتراكات: 4 نماذج نشغلها - المعمارية

النموذج 3: التبرعات المتكررة المرتبطة بكيانات

هذا الواحد لمبادرة خيرية حيوانية نبنيها -- فكر في "رعاية حيوان معين" مع تبرعات شهرية متكررة. يختار المتبرع حيوانًا ويضع مبلغًا شهريًا ويحصل على تحديثات صور.

الاشتراكات المرتبطة بالكيانات

الحيلة هنا هي ربط اشتراك Stripe بكيان محدد (حيوان) في قاعدة البيانات الخاصة بك. نحن نفعل هذا بالكامل من خلال البيانات الوصفية:

const subscription = await stripe.subscriptions.create({
  customer: donorCustomerId,
  items: [{
    price_data: {
      currency: 'usd',
      product: sponsorshipProductId,
      unit_amount: donorChosenAmount, // المتبرع يختار المبلغ الخاص به
      recurring: {
        interval: 'month',
      },
    },
  }],
  metadata: {
    entity_id: animal.id,
    entity_type: 'animal',
    entity_name: animal.name,
    sponsor_email: donor.email,
  },
});

يتيح استخدام price_data بدلاً من إنشاء مسبق للسعر للمتبرعين اختيار مبلغهم الشهري. هذا أنظف من إنشاء مئات كائنات السعر.

رسائل التحديث الشهرية

عندما تطلق invoice.paid لاشتراك الرعاية، نطلق تدفق التحديث الشهري:

async function handleSponsorshipInvoicePaid(invoice) {
  const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
  const entityId = subscription.metadata.entity_id;
  
  // قائمة انتظار رسالة التحديث الشهرية مع أحدث الصور
  await emailQueue.add('sponsorship-update', {
    donorEmail: subscription.metadata.sponsor_email,
    entityId,
    invoiceAmount: invoice.amount_paid,
    invoicePdf: invoice.invoice_pdf,
  });
}

تتضمن الرسالة ملف PDF الفاتورة (تولده Stripe تلقائيًا) وصور حديثة للحيوان المرعي وتحديث الرعاية. إنه اللمسة الصغيرة التي تقلل بشكل كبير من تراجع التبرعات المتكررة.

معالجة إلغاء التبرع

عندما يقوم شخص ما بإلغاء رعايته، تحتاج إلى التعامل معها بشكل مختلف عن إلغاء SaaS. لا يوجد "ترقية للأسفل" -- إنه الإلغاء أو لا شيء. لكنك تريد تسهيل إعادة الاشتراك لاحقًا:

async function handleSponsorshipCancellation(subscription) {
  const entityId = subscription.metadata.entity_id;
  
  // وضع علامة على الرعاية كغير نشطة، وليس محذوفة
  await db.sponsorships.update({
    where: { stripeSubscriptionId: subscription.id },
    data: { 
      status: 'inactive',
      cancelledAt: new Date(),
    },
  });
  
  // إرسال رسالة "سنفتقدك" مع رابط إعادة اشتراك سهل
  await sendCancellationEmail(subscription.metadata.sponsor_email, entityId);
}

النموذج 4: دفعات الخدمات لمرة واحدة

النموذج الأبسط، لكن هناك تفاصيل مهمة. هذا النمط مخصص لحجوزات الاستشارات حيث يدفع شخص ما مرة واحدة ويحصل على خدمة -- بدون فواتير متكررة.

جلسة Checkout مع بيانات الحجز

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{
    price: consultationPriceId,
    quantity: 1,
  }],
  customer_email: customer.email,
  metadata: {
    booking_id: booking.id,
    service_type: 'consultation',
    appointment_date: booking.date.toISOString(),
    practitioner_id: booking.practitionerId,
  },
  success_url: `${process.env.APP_URL}/booking/confirmed?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${process.env.APP_URL}/booking/${booking.id}`,
  expires_after: 1800, // 30 دقيقة
  payment_intent_data: {
    metadata: {
      booking_id: booking.id,
    },
  },
});

نقطتان يجب ملاحظتهما. أولاً: expires_after يمنع جلسات checkout المهجورة من الاستمرار. لا يجب الاحتفاظ بموقع الحجز إلى الأبد. ثانيًا: نقوم بتكرار booking_id في payment_intent_data.metadata لأن بيانات PaymentIntent الوصفية منفصلة عن بيانات Checkout Session الوصفية. عندما تتلقى webhook payment_intent.succeeded، ستريد معرف الحجز هناك مباشرة.

تأكيد الدفع والحجز

على checkout.session.completed، نؤكد الحجز وننقل كل شيء في اللقطة الواحدة:

async function handleCheckoutComplete(session) {
  const bookingId = session.metadata.booking_id;
  
  // تأكيد الحجز
  const booking = await db.bookings.update({
    where: { id: bookingId },
    data: { 
      status: 'confirmed',
      paymentSessionId: session.id,
      paidAt: new Date(),
    },
  });
  
  // إرسال تأكيد للعميل
  await sendBookingConfirmation(session.customer_email, booking);
  
  // إخطار الممارس
  await notifyPractitioner(booking.practitionerId, booking);
}

معمارية Webhook التي تعمل فعلاً

عبر جميع النماذج الأربعة، webhooks هي العمود الفقري. إليك المعمارية التي استقررنا عليها بعد جلسات تصحيح بلا نهاية:

const WEBHOOK_HANDLERS = {
  'checkout.session.completed': handleCheckoutComplete,
  'invoice.paid': handleInvoicePaid,
  'invoice.payment_failed': handlePaymentFailed,
  'customer.subscription.created': handleSubscriptionCreated,
  'customer.subscription.updated': handleSubscriptionUpdated,
  'customer.subscription.deleted': handleSubscriptionDeleted,
  'account.updated': handleConnectAccountUpdated,
  'payment_intent.succeeded': handlePaymentSucceeded,
};

async function webhookHandler(req, res) {
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody, // تحتاج إلى الجسم الخام، وليس JSON محلل
      req.headers['stripe-signature'],
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send();
  }

  // الإدمان: تحقق من أننا قد عالجنا هذا الحدث بالفعل
  const processed = await db.webhookEvents.findUnique({
    where: { stripeEventId: event.id },
  });
  if (processed) {
    return res.status(200).json({ received: true, duplicate: true });
  }

  const handler = WEBHOOK_HANDLERS[event.type];
  if (handler) {
    try {
      await handler(event.data.object, event);
      await db.webhookEvents.create({
        data: { stripeEventId: event.id, type: event.type, processedAt: new Date() },
      });
    } catch (err) {
      console.error(`Error processing ${event.type}:`, err);
      return res.status(500).send(); // ستعيد محاولة Stripe
    }
  }

  res.status(200).json({ received: true });
}

فحص الإدمان حرج. ستعيد محاولة Stripe لـ webhooks الفاشلة، وأنت بالتأكيد لا تريد معالجة نفس الحدث مرتين -- خاصة بالنسبة لأشياء مثل إنشاء الحجوزات أو تشغيل السحوبات.

منطق إعادة محاولة الدفع الفاشل والتحصيل

Stripe لديها Smart Retries المدمجة، لكن يجب عليك أن تضع منطق التحصيل الخاص بك فوقها:

async function handlePaymentFailed(invoice) {
  const attemptCount = invoice.attempt_count;
  const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
  
  if (attemptCount === 1) {
    // الفشل الأول: دفعة لطيفة
    await sendEmail(invoice.customer_email, 'payment-failed-soft', {
      updatePaymentUrl: await createPortalLink(invoice.customer),
    });
  } else if (attemptCount === 2) {
    // الفشل الثاني: أكثر إلحاحًا
    await sendEmail(invoice.customer_email, 'payment-failed-urgent', {
      updatePaymentUrl: await createPortalLink(invoice.customer),
      daysUntilCancellation: 7,
    });
  } else if (attemptCount >= 3) {
    // التحذير النهائي
    await sendEmail(invoice.customer_email, 'payment-failed-final', {
      updatePaymentUrl: await createPortalLink(invoice.customer),
    });
  }
}

قم بتكوين جدول إعادة المحاولة الخاص بـ Stripe في Dashboard → Settings → Subscriptions and emails → Manage failed payments. نحن نستخدم 3 محاولات على مدى 14 يومًا قبل الإلغاء.

خطأ العملة بدون عشريات الذي كلفنا المال

يستحق هذا قسمه الخاص لأنه خطأ يعض الجميع في النهاية. تستخدم Stripe سنتات (أصغر وحدة عملة) لمعظم العملات. $29.00 يصبح 2900. لكن بعض العملات ليس لديها منازل عشرية.

فيما يلي عملات بدون عشريات التي تهم:

العملة الرمز مثال: "معادل $29" ما تمرره لـ Stripe
الين الياباني JPY ¥4,200 4200 (ليس 420000)
الوون الكوري KRW ₩38,000 38000 (ليس 3800000)
الدونج الفيتنامي VND ₫700,000 700000
البيزو التشيلي CLP $25,000 25000
جوارانى باراغواى PYG ₲200,000 200000

إليك دالة المساعدة التي نستخدمها في كل مكان:

const ZERO_DECIMAL_CURRENCIES = [
  'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW',
  'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF',
  'XOF', 'XPF',
];

function toStripeAmount(amount, currency) {
  const curr = currency.toUpperCase();
  if (ZERO_DECIMAL_CURRENCIES.includes(curr)) {
    return Math.round(amount); // بالفعل في أصغر وحدة
  }
  return Math.round(amount * 100);
}

function fromStripeAmount(stripeAmount, currency) {
  const curr = currency.toUpperCase();
  if (ZERO_DECIMAL_CURRENCIES.includes(curr)) {
    return stripeAmount;
  }
  return stripeAmount / 100;
}

استخدم هذا في كل مكان. في إنشاء checkout الخاص بك، في معالجات webhook الخاصة بك، في عروض لوحة المعلومات الخاصة بك. في كل مكان. المرة الوحيدة التي تنسى فيها هي المرة التي تفرض فيها رسومًا على شخص ما بـ 100x مما يتوقعونه.

مقارنة النماذج الأربعة

الجانب الاشتراكات المرحلية السوق التبرع المتكرر الدفع لمرة واحدة
منتج Stripe منتجات متعددة، أسعار متعددة لكل منتج منتج واحد لكل نوع خدمة منتج واحد، تسعير ديناميكي منتج واحد، سعر ثابت
وضع الفواتير subscription payment مع Connect subscription payment
تعقيد Webhook عالي (أحداث دورة الحياة) عالي (أحداث Connect) متوسط منخفض
التعامل مع العملات مصفوفة التسعير الإقليمي عملة المزود عملة المتبرع عملة واحدة
دعم المحاولة نعم، يعتمد على المنطقة بلا بلا بلا
التوزيع نعم، في الترقيات بلا بلا بلا
تعقيد الاسترجاع حسابات التوزيع عكس رسم منصة استرجاع كامل بسيط استرجاع كامل بسيط
بوابة العميل أساسي غير مطلوب اللطيف أن يكون غير مطلوب
رسوم Stripe (2025) 2.9% + 30¢ 2.9% + 30¢ + 0.5% Connect 2.9% + 30¢ 2.9% + 30¢

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

كم عدد منتجات Stripe التي يجب أن أنشئها للتسعير المرحلي؟ منتج واحد لكل مستوى. لذا إذا كان لديك مجاني وأساسي واحترافي وبريميوم، فهذا أربعة منتجات. يحتوي كل منتج بعد ذلك على أسعار متعددة -- واحد لكل مجموعة عملة وفترة فواتير. مستوى Pro مع فواتير شهرية وسنوية عبر 10 عملات يعني 20 كائن سعر على هذا المنتج الواحد. يبدو أنه الكثير، لكن Stripe يتعامل مع هذا بشكل جيد وتنظيم الكتالوج الخاص بك بشكل جيد.

هل يمكنني استخدام Stripe Checkout للاشتراكات مع التسعير الإقليمي؟ نعم، لكن عليك تحديد منطقة العميل قبل إنشاء جلسة Checkout حتى تتمكن من تمرير معرف السعر الصحيح. نحن نستخدم تحديد الموقع الجغرافي بـ IP (عبر رؤوس Cloudflare) لاختيار مسبق العملة، ثم دع العميل يؤكد أو يغيره. لا تعتمد على عملة Checkout التلقائية -- تريد التحكم في السعر الذي يراه.

ما الفرق بين حسابات Stripe Connect Express والمخصصة؟ تتيح حسابات Express لـ Stripe التعامل مع الإدراج والتحقق من الهوية ولوحة المعلومات لمزودك. تعطيك الحسابات المخصصة السيطرة الكاملة لكن تتطلب منك بناء كل ذلك بنفسك. بالنسبة لمعظم الأسواق، يعتبر Express هو الخيار الصحيح. لم نواجه أبدًا حالة حيث كان فقدان التحكم يبرر تكلفة الهندسة للحسابات المخصصة. تتعامل حسابات Express أيضًا مع تقرير الضرائب (1099s في الولايات المتحدة) تلقائيًا، وهي عملية كسب الامتثال الضخمة.

كيف أتعامل مع دفعات الاشتراك الفاشلة دون فقدان العملاء؟ قم بتجميع ثلاثة أشياء: Smart Retries الخاصة بـ Stripe (ممكنة في Dashboard) ورسائل تحصيل مخصصة التي تشغلها webhooks invoice.payment_failed وفترة رحمة قبل الإلغاء. نعطي 14 يومًا عبر 3 محاولات إعادة. البريد الأول ودود ("مرحبا، قد تكون بطاقتك قد انتهت صلاحيتها")، والثاني عاجل، والثالث هو التحذير النهائي. قم بتضمين رابط مباشر لبوابة العميل حيث يمكنهم تحديث طريقة الدفع الخاصة بهم. وحده هذا يسترجع حوالي 30-40% من الدفعات الفاشلة.

هل أحتاج إلى نقاط نهاية webhook منفصلة لـ Stripe Connect؟ نعم. تستخدم أحداث المنصة وأحداث حساب Connect أسرارًا مختلفة و هياكل حدث مختلفة. تتضمن أحداث Connect حقل account يحدد حساب Connect الذي يتعلق به الحدث. سجل نقطتي نهاية في لوحة معلومات Stripe: واحدة لأحداث المنصة، واحدة لأحداث Connect. يسهل هذا الفصل أيضًا على التصحيح بكثير.

ما هي عملات بدون عشريات ولماذا يجب أن أهتم؟ عملات بدون عشريات مثل JPY (الين الياباني) و KRW (الوون الكوري) لا تستخدم وحدات كسرية. عندما تقول Stripe "المبلغ في أصغر وحدة عملة"، بالنسبة لـ USD هذا سنتات (2900 = $29.00)، لكن بالنسبة لـ JPY هو ين (4200 = ¥4,200). إذا ضربت في 100 كما تفعل مع USD، فقد فرضت رسومًا ¥420,000 بدلاً من ¥4,200. استخدم دالة مساعدة تتحقق من العملة قبل التحويل. تحتفظ Stripe بالقائمة الرسمية لعملات بدون عشريات في مستنداتهم.

هل يجب أن أستخدم بوابة العميل الخاصة بـ Stripe Billing أم أبني بنفسي؟ استخدم بوابة العميل لإدارة الاشتراك ما لم يكن لديك متطلبات واجهة مستخدم محددة جدًا. تتعامل مع تغييرات الخطة والإلغاء وتحديثات طريقة الدفع وسجل الفاتورة خارج الصندوق. يمكنك تخصيص العلامات التجارية وتكوين الإجراءات المسموحة. بناء بوابة الخاص بك يعني التعامل مع حسابات توزيع التكاليف وتمييز طريقة الدفع وتدفقات SCA/3D Secure بنفسك. البوابة مجانية -- تم تضمينها في رسوم اشتراك Stripe.

كيف أختبر التسعير الإقليمي والتعامل مع العملات محليًا؟ يدعم وضع اختبار Stripe جميع العملات. قم بإنشاء أسعار اختبار في كل عملة تخطط لدعمها، ثم استخدم Stripe CLI لإعادة توجيه webhooks إلى خادمك المحلي: stripe listen --forward-to localhost:3000/webhooks/stripe. لاختبار عملة بدون عشريات تحديدًا، أنشئ سعر JPY وتحقق من المبالغ في سجلات معالج webhook قبل البث المباشر. نحن أيضًا نحتفظ بمجموعة اختبار تشغل toStripeAmount و fromStripeAmount مقابل كل عملة مدعومة -- لقد اكتشفت مشاكل أكثر من مرة.

إذا كنت تبني منتجًا قائمًا على الاشتراك وتحتاج إلى مساعدة في معمارية الفواتير، أو إذا كنت تدمج Stripe مع إعداد CMS بدون رأس، فـ تواصل معنا. لقد بنينا هذه الأنماط عبر مشاريع CMS بدون رأس متعددة ويمكننا مساعدتك في تخطي الأخطاء المكلفة. تحقق من صفحة التسعير الخاصة بنا لنماذج التعاون -- نحن نفعل كلا البناء القائم على المشروع والاستشارة المستمرة.