תוכניות מנוי

תוכנית Xuda היא רשומה אחת עם חמישה עשר שדות, והשרת קורא ארבעה מהם.

שבעת שלבי המנוי הם רשומות ב-PLAN_OBJ: config.json:1820 free, :1850 pro, :1880 team, :1910 agency, :1940 enterprise_t1, :1969 enterprise_t2, :1998 enterprise_t3. כל אחת מהן נושאת מפת features זהה של חמישה עשר מפתחות, חמישה מספריים ועשרה מחרוזות, וקבוצת המפתחות היא אותם חמישה עשר בכל השבע, כך ששלב שאין לו יכולת מסוימת מאיית אותה N/A במקום להשמיט את המפתח. ארבעה מתוך החמישה עשר האלה נקראים על ידי קוד רץ: projects ו-team_members על ידי validate_user_plan (account_module:3932 ו-:3947), drive ו-ai_credits על ידי get_effective_entitlements (:666-701). האחד עשר הנותרים, ובכללם כל שמונה מחרוזות היכולת Available ו-N/A, לא נקראים על ידי דבר. מה שמכריע אם מתודה רצה הוא רישום נפרד לגמרי: 30 מתוך 1010 הרשומות ב-cpi_methods שב-config.json נושאות min_plan, ו-http_module משווה מולו דירוגי תוכנית בכל בקשה (:542-547, נאכף בשורה :868). המנוי הוא גם רק אחת מתוך שש עשרה קטגוריות תוכנית באותו אובייקט, 7 רשומות מתוך 72, כך שכל שאר המחיר הוא אוסף סולמות שנערמים לצידו ולא בתוכו.

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

מפתחות תכונה בכל רשומת מנוי, אותה קבוצה בדיוק בכל שבע הרשומות

4

מתוך החמישה עשר האלה, שקוד רץ כלשהו קורא

30

מתוך 1010 רשומות cpi_methods שנושאות min_plan, השער שבאמת מסרב

16

קטגוריות תוכנית ב-PLAN_OBJ, והמנוי הוא אחת מהן

שבעת השלבים

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

אלה חמשת המפתחות המספריים ממפת features של כל רשומת מנוי, מועתקים כמות שהם בלי עיצוב מחדש. שדה בשם plan_category מפצל את השבעה ל-4 standard ו-3 enterprise, וכל רשומה נושאת גם title בן מילה אחת: Try, Deploy, Colab, Sale, Sync, Host, Own. שדה note מוגדר בשתי רשומות בלבד, ב-pro הוא Best Value וב-team הוא Most Popular, בעוד ש-agency נושא מחרוזת ריקה. Instances מודפס כ-0 בשלושת השלבים התחתונים ולא מושאר ריק, מפני ש-agency עם 500 הוא הראשון שאינו אפס וזו כל הנקודה של אותה שורה. רק enterprise_t3 משתמש במחרוזת unlimited, והוא משתמש בה בכל חמשת התאים; planFeatures.js:5-15 מנסח את כלל הקריאה שהפלטפורמה מחייבת בו את עצמה, שמספר הוא תמיד תקרה אמיתית ורק המחרוזת הזו ממש פירושה בלי הגבלה, ולכן שום מספר גדול בטבלה הזו לא מצויר מחדש כמילה. המגבלה הכנה שייכת לאותה פסקה: ב-PLAN_OBJ אין שדה תקופת חיוב באף אחת משבע הרשומות, כך שהקריאה החודשית היא של רכיב התצוגה, pricing.vue:129 ו-:359, ולא של הרשומה.

פרויקטים
חברי צוות
מופעים
GB ב-Drive
קרדיטים ל-AI
Free, $05101530
Pro, $49502050600
Team, $14925010002501600
Agency, $3991000100050010003500
Enterprise T1, $1999500050002000500010000
Enterprise T2, $4999100001000050001000050000
Enterprise T3, callunlimitedunlimitedunlimitedunlimitedunlimited
מה קורא את הרשומה

חמישה עשר מפתחות נכנסים. ארבעה יוצאים.

