Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

אתר מכירת קורס – מדריך הפעלה

אתר מוכן להרצה שמאפשר למכור קורס וידאו יחיד: הרשמה, תשלום מאובטח דרך Invoice4U (Clearing API), וגישה לתוכן רק למי שנרשם ושילם בפועל. כולל גם עמוד ניהול (/admin) לעריכת שם הקורס, המחיר, התיאור והשיעורים - בלי לגעת בקוד.

מה בנוי כאן

  • Next.js 16 (App Router) + React 19 - קוד אחד גם לדפים וגם לשרת.
  • מסד נתונים: SQLite מובנה ב-Node.js (node:sqlite) - קובץ יחיד בתיקיית data/, בלי שום התקנה חיצונית.
  • הרשמה והתחברות: סיסמאות מוצפנות (bcrypt), session מאובטח ב-cookie שמאוחסן במסד הנתונים (לא JWT) - כך שתמיד אפשר "להתנתק" משתמש מסוים אם צריך.
  • תשלום: אינטגרציה עם ה-Clearing API של Invoice4U - פתיחת בקשת סליקה (iframe מאובטח), ובדיקת סטטוס פעילה בעמוד ההצלחה (Invoice4U לא שולח webhook לעסקאות רגילות, אז האתר שואל אותו ישירות מה קרה עם התשלום).
  • עמוד ניהול (/admin, מוגן בסיסמה אחת מסביבת השרת): עריכת כל תוכן הקורס והשיעורים.

הרצה מקומית (לבדיקות)

דרישה: Node.js גרסה 22.5 ומעלה (כדי ש-node:sqlite יעבוד).

cp .env.example .env
# ערכו את .env: לפחות SITE_URL ו-ADMIN_PASSWORD
npm install
npm run dev

האתר יעלה בכתובת http://localhost:3000. כנסו ל-/admin עם הסיסמה שהגדרתם ב-ADMIN_PASSWORD כדי לערוך את תוכן הקורס.

בלי מפתח API של Invoice4U (INVOICE4U_API_KEY) שלב התשלום בפועל יחזיר הודעת שגיאה ברורה ויפנה את המשתמש לעמוד "התשלום בוטל" - זה תקין ומכוון, ככה תוכלו לבדוק את כל שאר האתר לפני שמחברים תשלומים אמיתיים.

משתני סביבה (קובץ .env)

משתנה הסבר
SITE_URL כתובת האתר בפועל, בלי / בסוף. חשוב לעדכן כשעוברים מ-localhost לדומיין האמיתי.
ADMIN_PASSWORD הסיסמה לכניסה ל-/admin. בחרו סיסמה חזקה וייחודית - היא היחידה שמגנה על ניהול האתר.
DATA_DIR אופציונלי. נתיב לתיקייה שבה יישמר קובץ מסד הנתונים. כברירת מחדל זו תיקיית data/ בתוך הפרויקט. ברנדר (Render) ובדומיו - חובה להגדיר את זה לנתיב של דיסק קבוע (Persistent Disk), ראו הערה בהמשך.
INVOICE4U_API_KEY מפתח ה-API של Invoice4U (נוצר ב-private.invoice4u.co.il, ראו בהמשך).
INVOICE4U_CC_COMPANY קוד חברת הסליקה שמוגדרת בחשבון שלכם: Meshulam=7, UPay=6, YaadSarig=12, Cardcom=15.
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM אופציונלי - לשליחת מייל איפוס סיסמה. בלעדיהם, קישור האיפוס יודפס ללוג של השרת בלבד (ראו בהמשך).

