גביית תשלומים

  • שלב אפשרויות תשלום באמצעות תשלום (Payout Sessions) כדי לאסוף תשלומים חד פעמיים באפליקציה שלך.
  • הטמעת חיוב מנוי עבור מודלים של הכנסה חוזרת
  • הגדר מטפלי webhook לאימות תשלום אמין
  • יש ליישם שיטות עבודה מומלצות לאבטחת תשלומים
  • תהליך ביצוע התשלום: כפתור "תשלום" שפותח דף תשלום מאובטח
  • תשלום מנוי: אפשרויות תשלום חד פעמיות וחוזרות
  • Webhook Handler – מטפל תשלומים מאובטח ואמין ב-MVP שלך
  • התלמידים פיתחו אסטרטגיית תמחור ותקציב פשוט לפרויקט שלהם.
  • לסטודנטים יש מאגר MVP GitHub עם סביבת Codespaces.
  • תלמידים יישמו אימות משתמשים ב-MVP שלהם.

האפליקציה שלך עובדת. משתמשים בדקו אותה ותיקנת את הבאגים. פרסת אותה בפלטפורמה כמו Vercel כך שהיא זמינה לציבור. עכשיו מגיעה שאלה שכל סטארט-אפ מתמודד איתה: 

איך אנחנו מקבלים תשלום?

בשיעור אוריינות פיננסית, יצרתם תקציב פשוט עם תחזיות הכנסות ובחרתם אסטרטגיית תמחור למיזם שלכם. הערכתם כמה משתמשים אתם צריכים, כמה לגבות ומתי אתם צפויים להגיע לנקודת איזון. המספרים הללו היו הערכות. ביחידה זו, אתם הופכים את ההערכות הללו למערכת תשלומים אמיתית.

הערה: גביית תשלומים אינה נדרשת עבור אקסלרטור זה. עם זאת, זהו משהו שיש לשקול כצעד הכרחי הבא.

נשתמש ב- Stripe , פלטפורמת תשלום בה משתמשים עסקים ביותר מ-40 מדינות. Stripe מטפלת בחלקים המורכבים והרגישים של עיבוד התשלומים, שהם אימות כרטיס, גילוי הונאות, המרת מטבע, תאימות ואבטחה רגולטוריים - כך שאתם לא צריכים לעשות זאת. התפקיד שלכם הוא לחבר את האפליקציה שלכם ל-Stripe, להורות לה כמה לחייב ולטפל בתוצאה.

Stripe תומכת בעסקים ביותר מ-40 מדינות, אך הזמינות משתנה בהתאם לאזור. אם Stripe אינה זמינה במדינתך, יש לך אפשרויות אחרות. בדוק את הזמינות באזור שלך ואפשרויות חלופיות שעובדות היטב. הנה כמה הצעות:

תבניות הקוד ביחידה זו - יצירת סשנים של ביצוע רכישה, webhooks וחיוב מנויים - פועלות באותו אופן ברוב פלטפורמות התשלום, כך שמה שתלמדו כאן מועבר ישירות. המשיכו עם הפעילויות כדי ללמוד את המושגים, ולאחר מכן התאימו אותן באמצעות התיעוד של הפלטפורמה שלכם.

לאורך יחידה זו, נמשיך להשתמש ב-TutorMatch כדוגמה מודרכת. ביחידה זו, TutorMatch מוסיפה את האפשרות לשלם עבור מפגש הדרכה בודד או להירשם לתוכנית חודשית.

תעקוב אחר הדוגמאות של TutorMatch, ותתאים את התבניות ל-MVP משלך.

בסוף יחידה זו, ה-MVP שלכם יוכל לגבות תשלומים אמיתיים. תשתמשו במצב ניסיון לכל אורך המשחק, כך שלא יעבור כסף אמיתי ידיים, אך ניתן להשתמש בקוד שתכתבו אם תחליטו לגבות תשלומים בהמשך.

תחילת העבודה עם פלטפורמת התשלומים שלך

בשיעורים קודמים, הגדרתם את GitHub כבית הקוד שלכם. הגדרתם גם את Supabase כבסיס הנתונים שלכם בענן. כעת תגדירו את Stripe (או חלופה, בהתאם למדינה) כפלטפורמת התשלום שלכם. התבנית זהה:

  1. צור חשבון
  2. קבל מפתחות API
  3. חבר אותו לאפליקציה שלך
stripe logo

Stripe הוא לתשלומים מה ש-Supabase הוא לאחסון מסדי נתונים - הוא מטפל בחלקים הקשים כדי שתוכלו להתמקד במוצר שלכם. כאשר משתמש משלם דרך Stripe, Stripe:

  • מאמת את מספר הכרטיס ובודק הונאה
  • מצפין את פרטי התשלום כך שהם לעולם לא יהיו חשופים
  • שולח את הכסף לחשבונך (בניכוי עמלת עסקה)
  • מטפל בהמרת מטבע אם המשתמש שלך משלם במטבע אחר
  • ניהול תאימות רגולטורית בכל מדינה

בלי פלטפורמה כמו Stripe, הייתם צריכים לבנות את כל זה בעצמכם, מה שייקח חודשים וידרוש אישורי אבטחה. Stripe מטפלת במורכבויות אלו כך שתוכלו להוסיף תשלומים לאפליקציה שלכם עם כמות קוד ניתנת לניהול.

ישנן מספר פלטפורמות תשלום זמינות. כך משתווים ביניהן עבור מישהו שבונה MVP:

פּלַטפוֹרמָה

התקנה

חינם להתחיל

עמלת עסקה

הטוב ביותר עבור

פַּס

פָּשׁוּט

כֵּן

~2.9% + עמלה לכל עסקה (משתנה בהתאם למדינה)

MVPs, אפליקציות אינטרנט, מנויים

פייפאל

פָּשׁוּט

כֵּן

~3.49% + עמלה

שווקים, הגנת קונים

מְרוּבָּע

פָּשׁוּט

כֵּן

~2.6% + עמלה

תשלומים אישיים, קמעונאות

בנה משלך

מורכב מאוד

לא רלוונטי

אין (מלבד עלויות שרת)

אף פעם לא מומלץ ל-MVPs

עבור פעילויות אלו נשתמש ב-Stripe מכיוון ש:

  • הוא מיועד למפתחים ומשתלב עם Express.js (השרת האחורי שלך משיעורים קודמים)
  • הרמה החינמית עובדת עבור בדיקות ו-MVP בשלב מוקדם ללא עמלות חודשיות
  • אתם משלמים עמלות עסקה רק כשאתם גובים תשלומים אמיתיים
  • אותו קוד עובד ביותר מ-40 מדינות ויותר מ-135 מטבעות
  • הוא מציע חיוב מנוי מובנה עבור מודלים של הכנסה חוזרת

שוב, בדקו את אפשרות עיבוד התשלומים הטובה ביותר באזור או במדינה שלכם, והחילו את אותו תהליך עבור הפלטפורמה שבחרתם.

חיבור פלטפורמת תשלום לאפליקציה שלך

ישנן מספר דרכים לשלב עיבוד תשלומים. הבנת האפשרויות עוזרת לך לעשות את הבחירה הנכונה עבור MVP שלך.

גִישָׁה

מה זה אומר

זמן התקנה

בִּטָחוֹן

התאמה אישית

הטוב ביותר עבור

תשלום מארח

הפלטפורמה מארחת את דף התשלום. אתם מפנים לשם את המשתמשים.

~30 דקות

הגבוה ביותר - הפלטפורמה מטפלת בהכל

נמוך - הפלטפורמה שולטת במראה

MVPs וסטארטאפים (הבחירה שלנו)

תשלום מוטמע

ממשק המשתמש של הפלטפורמה מופיע בתוך הדף שלך כרכיב

~ שעה אחת

גבוה - הפלטפורמה עדיין מטפלת בנתוני כרטיס

בינוני - מתאים לפריסת הדף שלך

אפליקציות שצריכות עקביות מותגית

רכיב התשלום

רכיב ממשק משתמש מאובטח, מובנה מראש (iframe) המסופק על ידי פלטפורמת התשלומים, אותו "משחילים" לתוך קוד הקצה שלכם.

~2 שעות

גבוה - אבל יותר קוד לנהל

גבוה - שליטה מלאה בפריסה

אפליקציות הדורשות תהליך תשלום ייחודי

טופס מותאם אישית

לבנות הכל מאפס

ימים

אתה מטפל בתאימות PCI

שליטה מלאה