מפת features אינה אובייקט הגדרות שהפלטפורמה מתייעצת איתו. אלה חמישה עשר מפתחות שלארבעה מהם יש צרכן, וארבעת אלה לא מתנהגים אותו דבר: שניים מהם מסרבים לקריאה עם קוד, ושניים רק מעלים מספר שמשהו אחר מוציא או מחייב עליו. לאחד עשר הנותרים אין קורא בשום מקום ב-cpi, ב-dashboard/src או ב-shared.

המגבלה הכנה על שלוש השורות שנגמרות בכלום. היעדר תוצאה ב-grep על פני cpi, dashboard/src ו-shared הוא ראיה חזקה אבל אינו הוכחה מול מפתח שמורכב בזמן ריצה. לא נמצאה שום גישה דינמית למפתח ב-features באף אחד משלושת העצים, וזה המרב שהשיטה הזו יכולה לבסס.
השניים שנושכים

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

validate_user_plan (account_module:3891) אינה קוראת ל-get_effective_entitlements ואינה חולקת איתה קוד. היא סופרת את אפליקציות ה-master של החשבון מתוך view של CouchDB, קוראת את PLAN_OBJ[membership_plan].features ישירות מהרשומה ומשווה. שני הענפים נכנסים לפעולה רק כאשר app_obj.app_type הוא master, ופרויקט המערכת של החשבון עצמו פטור בזכות !app_obj.is_account_project, כך שחשבון שיושב על התקרה שלו עדיין יכול להתחבר במקום להינעל מחוץ לדבר שהיה מאפשר לו לשדרג.

  • פרויקטים: ההשוואה היא ret_obj.master >= plan.features.projects בשורה :3932, והיא נקראת מ-app_module:1282 ומ-:1301, שהוא הרגע שבו פרויקט נוצר.
  • חברי צוות: ספירת ה-view >= plan.features.team_members בשורה :3947, נקראת מ-team_module:265, :675 ו-:1280, כך שכל מסלולי השיתוף עוברים באותה שורה.
  • קמפייני פרסום פטורים לחלוטין בשורה :3894, וכל app_type שאינו master עוקף את שתי התקרות כליל. אילו סוגי אפליקציה מגיעים למסלול הזה בפועל לא נבדק, וזה לא מאומת.
  • שניים ממסלולי שיתוף הצוות לא מגיעים לסירוב נקי בכלל: team_module:271 ו-:1286 מבצעים כל אחד return ret; עם הערה שכבר יושבת במקור ואומרת ש-ret אינו מוגדר, כך שתקרת המושבים זורקת ReferenceError במקום להחזיר את אובייקט ה-9-. מה שמשתמש רואה בסוף בשני המסלולים האלה אינו מאומת.
מה קריאת יצירה עוברת
  1. ספירת אפליקציות ה-master
    מתוך view של CouchDB, כשפרויקט המערכת של החשבון עצמו לא נכלל
  2. קריאת רשומת התוכנית
    PLAN_OBJ[membership_plan].features, ישירות מהקונפיג, בלי עוזר הרשאות
  3. השוואה, גדול או שווה
    ret_obj.master >= plan.features.projects, וקריאת היצירה נעצרת שם
  4. ספירת שיתופי הצוות הפעילים
    ה-view השני, ההשוואה השנייה, קוד הסירוב השני
מה הסירוב אומר

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

שער היכולת מרכיב את ההודעה שלו משם התצוגה של המתודה ומשמות התצוגה שב-PLAN_OBJ (http_module:555-562), כך שהוא נוקב בשם התכונה, בשם התוכנית ובשם השדרוג. שני סירובי המכסה לא עושים דבר מזה. הם תבניות מחרוזת שמשלבות את מפתח התוכנית הפנימי במקום לפענח אותו, הם שגויים דקדוקית, והם לא מצביעים על שום מסלול שדרוג. app_module:1287 ו-:1306 מחזירים את האובייקט לקורא ללא שינוי, כך ששום דבר בין הבדיקה לבין הלקוח לא כותב אותם מחדש. שורת ה-8- למטה מודפסת עם קבוצת ערכים מוחשית אחת, חשבון agency על תקרת 1000 הפרויקטים שלו; בכל שאר המקומות הסוגריים המשולשים מייצגים שדה שמתמלא בזמן הקריאה, וזו הדרך הכנה היחידה להדפיס מחרוזת שהערכים שלה לא קיימים עד לקריאה עצמה.

account_module:3933 . תקרת הפרויקטים
{ code: -8, data: "Number of projects (agency) exceeds to the 1000 plan limits" }
account_module:3948 . תקרת המושבים
{ code: -9, data: "Number of team shares (<count>) exceeds to the <cap> plan limits" }
http_module:562 . אותו מוצר, קול אחר
{ code: -1, data:
"<Feature> isn't available on the <Plan> plan. Upgrade to <Plan> or higher to use it." }
מחרוזות המכסה משלבות את מפתח התוכנית, ומחרוזת השער מפענחת את שם התצוגה
אם לוח הבקרה כותב מחדש את מחרוזות ה-8- וה-9- לפני התצוגה אינו מאומת. מה שוודאי הוא שהשרת מחלק שלושה מרשמי לשון שונים לאותו אירוע עצמו, לקוח שאומרים לו לקנות משהו.
המילה unlimited

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

Enterprise T3 הוא השלב היחיד שחמש המכסות המספריות שלו הן המחרוזת unlimited ולא מספר, ושתי פונקציות שונות פוגשות את המחרוזת הזו בשני מסלולי קוד שונים. dashboard/src/lib/planFeatures.js:5-15 מנסח את החוזה שהפלטפורמה מתכוונת לקיים: מספר הוא תמיד תקרה אמיתית, ורק המילה unlimited ממש פירושה בלי הגבלה. get_effective_entitlements שומרת על החוזה הזה בכוונה. validate_user_plan שומרת עליו במקרה. שני המסלולים למטה עונים על אותן ארבע שאלות באותו סדר ומגיעים לאותו מקום, וזו בדיוק הסיבה שההבדל ראוי לשרטוט ולא רק לתיאור.

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

מה שהדף הזה לא יכול לומר לך על המספרים שלמעלה.

  • כל מה שלמעלה נקרא ממקור המאגר בענף main כשעבודה לא מקומיטת יושבת בעץ, ושום דבר כאן לא הורץ מול חשבון חי. במקום שבו המאגר ומכונה רצה חלוקים, הדף הזה טועה והמכונה צודקת.
  • ב-PLAN_OBJ אין שדה תקופת חיוב באף אחת משבע הרשומות. הקריאה החודשית מגיעה מרכיב התצוגה, website-nuxt/pages/pricing.vue:129 ו-:359, ולא מהרשומה, כך שכל מחיר שלמעלה הוא חודשי רק מפני שהדף שמציג אותו אומר זאת.
  • שום תעריף שנתי לא קיים בשום מקום ב-PLAN_OBJ. pricing.vue:476 אומר שהתשלום השנתי מוזל ושצריך לפנות למכירות, ולכן שום נתון שנתי ושום אחוז שנתי לא מופיעים בשום מקום בדף הזה. הנחה שאיננו יכולים לקרוא מתוך רשומה היא הנחה שלא נדפיס על כרטיס.
  • האם שבע מחרוזות ה-price_id מתפענחות למחירי Stripe חיים בסכומים האלה לא נבדק. נקראו רק המחרוזות שבקונפיג. ה-price_id גם שונה מסביבה לסביבה, ולכן שום דף לא אמור לחשוף אחד כזה.
  • רשומות הזיכרון מציינות ש-config.json מתבדר מקונפיג המאסטר החי, שלא נקרא כאן. כל נתון שלמעלה מוסמך מול המאגר בלבד, והעותקים של המאגר ושל סביבת הפיתוח מסכימים על כל שבע הרשומות פרט למזהי Stripe.
  • האכיפה מתחת לקו הזה נבדקה רק עד לגבול ה-HTTP. האם deploy_module בודקת שוב זכאות למרכז נתונים או למאזן עומסים בהמשך המסלול עבור חשבון Pro אינו מאומת, ולכן יש לקרוא את הבלוק הבא כמה שהדלת הקדמית מכריעה.
השער

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

מפת features שברשומת מנוי היא תיאור של תוכנית, לא מנגנון. המנגנון הוא רישום שני לגמרי, cpi_methods שב-config.json, שנקרא על ידי http_module בכל קריאה נכנסת. הוא אף פעם לא מסתכל על מפת features, הוא מסתכל על שדה אופציונלי אחד במתודה הנקראת, והוא מכריע בהשוואת דירוג יחידה.

אותה השוואת דירוג משוכפלת בצד הלקוח ב-shared/utils.js:1020-1030 כדי שלוח הבקרה יוכל לעמעם פקד עוד לפני שהבקשה יוצאת, וזו שבשרת היא זו שמכריעה.
טענות מול שערים

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

עשרה מתוך חמישה עשר מפתחות התכונה הם מחרוזות, ושמונה מתוך העשרה האלה הם בינאריים: N/A עד ששלב כלשהו מדליק אותם, ומשם דלוקים בכל שלב שמעליו, כשהסולם מונוטוני לחלוטין כך ששום דבר שדלוק בדירוג N אינו כבוי בדירוג N+1. מכיוון ש-agency מדליק שניים מהם ו-enterprise_t1 מדליק שניים, שמונת השערים נוחתים על שישה שלבים נבדלים ולא על שמונה. אף אחת משמונה המחרוזות אינה נקראת על ידי שום קוד. מה שבא עכשיו הוא מה כל אחת מהן באמת שווה.

מה כרטיס התוכנית טוען
מה באמת אוכף את זה
פסיקה
deployN/A ב-Free, Available מ-Pro ומעלהmin_plan pro on deploy_spa, deploy_mpa, deploy_pwa, deploy_embed, deploy_api, deploy_cron, deploy_emitter, deploy_preview, deploy_user_group and deploy_anotherתואם, פרט ל-deploy_mini_app שנושא min_plan free
marketplaceN/A עד Agencymin_plan agency on deploy_marketplace, deploy_instance, create_marketplace_listing, update_marketplace_listing and update_marketplace_listing_statתואם
upload pluginN/A עד Agencymin_plan agency on upload_pluginתואם
datacenter syncN/A עד Enterprise T1, $1,999min_plan pro על deploy_datacenter, שהוא $49הכרטיס מחמיר יותר מהקוד, והקוד הוא מה שרץ
load balancerN/A עד Enterprise T1, $1,999min_plan pro על deploy_load_balancer, שהוא $49הכרטיס מחמיר יותר מהקוד, והקוד הוא מה שרץ
outsourced serverN/A עד Enterprise T2כלום. אפס תוצאות על פני cpi, dashboard/src ו-shared בכל איות שהואטענה, לא שער
xuda sdkN/A עד Enterprise T3כלום, לפי אותו חיפוש ממצהטענה, לא שער
ai priorityN/A עד Teamכלום, לפי אותו חיפוש ממצהטענה, לא שער
הרישום

שלושים מתודות מתוך 1010 נושאות רצפת תוכנית, ואף אחת מהן אינה מעל Agency.

רישום השערים הוא cpi_methods שב-config.json: 1010 רשומות, 30 מהן נושאות min_plan ואחת נושאת min_support_plan. הרצפות הן pro על 19, agency על 6, team על 4 ו-free על אחת. שום מתודה בשום מקום לא נושאת min_plan מעל agency, ולכן שלושת שלבי ה-enterprise לא פותחים דבר בשער הזה ומה שהם משנים הוא חמשת המספרים. רצפה אחת בקונפיג פשוט שגויה: set_credit_rules מכילה min_plan pro בזמן ש-account_module:9339 מסרבת לכל דבר מתחת ל-Team במילים Credit management is available on the Team plan. הקוד הוא השער המחמיר יותר.

  • Pro, 19 מתודות. עשר מהן הן משפחת ה-deploy: deploy_spa, deploy_mpa, deploy_pwa, deploy_embed, deploy_api, deploy_cron, deploy_emitter, deploy_preview, deploy_user_group ו-deploy_another. תשע האחרות הן deploy_datacenter, deploy_load_balancer, app_visibility_off, set_login_screen, generate_login_screen, change_under_attack_mode, enable_app_branding, create_stripe_account_link ו-set_credit_rules.
  • Team, 4 מתודות: add_developer_member, add_admin_member, add_login_sponsor ו-list_sponsored_users. כלומר האם מותר לך להזמין מפתח נקבע לפי הדירוג, וכמה מותר לך להזמין נקבע לפי המספר team_members. שני מנגנונים על מסך אחד.
  • Agency, 6 מתודות: upload_plugin, deploy_marketplace, deploy_instance, create_marketplace_listing, update_marketplace_listing ו-update_marketplace_listing_stat.
  • Free, מתודה אחת: deploy_mini_app. רצפה שמכניסה את כולם, והסיבה לכך שמפת features שקוראת ל-deploy בשם N/A ב-Free אינה כל הסיפור.
ארבע רצפות
  1. free
    מתודה אחת, deploy_mini_app, חסומה ברצפה שכולם כבר עומדים בה
  2. pro, דירוג 1
    19 מתודות, משפחת ה-deploy ועוד תשעה פקדי אפליקציה וחשבון
  3. team, דירוג 2
    4 מתודות, כולן עוסקות בהוספת אדם לחשבון
  4. agency, דירוג 3
    6 מתודות, שוק ופרסום תוספים, והרצפה הגבוהה ביותר בקובץ
ערימה

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

ב-PLAN_OBJ יש 72 רשומות תוכנית על פני 16 קטגוריות, והמנוי הוא 7 מהן. פונקציה אחת עונה למה חשבון באמת זכאי, והיא קוראת שלושה שדות תוכנית מתוך השישה עשר: membership_plan, ai_workspace_plan ו-drive_plan. כל השאר באותו אובייקט חוסם את המודול שלו על הציר שלו ולעולם לא נוגע בקרדיטים או באחסון שלך.

הפונקציה היא account_module:666-701, 36 שורות. חוסר הסימטריה מכוון וההערה בשורות :660-664 אומרת למה: מניעת נפח שיש לו שורה משלו בחשבונית פירושה למכור כלום. האם התשלום עוצר חשבון עם מנוי free מלרכוש דרגת workspace בתשלום לא נבדק. לא מאומת.
שישה עשר סולמות

שישה עשר הסולמות, השלבים שלהם והתקרות שלהם.

ב-PLAN_OBJ יש 72 רשומות תוכנית על פני שש עשרה הקטגוריות האלה, והמנוי הוא רק 7 מהן. חמש עשרה האחרות מחזיקות ביניהן 65 שלבים, ולכל אחת מחמש עשרה יש שלב ב-$0, כך שכל סולם הוא בחירה ולא קיר שנתקלים בו. המחירים למטה הם שדה price שברשומות, מחירון חודשי בדולרים בקונפיג שבמאגר. העמודה השלישית היא המקום שבו נמצא הטיעון: בדיוק שתיים מתוך החמש עשרה משנות כמה שווה ההקצאה של המנוי שלך, מפני ש-get_effective_entitlements קוראת רק את membership_plan, ai_workspace_plan ו-drive_plan ומחזירה רק ai_credits ו-drive_gb. שלוש עשרה האחרות חוסמות את המודול שלהן, את המוצרים שלהן, תיבות דואר, כרטיסי תמיכה ועמלות, ולעולם לא נוגעות בקרדיטים או באחסון שלך. Xucode הוא המוצר היחיד שנחסם לפי דירוג המנוי ולא לפי סולם משלו: xucode_entitlement (deploy_module/xucode.mjs:32-66) משווה את דירוג החשבון מול config.json:414 שבו min_membership pro, ומזהה מנוי שאינו מוכר נכשל סגור, כלומר בלי מכונה ולא במכונה חינם.

שלבים
מחיר חודשי עליון
משנה את הקצאת המנוי שלך
membership7$4999, והשלב העליון הוא המילה callזו ההקצאה עצמה
ai_workspace8‏230 ‏$כן, קרדיטים ואחסון, במנוי בתשלום בלבד
drive4‏39 ‏$כן, אחסון, בכל דרגה כולל free
profile_phone6‏39 ‏$לא
support4‏4,500 ‏$לא
trust_center4‏299 ‏$לא
commerce4‏149 ‏$לא
shipping4‏149 ‏$לא
tickets4‏149 ‏$לא
marketplace4‏149 ‏$לא
finance4‏79 ‏$לא
bot_protection4‏59 ‏$לא
xucode3‏59 ‏$לא, וזה הסולם היחיד עם רצפת מנוי
email4‏49 ‏$לא
auto_response4‏49 ‏$לא
static_website4‏39 ‏$לא
מגבלות כנות, ציר שני

חמישה דברים על חמש עשרה הסולמות האחרים שהדף הזה לא ידפיס.

  • שני שלבים של ai_workspace חלוקים בין config.json לבין config_dev.json בשני סדרי גודל, והם הפרשי המחיר היחידים בכל 72 הרשומות, ולכן שום נתון עבור אחד מהם לא מופיע בשום מקום בדף הזה.
  • כל שלב בתשלום של email, tickets, trust_center ו-auto_response לא נושא price_id בכלל, ושלושה מתוך חמשת שלבי הטלפון בתשלום גם לא. stripe_module:251-258 מתייחס ל-price_id חסר ככסף שאינו שורת מנוי, והוא נוקב בטלפון כמחויב לפי מספר ובדואר כמחויב לפי תיבה. שום חיוב כזה לפי משאב לא מצוין עבור tickets, trust_center או auto_response, ולכן האם שלושת אלה ניתנים לחיוב היום אינו מאומת.
  • config.json:414 מגדיר את xucode.enabled כ-false לצד min_membership pro, כך של-Xucode יש שלושה שלבים מתומחרים, רצפת Pro ואולי אי אפשר לרכוש אותו בכלל כרגע.
  • הטבת החברים היא החלפת מחיר אחת בקטגוריה אחת. כל רשומת ai_workspace בתשלום נושאת member_price_id, ולשום קטגוריה אחרת ולשום שלב מנוי אין כזה. הערת המקור ב-stripe_module:211-223 מתארת אותה כ-10 אחוז הנחה, שנשמרת כמחיר של 90 אחוז על אותו מוצר Stripe. הסכומים חיים ב-Stripe ולא במאגר, ולכן שום סכום דולרי לחברים לא מופיע כאן.
  • שלבי תוספת רבים נושאים price_id זמני, ו-stripe_module:226-300 מטביע כעת מחיר Stripe אמיתי משדה price של רשומת הקטלוג עצמה במכירה הראשונה, עם מפתח חיפוש דטרמיניסטי כך שאתחול מחדש או אזור אחר ימצאו את אותו מחיר במקום להטביע שני. זו הסיבה שמספר הקונפיג הוא המספר שמחויב עבור הסולמות האלה. נקרא מהמקור, מעולם לא הורץ מול Stripe חי.
שדרוג

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

כל חשבון מתחיל בפריט המנוי free, ולכן רכישת דרגה היא תמחור מחדש של אותו פריט מנוי אחד ולא מנוי חדש, ו-stripe_update_plan מכריעה את הכיוון בהשוואת דירוג בתוך הקטגוריה (:1770-1787). מה שבא עכשיו אינו שינוי תוכנית שאחר כך מחייב אותך. זה חיוב שרק אחרי שהוא עובר הופך לשינוי תוכנית.

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

המספר שעל הכפתור הוא המספר שמחויב.

stripe_get_proration מחזירה מעטפת מנורמלת ולא חשבונית Stripe גולמית, ובה direction, plan_name, currency, renewal_amount, renewal_date, amount_due_now ו-proration_lines מפורטות (:446-500), וסיכום התשלום מציג בדיוק את זה ולא יותר. הערת הקוד מתעדת את הבאג שזה תיקן: הגרסה הישנה הציגה את כל החשבונית הבאה בזמן שהיא חייבה רק את ההפרש.

סיכום התשלום . כיוון: שדרוג
<Plan> prorated for the rest of this cycle
Amount due today $<amount_due_now>
Then $<renewal_amount>/month from <renewal_date>
[ Pay $<amount_due_now> ]
התצוגה המקדימה מחושבת בלי קוד הקידום בכוונה, כך שהנתון הזה שווה למה שמחויב
סיכום התשלום . כיוון: שנמוך
Due today $0.00
[ Schedule downgrade ]
ואחר כך, במסך החיוב:
Scheduled . Downgrades to <Plan> on <date> [ Cancel ]
a non numeric price renders Contact us for <plan> pricing instead of a number
שנמוך כותב לוח זמנים דו שלבי ב-Stripe: שלב 0 הוא בדיוק מערך הפריטים שאתה מחזיק היום ורץ עד סוף התקופה הטבעית, שלב 1 אותו מערך כשרק אותו פריט אחד מוחלף, לאיטרציה אחת, ואז end_behavior release מחזיר את המנוי לחידוש רגיל (:588-596). שום דבר לא מחויב עכשיו, התוכנית בתשלום רצה עד סוף התקופה, ו-stripe_cancel_scheduled_change משחררת את לוח הזמנים בכל רגע לפני שהוא נוחת (:775-802). שום מתודה בשום מקום ב-stripe_module לא מבטלת מנוי: יציאה מדרגה בתשלום היא שנמוך ל-free, מפני שמנוי free, support starter ו-workspace free הם שורות $0 אמיתיות באותו מנוי עצמו (:2239-2243). הביטול שלמעלה הוא מהסוג האחר, שחרור שינוי מתוזמן ולא סיום התוכנית.
כשכרטיס נכשל

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

שני דגלים על מסמך החשבון נושאים את כל העניין, account_billing_hold_status ו-account_suspension_status. שניהם נקבעים על ידי ה-webhook של invoice.payment_failed ושניהם מנוקים על ידי webhook אחר ולא על ידי משהו שנציג תמיכה עושה ידנית. המרחק בין הראשון לשני הוא שלושה ניסיונות נוספים על אותה חשבונית, ורק השני מפסיק להגיש את האפליקציות שלך.

  1. 01

    כישלון ראשון: עיכוב חיוב

    account_billing_hold_status נקבע ל-1 עם חותמת זמן ורשומת יומן בחשבון (stripe_module:1945-1949), ואותו מטפל גם שולח דואר ללקוח. לוח הבקרה מציג באנר שמפרט את החשבוניות שלא שולמו ואת הסכום הכולל לתשלום עם כפתור תשלום הכל, ואומר שחלק מפעולות החשבון חסומות, וזה הניסוח הכן מפני שהרשימה המלאה אינה מרוכזת במקום אחד. השוק מסרב לרכישה מכל וכל כל עוד העיכוב פעיל (marketplace_module:1252-1254).

  2. 02

    ניסיון רביעי: השעיה

    כאשר webhook_data.attempt_count מגיע ל-4 אותו מטפל קובע גם את account_suspension_status (:1953-1958). זה כבר לא קוסמטי: router_module:5697 זורק Account suspended ומפסיק להגיש את אפליקציות החשבון. לוח הניסיונות החוזרים שמאחורי attempt_count הוא הגדרה בלוח הבקרה של Stripe ואינו במאגר, ולכן אי אפשר לקבוע מהמקור כמה זמן לוח שנה עובר עד ההשעיה. לא מאומת.

  3. 03

    התשלום עובר: שניהם משוחררים

    customer.subscription.updated מנקה את שני הדגלים ברגע ששום מנוי אצל אותו לקוח אינו במצב לא פעיל (:1806-1822). שני פטורים לעולם לא מגיעים לכל זה: החשבונית החד פעמית של שדרוג היחסיות מתויגת כך ששדרוג שנדחה משאיר את התוכנית הנוכחית תקינה, ורשימת היתר של בעלים ומשתמשי על לעולם לא מעוכבת ולא מושעית אוטומטית.

זכאות

למה התכונה הזו לא בתוכנית שלי?

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

לאן ממשיכים

הדף הזה מסביר את הצורה. שלושת אלה עונים על המחיר.

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

סיור מודרך

מסכי החיוב, בתנועה.

זו ההקלטה שמסמך תוכניות המנוי הקודם כבר נשא בשדה ה-youtube שלו, ששמורה כאן ולא הושמטה. דף שמורכב ממפתחות קונפיג, השוואות דירוג ומחרוזות סירוב הוא דרך גרועה לצפות במסך חיוב בתנועה, והסיור המודרך הוא החלק מהמסמך הישן ששום דבר בכתיבה מחדש הזו לא מחליף. שני כללים לצפייה. במקום שבו הסיור והדף הזה חלוקים על נתון, הדף הוא זה שנבנה מהמקור: שבע רשומות המנוי ב-PLAN_OBJ, שלושים רצפות המתודות ב-cpi_methods, ומסלולי האכיפה ב-account_module, http_module ו-stripe_module. ובמקום שבו הסיור מראה כרטיס תוכנית שמפרט יכולת, החזיקו במה שכל הדף למעלה עסק בו. הכרטיס הוא אוסף מחרוזות קונפיג ששום קוד לא קורא, השער שבאמת רץ הוא השוואת דירוג מול רצפת מתודה, שתיים משמונה מחרוזות היכולת חסומות נמוך יותר מכפי שהכרטיס טוען ושלוש לא חסומות על ידי שום דבר שנמצא ב-cpi, בלוח הבקרה או ב-shared. סיור במסכים לא יכול להראות לכם דבר מזה, מפני שהקלטת מסך יכולה להראות תמיד רק את הכרטיס. הוא יושב אחרי המנגנון ולא במקומו מהסיבה הזו, וכדאי לצפות בו בשביל צורת המסלול, איפה שורות התוכנית יושבות, איך שינוי מתוזמן נראה כשהוא ממתין, ומה התצוגה המקדימה של התשלום שמה לפניכם לפני שהיא מחייבת משהו.

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

פתחו את מסך החיוב וקראו את הרשומה שלכם.

המנוי שלכם הוא מחרוזת אחת במסמך החשבון, membership_plan, שמחזיקה את המפתח של אחת משבע רשומות, וכל מה שלמעלה הוא מה שהמחרוזת הזו מושווית מולו. שתי השוואות יכולות לסרב לכם מכל וכל, ושתיהן בדיקות מספריות פשוטות על מפת features: מספר הפרויקטים שלכם מול features.projects ושיתופי הצוות הפעילים שלכם מול features.team_members, ב-account_module:3932 וב-:3947. שתיהן מדפיסות משפט שנוקב בתקרה, מוחזרות כקוד -8 וכקוד -9, ושתיהן נוקבות במפתח התוכנית הפנימי ולא בשם התצוגה. כל יכולת נקבעת במקום אחר לגמרי, על ידי רישום של שלושים רצפות מתודה ב-cpi_methods שמושווה לפי דירוג ב-http_module:868, וההשוואה הזו לעולם לא קוראת את כרטיס התוכנית בכלל. שניים מתוך חמישה עשר הסולמות האחרים משנים את ההקצאה שלכם, AI Workspace ו-Drive, ושלושה עשר לא, מפני ש-get_effective_entitlements קוראת בדיוק שלושה שדות תוכנית ומחזירה בדיוק שני מספרים. ארבעה דברים ניתנים עכשיו לבדיקה על המסך שלכם ולא על סמך הבטחה. שורת התוכנית מציגה את הדרגה שהחשבון שלכם באמת מחזיק. שינוי מתוזמן מציג את תאריך התחולה שלו ואת כפתור הביטול שמשחרר אותו, עד הרגע שבו הוא נוחת. המנוי המאוחד מציג מנוי, תמיכה ו-AI Workspace כשורות בחשבונית אחת ולא כשלושה חיובים. ויתרת הקרדיטים שמודפסת ליד הדרגה היא נתון מפנקס, וזו בדיוק הסיבה שהיא אינה המספר שעל כרטיס התוכנית. שום דבר לא מחויב מעצם ההסתכלות. אם המנוי שלכם הוא free, שימו לב לכלל הערימה האחד בזמן שאתם שם: תוספת AI Workspace נספרת רק כשהמנוי מוגדר ואינו free, ולכן דרגת workspace בתשלום על מנוי free לא מוסיפה לא את הקרדיטים שלה ולא את האחסון שלה. האם התשלום עוצר את הרכישה הזו מלכתחילה לא נבדק, והדף הזה אומר זאת במקום להניח שכן.