הגדרת Invoice4U (Clearing API) - שלב חשוב

  1. היכנסו לחשבון שלכם ב-private.invoice4u.co.il.
  2. עברו ל-הגדרות (Settings) → הגדרות חשבון → לשונית API, ולחצו Generate כדי ליצור מפתח API (GUID).
  3. ודאו שחברת הסליקה המוגדרת בחשבון תואמת למה שהגדרתם ב-INVOICE4U_CC_COMPANY (Meshulam=7, UPay=6, YaadSarig=12, Cardcom=15).
  4. הכניסו את המפתח ל-.env בתור INVOICE4U_API_KEY.
  5. בצעו תשלום בדיקה מקצה לקצה: הרשמה → תשלום → מילוי פרטי כרטיס ב-iframe → חזרה לאתר → קבלת גישה לקורס. עקבו בלוגים של השרת (console.log/ console.error שמודפסים לטרמינל) שהבקשה נפתחה ושבדיקת הסטטוס בעמוד ההצלחה אכן זיהתה תשלום מוצלח.

פרטים שאומתו מול חשבון אמיתי (ולא מופיעים בתיעוד הציבורי)

התיעוד הציבורי של Invoice4U (invoice4uclearingapis.docs.apiary.io) לא מדויק ב-100%. האינטגרציה נבדקה בפועל מול חשבון Invoice4U אמיתי (לא sandbox - אין כזה מתועד ל-Clearing API), וכמה דברים התבררו רק מהבדיקה הזו:

  • כל תגובות ה-API עטופות במעטפת "d" (מוסכמת JSON קלאסית של ASMX/WCF) - השדות האמיתיים נמצאים תחת json.d.*.
  • ProcessApiRequestV2 מזדהה עם Invoice4UUserApiKey (כמו בתיעוד), אבל GetClearingLogById משתמש בפרמטרים token ו-clearingLogId (לא Invoice4UUserApiKey/ClearingLogId) - זה לא מופיע בתיעוד.
  • הפעולה GetClearingLogByParams (המתועדת) החזירה תקלת רשת (ECONNRESET) בכל ניסיון - כנראה כי PaymentId שם מתייחס למספר הפנימי של Invoice4U (מתחיל מ-"0" ומתעדכן רק אחרי חיוב מוצלח), לא ל-OrderIdClientUsage שלנו. לכן משתמשים במקום זאת ב-GetClearingLogById עם המזהה המספרי (I4UClearingLogId) שמוחזר כבר בתגובת ProcessApiRequestV2 ונשמר בעמודת clearing_log_id.
  • IsSuccess בתגובת GetClearingLogById הוא true גם לפני שהתשלום בפועל הושלם - הוא מציין רק שרשומת הסליקה נוצרה בהצלחה, לא שהחיוב אושר. הזיהוי בפועל של תשלום מוצלח (בקוד: interpretClearingLog ב-lib/invoice4u.js) מתבסס על PaymentId שהופך למספר אמיתי (לא "0"), או TransactionId/ ClearingConfirmationNumber שהופכים לא-ריקים.
  • בניגוד ל-Grow, ל-Invoice4U אין webhook לעסקאות רגילות (Type=1) - יש כזה רק ל-Standing Orders (הוראות קבע, StandingOrderCallBackUrl). לכן אימות התשלום נעשה בבדיקה פעילה (GetClearingLogById) שקוראת לשרתי Invoice4U בכל פעם שהעמוד בודק סטטוס - לא ב"האזנה" פסיבית לאירוע.

כל תגובה (גם כשעדיין לא ברור אם שולם) נשמרת בעמודת raw_log בטבלת payments, כדי שיהיה קל לבדוק מול תשלום אמיתי בלי צורך בגישה ללוגים של השרת.

⚠️ שימו לב לפני כסף אמיתי: כל בדיקה (גם ניסיון שלא הושלם) יוצרת לקוח וחשבונית אמיתיים בחשבון Invoice4U (IsAutoCreateCustomer/IsDocCreate), כי אין sandbox נפרד. מומלץ לבקש מ-Invoice4U/Cardcom כרטיס בדיקה ייעודי, או לחלופין לבצע עסקה אמיתית בסכום סמלי ולבצע לה Refund (Refund: true ב-API).

איפוס סיסמה (שכחתי סיסמה)

יש קישור "שכחתם סיסמה?" בעמוד ההתחברות. כשמשתמש מבקש איפוס, האתר שולח מייל עם קישור חד-פעמי שתקף לשעה. הקישור מאפשר לבחור סיסמה חדשה ומתחבר אוטומטית לאחר מכן (וגם מבטל סשנים פתוחים אחרים של אותו משתמש, כהגנה).