לא מומלץ ל-MVPs

תשתמשו ב-Hosted Checkout . הנה הסיבה:

המהיר ביותר לבנייה

אתם יוצרים תהליך תשלום (Checkout Session) בשרת שלכם, ופלטפורמת התשלום מטפלת בשאר - דף התשלום, אימות הכרטיס, הודעות השגיאה והקבלה.

הכי מאובטח

האפליקציה שלך לעולם לא נוגעת במספרי כרטיסים. הפלטפורמה אוספת אותם בדף משלה, מה שאומר שאתה תואם PCI באופן אוטומטי.

מהימן על ידי משתמשים

דף התשלום של פלטפורמת התשלום מציג את המיתוג שלהם, אותו משתמשים מזהים ובוטחים.

תכונות מובנות

הדף המתארח של Stripe תומך אוטומטית במספר מטבעות, Apple Pay, Google Pay, UPI ושיטות תשלום אחרות בהתבסס על מיקום הלקוח. לרוב פלטפורמות התשלום יש תכונות דומות.

לקיחת התשלום הראשון שלך

כל מערכת תשלום מתחילה בעסקה אחת. בפעילות הראשונה הזו, תבנו תהליך תשלום באפליקציה שלכם.

הפעילויות הבאות ידריכו אתכם כיצד להוסיף כפתור "תשלום" באפליקציה שלכם, אשר מפנה משתמשים לדף התשלום המתארח של Stripe - דף מקצועי ומאובטח שבו משתמשים מזינים את פרטי התשלום שלהם.

לאחר שהם משלימים את התשלום, הם חוזרים לדף ההצלחה שלך שמאשר את הרכישה שלהם.

מיפוי מה קורה לפני, במהלך ואחרי התשלום חוסך עבודה חוזרת מאוחר יותר. אם תדלגו על כך, אתם עלולים לבנות תהליך תשלום שאינו תואם את מודל התמחור שלכם מהתקציב הפשוט שלכם, ותצטרכו לעשות אותו מחדש.

עבור אלו מכם המשתמשים בפלטפורמת תשלום חלופית, תוכלו להתאים את עצמכם לאופן שבו הפלטפורמה שלכם פועלת, אך בהתאם לתיעוד שלהם.

 

פתחו את התקציב הפשוט שלכם משיעור אוריינות פיננסית, ובדקו את אסטרטגיית התמחור שלכם.

ענו על השאלות הבאות:

  • עבור מה אתם גובים תשלום? (מוצר, שירות, גישה לתכונה?)
  • האם מדובר בתשלום חד פעמי או חוזר? (אנחנו מתחילים עם תשלום חד פעמי בפעילות זו, ואז מוסיפים מנויים בפעילות 3)
  • כמה? (השתמשו במחיר מתחזיות ההכנסות של התקציב הפשוט שלכם)
  • מה קורה לאחר התשלום? (האם המשתמש מקבל גישה מיידית? אימייל אישור? הורדה?)

עבור הדוגמה שלנו ב-TutorMatch , התשובות הן:

  • חיוב עבור: תשלום חד פעמי עבור מפגש לימוד
  • סוג: תשלום חד פעמי (נוסיף מנויים שנוספו בפעילות 3)
  • מחיר: התאם לשוק שלך 

רשמו את תשובותיכם. תשתמשו בהן בפעילות שלמטה.

הערה: כדי להשתמש ב-Stripe, עליך לספק כתובת URL של כל אתר אינטרנט, פרופיל מדיה חברתית או אפליקציה סלולרית שבהם אתה משתמש כדי לקדם את העסק שלך או למכור מוצרים. בנוסף, תצטרך לספק חשבון בנק ולאמת את זהותך . עבור הפעילות שלהלן, תוכל לדלג על פעולה זו, אך תצטרך לבצע את השלבים הבאים כדי לקבל תשלומים בפועל.

פעילות 1

הגדר את Stripe כדי לקבל תשלומים

זמן משוער: 20 דקות

  1. עבור אל stripe.com ולחץ על התחל עכשיו כדי ליצור חשבון חינמי באמצעות כתובת האימייל שלך.
  2. אשר את כתובת האימייל שלך והתחבר ללוח המחוונים של Stripe.
  3. בחר תשלומים חד פעמיים עבור אפשרויות התשלום שלך. תוכל לשנות זאת בכל עת.
  4. בחר ב-sandbox לעת עתה. כדי להפעיל את חשבונך ולקבל תשלומים בפועל, תצטרך לספק כתובת של חשבון בנק, אתר אינטרנט או אפליקציה לנייד, ולאמת את זהותך.
  5. תגיעו לדף הבית של לוח המחוונים - קחו רגע להסתכל מסביב.

לוח המחוונים של Stripe הוא מרכז הבקרה שלך לתשלומים. הנה התחומים העיקריים שבהם תשתמש:

  • תשלומים: ראה את כל העסקאות (לבדיקה ולצפייה בזמן אמת)
  • מנויים: ניהול חיוב חוזר (תשתמשו בזה בפעילות 3)
  • מפתחים → מפתחות API: אישורי ה-API שלך
  • מפתחים → Webhooks: התראות תשלום לשרת שלך (תגדיר זאת בפעילות 2)

Stripe מספקת לכם שתי קבוצות של מפתחות API - בדומה למפתחות הניתנים לפרסום ולמפתחות הסודיים של Supabase. המפתח הניתן לפרסום בטוח לשימוש ב-frontend, והמפתח הסודי אמור לשמש רק ב-backend.

  1. בלוח המחוונים של Stripe, לחצו על מפתחים בסרגל הצד השמאלי
  2. לחץ על מפתחות API
  3. העתק את שני המפתחות - תצטרך את שניהם.

הקידומת test בקידומת המפתח משמעה שאתה במצב test . לא יחויב כסף אמיתי. כאשר אתה מוכן לקבל תשלומים אמיתיים, עליך לעבור למפתחות חיים ( pk_live_, sk_live_ ).

נוסיף מוצרים עבור הדברים שאתם מתכננים לגבות עבורם תשלום באפליקציה שלכם.

עבור TutorMatch, נוסיף תשלום חד פעמי עבור מפגשי הדרכה ותשלום חוזר עבור מנוי האפליקציה.

  1. עבור אל קטלוג המוצרים מהתפריט הראשי.
  2. נְקִישָׁה צור מוצר.
    • תן שם למוצר שלך 
    • הוסף תיאור
    • בחרו חוזר או חד פעמי
    • הקלד מחיר ובחר את המטבע שלך
  3. הוסף את המוצר.
adding a stripe product
  1. לאחר שהמוצר מופיע ברשימה, לחצו עליו כדי לצפות בו.
  2. בשורת המחיר, לחצו על שלוש הנקודות ובחרו "העתק מזהה מחיר" .
stripe price id
  1. שמור את מזהה המחיר. תצטרך אותו בעת קידוד האפליקציה שלך.

הוסיפו כל פריט אחר שאתם גובים עבורו תשלום, ודאו שאתם מעתיקים את מזהה המחיר וסמנים לאיזה מוצר הוא משמש.

כעת, לאחר שהגדרתם את חשבון Stripe והמוצר/ים שלכם, תוכלו לעבוד על הוספת תהליך התשלום לאפליקציה שלכם. 

ראשית, עליך לשקול את זרימת המשתמשים באפליקציה שלך.

באפליקציה שלנו, נעקוב אחר התהליך הבא:

  1. המשתמש נרשם או מתחבר.
  2. במסך הראשי, מופיע כפתור חדש "שלם עבור מפגש הדרכה" .
  3. המשתמש לוחץ על הכפתור ומועבר לפלטפורמת Stripe.
  4. המשתמש מזין פרטי תשלום.
  5. המשתמש מוחזר לאפליקציה.
  6. המשתמש מקבל הודעה שהתשלום אושר.
  7. אם המשתמש מבטל תשלום בתוך פלטפורמת Stripe:
    1. הם מוחזרים לאפליקציה
    2. מופיעה הודעת ביטול

עבור הפעילויות הבאות, אנו מניחים שפרסת את האפליקציה שלך עם Vercel. משמעות הדבר היא שיש ארכיטקטורה חדשה ללא שרת, כך שעריכת קוד והרצתו ב-Codespace יהיו מעט שונות. בעבר, הרצנו את הקצה הקדמי בנפרד מהקצה האחורי של Express ב-Codespaces. שני תהליכים, שתי סביבות.

עם Vercel ללא שרת:

  • אין תהליך שרת מתמשך
  • כל פונקציה ב- api/index.js מופעלת לפי דרישה כאשר בקשה מגיעה אליה.
  • Vercel מזריק את משתני הסביבה מקובץ root .env (מקומי) או מלוח המחוונים של Vercel (ייצור) ישירות לכל קריאה לפונקציה.
  • גם ממשק המשתמש (Vite) קורא מאותו קובץ שורש .env , אך רק משתנים עם קידומת VITE_ חשופים לדפדפן. כל שאר משתני הסביבה מוגנים. 
פעילות 2

קבל תשלום באפליקציה שלך

זמן משוער: 45 דקות

לפני כתיבת קוד כלשהו, ​​תלמדו את יישומי הקוד הפתוח לדוגמה של Stripe. אלו הן יישומים ייחוס שפורסמו ב- github.com/stripe-samples - אותם תבניות קוד ש-Stripe ממליצה עליהם עבור יישומי ייצור. לימוד הדוגמאות הללו יספק לכם מפה ארכיטקטונית של אופן פעולת שילוב התשלומים, מה שהופך את היישום שלכם למהיר יותר ומספק לכם בסיס לניפוי שגיאות במקרה שמשהו משתבש.

לפני כתיבת קוד כלשהו, ​​למדו כיצד המהנדסים של Stripe בונים אינטגרציית תשלומים.

  1. פתחו את המאגר הזה בדפדפן שלכם: github.com/stripe-samples/accept-a-payment
  2. נווטו לתיקייה prebuilt-checkout-page ועיינו בשני קבצים:
    • השרת ( server/node/server.js ) - כ-100 שורות בסך הכל. תבנית הליבה היא:
				
					// The sample creates a Checkout Session on the server
app.post('/create-checkout-session', async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    line_items: [{ price: process.env.PRICE, quantity: 1 }],
    success_url: `${domainURL}/success.html?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${domainURL}/canceled.html`,
  });
  return res.redirect(303, session.url);
});

				
			
    • הלקוח — שני חלקים שונים
      • טריגר התשלום הוא כפתור טופס יחיד.
				
					<form  action="/he/create-checkout-session/" method="POST">
  <button id="submit">Buy</button>
</form>
				
			
      • ה-App.jsx הראשי צריך לנתב את התגובות מ-Stripe.
				
					
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/success.html" element={<Success />} />
        <Route path="/canceled.html" element={<Canceled />} />
        <Route path="/" element={<Checkout />} />
      </Routes>
    </BrowserRouter>
  );
}
				
			

זהו כל קוד צד הלקוח עבור כפתור תשלום. הזרימה היא:

  • האפליקציה שלך שולחת בקשה לשרת שלך →
  • השרת שלך יוצר סשן תשלום →
  • Stripe יוצר דף תשלום מתארח →
  • המשתמש מופנה לשם →
  • לאחר התשלום, הם חוזרים לאפליקציה שלך →
  • האפליקציה מנתבת את המשתמש כראוי בהתבסס על הצלחה או ביטול

שימו לב לשלושה דברים בנוגע לארכיטקטורה הזו:

  • האפליקציה שלך לעולם לא נוגעת בנתוני כרטיס אשראי. דף התשלום המתארח של Stripe אוסף פרטי תשלום. זה שומר על תאימות אוטומטית ל-PCI - לעולם לא תצטרך לדאוג לגבי תקני אבטחה של כרטיסי אשראי.
  • השרת שולט בסכום החיוב. המוצר, המחיר והמטבע נקבעים בצד השרת כך שמשתמשים אינם יכולים להתערב בהם.
  • כתובת ה-URL של ההצלחה כוללת מזהה סשן. זה מאפשר לדף ההצלחה שלך לאחזר את פרטי התשלום ולהציג אישור תקין.

זהו קוד הייחוס שלך. אם משהו לא עובד כשאתה בונה את האינטגרציה שלך עם Stripe, תוכל להשוות את הקוד שלך לדוגמה זו כדי למצוא את ההבדל.

אמנם ניתן להוסיף את המפתחות לקבצי ה-. env שלכם ב-Codespace, אך הוסיפו את מה שאתם יכולים ל-Codespace Secrets עבור המאגר שלכם. 

  1. כנסו ל-Github ולחצו על הפרופיל שלכם בפינה הימנית העליונה.
  2. לחץ על הגדרות
  3. בתפריט השמאלי, לחצו על מרחבי קוד .
  4. לחץ על סוד חדש.
  5. הוסף את ה-STRIPE_SECRET_KEY שלך, על ידי העתקת המפתח שקיבלת מ-Stripe.
  6. הוסף סוד נוסף עבור PRICE_ID שלך. תן לו תווית תיאורית. לדוגמה, עבור Tutormatch, שם הסוד שלנו הוא STRIPE_PRICE_ID_TUTOR .
  1. עבור למאגר Github שלך וצור ענף חדש. זכור, כעת לאחר שביצעת פריסה דרך Vercel, אתה רוצה לבצע כל פיתוח חדש על ענף נפרד מהענף הראשי.
  2. פתחו את מרחב הקוד שלכם (או מרחב חדש) ועברו לענף החדש שלכם.

כשפרסמת את האפליקציה שלך, הוספת קובץ vite.config.js כדי לאפשר פיתוח מקומי עם שרתים הן בצד הלקוח והן בצד השרת. כעת, לאחר שעברנו לארכיטקטורה נטולת שרתים עם Vercel, נסיר את קוד הפרוקסי בקובץ vite.config.js.

עדכן את הקובץ. להלן כל הקוד הדרוש כעת:

				
					import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({ 
    plugins: [react()], 
})
				
			

ישנם כמה משתני סביבה שתצטרכו לתשלום. Stripe צריכה לדעת את כתובת ה-URL שאליה ניתן לחזור לאחר תשלום מוצלח. זוהי כתובת ה-URL הראשית שלנו בחזית השרת.

  1. התקן את Vercel כדי שתוכל להפעיל את סביבת הפיתוח של Vercel. בחלון הטרמינל, הקלד:
    npm install -g vercel
    vercel link
  2. תידרש לקשר את חשבון GitHub שלך ואת המאגר.
    בחר 'כן' כדי לקשר את הפרויקט ולמשוך את משתני הסביבה.
  3. ייתכן שתצטרך גם להריץ את הפקודה ` npm install ` כדי להתקין מחדש את מודולי Node במרחב הקוד שלך.
  4. בך root .env קובץ, הוסף את השורה הזו:
    FRONTEND_URL=כתובת_ה-GitHub_שלך
    ניתן להשתמש בקובץ קובץ .env מכיוון שעברנו לארכיטקטורה ללא שרתים של Vercel. הפעל את האפליקציה כדי להשיג את כתובת ה-GitHub ולהכניס אותה כאן.

    חשוב: הסירו כל / שנגרר בסוף כתובת האתר!

  5. למרות שהוספנו את המפתח של Stripe ואת ה-price_id ב-Codespace Secrets, בואו נוסיף אותם ל- .env כמו כן, לצורך בדיקה. הוסף גם
    STRIPE_SECRET_KEY=מפתח_הסודי_שלך_ב-Stripe
    STRIPE_PRICE_ID_TUTOR=מזהה_המחיר_שלך_לתשלום 
    אל .env. תן שם מתאים ל-PRICE_ID שלך.

    ניתן להוסיף בבטחה את מפתח ה-Stripe ואת ה-price_id לתיקיית השורש .env. כל עוד המשתנה אינו מכיל את VITE_, הוא נשאר מוגן.

    ודאו שערכי הסביבה תואמים ב-Codespace Secrets ובקובץ .env. ל-Codespace Secrets יש קדימות על פני קובץ ה-.env. אם אתה מעדכן מפתח במקום אחד, עדכן אותו גם במקום השני — ערכים שאינם תואמים הם מקור שכיח לשגיאות שקשה לאתר.

כדי להבטיח שהאפליקציה תנתב את המשתמש לדף הנכון, בהתאם לשאלה האם הוא מחובר, האם הוא כבר שולם, ואם התשלום הצליח או לא, נשתמש בספריית ה-Router . 

  1. בחלון הטרמינל, הקלד npm install react-router-dom

יש כאן הרבה דברים מעורבים, אז אנחנו הולכים לעשות את זה צעד אחר צעד, ובמיוחד לבקש מ-Copilot (או מבעל תואר שני במשפטים) לייצר עבורכם את הקוד. 

  1. בקשו מה-LLM לעדכן את קובץ ה-backend api/index.js (או כל קובץ אחר של שרת ה-backend שלכם). זכרו, כשאנחנו עוברים למצב ייצור, אנחנו מעבירים את קוד ה-backend לכאן.
    נבקש מה-LLM לכתוב את בקשת ה-POST ל-Stripe, ולספק לה את המפרטים עבור מבנה התשלום הספציפי שלך.

הוסף נקודת קצה של תשלום ב-Stripe לשרת Express שלי ב- api/index.js.

  • השתמש ב-STRIPE_SECRET_KEY מקובץ .env
  • צור נתיב POST מסוג /api/create-checkout-session.
  • עליו להשתמש ב-pricedid, תוך שימוש ב-STRIPE_PRICE_ID_TUTOR מ-.env
  • השתמש בפונקציה stripe.checkout.sessions.create עם:
    • line_items: מערך המכיל את STRIPE_PRICE_ID_TUTOR מקובץ .env וכמות: 1.
    • מצב: הגדר את זה ל'תשלום' (מכיוון שמדובר בהדרכה חד פעמית).
    • success_url: Construct the base URL using:
      const baseURL = process.env.FRONTEND_URL || `https://${process.env.VERCEL_URL}`
      Then use baseURL + ‘/success’ for the success_url
    • cancel_url: השתמש ב-baseURL (אותו URL בסיסי, ללא נתיב)
  •  החזר את כתובת ה-URL של הסשן כ-JSON כדי שהממשק הראשי יוכל להפנות את המשתמש. אין לעדכן עדיין קוד ממשק ראשי.

הערה: פשטות - אין עדיין קוד למסד נתונים או קוד Supabase.

חשוב: אין לכלול את payment_method_types (תן ל-Stripe לבחור באופן דינמי)

ה-LLM צריך לעדכן את הקוד בקובץ index.js. 

  1. צפה בשינויים וקבל אותם (או בקש עריכה או הבהרה במידת הצורך).
  2. בקשו מה-LLM לעדכן את קובץ ה-frontend הראשי של App.jsx כדי לטפל בתשלומים.
  3. Copilot עשוי לבצע זאת עבורך, אך עליך להתקין את Stripe באמצעות הפקודה ` npm install stripe`.

הערה: שימו לב כיצד הנחינו את Copilot ליצור baseURL עבור כתובות ה-URL של ההפניה במקרה של הצלחה וביטול. קבענו שהוא ישתמש ב-FRONTEND_URL או ב-process.env.VERCEL_URL. FRONTEND_URL הוא המשתנה הסביבתי שלנו, והוא משמש כאן ב-Codespaces לצורכי בדיקה. כשאנחנו עוברים לסביבת הייצור, כתובת ההפניה צריכה להיות כתובת ה-URL של האפליקציה שלנו ב-Vercel. Vercel יוצר את המשתנה הזה באופן אוטומטי. עלינו רק להוסיף אותו לקוד שלנו כדי שהוא יעבוד הן בסביבת הבדיקה והן בסביבת הייצור.

בדוק את הקובץ index.js שלך כדי לוודא שהוא כולל את FRONTEND_URL ו-VERCEL_URL.

ה-LLM צריך לעדכן את הקוד בקובץ index.js. 

  1. צפה בשינויים וקבל אותם (או בקש עריכה או הבהרה במידת הצורך).
  2. בקשו מה-LLM לעדכן את קובץ ה-frontend הראשי של App.jsx כדי לטפל בתשלומים.
  3. Copilot עשוי לבצע זאת עבורך, אך עליך להתקין את Stripe באמצעות הפקודה ` npm install stripe`.

עדכן את קובץ ה-App.jsx שלי כדי לטפל בתשלומים. אנא הוסף את הפרטים הבאים:

  • עדכון ה-App.jsx שלי כדי לטפל בניתוב בסיסי ובטריגר התשלום.
  • הגדר שני מסלולים: / (דף הבית) ו-/הצלחה (דף הצלחה).
  • במסלול /, אם משתמש מחובר, יוצג כפתור 'הזמן מפגש (60 דולר)'.
  • צור פונקציית handlePay עבור כפתור זה. היא צריכה:
    • אחזור כתובת ה-URL של Stripe מה-backend שלי (/api/create-checkout-session).
    • הפניה לכתובת ה-URL שהוחזרה על ידי השרת.
  • אם המשתמש אינו מחובר, הצג את AuthForm המוגדר כברירת מחדל.
  1. בקשו מה-LLM ליצור את דפי ההצלחה והביטול לאחר החזרה מ-Stripe.

צרו דף React פשוט בשם Success.jsx והפכו אותו לנתיב ההצלחה.

  • אמורה להיות כותרת: 'התשלום הצליח!'.
  • כלול הודעה קצרה: 'תודה שהזמנת את פגישת ההוראה שלך. נשלח לך את הפרטים בדוא"ל בקרוב.'
  • הוסף כפתור 'חזור ללוח המחוונים' המשתמש בפונקציה useNavigate מ- react-router-dom כדי לשלוח את המשתמש חזרה לנתיב /

צור דף React פשוט בשם Cancel.jsx.

  • אמורה להופיע הודעה כמו: 'התשלום בוטל. הכרטיס שלך לא חויב'.
  • הוסף כפתור 'נסה שוב' המשתמש ב-useNavigate כדי לשלוח את המשתמש חזרה ללוח המחוונים הראשי (/).
  • שמרו על עיצוב ידידותי כדי שהמשתמש לא ירגיש שהוא עשה משהו לא בסדר.
  1. הפעל את האפליקציה שלך עם vercel dev בחלון הטרמינל. פקודה זו מחליפה את פקודות npm run dev הקודמות, ומריצה את ה-frontend וה-backend יחד בפורט אחד.
  2. לחצו על כפתור "שלם עכשיו". אתם אמורים להיות מועברים לדף תשלום המאוחסן על ידי Stripe, המציג את שם המוצר והמחיר.
  3. השתמשו בכרטיס הניסיון של Stripe כדי להשלים את התשלום:
    • מספר כרטיס: 4242 4242 4242 4242
    • תאריך תפוגה: כל תאריך עתידי (למשל, 30/12)
    • CVC: 3 ספרות כלשהן (לדוגמה 123)
    • שם: כל שם שהוא
  4. לאחר השליחה, עליך להגיע לדף ההצלחה שלך, בו תודה לך על התשלום.

  5. כדי לוודא שהתשלום בוצע, עבור אל לוח המחוונים של Stripe ולחץ על תשלומים . אתה אמור לראות את תשלום הניסיון שלך רשום שם.

  6. בדוק שוב. הפעם, בטל את התשלום בזמן שאתה נמצא בדף התשלום של Stripe. 
  7. זה אמור להוביל אותך לדף הביטול שלך, שיאפשר לך לנסות שוב. 

לאחר שבדקתם שתהליך התשלום שלכם פועל כהלכה, בצעו את ה-commit לעבודה שלכם - זהו נקודת ביקורת טובה לפני הוספת webhooks.

ניתן לבצע commit באמצעות לוח בקרת המקור (Source Control ) (לחץ על סמל בקרת המקור בסרגל הצד השמאלי, הוסף הודעת commit ולחץ על סימן הווי) או באמצעות הטרמינל:

וובים

תהליך התשלום שלך עובד, ודף ההצלחה מציג פרטי תשלום. אבל הנה הבעיה: דף ההצלחה אינו דרך אמינה לאשר תשלום.

קחו בחשבון מה יכול להשתבש:

  • המשתמש סוגר את הדפדפן לפני טעינת דף ההצלחה
  • הרשת נופלת במהלך ההפניה
  • המשתמש מנווט הרחק מדף ההצלחה

בכל המקרים הללו, Stripe עיבד את התשלום, אך האפליקציה שלכם מעולם לא גילתה זאת. המשתמש שילם, אך לשרת שלכם אין תיעוד לכך.

Webhooks פותרים את זה. Webhook הוא הודעה ש-Stripe שולחת ישירות לשרת שלך כאשר משהו קורה - כמו תשלום שהושלם. השרת שלך לא צריך לשאול את Stripe "האם הם שילמו?" - Stripe אומרת לשרת שלך באופן יזום.

להבין את זרימת ה-webhook

מניעת אישורי תשלום מזויפים היא קריטית. ללא אימות webhook, מישהו עלול לשלוח בקשה לשרת שלך בטענה שתשלום בוצע, בעוד שלא בוצע.

כך פועלים webhooks בתהליך התשלום שלך:

המשתמש לוחץ על "שלם" → שלב התשלום ב-Stripe → שלב התשלום → שני דברים קורים:

  1. המשתמש רואה דף הצלחה (לא אמין)
  2. Stripe שולחת webhook לשרת שלך (אמין)

השרת שלך מקבל את ה-webhook ללא קשר למה שהמשתמש עושה עם הדפדפן שלו. זו הסיבה ש-webhooks הם מקור אמין לאמת תשלום.

פעילות 3

בניית מטפל Webhook

זמן משוער: 30 דקות

תיצרו נקודת קצה בשרת ה-Express שלכם שתקבל אישורי תשלום מ-Stripe, תאמתו את אותנטיותם ותבדקו תרחישי תשלום מוצלחים וכושלים כאחד.

עליך להוסיף את נקודת הקצה של webhook לשרת ה-backend שלך. לנקודת הקצה של webhook יש דרישה ספציפית: היא זקוקה לגוף הבקשה הגולמי לאימות חתימה. אם Express מנתח את הגוף כ-JSON תחילה, בדיקת החתימה תיכשל. 

חשוב: אם לשרת שלך יש את הפונקציה app.use(express.json()) , יש להגדיר את נתיב ה-webhook לפני שורה זו. אחרת, Express ינתח את הגוף כ-JSON לפני שמטפל ה-webhook רואה אותו, ואימות החתימה ייכשל.

  1. פתחו את לוח הצ'אט של Copilot ושאלו:

הוסף נקודת קצה של webhook של Stripe לשרת Express שלי ב- api/index.js.

דרישות:

– נקודת קצה של POST ב-/webhook

– השתמשו בתוכנת הביניים express.raw() עבור נתיב זה בלבד (לא express.json())

– אימות חתימת ה-webhook באמצעות STRIPE_WEBHOOK_SECRET

– טיפול באירועים checkout.session.completed ו- checkout.session.expired

– רישום פרטי התשלום (מזהה סשן, סכום, כתובת דוא"ל של הלקוח) בקונסולה

– החזר 200 לאישור קבלה

– חשוב: מסלול זה חייב להשתמש בניתוח גוף גולמי, ולא בניתוח JSON

  1. בדוק את קובץ ה-index.js שלך וסדר מחדש במידת הצורך. נתיב ה-webhook שלך צריך להיות ראשון.
				
					// 1. Webhook route FIRST (needs raw body)
app.post('/webhook', express.raw({ type: 'application/json' }), webhookHandler);

// 2. JSON parsing for all other routes AFTER
app.use(express.json());

// 3. Your other routes
app.post('/create-checkout-session', ...);
app.get('/success', ...);
				
			

הרצת webhook מ-Codespaces היא דבר מסובך. עליכם להפוך את פורט השרת שלכם לציבורי, וגם עם זאת, לפעמים זה עדיין יכול לגרום לשגיאות. כדי לפשט את הבדיקות, נשתמש ב-Stripe CLI (ממשק שורת פקודה). הוא יוצר מנהרה מהאפליקציה שלכם ל-Stripe כדי לאפשר תקשורת ישירה.

  1. התקנת Stripe בטרמינל Codespaces חדש
    התקנת בישול פס/פס-קלי/פס
    אוֹ

    אם זה לא עובד (הדרך הידנית):

    • הוסף את המפתח: curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg –dearmor | sudo tee /usr/share/keyrings/stripe.gpg > /dev/null

    • הוסף את המאגר: echo “deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main” | sudo tee /etc/apt/sources.list.d/stripe.list

    • התקנה: sudo apt update ו- sudo apt install stripe

  2. התחברות. הקלד
    כניסה לפס

  3. זה ייתן לך קוד צימוד וקישור. לחץ על הקישור, התחבר ל-Stripe ואפשר את החיבור.

  4. בדוק באיזה יציאה האפליקציה פועלת. עבור לכרטיסייה "יציאות" ובדוק איזו יציאה מופיעה עבור האפליקציה שלך. סביר להניח שמדובר ביציאה 3000, אך ייתכן שמדובר ביציאה אחרת. 
  5. התחל להאזין. הקלד:
    stripe listen –forward-to localhost:[YOUR_PORT]/api/webhook

  6. ברגע שהוא יתחיל להאזין, תראה הודעה:
    מוכן! סוד החתימה שלך על webhook הוא whsec_xxxxxxxx

  7. העתק את סוד החתימה והדבק אותו מיד בקובץ קובץ ה-.env , בשדה STRIPE_WEBHOOK_SECRET.

  8. הפעל מחדש את האפליקציה כדי שהשינויים ייכנסו לתוקף.
  1. בדוק על ידי לחיצה על כפתור "שלם עכשיו" באפליקציה שלך.
  2. השלם את התשלום באמצעות כרטיס בדיקה 4242 4242 4242 4242.
  3. בדוק את מסוף ה-Codespace שלך ​​עבור רישום הקונסולה - אתה אמור לראות:
    התשלום אושר!
    מזהה סשן: cs_test_…
    סכום: 1500
    דוא"ל לקוח: [email protected]
  4. אם זה לא מופיע, בדוק אם יש שגיאות ובקש מ-Copilot לוודא שקוד console.log נמצא שם ועובד.

משתמשים אמיתיים לפעמים מקבלים כרטיסי אשראי שנדחים. בדיקת תרחיש זה עוזרת לך להבין מה קורה כאשר תשלום נכשל.

  1. עברו שוב על תהליך התשלום, אך הפעם השתמשו בכרטיס בדיקת הדחייה:
    • מספר כרטיס: 4000 0000 0000 0002
    • ערכים אחרים: זהים לאלו של תשלום תקף

Stripe יציג למשתמש הודעה "הכרטיס שלך נדחה" בדף התשלום. המשתמש נשאר בדף התשלום של Stripe ויכול לנסות כרטיס אחר.

שימו לב מה קורה בשרת שלכם: לא מופעל שום webhook . האירוע checkout.session.completed מופעל רק כאשר התשלום הצליח. זוהי ההתנהגות הנכונה - השרת שלכם צריך לפעול רק על תשלומים מוצלחים.

בצעו commit באמצעות לוח בקרת המקור או הטרמינל. פעולה זו שומרת על כל שלב של יישום התשלום ב-commits נפרדים.

חשוב: השתמשנו ב-Stripe CLI לבדיקות ב-Codespaces. בעת המעבר לאפליקציית הייצור שלך, עליך להגדיר את ה-webhook בלוח המחוונים של Stripe. יוקצה לך מפתח חתימה סודי שונה שתצטרך להוסיף לסודות פלטפורמת הייצור שלך, ולנתב מחדש את ה-webhook לכתובת URL של הייצור שלך. ההוראות מופיעות למטה.

  1. עבור אל לוח המחוונים של Stripe שלך.
  2. חפש בחלק העליון את Webhooks . לחץ על Webhooks . זה אמור להוביל אותך ל- Workbench .
  3. בתוך Workbench , עבור ללשונית Webhooks .
  4. לחץ על ה + הוסף יעד לַחְצָן.
    • אירועים מ: מצא את התשלום בתפריט הנפתח
    • בחירת אירועים: בחרו checkout.session.completed ו- checkout.session.expired
    • סוג יעד: בחר נקודת קצה של Webhook .
    • כתובת URL של נקודת קצה: הדבק את כתובת URL הייצור שלך (לדוגמה, https://your-vercel.app/api/webhook ).
  5. לאחר יצירת נקודת הקצה, לחצו על גילוי תחת סוד חתימה כדי לקבל את סוד ה-webhook שלכם (מתחיל ב-whsec_).
  6. הוסף את המפתח לסודות הייצור שלך (אותו תהליך כמו מפתחות ה-API של Stripe):
    • שם: STRIPE_WEBHOOK_SECRET
    • ערך: הדבק את הסוד whsec_… שלך

שום דבר בקוד שלך לא צריך להשתנות.

חיוב מנוי

לא כל עסק גובה תשלום פעם אחת. אם אסטרטגיית התמחור שלך כוללת הכנסות חוזרות - מנויים חודשיים, תוכניות שנתיות או דמי חבר - אז אתה צריך חיוב מנויים.

החדשות הטובות הן שהמרת תשלומים חד פעמיים למנויים ב-Stripe דורשת רק כמה שינויים בקוד שכבר כתבתם. חוויית התשלום עבור המשתמש נראית כמעט זהה. בחלק זה, תלמדו גם כיצד אפליקציית המנויים לדוגמה של Stripe מציגה אפשרויות תמחור ומנהלת מנויים.

בפעילות הבאה, תלמדו כיצד להוסיף אפשרות תשלום עבור מנוי באפליקציה שלכם. נשמור על אפשרות התשלום הבודד, במקרה שלנו עבור מפגש הדרכה, אך נוסיף סוג תשלום שני עבור מנוי שנתי לתכונות האפליקציה.

פעילות 4

הוסף תשלום מנוי

זמן משוער: 45 דקות

תוסיף סוג תשלום שני, מנוי חוזר, לאפליקציה שלך. לאחר ההצלחה, תעדכן את מסד הנתונים של Supabase שלך ​​כדי לסמן משתמש זה כמשתמש ששולם.

  • פתחו את המאגר הזה בדפדפן שלכם: github.com/stripe-samples/checkout-single-subscription

    ה קוד השרת (כ-120 שורות) דומה מאוד למה שבנית בפעילות 1. ההבדלים העיקריים הם:
    • המצב הוא 'מנוי' במקום 'תשלום'
    • הדוגמה כוללת גם נקודת קצה של פורטל לקוחות - זה מאפשר למנויים לנהל את המנוי שלהם (לבטל, לשנות תוכנית) מבלי ליצור איתך קשר.

הוסף נקודת קצה של webhook של Stripe לשרת ה-Express שלי.

דרישות:

– נקודת קצה של POST ב-/webhook

– השתמשו בתוכנת הביניים express.raw() עבור נתיב זה בלבד (לא express.json())

– אימות חתימת ה-webhook באמצעות STRIPE_WEBHOOK_SECRET

– טיפול באירועים checkout.session.completed ו- checkout.session.expired

– רישום פרטי התשלום (מזהה סשן, סכום, כתובת דוא"ל של הלקוח) בקונסולה

– החזר 200 לאישור קבלה

– חשוב: מסלול זה חייב להשתמש בניתוח גוף גולמי, ולא בניתוח JSON

  1. בדוק את קובץ ה-server.js שלך וסדר מחדש במידת הצורך. נתיב ה-webhook שלך צריך להיות ראשון.
				
					// 1. Webhook route FIRST (needs raw body)
app.post('/webhook', express.raw({ type: 'application/json' }), webhookHandler);

// 2. JSON parsing for all other routes AFTER
app.use(express.json());

// 3. Your other routes
app.post('/create-checkout-session', ...);
app.get('/success', ...);
				
			
  1. אם עדיין לא עשית זאת, הוסף מוצר מנוי לחשבון Stripe שלך.
    1. עבור אל קטלוג המוצרים מהתפריט הראשי.

    2. לחץ על צור מוצר
      • תן שם למוצר שלך 
      • הוסף תיאור
      • בחר מחזורי
      • הקלד מחיר ובחר את המטבע שלך
    3. הוסף את המוצר.
    4. לאחר שהמוצר מופיע ברשימה, לחצו עליו כדי לצפות בו.
    5. בשורת המחיר, לחצו על שלוש הנקודות ובחרו "העתק מזהה מחיר" .
  1. חזור לחשבון Github והוסף סוד חדש של Codespace.
    1. מהפרופיל שלך, לחץ על הגדרות → מרחבי קוד .
    2. הוסף סוד חדש, תן לו את השם STRIPE_PRICE_ID_SUBSCRIPTION או שם מתאים אחר.
    3. הדבק את מזהה המחיר שהעתקת מ-Stripe.
    4. כמו כן, היכנסו ל-Codespace שלכם והוסיפו אותו לקובץ ה-.env . גיבוי זה מונע ממך את הצורך לבנות מחדש את ה-Codespace כדי לשלב את ה-Codespace Secret החדש. ראה הערה להלן.

הערה: ייתכן שתצטרכו לבנות מחדש את המכולה של Codespace כדי שהיא תזהה את הסוד החדש. ניתן לעשות זאת מ-Codespace, על ידי לחיצה על F1 עבור לוח הפקודות וחיפוש אחר Codespaces: Rebuild Container. ניתן גם להוסיף אותו ל-server/.env כגיבוי, כך שלא תצטרכו לבנות מחדש את המכולה עבור הפעלה זו.

אם אנחנו רוצים שמשתמשים ישלמו כדי להשתמש באפליקציה שלנו, אנחנו רוצים לעקוב אחר זה ב-Supabase. נכון לעכשיו, משתמשים יכולים להירשם על סמך אימות Supabase וכניסה לגוגל.

איננו יכולים להתעסק עם תהליך אימות המשתמש של Supabase, לכן נוסיף טבלת פרופילים חדשה שתכיל את פרטי התשלום.

  1. עבור אל פרויקט Supabase שלך ​​← אימות ← משתמשים .
    תראה את כל המשתמשים שנרשמו לאפליקציה שלך. 
  2. עבור אל עורך ה-SQL מהתפריט הראשי.
  3. הדבק את פקודת ה-SQL הזו כדי ליצור את טבלת הפרופילים . היא מקשרת לטבלת אימות המשתמש עם מזהה המשתמש מאותה טבלה.
				
					create table profiles (
  id uuid references auth.users not null primary key,
  email text,
  is_paid boolean default false
);
				
			

יש ליצור את טבלת הפרופילים .

  1. עבור אל עורך הטבלאות ותראה טבלת פרופילים ריקה.
    אם כבר יש לך משתמשים, עלינו להריץ פקודה כדי להוסיף את המשתמשים הקיימים לטבלת הפרופילים.
  2. חזור לעורך SQL והפעל פקודה זו.
				
					INSERT INTO public.profiles (id, email)
SELECT id, email 
FROM auth.users
ON CONFLICT (id) DO NOTHING;
				
			
  1. חזור לעורך הטבלאות.
  2. ודא שכל המשתמשים נוספו לטבלת הפרופילים .
  3. חפשו בחלק העליון של המסך כפתור שעליו כתוב מדיניות RLS אוֹ RLS מושבת.
    • אם כתוב " RLS מושבת", לחץ על הכפתור ובחר באפשרות " הפעל RLS עבור טבלה זו". 
    • אם כתוב שם " מדיניות RLS", לחץ עליה. אמור להיות כתוב שם: "משתמשים יכולים לעדכן את הפרופיל שלהם".
supabase screen with RLS disabled

Supabase צריכה לדעת שכאשר משתמש חדש נרשם, עליה ליצור שורה חדשה בטבלת הפרופילים. ניתן לעשות זאת על ידי הגדרת טריגר ב-Supabase.

  1. עבור לעורך ה-SQL ב-Supabase והדבק את הקוד הבא.
				
					create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, email)
  values (new.id, new.email);
  return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();
				
			

יש ליצור את הטריגר.

  1. כדי לבדוק אם הפונקציה קיימת, כדי לבדוק אם הפונקציה קיימת, עבור אל "מסד נתונים" → "פונקציות". שם אמורה להופיע הפונקציה handle_new_user .

אימות המשתמש שלך עם Supabase פעל מה-frontend והשתמש במפתח הניתן לפרסום. עדכון טבלת הפרופילים יתרחש מה-backend, ונשתמש במפתח הסודי של Supabase לשם כך. משמעות הדבר היא שעלינו להוסיף אותו לאפליקציה שלנו כמשתנה סביבה.

  1. עבור אל פרויקט Supabase שלך ​​ולחץ על הגדרות פרויקט .
  2. לחץ על מפתחות API .
  3. גללו למטה ומצאו את המפתח הסודי. העתיקו אותו.
  4. בלוח המחוונים של Github, עבור אל הפרופיל שלך → מרחבי קוד . 
  5. הוסף סוד חדש, קרא לו SUPABASE_SERVICE_ROLE_KEY , והדבק את המפתח כערך.
  6. עכשיו הוסיפו את כתובת_SUPABASE כסוד נוסף של Codespace. ייתכן שזה כבר קיים בתיקיית השורש שלכם .env קובץ, אך תוכל למצוא אותו שוב בלוח המחוונים של Supabase.
    1. עבור אל הגדרות הפרויקט → API של נתונים.
    2. העתק את כתובת ה-URL של ה-API והדבק אותה כערך הסוד של Codespace שלך.
  7. כדי למנוע את הצורך בבניית מחדש של מכולת ה-Codespace שלכם (ראו הערה להלן), הוסיפו גם את שני המשתנים הללו לקובץ השורש .env ב-Codespace.

הערה: ייתכן שתצטרכו לבנות מחדש את המכולה של Codespace כדי שהיא תזהה סודות חדשים. ניתן לעשות זאת מ-Codespace, על ידי לחיצה על F1 עבור לוח הפקודות וחיפוש אחר Codespaces: Rebuild Container. ניתן גם להוסיף אותו ל-server/.env כגיבוי, כך שלא תצטרכו לבנות מחדש את המכולה עבור הפעלה זו.

אנחנו הולכים לעשות זאת שלב אחר שלב כדי לגרום ל-Copilot להוסיף את הקוד הדרוש.

  1. חזור לסביבת Codespace שלך. ודא שעדכונים קודמים בוצעו באמצעות commit, כך שאתה מתחיל עם commit חדש.
  2. בקש מ-Copilot לעדכן את שרת ה-backend. הנה דוגמה להודעה:

שמור את נקודת הקצה הקיימת של Stripe לתשלומים עבור שיעורי ההדרכה שלי, והוסף נקודת קצה חדשה למנוי בקובץ api/index.js.

המצב הוא מנוי והמחיר הוא STRIPE_PRICE_ID_SUBSCRIPTION

Copilot אמור ליצור קובץ app/post חדש בכתובת api/index.js. לחץ למטה כדי לראות את הקוד שהוא יצר עבור Tutormatch.

				
					app.post('/api/create-subscription-session', async (req, res) => {
  try {
    const priceId = process.env.STRIPE_PRICE_ID_SUBSCRIPTION;
    if (!priceId) {
      return res.status(400).json({ error: 'Subscription Price ID not configured' });
    }
    if (!process.env.FRONTEND_URL) {
      return res.status(500).json({ error: 'FRONTEND_URL not configured' });
    }
    const session = await stripe.checkout.sessions.create({
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
      ],
      mode: 'subscription',
      success_url: process.env.FRONTEND_URL + '/success',
      cancel_url: process.env.FRONTEND_URL,
    });
    res.json({ url: session.url });
  } catch (error) {
    console.error('Stripe subscription error:', error);
    res.status(500).json({ error: error.message });
  }
});
				
			
  1. בקשו מ-Copilot להוסיף את אפשרות ההרשמה לטיפול ב-webhook, כך שיעדכן את Supabase עבור הרשמה בתשלום.

    הנה דוגמה להנחיה:

הוסף למטפל ה-webhook שלי כך שיטפל במצב התשלום כפי שהוא מטפל כעת.

אם מדובר במצב מנוי, עדכן את טבלת הפרופילים ב-supabase עבור משתמש זה. הגדר את is_paid ל-true. SUPABASE_URL הוא כתובת ה-URL של הפרויקט והמפתח הסודי הוא SUPABASE_SERVICE_ROLE_KEY

כמו כן, טפל ב-subscription.deleted, ועדכן פרופילים עבור משתמש זה כך ש-is_paid יהיה false.

לחץ למטה כדי לראות את הקוד עבור מטפל ה-webhook של Tutormatch. שימו לב, יש לנו הרבה קוד console.log, כדי לוודא שה-webhook פועל והתשלומים מתקבלים.

				
					app.post('/api/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;

    try {
        // This verifies the message actually came from Stripe
        event = stripe.webhooks.constructEvent(
            req.body, 
            sig, 
            process.env.STRIPE_WEBHOOK_SECRET // This is your whsec_ key
        );
    } catch (err) {
        console.error(`Webhook Error: ${err.message}`);
        return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    // 2. HANDLE THE EVENT
      if (event.type === 'checkout.session.completed') {
          const session = event.data.object;
          const mode = session.mode;
          const email = session.customer_details?.email;

          console.log('Session mode:', mode);
          console.log('Session email:', email);
          console.log('Session details:', session);

          if (mode === 'payment') {
              console.log('Payment Successful!');
              console.log('Customer Email:', email);
              console.log('Amount Paid:', session.amount_total / 100); // Stripe uses cents
              // This is where you would update your database (e.g., User.paid = true)
          } else if (mode === 'subscription') {
              console.log('Subscription Successful!');
              console.log('Customer Email:', email);
              // Update Supabase profiles table: set is_paid true for this user
              if (email) {
                  const supabaseUrl = `${process.env.SUPABASE_URL}/rest/v1/profiles?email=eq.${encodeURIComponent(email)}`;
                  const supabaseHeaders = {
                      'Content-Type': 'application/json',
                      'apikey': process.env.SUPABASE_SERVICE_ROLE_KEY,
                      'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`,
                  };
                  const supabaseBody = JSON.stringify({ is_paid: true });
                  console.log('Supabase PATCH URL:', supabaseUrl);
                  console.log('Supabase PATCH Headers:', supabaseHeaders);
                  console.log('Supabase PATCH Body:', supabaseBody);
                  fetch(supabaseUrl, {
                      method: 'PATCH',
                      headers: supabaseHeaders,
                      body: supabaseBody
                  })
                  .then(res => {
                      console.log('Supabase PATCH response status:', res.status);
                      return res.json();
                  })
                  .then(data => {
                      console.log('Supabase update (is_paid true):', data);
                  })
                  .catch(err => {
                      console.error('Supabase update error:', err);
                  });
              } else {
                  console.log('No email found for subscription session. Supabase not updated.');
              }
          }
      } else if (event.type === 'checkout.session.expired') {
          const session = event.data.object;
          console.log('Session expired. Releasing the tutor slot for:', session.id);
      } else if (event.type === 'subscription.deleted') {
          const subscription = event.data.object;
          const email = subscription.customer_email || subscription.customer?.email;
          console.log('Subscription deleted. Email:', email);
          console.log('Subscription details:', subscription);
          // Update Supabase profiles table: set is_paid false for this user
          if (email) {
              const supabaseUrl = `${process.env.SUPABASE_URL}/rest/v1/profiles?email=eq.${encodeURIComponent(email)}`;
              const supabaseHeaders = {
                  'Content-Type': 'application/json',
                  'apikey': process.env.SUPABASE_SERVICE_ROLE_KEY,
                  'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`,
              };
              const supabaseBody = JSON.stringify({ is_paid: false });
              console.log('Supabase PATCH URL:', supabaseUrl);
              console.log('Supabase PATCH Headers:', supabaseHeaders);
              console.log('Supabase PATCH Body:', supabaseBody);
              fetch(supabaseUrl, {
                  method: 'PATCH',
                  headers: supabaseHeaders,
                  body: supabaseBody
              })
              .then(res => {
                  console.log('Supabase PATCH response status:', res.status);
                  return res.json();
              })
              .then(data => {
                  console.log('Supabase update (is_paid false):', data);
              })
              .catch(err => {
                  console.error('Supabase update error:', err);
              });
          } else {
              console.log('No email found for deleted subscription. Supabase not updated.');
          }
      }

    // Return a 200 response to acknowledge receipt
    res.json({ received: true });
});
				
			

עכשיו לממשק הקצה. חילקנו את ההנחיה לשני חלקים. ראשית, להוסיף את הכפתור לתשלום המנוי, ולאחר מכן להוסיף את הלוגיקה להפעלה/השבתה של תכונות בהתבסס על תשלום המנוי או לא.

  1. בקשו מ-Copilot להוסיף כפתור תשלום חדש למנוי. הנה דוגמה להנחיה.

בממשק הקצה, ב-App.jsx, הוסיפו כפתור חדש, הירשמו תמורת $9.99 לשנה.
לחיצה על הכפתור אמורה להפעיל את הפוסט create_subscription_session.

  1. בקשו מ-Copilot להוסיף את הלוגיקה להשבתת תכונות אם המשתמש לא מקבל תשלום, והפעלתן אם המשתמש מקבל תשלום.

יש להשבית את הכפתורים "קבל עצה" ו"מצא מורה פרטי" אם הערך שקר הוא user_paid, ולאפשר אותם אם הערך שקר הוא user_paid.
הוסף תווית מידע - "פתח תכונות אלה על ידי הרשמה" והנח אותה מעל שני כפתורי התכונות.

לחץ למטה כדי לראות את קוד הקצה של Tutormatch.

				
					function Home({ user, setUser }) {
  const [screen, setScreen] = useState('advice');
  const [isPaid, setIsPaid] = useState(false);
  const navigate = useNavigate();

  useEffect(() => {
    async function fetchProfile() {
      if (user && user.email) {
        try {
          const res = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/rest/v1/profiles?email=eq.${encodeURIComponent(user.email)}`, {
            headers: {
              'apikey': import.meta.env.VITE_SUPABASE_KEY,
              'Authorization': `Bearer ${import.meta.env.VITE_SUPABASE_KEY}`,
            },
          });
          if (res.ok) {
            const data = await res.json();
            // Supabase may return booleans or truthy strings; normalize to boolean
            setIsPaid(Boolean(data[0]?.is_paid));
          } else {
            setIsPaid(false);
          }
        } catch {
          setIsPaid(false);
        }
      } else {
        setIsPaid(false);
      }
    }
    fetchProfile();
  }, [user]);

  const handlePay = async () => {
    try {
      const res = await fetch('/api/create-checkout-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
      });
      if (!res.ok) {
        const text = await res.text();
        alert('Payment error: ' + text);
        return;
      }
      const data = await res.json();
      if (data.url) {
        window.location.href = data.url;
      } else {
        alert('Could not start payment session.');
      }
    } catch (err) {
      alert('Payment error: ' + err.message);
    }
  };

    const handleSubscribe = async () => {
      try {
        const res = await fetch('/api/create-subscription-session', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
        });
        if (!res.ok) {
          const text = await res.text();
          alert('Subscription error: ' + text);
          return;
        }
        const data = await res.json();
        if (data.url) {
          window.location.href = data.url;
        } else {
          alert('Could not start subscription session.');
        }
      } catch (err) {
        alert('Subscription error: ' + err.message);
      }
    };

  const handleLogout = async () => {
    await supabase.auth.signOut();
    setUser(null);
  };

  return (
    <div className="app">
      <header className="header">
        <h1 className="title">TutorMatch</h1>
        <p className="subtitle">Get Personalized Tutor Advice</p>
      </header>
      {!user ? (
        <AuthForm onLogin={() => {
          supabase.auth.getSession().then(({ data }) => {
            setUser(data?.session?.user || null);
          });
        }} />
      ) : (
        <>
          <button onClick={handlePay} style={{ margin: '1rem', padding: '0.75rem 1.5rem', fontSize: '1.1rem', background: '#635bff', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>
            Book a Session ($60)
          </button>
          <button onClick={handleSubscribe} style={{ margin: '1rem', padding: '0.75rem 1.5rem', fontSize: '1.1rem', background: '#00b386', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>
            Subscribe for $9.99/year
          </button>
          {!isPaid && (
            <div style={{ margin: '1.5rem 0 0.5rem 0', color: '#b00', fontWeight: 500, fontSize: '1.05rem', textAlign: 'center' }}>
              Unlock these features by subscribing
            </div>
          )}
          <NavTabs
            screen={screen}
            setScreen={setScreen}
            onLogout={handleLogout}
            adviceDisabled={!isPaid}
            findTutorDisabled={!isPaid}
          />
          <main className="container">
            {screen === 'advice' ? <GetAdvice disabled={!isPaid} /> : <FindTutor disabled={!isPaid} />}
          </main>
        </>
      )}
    </div>
  );
}


function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const { subscription } = supabase.auth.onAuthStateChange((event, session) => {
      setUser(session?.user || null);
    });
    supabase.auth.getSession().then(({ data }) => {
      setUser(data?.session?.user || null);
    });
    return () => {
      subscription?.unsubscribe && subscription.unsubscribe();
    };
  }, []);

  return (
    <Routes>
      <Route path="/" element={<Home user={user} setUser={setUser} />} />
      <Route path="/success" element={<Success />} />
      <Route path="/cancel" element={<Cancel />} />
    </Routes>
  );
}

export default App

				
			
  1. תשלום חד פעמי לניסיון : לחץ על האפשרות החד פעמית, השתמש בכרטיס ניסיון 4242 4242 4242 4242, השלם את התשלום
  2. מנוי ניסיון: לחצו על אפשרות המנוי, השתמשו באותו כרטיס ניסיון, השלימו את התשלום
  3. בדוק את הטרמינל שלך - ה-webhook אמור לרשום הודעות שונות עבור כל מצב
  4. בלוח המחוונים של Stripe, עבור אל:
    • תשלומים כדי לראות את התשלום החד-פעמי שלך
    • מנויים כדי לראות את המנוי החדש שלך (מקטע זה מופיע לאחר יצירת מנוי)
  5. עבור אל Supabase ובדוק את טבלת הפרופילים. למשתמש שלך ששילם צריך להיות מוגדר is_paid כ-true.
  6. בדוק באפליקציה שלך שלמשתמש בתשלום יש גישה לתכונות הלא פתוחות.
  7. התנתק והתחבר שוב. ודא שעדיין יש להם גישה לכל התכונות.
  8. התנתק והתחבר כמשתמש ללא תשלום. בדוק אם התכונות נעולות.

לאחר שתהיו מרוצים שהקוד שלכם פועל כהלכה עבור שני סוגי התשלומים, בצעו את השינויים לענף המאגר שלכם.

לאחר מכן תוכל לחזור ל-GitHub ולמזג את קוד עיבוד התשלומים שלך עם הענף הראשי. פעולה זו אמורה לפרוס את השינויים שלך ב-Vercel באופן מיידי.

הוספת מספר משתני סביבה חדשים בסודות של Codespace. עליך להוסיף אותם ב-Vercel כדי שימשיכו לפעול בסביבת הייצור.

  1. היכנס למרכז השליטה של פרויקט Vercel שלך.
  2. לחץ על 'הגדרות' → 'משתני סביבה'.
  3. הוסף את משתני הסביבה הבאים עם הערכים המתאימים:
    • STRIPE_SECRET_KEY
    • STRIPE_PRICE_ID_TUTOR (או השם שהענקת למזהה המחיר שלך)
    • STRIPE_PRICE_ID_SUBSCRIPTION (או שם מזהה המחיר שלך)
    • STRIPE_WEBHOOK_SECRET
    • כתובת_SUPABASE
    • SUPABASE_SERVICE_ROLE_KEY

אין צורך להוסיף את FRONTEND_URL ל-Vercel. המשתנה הסביבתי VERCEL_URL מוגדר באופן אוטומטי על ידי Vercel, והוספנו אותו לקוד של דפי ה-Success וה-Cancel שלנו (פעילות 2).

אם עדיין לא עשית זאת בשיעור על הפריסה, ודא שכתובת ה-URL של Vercel שלך נוספה לרשימת כתובות ה-URL המותרות להפניה מחדש ב-Supabase — ראה את השיעור על הפריסה לקבלת הוראות.

הִשׁתַקְפוּת

עיבוד תשלומים הוא חלק חשוב בהצלחת העסק שלך, לכן עליך לוודא שהוא פועל כהלכה. שקול את השאלות הבאות:

Sunset and reflection over lake
01

אסטרטגיית תמחור
עד כמה אסטרטגיית התמחור המקורית שתוכננה בתקציב שלך תורגמה ליישום של Stripe? האם היית צריך להתאים משהו - מחיר, מודל או מבנה תוכנית - כשראית איך זה עובד טכנית?
02

קוד לדוגמה
האם קוד הייחוס של Stripe עזר לך בהבנה וביישום? האם היית משתמש בתבנית "תחילה ללמוד, אחר כך לבנות" עבור אינטגרציות אחרות?
03

מודל עסקי
במבט לאחור על התקציב הפשוט שלך, האם תשלום חד פעמי, מנוי או שילוב הגיוני יותר עבור המיזם שלך?

מונחים מרכזיים

  • תשלום מתארח - דף תשלום המסופק על ידי Stripe שאליו האפליקציה שלך מפנה משתמשים, שם Stripe אוספת פרטי תשלום כך שהאפליקציה שלך לעולם לא תיגע בנתוני כרטיס.

  • סשן תשלום - אובייקט בצד השרת שאתה יוצר ב-Stripe שמגדיר מה לחייב, כמה ולאן להפנות את המשתמש לאחר התשלום.

  • Webhook - בקשת HTTP ש-Stripe שולחת ישירות לשרת שלך כאשר מתרחש אירוע תשלום, ומספקת אישור תשלום אמין ללא קשר לפעולות המשתמש בדפדפן שלו.

  • express.raw() - תוכנת ביניים אקספרס ששומרת על גוף הבקשה שלא שונה, הנדרש לאימות חתימת webhook שיפעל כראוי.

  • ארגז חול עסקי - סביבת Stripe שבה תשלומים מדומים באמצעות מספרי כרטיסי בדיקה כך שלא יחויב כסף אמיתי במהלך הפיתוח.