בלי הגדרת SMTP - האתר עדיין "עובד", אבל במקום לשלוח מייל, קישור האיפוס יודפס ללוג השרת (console.warn) עם הכתובת שביקשה אותו. זה שימושי לבדיקות, אבל לאתר חי כדאי להגדיר שליחת מייל אמיתית.

הדרך הקלה ביותר: Gmail עם App Password

  1. בחשבון ה-Gmail שתרצו לשלוח ממנו, הפעילו אימות דו-שלבי (חובה כדי ליצור App Password).
  2. ב-הגדרות אבטחה של Google צרו "App Password" חדש (לדוגמה בשם "course-site").
  3. הגדירו ב-.env:
    SMTP_HOST=smtp.gmail.com
    SMTP_PORT=587
    SMTP_USER=youraddress@gmail.com
    SMTP_PASS=<ה-App Password שקיבלתם, 16 תווים בלי רווחים>
    SMTP_FROM=youraddress@gmail.com
    

זה מתאים להיקף שליחה קטן (כמה התראות איפוס סיסמה ביום). אם בעתיד תרצו נפח גדול יותר או כתובת שולח מהדומיין שלכם - אפשר לעבור לכל ספק דואר טרנזקציוני (SendGrid, Mailgun, Resend וכו') ולהשתמש בפרטי ה-SMTP שהם נותנים, בלי לשנות קוד.

וידאו לשיעורים

בעמוד הניהול מדביקים קישור לסרטון - האתר תומך אוטומטית ב:

  • YouTube (קישור רגיל או youtu.be)
  • Vimeo
  • קובץ וידאו ישיר (מסתיים ב-.mp4/.webm)
  • כל קישור הטמעה אחר (כ-fallback גנרי)

המלצה לאבטחת התוכן: קישור YouTube "לא רשום" (unlisted) נוח, אבל מי שמקבל את הקישור הישיר יכול לצפות גם בלי להתחבר לאתר. אם חשוב לכם שהווידאו יהיה נגיש רק מתוך האתר שלכם, Vimeo מאפשר הגדרת "domain-level privacy" שמגבילה הטמעה רק לדומיין שלכם.

פריסה לאחסון עצמי (VPS)

  1. דרישה: שרת עם Node.js 22.5+. (אם הספק שלכם מתקין Node ישן יותר - צריך לשדרג, אחרת node:sqlite לא יעבוד.)
  2. העלו את כל התיקייה (חוץ מ-node_modules ו-.next) לשרת.
  3. בשרת:
    npm install
    npm run build
  4. הריצו עם מנהל תהליכים כדי שהאתר יישאר פעיל וגם יקום מחדש לאחר קריסה או הפעלה מחדש של השרת, למשל עם pm2:
    npm install -g pm2
    pm2 start npm --name course-site -- start
    pm2 save
    pm2 startup
  5. הציבו Nginx (או שרת web אחר) כ-reverse proxy לפורט 3000, עם תעודת SSL (למשל באמצעות certbot) - כדי שהאתר ירוץ על https:// עם הדומיין שלכם. תשלומים אמיתיים חייבים HTTPS.
  6. עדכנו את SITE_URL ב-.env לכתובת הדומיין האמיתית - כתובת ה-ReturnUrl שנשלחת ל-Invoice4U נבנית אוטומטית ממנה, כך שאין צורך בעדכון נוסף.

חשוב: קובץ מסד הנתונים נשמר בתיקיית data/ על דיסק השרת. כדי שהמידע (משתמשים, תשלומים, שיעורים) לא יימחק בכל פריסה מחדש - ודאו שהתיקייה הזו נשמרת בין פריסות (לא נמחקת/משוכתבת), וגבו אותה מדי פעם (cp -r data data-backup-$(date +%F)). הפרויקט לא מתאים כמו שהוא לאחסון serverless טהור (כמו Vercel) ללא דיסק קבוע, כי שם הקובץ יימחק בכל deploy.

פריסה ל-Render

Render הוא דוגמה נפוצה לפלטפורמה כזו, וטעון תשומת לב מיוחדת כי שטח האחסון הרגיל של שירות Web Service ב-Render הוא ארעי (ephemeral) - כל מה שלא על דיסק קבוע נמחק בכל פריסה מחדש (ולפעמים גם בכל הפעלה מחדש של השירות).

  1. בעמוד השירות ב-Render: Disks → הוסיפו דיסק קבוע (Persistent Disk), ובחרו לו נתיב חיבור (Mount Path), למשל /var/data.
  2. בעמוד Environment של השירות, הוסיפו משתנה סביבה:
    DATA_DIR=/var/data
    
    (כך קובץ ה-SQLite יישמר על הדיסק הקבוע, ולא בתיקיית הפרויקט שנמחקת בכל פריסה.)
  3. הגדירו גם את שאר משתני הסביבה (SITE_URL, ADMIN_PASSWORD, INVOICE4U_API_KEY, INVOICE4U_CC_COMPANY) באותו עמוד.
  4. Build command: npm install && npm run build. Start command: npm start.

אם תפרסו בלי דיסק קבוע ובלי DATA_DIR - האתר יעבוד, אבל כל משתמש, תשלום ושינוי שעשיתם ב-/admin יימחקו בפריסה הבאה.

מבנה הפרויקט (למי שרוצה להתאים)

app/
  page.js              עמוד הבית / מכירה
  register, login      הרשמה והתחברות
  forgot-password, reset-password   איפוס סיסמה
  course/page.js        עמוד הקורס המוגן (paywall + נגן שיעורים)
  payment/success, cancel   מסכי חזרה מהתשלום
  admin/page.js          עמוד ניהול
  actions/             כל הלוגיקה בצד שרת (Server Actions)
lib/
  db.js                חיבור למסד הנתונים + יצירת הטבלאות
  auth.js, admin-auth.js   הרשמה/התחברות/הרשאות
  invoice4u.js         אינטגרציית הסליקה (Invoice4U Clearing API)
  mailer.js            שליחת מייל איפוס סיסמה
  settings.js, video.js   עזרים
  • עיצוב: כל הצבעים, הגופנים והרווחים מוגדרים כמשתני CSS בראש app/globals.css - שינוי שם משפיע על כל האתר בבת אחת.
  • שאלות נפוצות: כרגע כתובות ישירות בקוד (app/page.js) - אם תרצו לערוך אותן, צריך לערוך את הקובץ הזה (לא דרך /admin).
  • כל תוכן אחר (שם הקורס, מחיר, תיאור, שיעורים) - דרך /admin.

הערות אבטחה

  • הסשנים (גם של משתמשים וגם של מנהל) הם opaque tokens שמאוחסנים במסד הנתונים, ב-cookie מסוג httpOnly + sameSite=lax - לא JWT, כדי שתמיד אפשר לבטל סשן ספציפי.
  • בדיקת התשלום לא מסתפקת ב-redirect שהמשתמש חוזר ממנו - היא שואלת ישירות את שרתי Invoice4U (GetClearingLogById) לפני שמסמנת משתמש כ"שילם".
  • קישור איפוס הסיסמה הוא טוקן חד-פעמי שתקף לשעה; במסד הנתונים נשמר רק hash שלו (לא הטוקן עצמו), והעמוד "שכחתי סיסמה" מגיב באותו אופן בין אם האימייל קיים במערכת ובין אם לא - כדי שלא יהיה אפשר להשתמש בו לבדוק אילו כתובות רשומות באתר.
  • לא הוספנו הגבלת קצב (rate limiting) על הרשמה/התחברות/איפוס סיסמה. באתר עם תנועה גבוהה כדאי להוסיף הגנה כזו (לדוגמה דרך Nginx או שירות כמו Cloudflare) כדי למנוע ניסיונות brute-force או spam של בקשות איפוס.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages