Changeset 3651653
- Timestamp:
- 08/17/2026 07:50:43 PM (2 months ago)
- Location:
- subscription-system/trunk
- Files:
-
- 5 added
- 4 deleted
- 21 edited
-
README.txt (modified) (3 diffs)
-
admin/partials/appearance-settings-display.php (modified) (2 diffs)
-
admin/partials/plans-display.php (modified) (3 diffs)
-
admin/partials/pricing-settings-display.php (modified) (11 diffs)
-
includes/class-subs-sys-access.php (modified) (1 diff)
-
includes/class-subs-sys-checkout.php (modified) (1 diff)
-
includes/class-subs-sys-db-updater.php (modified) (2 diffs)
-
includes/class-subs-sys-layout-settings.php (added)
-
includes/class-subs-sys-paywall.php (deleted)
-
includes/class-subs-sys-shortcodes.php (modified) (2 diffs)
-
includes/class-subs-sys-table-registry.php (modified) (1 diff)
-
includes/class-subs-sys.php (modified) (4 diffs)
-
public/class-subs-sys-public.php (modified) (1 diff)
-
public/css/interest-subscribe.css (modified) (2 diffs)
-
public/css/paywall.css (deleted)
-
public/css/pricing-layout1.css (modified) (4 diffs)
-
public/css/pricing-layout2.css (modified) (2 diffs)
-
public/css/pricing-layout3.css (modified) (2 diffs)
-
public/css/pricing-shared.css (added)
-
public/js/interest-subscribe.js (modified) (8 diffs)
-
public/js/paywall.js (deleted)
-
public/js/ss-carousel.js (added)
-
public/partials/components/pricing-assets.php (added)
-
public/partials/components/pricing-plan-card.php (added)
-
public/partials/paywall-cta.php (deleted)
-
public/partials/pricing-plans-layout1.php (modified) (3 diffs)
-
public/partials/pricing-plans-layout2.php (modified) (3 diffs)
-
public/partials/pricing-plans-layout3.php (modified) (3 diffs)
-
subscription-system.php (modified) (4 diffs)
-
uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
subscription-system/trunk/README.txt
r3650727 r3651653 6 6 Tested up to: 7.0 7 7 Requires PHP: 8.0 8 Stable tag: 1.0.2 18 Stable tag: 1.0.22 9 9 License: GPLv2 or later 10 10 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 86 86 == Changelog == 87 87 88 = 1.0.22 = 89 **Maintenance release — admin fixes, checkout stability, and feature toggles** 90 * FIX: Subscribers admin page stylesheet now loads correctly (fixed screen ID mismatch that caused overlapping layout). 91 * FIX: Login, register, account, and checkout redirect URLs now respect custom Pages Settings instead of always sending visitors to wp-login.php. 92 * FIX: Stale Stripe customer IDs no longer crash checkout when a stored customer was deleted or belongs to a different Stripe mode. 93 * FIX: Content notification popup buttons ("Subscribe" / "Remind me later") now work reliably; consolidated into content-notification.js. 94 * FIX: Database updater self-heals missing columns on upgrade (Stripe mode stamping, plan-switch scheduling, content subscription payment fields). 95 * FIX: Plan category labels no longer render blank when only one of the category_name/name columns is populated. 96 * FIX: Featured plan badge no longer fails when plan index is stored as a float string. 97 * NEW: Features tab in Settings — enable or disable Subscription Plans and Content Subscriptions independently; admin menu shows only enabled features. 98 * NEW: Per-plan customizable button label (e.g. "Buy Now" instead of "Subscribe Now"). 99 * NEW: Improved front-end asset cache busting (file modification time appended to script/style version strings). 100 * IMPROVED: Admin menu reorganized into logical feature blocks (Plans, Content, shared settings). 101 * REMOVED: The "This content is for subscribers" paywall. Gated posts no longer show a teaser + subscribe prompt — full content is always rendered. Existing installs are cleaned up automatically on update (the paywall's stored setting is deleted); plans, checkout, webhooks and all other subscription features are unaffected. 102 88 103 = 1.0.21 = 104 * Version bump for deployment tracking. 105 106 = 1.1.0 = 89 107 **Centralized plans, and the content paywall — a core rebuild** 90 108 * NEW: Content actually gets locked now. A the_content-based paywall shows a teaser + subscribe prompt on gated posts and grants full access only once Stripe confirms payment — previously every "gated" post was fully readable by anyone. … … 276 294 == Upgrade Notice == 277 295 278 = 1.0.2 1=279 Major rebuild: new content paywall, unified Plans system, consolidated Stripe/webhook flow, and several security fixes (removed client-side Stripe secret key, fixed authorization gaps on subscription/admin actions). Recommended update.296 = 1.0.22 = 297 Recommended update. Fixes Subscribers admin layout, checkout crashes from stale Stripe customers, content notification popup buttons, and Pages Settings login/register redirects. Adds feature toggles for Plans vs Content subscriptions and per-plan button labels. 280 298 281 299 = 1.0.20 = -
subscription-system/trunk/admin/partials/appearance-settings-display.php
r3650727 r3651653 4 4 * 5 5 * Every picker maps to a design token (--ss-*) that all public stylesheets 6 * read, so a change here recolors buttons, headings, plans, paywalls,modals6 * read, so a change here recolors buttons, headings, plans, modals 7 7 * and emails at once. Pale "tint" shades are derived automatically from the 8 8 * color they belong to. … … 23 23 <h2><?php esc_html_e('Global Colors', 'subscription-system'); ?></h2> 24 24 <p class="description" style="max-width: 640px;"> 25 <?php esc_html_e('These colors apply to everything the plugin shows to visitors — buttons, headings, pricing plans, paywalls,modals, account pages and notification emails. Pale background shades are derived automatically from the colors you pick.', 'subscription-system'); ?>25 <?php esc_html_e('These colors apply to everything the plugin shows to visitors — buttons, headings, pricing plans, modals, account pages and notification emails. Pale background shades are derived automatically from the colors you pick.', 'subscription-system'); ?> 26 26 </p> 27 27 -
subscription-system/trunk/admin/partials/plans-display.php
r3650727 r3651653 37 37 } 38 38 wp_enqueue_style( 'subs-sys-tokens', SUBS_SYS_PLUGIN_URL . 'public/css/subscription-system-tokens.css', array(), SUBS_SYS_VERSION ); 39 wp_enqueue_style( 'subscription-pricing-' . $preview_layout, SUBS_SYS_PLUGIN_URL . 'public/css/pricing-' . $preview_layout . '.css', array( 'subs-sys-tokens' ), SUBS_SYS_VERSION ); 39 wp_enqueue_style( 'subs-sys-pricing-shared', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-shared.css', array( 'subs-sys-tokens' ), subs_sys_asset_version( 'public/css/pricing-shared.css' ) ); 40 wp_enqueue_style( 'subscription-pricing-' . $preview_layout, SUBS_SYS_PLUGIN_URL . 'public/css/pricing-' . $preview_layout . '.css', array( 'subs-sys-tokens', 'subs-sys-pricing-shared' ), subs_sys_asset_version( 'public/css/pricing-' . $preview_layout . '.css' ) ); 41 wp_add_inline_style( 'subs-sys-tokens', Subs_Sys_Colors::inline_css() . Subs_Sys_Layout_Settings::inline_css() ); 40 42 41 43 $notice = ''; … … 447 449 ?> 448 450 <div class="ss-preview-panel__stage"> 449 <div class="subscription-pricing-plans <?php echo esc_attr( $preview_layout ); ?> ss-responsive ">451 <div class="subscription-pricing-plans <?php echo esc_attr( $preview_layout ); ?> ss-responsive <?php echo esc_attr( Subs_Sys_Layout_Settings::wrapper_classes( 'vertical' ) ); ?>"> 450 452 <div class="ss-pricing-container ss-<?php echo esc_attr( $preview_layout ); ?>"> 451 453 <div class="ss-pricing-plans"> … … 611 613 612 614 <style> 615 /* The editable price digits sit in a span so the form can update them 616 * live; keep them at full price size — only the period label is small. */ 617 .ss-preview-panel__stage .ss-price #ss-preview-price { 618 font-size: inherit; 619 font-weight: inherit; 620 color: inherit; 621 letter-spacing: inherit; 622 } 623 613 624 .ss-mode-heading { 614 625 margin: 28px 0 8px; -
subscription-system/trunk/admin/partials/pricing-settings-display.php
r3650727 r3651653 1 1 <?php 2 2 /** 3 * Provide a admin area view for pricing plan settings 3 * Pricing Plan Settings — layout choice, design options, display behavior, 4 * and live previews of all three layouts. 4 5 * 5 * @link https://yourwebsite.com 6 * @since 1.0.0 6 * Every design control here writes to Subs_Sys_Layout_Settings; the previews 7 * below are drawn by the real frontend stylesheets and re-painted live as 8 * controls change, so what the admin sees is what the visitor gets. 7 9 * 8 10 * @package Subscription_System … … 24 26 // below are styled by the real thing rather than a hand-copied approximation. 25 27 wp_enqueue_style('subs-sys-tokens', SUBS_SYS_PLUGIN_URL . 'public/css/subscription-system-tokens.css', array(), SUBS_SYS_VERSION); 26 wp_enqueue_style('subscription-pricing-layout1', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-layout1.css', array('subs-sys-tokens'), SUBS_SYS_VERSION); 27 wp_enqueue_style('subscription-pricing-layout2', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-layout2.css', array('subs-sys-tokens'), SUBS_SYS_VERSION); 28 wp_enqueue_style('subscription-pricing-layout3', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-layout3.css', array('subs-sys-tokens'), SUBS_SYS_VERSION); 28 wp_enqueue_style('subs-sys-pricing-shared', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-shared.css', array('subs-sys-tokens'), subs_sys_asset_version('public/css/pricing-shared.css')); 29 wp_enqueue_style('subscription-pricing-layout1', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-layout1.css', array('subs-sys-tokens', 'subs-sys-pricing-shared'), subs_sys_asset_version('public/css/pricing-layout1.css')); 30 wp_enqueue_style('subscription-pricing-layout2', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-layout2.css', array('subs-sys-tokens', 'subs-sys-pricing-shared'), subs_sys_asset_version('public/css/pricing-layout2.css')); 31 wp_enqueue_style('subscription-pricing-layout3', SUBS_SYS_PLUGIN_URL . 'public/css/pricing-layout3.css', array('subs-sys-tokens', 'subs-sys-pricing-shared'), subs_sys_asset_version('public/css/pricing-layout3.css')); 32 33 // The saved Appearance colors and design variables, exactly as the front end 34 // prints them. 35 wp_add_inline_style('subs-sys-tokens', Subs_Sys_Colors::inline_css() . Subs_Sys_Layout_Settings::inline_css()); 29 36 30 37 // Get current settings 31 38 $default_layout = get_option('subscription_default_layout', 'layout1'); 32 39 33 // Add this at the top of the file 34 global $wp_settings_fields, $wp_settings_sections, $wp_registered_settings; 35 36 // At the top of the file, after checking for direct access 40 // Save both the default layout and the design settings from one form. 37 41 if (isset($_POST['save_layout_settings']) && check_admin_referer('save_layout_settings_nonce')) { 38 42 $layout = isset($_POST['subscription_default_layout']) ? sanitize_text_field(wp_unslash($_POST['subscription_default_layout'])) : 'layout1'; 39 43 update_option('subscription_default_layout', $layout); 40 41 // Show success message 44 45 if (isset($_POST['ssl']) && is_array($_POST['ssl'])) { 46 update_option(Subs_Sys_Layout_Settings::OPTION, Subs_Sys_Layout_Settings::sanitize(wp_unslash($_POST['ssl']))); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- sanitized field-by-field in sanitize(). 47 } 48 42 49 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('Settings saved successfully.', 'subscription-system') . '</p></div>'; 43 44 // Refresh the default layout variable 50 45 51 $default_layout = get_option('subscription_default_layout', 'layout1'); 46 } 52 // Re-print the variables with the values just saved. 53 wp_add_inline_style('subs-sys-tokens', Subs_Sys_Layout_Settings::inline_css()); 54 } 55 56 $ss = Subs_Sys_Layout_Settings::all(); 57 $ss_wrapper_classes = Subs_Sys_Layout_Settings::wrapper_classes(); 58 59 /** 60 * Small field helpers, local to this screen. 61 */ 62 $ss_select = function ($key, $choices) use ($ss) { 63 $out = '<select name="ssl[' . esc_attr($key) . ']" class="ss-design-control" data-key="' . esc_attr($key) . '">'; 64 foreach ($choices as $value => $label) { 65 $out .= '<option value="' . esc_attr($value) . '"' . selected($ss[$key], $value, false) . '>' . esc_html($label) . '</option>'; 66 } 67 return $out . '</select>'; 68 }; 69 $ss_check = function ($key, $label) use ($ss) { 70 return '<label><input type="checkbox" name="ssl[' . esc_attr($key) . ']" value="1" class="ss-design-control" data-key="' . esc_attr($key) . '"' . checked($ss[$key], '1', false) . '> ' . esc_html($label) . '</label>'; 71 }; 72 $ss_range = function ($key, $min, $max, $unit) use ($ss) { 73 return '<span class="ss-range-wrap"><input type="range" name="ssl[' . esc_attr($key) . ']" min="' . esc_attr($min) . '" max="' . esc_attr($max) . '" value="' . esc_attr($ss[$key]) . '" class="ss-design-control" data-key="' . esc_attr($key) . '"> <output>' . esc_html($ss[$key]) . '</output>' . esc_html($unit) . '</span>'; 74 }; 47 75 ?> 48 76 49 77 <div class="wrap subscription-settings-page"> 50 78 <h1><?php echo esc_html__('Pricing Plan Settings', 'subscription-system'); ?></h1> 51 79 52 80 <!-- Shortcode Info Box --> 53 81 <div class="notice notice-info is-dismissible"> 54 82 <h3><?php echo esc_html__('Subscription Plans Shortcode:', 'subscription-system'); ?></h3> 55 83 <p><?php echo esc_html__('Use the following shortcode to display your subscription plans:', 'subscription-system'); ?></p> 56 <code>[pricing_plans]</code> <?php echo esc_html__('– This will use the default layout set in the admin settings.', 'subscription-system'); ?> 57 <p><code>[pricing_plans layout="layout1" code="2"]</code> <?php echo esc_html__('– This will display plans in Layout 1 filtered by category ID.', 'subscription-system'); ?></p> 58 <p><code>[pricing_plans layout="layout2" code="3"]</code> <?php echo esc_html__('– This will display plans in Layout 2 filtered by category ID.', 'subscription-system'); ?></p> 59 <p><code>[pricing_plans layout="layout3" orientation="horizontal"]</code> <?php echo esc_html__('– This will display plans in Layout 3 with horizontal orientation.', 'subscription-system'); ?></p> 60 <p><?php echo esc_html__('Customize your subscription plans display by adding this shortcode to any post or page.', 'subscription-system'); ?></p> 84 <code>[pricing_plans]</code> <?php echo esc_html__('– This will use the default layout and design settings below.', 'subscription-system'); ?> 85 <p><code>[pricing_plans layout="layout2" code="3"]</code> <?php echo esc_html__('– Layout 2, filtered by category ID.', 'subscription-system'); ?></p> 86 <p><code>[pricing_plans behavior="carousel"]</code> <?php echo esc_html__('– Override the display behavior for one page (auto, horizontal, vertical, scroll, carousel).', 'subscription-system'); ?></p> 61 87 </div> 62 88 63 89 <form method="post" action=""> 64 90 <?php wp_nonce_field('save_layout_settings_nonce'); ?> 65 91 66 92 <div class="layout-settings-section"> 67 93 <h2><?php echo esc_html__('Layout Settings', 'subscription-system'); ?></h2> … … 71 97 <td> 72 98 <select name="subscription_default_layout" id="default-layout"> 73 <option value="layout1" <?php selected($default_layout, 'layout1'); ?>><?php echo esc_html__('Layout 1 ', 'subscription-system'); ?></option>74 <option value="layout2" <?php selected($default_layout, 'layout2'); ?>><?php echo esc_html__('Layout 2 ', 'subscription-system'); ?></option>75 <option value="layout3" <?php selected($default_layout, 'layout3'); ?>><?php echo esc_html__('Layout 3 ', 'subscription-system'); ?></option>99 <option value="layout1" <?php selected($default_layout, 'layout1'); ?>><?php echo esc_html__('Layout 1 — Classic cards', 'subscription-system'); ?></option> 100 <option value="layout2" <?php selected($default_layout, 'layout2'); ?>><?php echo esc_html__('Layout 2 — Ledger sheet', 'subscription-system'); ?></option> 101 <option value="layout3" <?php selected($default_layout, 'layout3'); ?>><?php echo esc_html__('Layout 3 — Spotlight stage', 'subscription-system'); ?></option> 76 102 </select> 77 103 </td> 78 104 </tr> 79 105 </table> 80 <p class="submit">81 <input type="submit" name="save_layout_settings" class="button button-primary" value="<?php esc_attr_e('Save Changes', 'subscription-system'); ?>">82 </p>83 106 </div> 84 107 108 <div class="ss-design-settings"> 109 <h2><?php esc_html_e('Design Options', 'subscription-system'); ?></h2> 110 <p class="description"><?php esc_html_e('These options apply to all three layouts. Colors come from Settings → Appearance. Changes preview live below — nothing is applied until you save.', 'subscription-system'); ?></p> 111 112 <div class="ss-design-grid"> 113 114 <fieldset class="ss-design-group"> 115 <legend><?php esc_html_e('Featured badge', 'subscription-system'); ?></legend> 116 <p><?php echo $ss_check('badge_enabled', __('Show a badge on the featured plan', 'subscription-system')); // phpcs:ignore WordPress.Security.EscapeOutput ?></p> 117 <p> 118 <label for="ssl-badge-text"><?php esc_html_e('Badge text', 'subscription-system'); ?></label><br> 119 <input type="text" id="ssl-badge-text" name="ssl[badge_text]" value="<?php echo esc_attr($ss['badge_text']); ?>" maxlength="40" list="ssl-badge-suggestions" class="regular-text ss-design-control" data-key="badge_text"> 120 <datalist id="ssl-badge-suggestions"> 121 <option value="<?php esc_attr_e('Most Popular', 'subscription-system'); ?>"></option> 122 <option value="<?php esc_attr_e('Best Value', 'subscription-system'); ?>"></option> 123 <option value="<?php esc_attr_e('Recommended', 'subscription-system'); ?>"></option> 124 <option value="<?php esc_attr_e('Featured', 'subscription-system'); ?>"></option> 125 <option value="<?php esc_attr_e('Editor’s Choice', 'subscription-system'); ?>"></option> 126 </datalist> 127 </p> 128 <p> 129 <label><?php esc_html_e('Badge style', 'subscription-system'); ?><br> 130 <?php echo $ss_select('badge_style', array( // phpcs:ignore WordPress.Security.EscapeOutput 131 'auto' => __('Match the layout (recommended)', 'subscription-system'), 132 'pill' => __('Floating pill', 'subscription-system'), 133 'ribbon' => __('Corner ribbon', 'subscription-system'), 134 'stripe' => __('Top stripe', 'subscription-system'), 135 )); ?></label> 136 </p> 137 <p> 138 <label><?php esc_html_e('Badge color', 'subscription-system'); ?><br> 139 <?php echo $ss_select('badge_color', array( // phpcs:ignore WordPress.Security.EscapeOutput 140 'accent' => __('Accent color', 'subscription-system'), 141 'primary' => __('Primary color', 'subscription-system'), 142 )); ?></label> 143 </p> 144 </fieldset> 145 146 <fieldset class="ss-design-group"> 147 <legend><?php esc_html_e('Card style', 'subscription-system'); ?></legend> 148 <p><label><?php esc_html_e('Shadow', 'subscription-system'); ?><br> 149 <?php echo $ss_select('card_shadow', array('none' => __('None', 'subscription-system'), 'soft' => __('Soft', 'subscription-system'), 'medium' => __('Medium', 'subscription-system'), 'bold' => __('Bold', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 150 <p><label><?php esc_html_e('Border width', 'subscription-system'); ?><br> 151 <?php echo $ss_select('card_border', array('0' => __('None', 'subscription-system'), '1' => __('Hairline (1px)', 'subscription-system'), '2' => __('Strong (2px)', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 152 <p><label><?php esc_html_e('Corner radius', 'subscription-system'); ?><br> 153 <?php echo $ss_range('card_radius', 0, 32, 'px'); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 154 <p><label><?php esc_html_e('Card background', 'subscription-system'); ?><br> 155 <?php echo $ss_select('card_bg', array('surface' => __('Card color (from Appearance)', 'subscription-system'), 'tint' => __('Primary tint', 'subscription-system'), 'transparent' => __('Transparent', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 156 <p><label><?php esc_html_e('Card padding', 'subscription-system'); ?><br> 157 <?php echo $ss_select('card_padding', array('compact' => __('Compact', 'subscription-system'), 'cozy' => __('Comfortable', 'subscription-system'), 'spacious' => __('Spacious', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 158 <p><label><?php esc_html_e('Space between cards', 'subscription-system'); ?><br> 159 <?php echo $ss_range('card_gap', 0, 64, 'px'); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 160 <p><label><?php esc_html_e('Hover effect', 'subscription-system'); ?><br> 161 <?php echo $ss_select('hover_effect', array('none' => __('None', 'subscription-system'), 'lift' => __('Lift', 'subscription-system'), 'glow' => __('Glow', 'subscription-system'), 'scale' => __('Scale up', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 162 <p><label><?php esc_html_e('Text alignment', 'subscription-system'); ?><br> 163 <?php echo $ss_select('align', array('auto' => __('Match the layout (recommended)', 'subscription-system'), 'left' => __('Left', 'subscription-system'), 'center' => __('Center', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 164 </fieldset> 165 166 <fieldset class="ss-design-group"> 167 <legend><?php esc_html_e('Feature icons', 'subscription-system'); ?></legend> 168 <p><label><?php esc_html_e('Icon', 'subscription-system'); ?><br> 169 <?php echo $ss_select('icon_style', array('check' => __('Check ✓', 'subscription-system'), 'arrow' => __('Arrow →', 'subscription-system'), 'star' => __('Star ★', 'subscription-system'), 'dot' => __('Dot •', 'subscription-system'), 'none' => __('No icon', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 170 <p><label><?php esc_html_e('Icon size', 'subscription-system'); ?><br> 171 <?php echo $ss_select('icon_size', array('sm' => __('Small', 'subscription-system'), 'md' => __('Medium', 'subscription-system'), 'lg' => __('Large', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 172 <p><label><?php esc_html_e('Icon position', 'subscription-system'); ?><br> 173 <?php echo $ss_select('icon_position', array('left' => __('Before the text', 'subscription-system'), 'right' => __('At the right edge', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 174 </fieldset> 175 176 <fieldset class="ss-design-group"> 177 <legend><?php esc_html_e('Subscribe button', 'subscription-system'); ?></legend> 178 <p><label><?php esc_html_e('Style', 'subscription-system'); ?><br> 179 <?php echo $ss_select('button_style', array('solid' => __('Solid', 'subscription-system'), 'outline' => __('Outline', 'subscription-system'), 'soft' => __('Soft tint', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 180 <p><label><?php esc_html_e('Shape', 'subscription-system'); ?><br> 181 <?php echo $ss_select('button_shape', array('pill' => __('Pill', 'subscription-system'), 'rounded' => __('Rounded', 'subscription-system'), 'square' => __('Square', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 182 <p><label><?php esc_html_e('Size', 'subscription-system'); ?><br> 183 <?php echo $ss_select('button_size', array('sm' => __('Small', 'subscription-system'), 'md' => __('Medium', 'subscription-system'), 'lg' => __('Large', 'subscription-system'))); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 184 <p><?php echo $ss_check('button_full', __('Stretch to the card’s full width', 'subscription-system')); // phpcs:ignore WordPress.Security.EscapeOutput ?></p> 185 </fieldset> 186 187 <fieldset class="ss-design-group"> 188 <legend><?php esc_html_e('Display behavior', 'subscription-system'); ?></legend> 189 <p><label><?php esc_html_e('How the cards occupy the page', 'subscription-system'); ?><br> 190 <?php echo $ss_select('behavior', array( // phpcs:ignore WordPress.Security.EscapeOutput 191 'auto' => __('Auto responsive (recommended)', 'subscription-system'), 192 'horizontal' => __('Horizontal row', 'subscription-system'), 193 'vertical' => __('Vertical / stacked', 'subscription-system'), 194 'scroll' => __('Scrollable strip', 'subscription-system'), 195 'carousel' => __('Slider / carousel', 'subscription-system'), 196 )); ?></label></p> 197 <p class="description"><?php esc_html_e('Auto responsive lays cards out in a grid on large screens and switches to one swipeable card per view on phones.', 'subscription-system'); ?></p> 198 199 <div class="ss-carousel-options" <?php echo ('carousel' === $ss['behavior']) ? '' : 'hidden'; ?>> 200 <p><label><?php esc_html_e('Cards visible at once (desktop)', 'subscription-system'); ?><br> 201 <?php echo $ss_range('carousel_visible', 1, 4, ''); // phpcs:ignore WordPress.Security.EscapeOutput ?></label></p> 202 <p><?php echo $ss_check('carousel_arrows', __('Show previous / next arrows', 'subscription-system')); // phpcs:ignore WordPress.Security.EscapeOutput ?></p> 203 <p><?php echo $ss_check('carousel_dots', __('Show dots', 'subscription-system')); // phpcs:ignore WordPress.Security.EscapeOutput ?></p> 204 <p><?php echo $ss_check('carousel_loop', __('Loop back to the first plan at the end', 'subscription-system')); // phpcs:ignore WordPress.Security.EscapeOutput ?></p> 205 <p><?php echo $ss_check('carousel_autoplay', __('Advance automatically', 'subscription-system')); // phpcs:ignore WordPress.Security.EscapeOutput ?></p> 206 <p><label><?php esc_html_e('Autoplay speed (milliseconds)', 'subscription-system'); ?><br> 207 <input type="number" name="ssl[carousel_speed]" min="1500" max="20000" step="500" value="<?php echo esc_attr($ss['carousel_speed']); ?>" class="small-text ss-design-control" data-key="carousel_speed"></label></p> 208 <p class="description"><?php esc_html_e('Swiping is always available on touch screens. Tablets show at most 2 cards, phones 1.', 'subscription-system'); ?></p> 209 </div> 210 </fieldset> 211 212 </div> 213 </div> 214 215 <p class="submit"> 216 <input type="submit" name="save_layout_settings" class="button button-primary" value="<?php esc_attr_e('Save Changes', 'subscription-system'); ?>"> 217 </p> 218 85 219 <!-- Layout Previews --> 86 220 <div class="layout-previews-section"> 87 221 <h2><?php echo esc_html__('Layout Previews', 'subscription-system'); ?></h2> 88 <p><?php echo esc_html__('Current default layout:', 'subscription-system'); ?> <strong><?php echo esc_html(ucfirst($default_layout)); ?></strong></p> 89 222 <p><?php echo esc_html__('Current default layout:', 'subscription-system'); ?> <strong><?php echo esc_html(ucfirst($default_layout)); ?></strong> 223 <span class="description"><?php esc_html_e('— previews repaint as you change the options above.', 'subscription-system'); ?></span></p> 224 90 225 <div class="layout-tabs"> 91 <div class="layout-tab <?php echo ($default_layout === 'layout1') ? 'active' : ''; ?>" data-layout="layout1"><?php echo esc_html__('Layout 1 ', 'subscription-system'); ?></div>92 <div class="layout-tab <?php echo ($default_layout === 'layout2') ? 'active' : ''; ?>" data-layout="layout2"><?php echo esc_html__('Layout 2 ', 'subscription-system'); ?></div>93 <div class="layout-tab <?php echo ($default_layout === 'layout3') ? 'active' : ''; ?>" data-layout="layout3"><?php echo esc_html__('Layout 3 ', 'subscription-system'); ?></div>226 <div class="layout-tab <?php echo ($default_layout === 'layout1') ? 'active' : ''; ?>" data-layout="layout1"><?php echo esc_html__('Layout 1 · Classic', 'subscription-system'); ?></div> 227 <div class="layout-tab <?php echo ($default_layout === 'layout2') ? 'active' : ''; ?>" data-layout="layout2"><?php echo esc_html__('Layout 2 · Ledger', 'subscription-system'); ?></div> 228 <div class="layout-tab <?php echo ($default_layout === 'layout3') ? 'active' : ''; ?>" data-layout="layout3"><?php echo esc_html__('Layout 3 · Spotlight', 'subscription-system'); ?></div> 94 229 </div> 95 230 96 231 <?php 97 232 // Sample plans used by all three previews below. … … 101 236 array('name' => 'PREMIUM', 'price' => '49.99', 'featured' => false, 'features' => array('50GB Disk Space', '50 Domain Names', '20 E-Mail Address', '300GB Monthly Bandwidth', 'Fully Support')), 102 237 ); 103 // Markup below is deliberately identical to what each 104 // public/partials/pricing-plans-layoutN.php template renders, 105 // wrapped in the same .subscription-pricing-plans.layoutN 106 // class the real CSS files are scoped to — so this preview is 107 // styled by the exact same stylesheet the front end uses, 108 // not a hand-copied approximation of it. 109 $cards_markup = function ($plans, $button_class) { 238 // Markup below is deliberately identical to what 239 // components/pricing-plan-card.php renders on the front end, 240 // wrapped in the same classes the real CSS files are scoped to — 241 // so this preview is styled by the exact stylesheets the visitor 242 // sees, not a hand-copied approximation of them. 243 $cards_markup = function ($plans) { 110 244 ob_start(); 111 245 foreach ($plans as $plan) : … … 113 247 ?> 114 248 <div class="ss-pricing-plan <?php echo esc_attr($featured_class); ?>"> 249 <?php if ($plan['featured']) { echo wp_kses_post(Subs_Sys_Layout_Settings::badge_html()); } ?> 115 250 <div class="ss-plan-header"> 116 251 <h3><?php echo esc_html($plan['name']); ?></h3> … … 125 260 </div> 126 261 <div class="ss-plan-footer"> 127 <a href="#" class=" <?php echo esc_attr($button_class); ?>" onclick="return false;">Order Now</a>262 <a href="#" class="ss-button" onclick="return false;">Order Now</a> 128 263 </div> 129 264 </div> … … 133 268 ?> 134 269 135 <div class="layout-preview layout1-preview <?php echo ($default_layout === 'layout1') ? 'active' : ''; ?>"> 136 <h3><?php echo esc_html__('Layout 1 Preview', 'subscription-system'); ?></h3> 137 <div class="subscription-pricing-plans layout1"> 138 <div class="ss-pricing-container ss-layout1"> 139 <div class="ss-pricing-plans"> 140 <?php echo $cards_markup($sample_plans, 'ss-button'); ?> 270 <?php foreach (array('layout1', 'layout2', 'layout3') as $ss_preview_layout) : ?> 271 <div class="layout-preview <?php echo esc_attr($ss_preview_layout); ?>-preview <?php echo ($default_layout === $ss_preview_layout) ? 'active' : ''; ?>"> 272 <div class="subscription-pricing-plans <?php echo esc_attr($ss_preview_layout); ?> ss-responsive <?php echo esc_attr($ss_wrapper_classes); ?> ss-admin-preview"> 273 <div class="ss-pricing-container ss-<?php echo esc_attr($ss_preview_layout); ?>"> 274 <div class="ss-pricing-plans"> 275 <?php echo $cards_markup($sample_plans); // phpcs:ignore WordPress.Security.EscapeOutput -- built from escaped parts above. ?> 276 </div> 141 277 </div> 142 278 </div> 143 279 </div> 144 </div> 145 146 <div class="layout-preview layout2-preview <?php echo ($default_layout === 'layout2') ? 'active' : ''; ?>"> 147 <h3><?php echo esc_html__('Layout 2 Preview', 'subscription-system'); ?></h3> 148 <div class="subscription-pricing-plans layout2"> 149 <div class="ss-pricing-container ss-layout2"> 150 <?php echo $cards_markup($sample_plans, 'ss-button'); ?> 151 </div> 152 </div> 153 </div> 154 155 <div class="layout-preview layout3-preview <?php echo ($default_layout === 'layout3') ? 'active' : ''; ?>"> 156 <h3><?php echo esc_html__('Layout 3 Preview', 'subscription-system'); ?></h3> 157 <div class="subscription-pricing-plans layout3"> 158 <div class="ss-pricing-container ss-layout3"> 159 <?php echo $cards_markup($sample_plans, 'ss-pricing-button'); ?> 160 </div> 161 </div> 162 </div> 280 <?php endforeach; ?> 281 <p class="description ss-carousel-preview-note" hidden><?php esc_html_e('Carousel arrows, dots and autoplay run on the live site; the preview shows the strip.', 'subscription-system'); ?></p> 163 282 </div> 164 283 </form> … … 166 285 167 286 <style> 287 /* Design option groups */ 288 .ss-design-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; max-width: 1400px; } 289 .ss-design-group { background: #fff; border: 1px solid var(--ss-border, #E1E6EA); border-radius: 8px; padding: 4px 18px 12px; margin: 0; } 290 .ss-design-group legend { font-weight: 600; padding: 0 6px; } 291 .ss-design-group label { font-weight: 500; } 292 .ss-design-group .description { color: #667085; } 293 .ss-range-wrap { display: inline-flex; align-items: center; gap: 8px; } 294 .ss-range-wrap input[type="range"] { width: 140px; } 295 .ss-range-wrap output { min-width: 2ch; font-variant-numeric: tabular-nums; font-weight: 600; } 296 .ss-carousel-options { border-top: 1px dashed var(--ss-border, #E1E6EA); margin-top: 10px; padding-top: 6px; } 297 168 298 /* Layout Preview Styles */ 169 .layout-previews-section { 170 margin-top: 30px; 171 } 172 173 .layout-tabs { 174 display: flex; 175 margin-bottom: 0; 176 border-bottom: 1px solid var(--ss-border); 177 } 299 .layout-previews-section { margin-top: 30px; } 300 301 .layout-tabs { display: flex; margin-bottom: 0; border-bottom: 1px solid var(--ss-border); } 178 302 179 303 .layout-tab { … … 189 313 } 190 314 191 .layout-tab:hover {192 background-color: var(--ss-bg);193 }194 195 315 .layout-tab.active { 196 316 background-color: var(--ss-surface); … … 204 324 display: none; 205 325 padding: 30px; 206 background-color: var(--ss- surface);326 background-color: var(--ss-bg); 207 327 border: 1px solid var(--ss-border); 208 328 border-top: none; … … 211 331 } 212 332 213 .layout-preview.active { 214 display: block; 215 } 216 217 .layout-preview h3 { 218 margin-top: 0; 219 margin-bottom: 20px; 220 font-size: 18px; 221 color: var(--ss-ink); 222 font-weight: 500; 223 } 224 333 .layout-preview.active { display: block; } 334 335 /* Give badge pills room above the cards inside the preview boxes. */ 336 .layout-preview .subscription-pricing-plans { padding-top: 10px; } 225 337 </style> 226 338 227 339 <script> 228 340 jQuery(document).ready(function($) { 229 // Switch tabs341 // ── Preview tabs ──────────────────────────────────────────────────── 230 342 $('.layout-tab').on('click', function() { 231 343 var layout = $(this).data('layout'); 232 233 // Update tabs234 344 $('.layout-tab').removeClass('active'); 235 345 $(this).addClass('active'); 236 237 // Update previews238 346 $('.layout-preview').removeClass('active'); 239 347 $('.' + layout + '-preview').addClass('active'); 240 241 // Update select242 348 $('#default-layout').val(layout); 243 349 }); 244 245 // Update active tab when changing default layout select 350 246 351 $('#default-layout').on('change', function() { 247 352 var layout = $(this).val(); 248 353 $('.layout-tab').removeClass('active'); 249 354 $('.layout-tab[data-layout="' + layout + '"]').addClass('active'); 250 251 355 $('.layout-preview').removeClass('active'); 252 356 $('.' + layout + '-preview').addClass('active'); 253 357 }); 358 359 // ── Live design preview ───────────────────────────────────────────── 360 // Mirrors Subs_Sys_Layout_Settings::inline_css() + wrapper_classes(): 361 // controls repaint the preview wrappers without a round trip. 362 var previews = $('.ss-admin-preview'); 363 364 var shadows = { 365 none: ['none', 'none'], 366 soft: ['0 4px 14px rgba(17,24,39,0.07)', '0 10px 24px rgba(17,24,39,0.11)'], 367 medium: ['0 8px 28px rgba(17,24,39,0.12)', '0 16px 40px rgba(17,24,39,0.16)'], 368 bold: ['0 16px 44px rgba(17,24,39,0.18)', '0 24px 60px rgba(17,24,39,0.22)'] 369 }; 370 var paddings = { compact: '16px', cozy: '24px', spacious: '34px' }; 371 var iconSizes = { sm: '14px', md: '17px', lg: '21px' }; 372 var btnPad = { sm: '8px 18px', md: '12px 26px', lg: '16px 34px' }; 373 var btnFont = { sm: '12px', md: '14px', lg: '16px' }; 374 var btnRadius = { pill: '999px', rounded: '8px', square: '0px' }; 375 376 var classGroups = { 377 card_bg: 'ss-card-bg-', 378 hover_effect: 'ss-hover-', 379 align: 'ss-align-', 380 icon_style: 'ss-icon-', 381 icon_position: 'ss-icon-pos-', 382 button_style: 'ss-btn-', 383 badge_style: 'ss-badge-', 384 behavior: 'ss-behave-' 385 }; 386 // ss-icon-pos-* must be swapped before ss-icon-* could eat its prefix, 387 // so each group strips only its own exact classes: 388 var classValues = { 389 card_bg: ['surface', 'tint', 'transparent'], 390 hover_effect: ['none', 'lift', 'glow', 'scale'], 391 align: ['auto', 'left', 'center'], 392 icon_style: ['check', 'arrow', 'star', 'dot', 'none'], 393 icon_position: ['left', 'right'], 394 button_style: ['solid', 'outline', 'soft'], 395 badge_style: ['auto', 'pill', 'ribbon', 'stripe'], 396 behavior: ['auto', 'horizontal', 'vertical', 'scroll', 'carousel'] 397 }; 398 399 function setVar(name, value) { 400 previews.each(function() { this.style.setProperty(name, value); }); 401 } 402 403 function swapClass(group, value) { 404 var prefix = classGroups[group]; 405 classValues[group].forEach(function(v) { previews.removeClass(prefix + v); }); 406 previews.addClass(prefix + value); 407 } 408 409 function apply(key, control) { 410 var $c = $(control); 411 var value = control.type === 'checkbox' ? ($c.prop('checked') ? '1' : '0') : String($c.val()); 412 413 switch (key) { 414 case 'card_shadow': 415 setVar('--ssl-shadow', shadows[value][0]); 416 setVar('--ssl-shadow-hover', shadows[value][1]); 417 break; 418 case 'card_border': setVar('--ssl-border-w', value + 'px'); break; 419 case 'card_radius': setVar('--ssl-radius', value + 'px'); $c.siblings('output').text(value); break; 420 case 'card_gap': setVar('--ssl-gap', value + 'px'); $c.siblings('output').text(value); break; 421 case 'card_padding': setVar('--ssl-pad', paddings[value]); break; 422 case 'icon_size': setVar('--ssl-icon-size', iconSizes[value]); break; 423 case 'button_size': 424 setVar('--ssl-btn-pad', btnPad[value]); 425 setVar('--ssl-btn-font', btnFont[value]); 426 break; 427 case 'button_shape': setVar('--ssl-btn-radius', btnRadius[value]); break; 428 case 'badge_color': setVar('--ssl-badge-bg', value === 'primary' ? 'var(--ss-primary)' : 'var(--ss-accent)'); break; 429 case 'badge_text': $('.ss-admin-preview .ss-plan-badge').text(value); break; 430 case 'badge_enabled': $('.ss-admin-preview .ss-plan-badge').toggle(value === '1'); break; 431 case 'button_full': previews.toggleClass('ss-btn-full', value === '1'); break; 432 case 'carousel_visible': $c.siblings('output').text(value); break; 433 case 'behavior': 434 swapClass(key, value); 435 $('.ss-carousel-options').prop('hidden', value !== 'carousel'); 436 $('.ss-carousel-preview-note').prop('hidden', value !== 'carousel'); 437 break; 438 default: 439 if (classGroups[key]) { swapClass(key, value); } 440 } 441 } 442 443 $('.ss-design-control').on('input change', function() { 444 apply($(this).data('key'), this); 445 }); 254 446 }); 255 </script> 447 </script> -
subscription-system/trunk/includes/class-subs-sys-access.php
r3650727 r3651653 2 2 /** 3 3 * Centralized access-control API — the single place every shortcode, template, 4 * the paywall content filter,and AJAX handler asks "can this user see this?"4 * and AJAX handler asks "can this user see this?" 5 5 * instead of each re-deriving its own version of that logic (README §10.4). 6 6 * -
subscription-system/trunk/includes/class-subs-sys-checkout.php
r3650727 r3651653 2 2 /** 3 3 * The one checkout entry point. Every current call site that builds a Stripe 4 * Checkout Session (the [pricing_plans] AJAX handlers, the paywall CTA, and any5 * future entrypoint) calls Subs_Sys_Checkout::start() instead of each4 * Checkout Session (the [pricing_plans] AJAX handlers, and any future entry 5 * point) calls Subs_Sys_Checkout::start() instead of each 6 6 * constructing its own session with copy-pasted, sometimes-hardcoded logic 7 7 * (README §6.2). -
subscription-system/trunk/includes/class-subs-sys-db-updater.php
r3650727 r3651653 31 31 * Current database version 32 32 */ 33 const DB_VERSION = '1.6. 0';33 const DB_VERSION = '1.6.1'; 34 34 35 35 /** … … 312 312 Subs_Sys_Content_Schema::create_tables(); 313 313 } 314 } 315 316 // Paywall removal (1.6.1): the "This content is for subscribers" content 317 // gate no longer exists, so sites upgrading from a version that had it 318 // must not keep its orphaned setting around. 319 if ( version_compare( $old_version, '1.6.1', '<' ) ) { 320 delete_option( 'ss_paywall_teaser_word_count' ); 314 321 } 315 322 -
subscription-system/trunk/includes/class-subs-sys-shortcodes.php
r3650727 r3651653 139 139 'columns' => '3', 140 140 'orientation' => 'vertical', // New attribute for orientation 141 'behavior' => '', // auto|horizontal|vertical|scroll|carousel — overrides the saved display behavior 141 142 'filters' => '', // "yes" renders the category filter bar 142 143 ), … … 213 214 // Add horizontal class if orientation is horizontal 214 215 $orientation_class = ($atts['orientation'] === 'horizontal') ? 'horizontal' : ''; 216 217 // Per-shortcode display behavior. The legacy orientation="horizontal" 218 // attribute maps onto the horizontal behavior so old embeds keep 219 // their meaning under the new behavior system. 220 $ss_behavior = strtolower(trim((string) $atts['behavior'])); 221 if ('' === $ss_behavior && 'horizontal' === $atts['orientation']) { 222 $ss_behavior = 'horizontal'; 223 } 215 224 216 225 // Include the appropriate template based on layout -
subscription-system/trunk/includes/class-subs-sys-table-registry.php
r3650727 r3651653 57 57 'ss_subscriptions' => array( 'status' => 'active', 'feature' => __( 'Site subscriptions (unified)', 'subscription-system' ), 'note' => __( 'Written by checkout/webhooks; read by access control and the account page.', 'subscription-system' ) ), 58 58 'ss_orders' => array( 'status' => 'active', 'feature' => __( 'Orders (unified)', 'subscription-system' ), 'note' => __( 'Payment records from the unified checkout.', 'subscription-system' ) ), 59 'ss_entitlements' => array( 'status' => 'active', 'feature' => __( 'Access entitlements', 'subscription-system' ), 'note' => __( 'What each subscription unlocks; read by the paywall.', 'subscription-system' ) ),59 'ss_entitlements' => array( 'status' => 'active', 'feature' => __( 'Access entitlements', 'subscription-system' ), 'note' => __( 'What each subscription unlocks; read by access checks.', 'subscription-system' ) ), 60 60 'ss_entitlement_rules' => array( 'status' => 'active', 'feature' => __( 'Entitlement rules', 'subscription-system' ), 'note' => __( 'Which content each plan gates.', 'subscription-system' ) ), 61 61 'ss_webhook_events' => array( 'status' => 'active', 'feature' => __( 'Stripe webhook idempotency ledger', 'subscription-system' ), 'note' => __( 'Stops a replayed webhook from acting twice.', 'subscription-system' ) ), -
subscription-system/trunk/includes/class-subs-sys.php
r3650727 r3651653 223 223 require_once __DIR__ . '/class-subs-sys-colors.php'; 224 224 225 // Pricing layout design settings (badge, card chrome, display behavior) — 226 // read by the pricing templates, the Pricing Settings screen, and the 227 // public side that prints the --ssl-* variables. 228 require_once __DIR__ . '/class-subs-sys-layout-settings.php'; 229 225 230 // Auto-attached Login/Register items in the site navigation. 226 231 require_once __DIR__ . '/class-subs-sys-menu.php'; … … 257 262 require_once __DIR__ . '/class-subs-sys-reconciler.php'; 258 263 Subs_Sys_Reconciler::init(); 259 260 /**261 * The content paywall - the plugin's core stated purpose, previously absent262 * from the codebase entirely.263 */264 require_once __DIR__ . '/class-subs-sys-paywall.php';265 264 266 265 /** … … 411 410 $plugin_webhook = new Subs_Sys_Webhook(); 412 411 413 // The single unified webhook endpoint + the content paywall.412 // The single unified webhook endpoint. 414 413 $webhook_v2 = new Subs_Sys_Webhook_V2(); 415 $paywall = new Subs_Sys_Paywall();416 414 417 415 // Keep ss_entitlements in sync whenever a subscription's status changes 418 416 // (fired by Subs_Sys_Webhook_V2's event handlers). 419 417 $this->loader->add_action( 'ss_subscription_status_changed', $this, 'on_subscription_status_changed', 10, 3 ); 420 421 // Single checkout entry point for the paywall CTA.422 $this->loader->add_action( 'wp_ajax_ss_start_checkout', $this, 'ajax_start_checkout' );423 418 424 419 // Initialize content subscription manager … … 488 483 Subs_Sys_Access::refresh_entitlements_for_user( (int) $user_id ); 489 484 } 490 }491 492 /**493 * AJAX: start a Stripe Checkout Session for a plan price (the paywall CTA's494 * "Subscribe to unlock" button).495 */496 public function ajax_start_checkout() {497 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'ss_paywall_checkout' ) ) {498 wp_send_json_error( array( 'message' => __( 'Security check failed.', 'subscription-system' ) ) );499 return;500 }501 if ( ! is_user_logged_in() ) {502 wp_send_json_error( array( 'message' => __( 'Please log in to subscribe.', 'subscription-system' ) ) );503 return;504 }505 506 $plan_price_id = isset( $_POST['plan_price_id'] ) ? absint( $_POST['plan_price_id'] ) : 0;507 if ( ! $plan_price_id ) {508 wp_send_json_error( array( 'message' => __( 'Invalid plan.', 'subscription-system' ) ) );509 return;510 }511 512 $context = array();513 if ( ! empty( $_POST['return_post_id'] ) ) {514 $post_id = absint( $_POST['return_post_id'] );515 $context['return_post_id'] = $post_id;516 $context['success_url'] = get_permalink( $post_id ) ?: home_url( '/' );517 $context['cancel_url'] = get_permalink( $post_id ) ?: home_url( '/' );518 }519 520 $result = Subs_Sys_Checkout::start( get_current_user_id(), $plan_price_id, $context );521 522 if ( is_wp_error( $result ) ) {523 wp_send_json_error( array( 'message' => $result->get_error_message() ) );524 return;525 }526 527 wp_send_json_success( array( 'url' => $result['url'] ) );528 485 } 529 486 -
subscription-system/trunk/public/class-subs-sys-public.php
r3650727 r3651653 86 86 if ( $color_overrides ) { 87 87 wp_add_inline_style( $this->plugin_name . '-tokens', $color_overrides ); 88 } 89 90 // Pricing layout design settings (--ssl-* chrome variables) print in 91 // the same slot, so every pricing stylesheet can rely on them. 92 if ( class_exists( 'Subs_Sys_Layout_Settings' ) ) { 93 wp_add_inline_style( $this->plugin_name . '-tokens', Subs_Sys_Layout_Settings::inline_css() ); 88 94 } 89 95 -
subscription-system/trunk/public/css/interest-subscribe.css
r3650727 r3651653 276 276 } 277 277 278 /* Feedback alert: hidden until it has a state, then a full-width tinted 279 * panel with an icon — not a bare line of colored text a reader can miss. */ 278 280 .ss-interest-message { 279 min-height: 20px; 280 margin-top: 8px; 281 font-size: 14px; 281 display: none; 282 align-items: flex-start; 283 gap: 10px; 284 margin-top: 14px; 285 padding: 12px 14px; 286 border-radius: 8px; 287 border: 1px solid transparent; 288 font-size: 13px; 289 font-weight: 500; 290 line-height: 1.5; 291 animation: ss-interest-msg-in 0.25s ease; 292 } 293 294 .ss-interest-message.is-error, 295 .ss-interest-message.is-success { 296 display: flex; 297 } 298 299 .ss-interest-message::before { 300 flex: 0 0 auto; 301 width: 18px; 302 height: 18px; 303 margin-top: 1px; 304 border-radius: 50%; 305 display: inline-flex; 306 align-items: center; 307 justify-content: center; 308 font-size: 11px; 309 font-weight: 700; 310 line-height: 1; 311 color: #fff; 312 } 313 314 .ss-interest-message.is-success { 315 background: var(--ss-success-tint); 316 border-color: var(--ss-success); 317 color: var(--ss-success); 318 } 319 320 .ss-interest-message.is-success::before { 321 content: "\2713"; /* ✓ */ 322 background: var(--ss-success); 282 323 } 283 324 284 325 .ss-interest-message.is-error { 326 background: var(--ss-danger-tint); 327 border-color: var(--ss-danger); 285 328 color: var(--ss-danger); 286 329 } 287 330 288 .ss-interest-message.is-success { 289 color: var(--ss-success); 331 .ss-interest-message.is-error::before { 332 content: "!"; 333 background: var(--ss-danger); 334 } 335 336 @keyframes ss-interest-msg-in { 337 from { 338 opacity: 0; 339 transform: translateY(4px); 340 } 341 to { 342 opacity: 1; 343 transform: none; 344 } 345 } 346 347 @media (prefers-reduced-motion: reduce) { 348 .ss-interest-message { 349 animation: none; 350 } 290 351 } 291 352 … … 357 418 opacity: 0.65; 358 419 cursor: not-allowed; 420 } 421 422 /* Success state after the subscription completes — the button confirms the 423 * outcome instead of sitting on its "Setting up…" busy label. !important so 424 * it outranks the theme-armor rule painting the primary button. */ 425 .ss-interest-btn--primary.ss-interest-btn--done, 426 .ss-interest-btn--primary.ss-interest-btn--done:disabled { 427 background: var(--ss-success) !important; 428 color: #fff !important; 429 opacity: 1; 430 cursor: default; 359 431 } 360 432 -
subscription-system/trunk/public/css/pricing-layout1.css
r3650727 r3651653 1 1 /** 2 * Pricing Layout 1 - Clean Modern Style2 * Pricing Layout 1 — "Classic" 3 3 * 4 * This is the ONLY stylesheet for this layout — the template 5 * (pricing-plans-layout1.php) used to also echo a duplicate inline 6 * <style> block after this file's <link>, which fought it for control 7 * of .ss-pricing-plan's background (both rules used !important at tied 8 * specificity, so the later-in-document inline block always won, 9 * making every card render transparent instead of white). That inline 10 * block has been removed; its design (rounded pill buttons, hover 11 * lift) has been merged in here instead, so there is exactly one rule 12 * that can win for any given property. 4 * Centered floating cards: uppercase plan name, big tabular price over a 5 * hairline divider, check list, full-width CTA. The badge (settings-driven, 6 * see pricing-shared.css) floats through the featured card's top edge. 7 * 8 * Card chrome (radius, border, shadow, padding, gap), icons, buttons, badge 9 * and display behaviors all come from pricing-shared.css + the --ssl-* vars; 10 * this file only supplies Layout 1's own structure and type rhythm. 13 11 */ 14 12 15 *,16 *::before,17 *::after {18 box-sizing: border-box;19 }20 21 /* Container and base layout */22 13 .subscription-pricing-plans.layout1 { 23 14 padding: 40px 20px; 24 15 background: transparent !important; 25 min-height: auto;26 display: flex;27 align-items: center;28 justify-content: center;29 16 } 30 17 … … 40 27 display: grid; 41 28 grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); 42 align-items: st art;29 align-items: stretch; 43 30 justify-content: center; 44 31 max-width: 1200px; 45 32 width: 100%; 46 33 margin: 0 auto; 47 gap: 20px;48 34 } 49 35 50 /* Individual plan cards — size to their own content (no forced51 * equal-height stretch across a row). */52 36 .subscription-pricing-plans.layout1 .ss-pricing-plan { 53 max-width: 3 50px;37 max-width: 360px; 54 38 min-width: 0; 55 39 width: 100%; 56 40 justify-self: center; 57 background-color: var(--ss-surface);58 border-radius: 8px;59 box-shadow: 0 5px 15px rgba(24, 38, 32, 0.08);60 overflow: hidden;61 transition: all 0.3s ease;62 border: 1px solid var(--ss-border);63 position: relative;64 41 } 65 42 66 .subscription-pricing-plans.layout1 .ss-pricing-plan:hover { 67 transform: translateY(-5px); 68 box-shadow: 0 8px 25px rgba(24, 38, 32, 0.12); 43 /* Featured plan: keyline in the brand color, gently raised. */ 44 .subscription-pricing-plans.layout1 .ss-pricing-plan.ss-featured { 45 border-color: var(--ss-primary); 46 border-width: max(var(--ssl-border-w, 1px), 2px); 69 47 } 70 48 71 /* Featured plan styling — gold is reserved for exactly this moment. */ 72 .subscription-pricing-plans.layout1 .ss-pricing-plan.ss-featured { 73 border: 2px solid var(--ss-accent); 74 position: relative; 75 } 76 77 .subscription-pricing-plans.layout1 .ss-pricing-plan.ss-featured::after { 78 content: 'POPULAR'; 79 position: absolute; 80 top: 15px; 81 right: 15px; 82 background-color: var(--ss-accent); 83 color: white; 84 padding: 5px 10px; 85 font-size: 12px; 86 border-radius: 20px; 87 font-weight: bold; 88 } 89 90 /* Plan header */ 49 /* Header */ 91 50 .subscription-pricing-plans.layout1 .ss-plan-header { 92 padding: 25px 20px;51 padding: calc(var(--ssl-pad, 24px) + 6px) var(--ssl-pad, 24px) calc(var(--ssl-pad, 24px) - 4px); 93 52 text-align: center; 94 53 border-bottom: 1px solid var(--ss-border); 95 position: relative;96 54 } 97 55 98 56 .subscription-pricing-plans.layout1 .ss-plan-header h3 { 99 57 margin: 0; 100 font-size: 20px;101 font-weight: 600;58 font-size: 14px; 59 font-weight: 700; 102 60 text-transform: uppercase; 103 letter-spacing: 1px;61 letter-spacing: 0.14em; 104 62 color: var(--ss-ink); 105 63 } … … 110 68 111 69 .subscription-pricing-plans.layout1 .ss-price { 112 font-size: 34px; 113 font-weight: bold; 114 margin-top: 10px; 115 color: var(--ss-primary); 70 margin-top: 12px; 71 font-size: 40px; 72 font-weight: 800; 73 line-height: 1; 74 letter-spacing: -0.02em; 75 font-variant-numeric: tabular-nums; 76 color: var(--ss-ink); 116 77 } 117 78 118 79 .subscription-pricing-plans.layout1 .ss-price span { 119 font-size: 16px; 120 font-weight: normal; 121 opacity: 0.8; 80 font-size: 15px; 81 font-weight: 500; 82 letter-spacing: 0; 83 color: var(--ss-muted); 122 84 } 123 85 124 /* Plan features */86 /* Features */ 125 87 .subscription-pricing-plans.layout1 .ss-plan-features { 126 padding: 0; 127 } 128 129 .subscription-pricing-plans.layout1 .ss-plan-features ul { 130 list-style: none; 131 padding: 0; 132 margin: 0; 88 padding: calc(var(--ssl-pad, 24px) - 8px) var(--ssl-pad, 24px); 133 89 } 134 90 135 91 .subscription-pricing-plans.layout1 .ss-plan-features li { 136 padding: 15px 20px; 137 border-bottom: 1px solid var(--ss-border); 138 text-align: center; 92 padding: 8px 0; 139 93 font-size: 14px; 140 color: var(--ss-body);141 94 } 142 95 143 /* Plan footer and button*/96 /* Footer */ 144 97 .subscription-pricing-plans.layout1 .ss-plan-footer { 145 padding: 25px;98 padding: calc(var(--ssl-pad, 24px) - 6px) var(--ssl-pad, 24px) var(--ssl-pad, 24px); 146 99 text-align: center; 147 background-color: transparent;148 100 } 149 101 150 .subscription-pricing-plans.layout1 .ss-button, 151 .subscription-pricing-plans.layout1 .ss-pricing-button { 152 display: inline-block; 153 padding: 12px 30px; 154 border-radius: 50px; 155 text-decoration: none; 156 font-weight: 600; 157 transition: all 0.3s ease; 158 font-size: 15px; 159 letter-spacing: 0.5px; 160 text-transform: uppercase; 161 background-color: var(--ss-primary); 162 color: white; 163 border: none; 164 cursor: pointer; 165 } 166 167 .subscription-pricing-plans.layout1 .ss-button:hover, 168 .subscription-pricing-plans.layout1 .ss-pricing-button:hover { 169 transform: translateY(-2px); 170 box-shadow: 0 5px 15px rgba(24, 38, 32, 0.15); 171 background-color: var(--ss-primary-dark); 172 } 173 174 /* Responsive Design 175 * Card width adapts to the container; no forced column count. 176 @media (max-width: 992px) { 177 .subscription-pricing-plans.layout1 .ss-pricing-plan { 178 width: 100%; 179 max-width: 450px; 180 } 181 } 182 102 /* Responsive type + spacing. Structure changes (the mobile swipe strip, 103 * stacking, carousels) are handled by the behavior classes in 104 * pricing-shared.css. */ 183 105 @media (max-width: 768px) { 184 106 .subscription-pricing-plans.layout1 { … … 186 108 } 187 109 188 .subscription-pricing-plans.layout1 .ss-pricing-plan {189 max-width: 100%;190 margin: 10px 0;191 }192 193 .subscription-pricing-plans.layout1 .ss-plan-header h3 {194 font-size: 1.5rem;195 }196 197 110 .subscription-pricing-plans.layout1 .ss-price { 198 font-size: 1.8rem; 199 } 200 201 .subscription-pricing-plans.layout1 .ss-price span { 202 font-size: 0.9rem; 203 } 204 205 .subscription-pricing-plans.layout1 .ss-plan-features li { 206 font-size: 0.9rem; 207 padding: 8px 10px; 111 font-size: 34px; 208 112 } 209 113 } 210 211 @media (max-width: 480px) {212 .subscription-pricing-plans.layout1 .ss-plan-header h3 {213 font-size: 1.3rem;214 }215 216 .subscription-pricing-plans.layout1 .ss-price {217 font-size: 1.6rem;218 }219 }220 221 /* Feature list: check marks + tighter rhythm, so a one-line plan222 * doesn't render as a mostly-empty panel. */223 .subscription-pricing-plans.layout1 .ss-plan-features {224 padding: 6px 0;225 }226 227 .subscription-pricing-plans.layout1 .ss-plan-features li {228 display: flex;229 align-items: flex-start;230 gap: 10px;231 text-align: left;232 padding: 10px 24px;233 border-bottom: none;234 line-height: 1.5;235 }236 237 .subscription-pricing-plans.layout1 .ss-plan-features li::before {238 content: "✓";239 color: var(--ss-primary);240 font-weight: 700;241 line-height: 1.4;242 flex-shrink: 0;243 }244 245 .subscription-pricing-plans.layout1 .ss-plan-footer {246 padding: 18px 24px 26px;247 }248 249 .subscription-pricing-plans.layout1 .ss-button,250 .subscription-pricing-plans.layout1 .ss-pricing-button {251 width: 100%;252 }253 254 /* "Current Plan" — a plan the logged-in user already owns. Disabled, so they255 can see they have it and cannot buy it twice. */256 .ss-button--owned,257 .ss-button--owned:hover {258 background: var(--ss-success-tint, #d8f2e0) !important;259 color: var(--ss-success, #14603a) !important;260 border: 1px solid var(--ss-success, #86c9a3) !important;261 cursor: not-allowed;262 box-shadow: none !important;263 transform: none !important;264 } -
subscription-system/trunk/public/css/pricing-layout2.css
r3650727 r3651653 1 1 /** 2 * Pricing Layout 2 - Elegant Style2 * Pricing Layout 2 — "Ledger" 3 3 * 4 * This is the ONLY stylesheet for this layout — the template 5 * (pricing-plans-layout2.php) used to also echo a duplicate inline 6 * <style> block after this file's <link>. Because the inline block's 7 * base-card rule used a shorter selector than this file's, it lost for 8 * ordinary cards but WON for the featured card (tied specificity, 9 * later in the document) — so only the "Popular" card rendered 10 * transparent instead of white. That inline block has been removed 11 * entirely; there is exactly one rule that can win for any property. 4 * A single flat, hairline-ruled sheet: the plans are columns of one 5 * continuous panel rather than three floating cards. Left-aligned editorial 6 * type — small tracked eyebrow for the plan name, a large baseline-set 7 * price, thin rules between features. The featured plan is a fully 8 * inverted column in the brand color. 9 * 10 * The sheet only holds together in the grid behaviors; when a display 11 * behavior turns the set into a strip, stack or carousel, each column 12 * gets its own card chrome back (see the overrides at the bottom). 12 13 */ 13 14 14 *,15 *::before,16 *::after {17 box-sizing: border-box;18 }19 20 15 .subscription-pricing-plans.layout2 { 16 padding: 40px 20px; 21 17 background: transparent !important; 22 padding: 40px 20px;23 18 } 24 19 … … 27 22 justify-content: center; 28 23 width: 100%; 29 padding: 20px 0;30 24 background: transparent !important; 31 25 } 32 26 33 .subscription-pricing-plans.layout2 .ss-pricing-plans, 34 .subscription-pricing-plans.layout2 .ss-pricing- container{27 /* The sheet: one bordered panel; the cards inside are flat columns. */ 28 .subscription-pricing-plans.layout2 .ss-pricing-plans { 35 29 display: grid; 36 grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); 37 align-items: start; 38 justify-content: center; 39 max-width: 1200px; 30 grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr)); 31 align-items: stretch; 32 max-width: 1160px; 40 33 width: 100%; 41 margin: 0 auto; 42 gap: 20px; 43 } 44 45 /* Individual plan cards — size to their own content (no forced 46 * equal-height stretch across a row). */ 34 gap: 0; 35 background: var(--ss-surface); 36 border: 1px solid var(--ss-border); 37 border-radius: var(--ssl-radius, 12px); 38 box-shadow: var(--ssl-shadow, none); 39 overflow: hidden; 40 } 41 47 42 .subscription-pricing-plans.layout2 .ss-pricing-plan { 48 max-width: 350px; 49 width: 100%; 50 justify-self: center; 51 background-color: var(--ss-surface); 52 border: 1px solid var(--ss-border); 53 box-shadow: 0 8px 20px rgba(24, 38, 32, 0.06); 54 border-radius: 8px; 55 overflow: hidden; 56 transition: all 0.3s ease; 57 position: relative; 58 } 59 60 .subscription-pricing-plans.layout2 .ss-pricing-plan:hover { 61 transform: translateY(-5px); 62 box-shadow: 0 15px 30px rgba(24, 38, 32, 0.1); 63 } 64 65 /* Featured plan styling — gold is reserved for exactly this moment. */ 43 background: transparent; 44 border: none; 45 border-left: 1px solid var(--ss-border); 46 border-radius: 0; 47 box-shadow: none; 48 max-width: none; 49 padding: calc(var(--ssl-pad, 24px) + 8px) var(--ssl-pad, 24px); 50 } 51 52 .subscription-pricing-plans.layout2 .ss-pricing-plan:first-child { 53 border-left: none; 54 } 55 56 /* Columns don't lift out of the sheet — the sheet is the object. */ 57 .subscription-pricing-plans.ss-responsive.ss-hover-lift.layout2 .ss-pricing-plan:hover, 58 .subscription-pricing-plans.ss-responsive.ss-hover-scale.layout2 .ss-pricing-plan:hover { 59 transform: none; 60 box-shadow: none; 61 } 62 63 .subscription-pricing-plans.ss-responsive.ss-hover-glow.layout2 .ss-pricing-plan:hover { 64 box-shadow: inset 0 0 0 2px var(--ss-primary); 65 border-color: var(--ss-border); 66 } 67 68 /* Header: eyebrow name, then the price set big on its baseline. */ 69 .subscription-pricing-plans.layout2 .ss-plan-header { 70 text-align: left; 71 padding: 0 0 18px; 72 border-bottom: 1px solid var(--ss-border); 73 } 74 75 .subscription-pricing-plans.layout2 .ss-plan-header h3 { 76 margin: 0 0 14px; 77 font-size: 12px; 78 font-weight: 700; 79 text-transform: uppercase; 80 letter-spacing: 0.18em; 81 color: var(--ss-muted); 82 } 83 84 .subscription-pricing-plans.layout2 .ss-price { 85 font-size: 44px; 86 font-weight: 750; 87 line-height: 1; 88 letter-spacing: -0.03em; 89 font-variant-numeric: tabular-nums; 90 color: var(--ss-ink); 91 } 92 93 .subscription-pricing-plans.layout2 .ss-price span { 94 font-size: 14px; 95 font-weight: 500; 96 letter-spacing: 0; 97 color: var(--ss-muted); 98 margin-left: 2px; 99 } 100 101 /* Features: compact ruled list. */ 102 .subscription-pricing-plans.layout2 .ss-plan-features { 103 padding: 10px 0 22px; 104 } 105 106 .subscription-pricing-plans.layout2 .ss-plan-features li { 107 padding: 10px 0; 108 font-size: 14px; 109 border-bottom: 1px solid color-mix(in srgb, var(--ss-border) 55%, transparent); 110 } 111 112 .subscription-pricing-plans.layout2 .ss-plan-features li:last-child { 113 border-bottom: none; 114 } 115 116 .subscription-pricing-plans.layout2 .ss-plan-footer { 117 text-align: left; 118 padding: 0; 119 } 120 121 /* ── The inverted featured column ───────────────────────────────────────── */ 122 66 123 .subscription-pricing-plans.layout2 .ss-pricing-plan.ss-featured { 67 background-color: var(--ss-surface); 68 border: 2px solid var(--ss-accent); 69 transform: scale(1.05); 70 z-index: 1; 71 box-shadow: 0 15px 30px rgba(192, 139, 30, 0.25); 72 position: relative; 73 } 74 75 .subscription-pricing-plans.layout2 .ss-pricing-plan.ss-featured::before { 76 content: 'POPULAR'; 77 position: absolute; 78 top: 15px; 79 right: 15px; 80 background-color: var(--ss-accent); 81 color: white; 82 padding: 5px 10px; 83 font-size: 12px; 84 border-radius: 20px; 85 font-weight: bold; 86 } 87 88 .subscription-pricing-plans.layout2 .ss-plan-header { 89 padding: 30px 20px; 90 text-align: center; 91 border-bottom: 1px solid var(--ss-border); 92 position: relative; 93 } 94 95 .subscription-pricing-plans.layout2 .ss-plan-header h3 { 96 margin: 0; 97 font-size: 20px; 98 font-weight: 600; 99 text-transform: uppercase; 100 letter-spacing: 1px; 101 color: var(--ss-ink); 124 background: var(--ss-primary); 125 border-left-color: var(--ss-primary); 126 } 127 128 .subscription-pricing-plans.layout2 .ss-featured + .ss-pricing-plan { 129 border-left-color: var(--ss-primary); 130 } 131 132 .subscription-pricing-plans.layout2 .ss-featured .ss-plan-header { 133 border-bottom-color: rgba(255, 255, 255, 0.25); 102 134 } 103 135 104 136 .subscription-pricing-plans.layout2 .ss-featured .ss-plan-header h3 { 137 color: rgba(255, 255, 255, 0.75); 138 } 139 140 .subscription-pricing-plans.layout2 .ss-featured .ss-price, 141 .subscription-pricing-plans.layout2 .ss-featured .ss-price span { 142 color: #fff; 143 } 144 145 .subscription-pricing-plans.layout2 .ss-featured .ss-price span { 146 color: rgba(255, 255, 255, 0.72); 147 } 148 149 .subscription-pricing-plans.layout2 .ss-featured .ss-plan-features li { 150 color: rgba(255, 255, 255, 0.88); 151 border-bottom-color: rgba(255, 255, 255, 0.18); 152 } 153 154 .subscription-pricing-plans.ss-responsive.layout2 .ss-featured .ss-plan-features li::before { 155 color: #fff; 156 } 157 158 /* Badge chip on the inverted column: white chip, primary text. */ 159 .subscription-pricing-plans.ss-responsive.layout2 .ss-featured .ss-plan-badge { 160 background: #fff; 105 161 color: var(--ss-primary); 106 162 } 107 163 108 .subscription-pricing-plans.layout2 .ss-price { 109 font-size: 36px; 110 font-weight: bold; 111 margin-top: 10px; 164 /* The featured CTA must read on the primary background whatever the global 165 * button variant is: white button, primary label. */ 166 .subscription-pricing-plans.ss-responsive.layout2 .ss-featured .ss-button, 167 .subscription-pricing-plans.ss-responsive.layout2 .ss-featured .ss-pricing-button { 168 background: #fff; 169 border-color: #fff; 112 170 color: var(--ss-primary); 113 171 } 114 172 115 .subscription-pricing-plans.layout2 .ss-price span { 116 font-size: 16px; 117 font-weight: normal; 118 opacity: 0.8; 119 } 120 121 .subscription-pricing-plans.layout2 .ss-plan-features { 122 padding: 0; 123 } 124 125 .subscription-pricing-plans.layout2 .ss-plan-features ul { 126 list-style: none; 127 padding: 0; 128 margin: 0; 129 } 130 131 .subscription-pricing-plans.layout2 .ss-plan-features li { 132 padding: 15px 20px; 133 border-bottom: 1px solid var(--ss-border); 134 text-align: center; 135 font-size: 14px; 136 color: var(--ss-body); 137 } 138 139 .subscription-pricing-plans.layout2 .ss-plan-footer { 140 padding: 30px; 141 text-align: center; 142 background-color: transparent; 143 } 144 145 .subscription-pricing-plans.layout2 .ss-button, 146 .subscription-pricing-plans.layout2 .ss-pricing-button { 147 display: inline-block; 148 padding: 12px 30px; 149 border-radius: 50px; 150 text-decoration: none; 151 font-weight: 600; 152 transition: all 0.3s ease; 153 font-size: 15px; 154 letter-spacing: 0.5px; 155 text-transform: uppercase; 173 .subscription-pricing-plans.ss-responsive.layout2 .ss-featured .ss-button:hover, 174 .subscription-pricing-plans.ss-responsive.layout2 .ss-featured .ss-pricing-button:hover { 175 background: rgba(255, 255, 255, 0.88); 176 border-color: rgba(255, 255, 255, 0.88); 177 color: var(--ss-primary-dark); 178 } 179 180 /* ── When a behavior breaks up the sheet, columns become cards again ────── */ 181 182 .subscription-pricing-plans.layout2:is(.ss-behave-vertical, .ss-behave-horizontal, .ss-behave-scroll, .ss-behave-carousel) .ss-pricing-plans { 183 background: transparent; 184 border: none; 185 border-radius: 0; 186 box-shadow: none; 187 overflow: visible; 188 gap: var(--ssl-gap, 24px); 189 } 190 191 .subscription-pricing-plans.layout2:is(.ss-behave-vertical, .ss-behave-horizontal, .ss-behave-scroll, .ss-behave-carousel) .ss-pricing-plan, 192 .subscription-pricing-plans.layout2:is(.ss-behave-vertical, .ss-behave-horizontal, .ss-behave-scroll, .ss-behave-carousel) .ss-pricing-plan:first-child { 193 background: var(--ss-surface); 194 border: var(--ssl-border-w, 1px) solid var(--ss-border); 195 border-radius: var(--ssl-radius, 12px); 196 box-shadow: var(--ssl-shadow, none); 197 } 198 199 .subscription-pricing-plans.layout2:is(.ss-behave-vertical, .ss-behave-horizontal, .ss-behave-scroll, .ss-behave-carousel) .ss-pricing-plan.ss-featured { 156 200 background: var(--ss-primary); 157 color: white; 158 border: none; 159 cursor: pointer; 160 box-shadow: var(--ss-shadow); 161 } 162 163 .subscription-pricing-plans.layout2 .ss-featured .ss-button, 164 .subscription-pricing-plans.layout2 .ss-featured .ss-pricing-button { 165 background: var(--ss-primary); 166 color: white; 167 } 168 169 .subscription-pricing-plans.layout2 .ss-button:hover, 170 .subscription-pricing-plans.layout2 .ss-pricing-button:hover { 171 background: var(--ss-primary-dark); 172 transform: translateY(-3px); 173 box-shadow: 0 8px 20px rgba(24, 38, 32, 0.18); 174 } 175 176 /* Responsive styles */ 177 @media screen and (max-width: 992px) { 178 .subscription-pricing-plans.layout2 .ss-pricing-plan { 179 max-width: 100%; 180 } 181 182 .subscription-pricing-plans.layout2 .ss-pricing-plan.ss-featured { 183 transform: none; 184 } 185 186 .subscription-pricing-plans.layout2 .ss-button { 187 width: 100%; 188 padding: 12px 10px; 189 } 190 } 191 192 /* Responsive type + spacing. 193 @media screen and (max-width: 768px) { 194 .subscription-pricing-plans.layout2 .ss-plan-header h3 { 195 font-size: 1.5rem; 201 border-color: var(--ss-primary); 202 } 203 204 /* Same again for the automatic mobile swipe strip. */ 205 @media (max-width: 767px) { 206 .subscription-pricing-plans.layout2.ss-behave-auto .ss-pricing-plans { 207 background: transparent; 208 border: none; 209 border-radius: 0; 210 box-shadow: none; 211 overflow: visible; 212 gap: var(--ssl-gap, 24px); 213 } 214 215 .subscription-pricing-plans.layout2.ss-behave-auto .ss-pricing-plan, 216 .subscription-pricing-plans.layout2.ss-behave-auto .ss-pricing-plan:first-child { 217 background: var(--ss-surface); 218 border: var(--ssl-border-w, 1px) solid var(--ss-border); 219 border-radius: var(--ssl-radius, 12px); 220 box-shadow: var(--ssl-shadow, none); 221 } 222 223 .subscription-pricing-plans.layout2.ss-behave-auto .ss-pricing-plan.ss-featured { 224 background: var(--ss-primary); 225 border-color: var(--ss-primary); 226 } 227 228 .subscription-pricing-plans.layout2 { 229 padding: 20px 10px; 196 230 } 197 231 198 232 .subscription-pricing-plans.layout2 .ss-price { 199 font-size: 1.8rem; 200 } 201 202 .subscription-pricing-plans.layout2 .ss-price span { 203 font-size: 0.9rem; 204 } 205 206 .subscription-pricing-plans.layout2 .ss-plan-features li { 207 font-size: 0.9rem; 208 padding: 8px 10px; 209 } 210 211 .subscription-pricing-plans.layout2 .ss-button { 212 padding: 14px 20px; 213 font-size: 1rem; 214 } 215 216 .subscription-pricing-plans.layout2 .ss-plan-header, 217 .subscription-pricing-plans.layout2 .ss-plan-features { 218 margin-bottom: 15px; 219 } 220 } 221 222 @media screen and (max-width: 480px) { 223 .subscription-pricing-plans.layout2 .ss-plan-header h3 { 224 font-size: 1.3rem; 225 } 226 227 .subscription-pricing-plans.layout2 .ss-price { 228 font-size: 1.6rem; 229 } 230 } 231 232 /* Feature list: check marks + tighter rhythm. */ 233 .subscription-pricing-plans.layout2 .ss-plan-features li { 234 display: flex; 235 align-items: flex-start; 236 gap: 10px; 237 text-align: left; 238 padding: 10px 24px; 239 border-bottom: none; 240 line-height: 1.5; 241 } 242 243 .subscription-pricing-plans.layout2 .ss-plan-features li::before { 244 content: "✓"; 245 color: var(--ss-primary); 246 font-weight: 700; 247 line-height: 1.4; 248 flex-shrink: 0; 249 margin-right: 0; 250 } 251 252 .subscription-pricing-plans.layout2 .ss-plan-footer { 253 padding: 18px 24px 26px; 254 } 255 256 .subscription-pricing-plans.layout2 .ss-button, 257 .subscription-pricing-plans.layout2 .ss-pricing-button { 258 width: 100%; 259 } 260 261 /* "Current Plan" — a plan the logged-in user already owns. Disabled, so they 262 can see they have it and cannot buy it twice. */ 263 .ss-button--owned, 264 .ss-button--owned:hover { 265 background: var(--ss-success-tint, #d8f2e0) !important; 266 color: var(--ss-success, #14603a) !important; 267 border: 1px solid var(--ss-success, #86c9a3) !important; 268 cursor: not-allowed; 269 box-shadow: none !important; 270 transform: none !important; 271 } 233 font-size: 36px; 234 } 235 } -
subscription-system/trunk/public/css/pricing-layout3.css
r3650727 r3651653 1 1 /** 2 * Pricing Layout 3 - Modern Cards Style2 * Pricing Layout 3 — "Spotlight" 3 3 * 4 * This is the ONLY stylesheet for this layout — the template 5 * (pricing-plans-layout3.php) used to also echo a duplicate inline 6 * <style> block after this file's <link>, with the same specificity 7 * mismatch as layout 2 (base rule loses, featured-card rule wins) — 8 * so only the "Best Value" card rendered transparent instead of 9 * white. That inline block has been removed entirely. 4 * A premium dark stage: the whole strip sits on a deep panel mixed from the 5 * ink and primary tokens. Non-featured plans are translucent glass cards on 6 * the dark; the featured plan is the one card in the light — a solid surface 7 * card with an accent keyline, raised and gently scaled. Check icons go 8 * accent-colored here so they read on the dark ground. 9 * 10 * Everything still derives from the Appearance tokens: repaint the palette 11 * and the stage repaints with it. 10 12 */ 11 13 12 *,13 *::before,14 *::after {15 box-sizing: border-box;16 }17 18 14 .subscription-pricing-plans.layout3 { 15 padding: 40px 20px; 19 16 background: transparent !important; 20 padding: 40px 20px; 21 } 22 17 } 18 19 /* The stage. */ 23 20 .subscription-pricing-plans.layout3 .ss-pricing-container { 24 display: flex; 25 justify-content: center; 26 width: 100%; 27 padding: 20px 0; 28 background: transparent !important; 29 } 30 31 .subscription-pricing-plans.layout3 .ss-pricing-plans, 32 .subscription-pricing-plans.layout3 .ss-pricing-container { 21 position: relative; 22 max-width: 1200px; 23 margin: 0 auto; 24 padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 48px); 25 border-radius: max(var(--ssl-radius, 12px), 18px); 26 background: var(--ss-ink); 27 background: linear-gradient( 28 140deg, 29 color-mix(in srgb, var(--ss-ink) 94%, var(--ss-primary) 6%) 0%, 30 color-mix(in srgb, var(--ss-ink) 62%, var(--ss-primary-dark) 38%) 100% 31 ); 32 overflow: hidden; 33 } 34 35 /* A faint top-light so the panel reads lit, not flat. */ 36 .subscription-pricing-plans.layout3 .ss-pricing-container::before { 37 content: ""; 38 position: absolute; 39 inset: 0; 40 background: radial-gradient(60% 45% at 50% 0%, rgba(255, 255, 255, 0.10) 0%, transparent 70%); 41 pointer-events: none; 42 } 43 44 .subscription-pricing-plans.layout3 .ss-pricing-plans { 45 position: relative; 33 46 display: grid; 34 grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); 35 align-items: start; 36 justify-content: center; 37 max-width: 1200px; 38 width: 100%; 39 margin: 0 auto; 40 gap: 20px; 41 } 42 43 /* Individual plan cards — size to their own content (no forced 44 * equal-height stretch across a row). */ 47 grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); 48 align-items: center; 49 } 50 51 /* Glass cards on the dark. */ 45 52 .subscription-pricing-plans.layout3 .ss-pricing-plan { 46 max-width: 350px; 53 background: rgba(255, 255, 255, 0.06); 54 border: 1px solid rgba(255, 255, 255, 0.14); 55 box-shadow: none; 56 max-width: 380px; 47 57 width: 100%; 48 58 justify-self: center; 49 background-color: var(--ss-surface); 50 border-radius: 15px; 51 border: 1px solid var(--ss-border); 52 transition: all 0.3s ease; 53 box-shadow: 0 10px 25px rgba(24, 38, 32, 0.08); 54 overflow: hidden; 55 position: relative; 56 } 57 58 .subscription-pricing-plans.layout3 .ss-pricing-plan:hover { 59 transform: translateY(-10px); 60 box-shadow: 0 20px 40px rgba(24, 38, 32, 0.12); 61 } 62 63 /* Featured plan styling — gold is reserved for exactly this moment. */ 64 .subscription-pricing-plans.layout3 .ss-pricing-plan.ss-featured { 65 background-color: var(--ss-surface); 66 border: 2px solid var(--ss-accent); 67 box-shadow: 0 15px 30px rgba(192, 139, 30, 0.25); 68 position: relative; 69 } 70 71 .subscription-pricing-plans.layout3 .ss-pricing-plan.ss-featured::before { 72 content: 'BEST VALUE'; 73 position: absolute; 74 top: 15px; 75 right: -35px; 76 background-color: var(--ss-accent); 77 color: white; 78 padding: 5px 40px; 79 font-size: 12px; 80 transform: rotate(45deg); 81 font-weight: bold; 82 box-shadow: 0 2px 5px rgba(24, 38, 32, 0.1); 83 z-index: 1; 84 } 85 59 padding: var(--ssl-pad, 24px); 60 } 61 62 .subscription-pricing-plans.ss-responsive.ss-hover-lift.layout3 .ss-pricing-plan:hover, 63 .subscription-pricing-plans.ss-responsive.ss-hover-glow.layout3 .ss-pricing-plan:hover { 64 background: rgba(255, 255, 255, 0.09); 65 border-color: rgba(255, 255, 255, 0.26); 66 box-shadow: 0 14px 34px rgba(0, 0, 0, 0.35); 67 } 68 69 /* Header */ 86 70 .subscription-pricing-plans.layout3 .ss-plan-header { 87 padding: 30px 20px;88 position: relative;89 overflow: hidden;90 border-bottom: none;91 71 text-align: center; 72 padding: 8px 0 18px; 73 border-bottom: 1px solid rgba(255, 255, 255, 0.14); 92 74 } 93 75 94 76 .subscription-pricing-plans.layout3 .ss-plan-header h3 { 95 77 margin: 0; 96 font-size: 20px;78 font-size: 13px; 97 79 font-weight: 600; 98 80 text-transform: uppercase; 99 letter-spacing: 1px; 100 color: var(--ss-ink); 81 letter-spacing: 0.16em; 82 color: rgba(255, 255, 255, 0.82); 83 } 84 85 .subscription-pricing-plans.layout3 .ss-price { 86 margin-top: 14px; 87 font-size: 46px; 88 font-weight: 800; 89 line-height: 1; 90 letter-spacing: -0.02em; 91 font-variant-numeric: tabular-nums; 92 color: #fff; 93 } 94 95 .subscription-pricing-plans.layout3 .ss-price span { 96 font-size: 14px; 97 font-weight: 500; 98 letter-spacing: 0; 99 color: rgba(255, 255, 255, 0.6); 100 } 101 102 /* Features */ 103 .subscription-pricing-plans.layout3 .ss-plan-features { 104 padding: 16px 4px; 105 } 106 107 .subscription-pricing-plans.layout3 .ss-plan-features li { 108 padding: 8px 0; 109 font-size: 14px; 110 color: rgba(255, 255, 255, 0.78); 111 } 112 113 .subscription-pricing-plans.ss-responsive.layout3 .ss-plan-features li::before { 114 color: var(--ss-accent); 115 } 116 117 /* CTA on glass cards: ghost white, whatever the global variant. */ 118 .subscription-pricing-plans.ss-responsive.layout3 .ss-button, 119 .subscription-pricing-plans.ss-responsive.layout3 .ss-pricing-button { 120 background: transparent; 121 border-color: rgba(255, 255, 255, 0.45); 122 color: #fff; 123 } 124 125 .subscription-pricing-plans.ss-responsive.layout3 .ss-button:hover, 126 .subscription-pricing-plans.ss-responsive.layout3 .ss-pricing-button:hover { 127 background: #fff; 128 border-color: #fff; 129 color: var(--ss-primary-dark); 130 } 131 132 .subscription-pricing-plans.layout3 .ss-plan-footer { 133 text-align: center; 134 padding: 6px 0 4px; 135 } 136 137 /* ── The card in the spotlight ──────────────────────────────────────────── */ 138 139 .subscription-pricing-plans.layout3 .ss-pricing-plan.ss-featured { 140 background: var(--ss-surface); 141 border-color: var(--ss-surface); 142 box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45); 143 transform: scale(1.045); 144 z-index: 1; 145 padding-top: calc(var(--ssl-pad, 24px) + 6px); 146 padding-bottom: calc(var(--ssl-pad, 24px) + 6px); 147 } 148 149 /* Accent keyline across the featured card's top. */ 150 .subscription-pricing-plans.layout3 .ss-pricing-plan.ss-featured::before { 151 content: ""; 152 position: absolute; 153 top: 0; 154 left: 0; 155 right: 0; 156 height: 4px; 157 background: linear-gradient(90deg, var(--ss-accent), var(--ss-primary)); 158 } 159 160 .subscription-pricing-plans.ss-responsive.ss-hover-lift.layout3 .ss-featured:hover, 161 .subscription-pricing-plans.ss-responsive.ss-hover-scale.layout3 .ss-featured:hover, 162 .subscription-pricing-plans.ss-responsive.ss-hover-glow.layout3 .ss-featured:hover { 163 background: var(--ss-surface); 164 transform: scale(1.045) translateY(-6px); 165 box-shadow: 0 30px 70px rgba(0, 0, 0, 0.5); 166 border-color: var(--ss-surface); 167 } 168 169 .subscription-pricing-plans.layout3 .ss-featured .ss-plan-header { 170 border-bottom-color: var(--ss-border); 101 171 } 102 172 … … 105 175 } 106 176 107 .subscription-pricing-plans.layout3 .ss-price { 108 font-size: 34px; 109 font-weight: bold; 110 margin-top: 10px; 177 .subscription-pricing-plans.layout3 .ss-featured .ss-price { 178 color: var(--ss-ink); 179 } 180 181 .subscription-pricing-plans.layout3 .ss-featured .ss-price span { 182 color: var(--ss-muted); 183 } 184 185 .subscription-pricing-plans.layout3 .ss-featured .ss-plan-features li { 186 color: var(--ss-body); 187 } 188 189 .subscription-pricing-plans.ss-responsive.layout3 .ss-featured .ss-plan-features li::before { 111 190 color: var(--ss-primary); 112 191 } 113 192 114 .subscription-pricing-plans.layout3 .ss-price span { 115 font-size: 16px; 116 font-weight: normal; 117 opacity: 0.8; 118 } 119 120 .subscription-pricing-plans.layout3 .ss-plan-features { 121 padding: 0; 122 } 123 124 .subscription-pricing-plans.layout3 .ss-plan-features ul { 125 list-style: none; 126 padding: 0; 127 margin: 0; 128 } 129 130 .subscription-pricing-plans.layout3 .ss-plan-features li { 131 padding: 15px 20px; 132 border-bottom: 1px solid var(--ss-border); 133 text-align: left; 134 display: flex; 135 align-items: center; 136 font-size: 14px; 137 color: var(--ss-body); 138 } 139 140 .subscription-pricing-plans.layout3 .ss-plan-features li:before { 141 content: "✓"; 142 color: var(--ss-primary); 143 margin-right: 15px; 144 font-weight: bold; 145 } 146 147 .subscription-pricing-plans.layout3 .ss-plan-footer { 148 padding: 30px; 149 text-align: center; 150 background-color: transparent; 151 } 152 153 .subscription-pricing-plans.layout3 .ss-button, 154 .subscription-pricing-plans.layout3 .ss-pricing-button { 155 display: inline-block; 156 padding: 14px 35px; 157 background-color: var(--ss-primary); 158 color: white; 159 border-radius: 30px; 160 text-decoration: none; 161 font-weight: 600; 162 font-size: 15px; 163 letter-spacing: 0.5px; 164 text-transform: uppercase; 165 transition: all 0.3s ease; 166 box-shadow: var(--ss-shadow); 167 border: none; 168 cursor: pointer; 169 } 170 171 .subscription-pricing-plans.layout3 .ss-featured .ss-button, 172 .subscription-pricing-plans.layout3 .ss-featured .ss-pricing-button { 173 background-color: var(--ss-primary); 174 color: white; 175 } 176 177 .subscription-pricing-plans.layout3 .ss-button:hover, 178 .subscription-pricing-plans.layout3 .ss-pricing-button:hover { 179 transform: translateY(-3px); 180 box-shadow: 0 8px 20px rgba(24, 38, 32, 0.18); 181 background-color: var(--ss-primary-dark); 182 } 183 184 /* Responsive styles */ 185 @media screen and (max-width: 992px) { 186 .subscription-pricing-plans.layout3 .ss-pricing-plan { 187 max-width: 100%; 193 /* Featured CTA: solid primary, full strength. */ 194 .subscription-pricing-plans.ss-responsive.layout3 .ss-featured .ss-button, 195 .subscription-pricing-plans.ss-responsive.layout3 .ss-featured .ss-pricing-button { 196 background: var(--ss-primary); 197 border-color: var(--ss-primary); 198 color: #fff; 199 } 200 201 .subscription-pricing-plans.ss-responsive.layout3 .ss-featured .ss-button:hover, 202 .subscription-pricing-plans.ss-responsive.layout3 .ss-featured .ss-pricing-button:hover { 203 background: var(--ss-primary-dark); 204 border-color: var(--ss-primary-dark); 205 } 206 207 /* Owned state must stay readable on both card types. */ 208 .subscription-pricing-plans.ss-responsive.layout3 .ss-button--owned, 209 .subscription-pricing-plans.ss-responsive.layout3 .ss-button--owned:hover { 210 background: var(--ss-success-tint, #D8F2E0) !important; 211 color: var(--ss-success, #14603A) !important; 212 border-color: var(--ss-success, #86C9A3) !important; 213 } 214 215 /* ── Behaviors inside the stage ─────────────────────────────────────────── */ 216 217 /* The scale transform fights carousel/scroll geometry — level the featured 218 * card when the set becomes a strip or slider. */ 219 .subscription-pricing-plans.layout3:is(.ss-behave-scroll, .ss-behave-carousel, .ss-behave-horizontal, .ss-behave-vertical) .ss-pricing-plan.ss-featured { 220 transform: none; 221 } 222 223 @media (max-width: 767px) { 224 .subscription-pricing-plans.layout3.ss-behave-auto .ss-pricing-plan.ss-featured { 225 transform: none; 188 226 } 189 } 190 191 /* Responsive type + spacing. 192 @media screen and (max-width: 768px) { 193 .subscription-pricing-plans.layout3 .ss-pricing-plan.ss-featured { 194 order: -1; /* Move featured plan to the top on mobile */ 227 228 .subscription-pricing-plans.layout3 { 229 padding: 20px 10px; 195 230 } 196 231 197 .subscription-pricing-plans.layout3 .ss-p lan-header h3{198 font-size: 1.5rem;232 .subscription-pricing-plans.layout3 .ss-price { 233 font-size: 38px; 199 234 } 200 201 .subscription-pricing-plans.layout3 .ss-price { 202 font-size: 1.8rem; 203 } 204 205 .subscription-pricing-plans.layout3 .ss-price span { 206 font-size: 0.9rem; 207 } 208 209 .subscription-pricing-plans.layout3 .ss-plan-features li { 210 font-size: 0.9rem; 211 padding: 8px 10px; 212 } 213 214 .subscription-pricing-plans.layout3 .ss-pricing-button { 215 padding: 10px 15px; 216 } 217 } 218 219 @media screen and (max-width: 480px) { 220 .subscription-pricing-plans.layout3 .ss-plan-header h3 { 221 font-size: 1.3rem; 222 } 223 224 .subscription-pricing-plans.layout3 .ss-price { 225 font-size: 1.6rem; 226 } 227 } 228 229 /* Feature list: check marks + tighter rhythm. */ 230 .subscription-pricing-plans.layout3 .ss-plan-features li { 231 display: flex; 232 align-items: flex-start; 233 gap: 10px; 234 text-align: left; 235 padding: 10px 24px; 236 border-bottom: none; 237 line-height: 1.5; 238 } 239 240 .subscription-pricing-plans.layout3 .ss-plan-features li::before { 241 content: "✓"; 242 color: var(--ss-primary); 243 font-weight: 700; 244 line-height: 1.4; 245 flex-shrink: 0; 246 margin-right: 0; 247 } 248 249 .subscription-pricing-plans.layout3 .ss-plan-footer { 250 padding: 18px 24px 26px; 251 } 252 253 .subscription-pricing-plans.layout3 .ss-button, 254 .subscription-pricing-plans.layout3 .ss-pricing-button { 255 width: 100%; 256 } 257 258 /* "Current Plan" — a plan the logged-in user already owns. Disabled, so they 259 can see they have it and cannot buy it twice. */ 260 .ss-button--owned, 261 .ss-button--owned:hover { 262 background: var(--ss-success-tint, #d8f2e0) !important; 263 color: var(--ss-success, #14603a) !important; 264 border: 1px solid var(--ss-success, #86c9a3) !important; 265 cursor: not-allowed; 266 box-shadow: none !important; 267 transform: none !important; 268 } 235 } 236 237 /* Filter bar sits above the dark stage, keep it outside the panel look. */ 238 .subscription-pricing-plans.layout3 .ss-plan-filters { 239 margin-bottom: 18px; 240 } -
subscription-system/trunk/public/js/interest-subscribe.js
r3650727 r3651653 72 72 $('body').addClass('ss-interest-modal-open'); 73 73 $modal.find('.ss-interest-message').removeClass('is-error is-success').text(''); 74 75 // A previous run may have left the submit button disabled (busy) or 76 // in its green done state; reopening starts a fresh attempt. Only 77 // buttons WE disabled are re-enabled — the "no plans exist" disable 78 // set server-side carries no ss-busy flag and stays off. 79 var $submit = $modal.find('.ss-interest-form [type="submit"]'); 80 if ($submit.hasClass('ss-interest-btn--done') || $submit.data('ssBusy')) { 81 $submit.prop('disabled', false).removeClass('ss-interest-btn--done').removeData('ssBusy'); 82 } 74 83 75 84 // Always open on step 1, even if a previous run left it on the auth step. … … 279 288 var $plan = getSelectedPlan($form); 280 289 281 $submit.prop('disabled', true). text(i18n().finishing || 'Setting up your subscription…');290 $submit.prop('disabled', true).data('ssBusy', 1).text(i18n().finishing || 'Setting up your subscription…'); 282 291 283 292 $.ajax({ … … 296 305 if (response.data.checkoutUrl) { 297 306 showMessage($form, i18n().to_checkout || 'Taking you to secure checkout…', 'success'); 307 $submit.text(i18n().to_checkout || 'Taking you to secure checkout…'); 298 308 window.location.href = response.data.checkoutUrl; 299 309 return; … … 302 312 // then land on the success page — same destination the paid 303 313 // flow reaches via Stripe — so what they subscribed to and 304 // its benefits are played back to them. 314 // its benefits are played back to them. The button flips to a 315 // green done state so it agrees with the message instead of 316 // staying stuck on its "Setting up…" busy label. 305 317 showMessage($form, response.data.message || 'You are subscribed.', 'success'); 318 $submit.addClass('ss-interest-btn--done').text(i18n().done || 'All set ✓'); 306 319 if (response.data.redirectUrl) { 307 320 setTimeout(function () { window.location.href = response.data.redirectUrl; }, 1200); … … 311 324 } else { 312 325 showMessage($form, (response.data && response.data.message) || i18n().error || 'Error.', 'error'); 313 $submit.prop('disabled', false) ; refreshSubmitLabel($form);326 $submit.prop('disabled', false).removeData('ssBusy'); refreshSubmitLabel($form); 314 327 } 315 328 }).fail(function () { 316 329 showMessage($form, i18n().error || 'Error.', 'error'); 317 $submit.prop('disabled', false) ; refreshSubmitLabel($form);330 $submit.prop('disabled', false).removeData('ssBusy'); refreshSubmitLabel($form); 318 331 }); 319 332 } … … 348 361 subs_sys_hp: '' 349 362 }; 350 $submit.prop('disabled', true). text(i18n().registering || 'Creating your account…');363 $submit.prop('disabled', true).data('ssBusy', 1).text(i18n().registering || 'Creating your account…'); 351 364 } else { 352 365 if (!val('login_identifier') || !val('login_password')) { … … 360 373 password: val('login_password') 361 374 }; 362 $submit.prop('disabled', true). text(i18n().logging_in || 'Signing in…');375 $submit.prop('disabled', true).data('ssBusy', 1).text(i18n().logging_in || 'Signing in…'); 363 376 } 364 377 … … 383 396 } 384 397 showMessage($form, (response && response.message) || i18n().error || 'Error.', 'error'); 385 $submit.prop('disabled', false) ; refreshSubmitLabel($form);398 $submit.prop('disabled', false).removeData('ssBusy'); refreshSubmitLabel($form); 386 399 } 387 400 }) 388 401 .fail(function () { 389 402 showMessage($form, i18n().error || 'Error.', 'error'); 390 $submit.prop('disabled', false) ; refreshSubmitLabel($form);403 $submit.prop('disabled', false).removeData('ssBusy'); refreshSubmitLabel($form); 391 404 }); 392 405 } -
subscription-system/trunk/public/partials/pricing-plans-layout1.php
r3650727 r3651653 1 1 <?php 2 2 /** 3 * Template for displaying pricing plans (Layout 1) 3 * Template for displaying pricing plans (Layout 1 — "Classic") 4 * 5 * Centered floating cards. Structure is shared with the other layouts via 6 * components/pricing-plan-card.php; this file only sets the wrapper. 4 7 * 5 8 * @package Subscription_System … … 11 14 } 12 15 13 // Enqueue the layout-specific CSS 14 wp_enqueue_style('subscription-pricing-layout1', plugin_dir_url(dirname(__FILE__)) . 'css/pricing-layout1.css', array('subs-sys-tokens'), '1.0.0', 'all'); 16 $layout = 'layout1'; 17 $ss_behavior = isset($ss_behavior) ? $ss_behavior : ''; 18 require __DIR__ . '/components/pricing-assets.php'; 15 19 16 // Add responsive viewport meta tag if not already present 17 18 19 // Enqueue Stripe JS 20 wp_enqueue_script('stripe-js', 'https://js.stripe.com/v3/', array(), null, true); 21 22 // Enqueue our custom JS 23 wp_enqueue_script('subscription-pricing-js', plugin_dir_url(dirname(__FILE__)) . 'js/subscription-pricing.js', array('jquery', 'stripe-js'), '1.0.0', true); 24 25 // Localize script with necessary data 26 wp_localize_script('subscription-pricing-js', 'subscription_data', array( 27 'ajax_url' => esc_url(admin_url('admin-ajax.php')), 28 'stripe_publishable_key' => esc_attr( class_exists('Subs_Sys_Stripe_Sync') ? Subs_Sys_Stripe_Sync::get_publishable_key() : get_option('subscription_stripe_publishable_key') ), 29 'checkout_nonce' => wp_create_nonce('subscription_checkout_nonce'), 30 'is_user_logged_in' => is_user_logged_in(), 31 'register_page_url' => esc_url(get_permalink(get_option('register_page'))), 32 'account_page_url' => esc_url(get_permalink(get_option('account_page'))), 33 'thank_you_page_url' => esc_url(get_permalink(get_option('thank_you_page'))), 34 'pricing_plans_page_url' => esc_url(get_permalink(get_option('pricing_plans_page'))), 35 'nonce' => esc_attr(wp_create_nonce('subscription_check_nonce')), 36 'subscribe_now_text' => esc_html__('Subscribe Now', 'subscription-system') 37 )); 38 39 20 $ss_design_classes = class_exists('Subs_Sys_Layout_Settings') ? Subs_Sys_Layout_Settings::wrapper_classes($ss_behavior) : ''; 21 $ss_design_attrs = class_exists('Subs_Sys_Layout_Settings') ? Subs_Sys_Layout_Settings::data_attrs() : ''; 40 22 ?> 41 23 42 <div class="subscription-pricing-plans layout1 <?php echo esc_attr($orientation_class); ?> columns-<?php echo esc_attr($atts['columns']); ?> ss-responsive ">24 <div class="subscription-pricing-plans layout1 <?php echo esc_attr($orientation_class); ?> columns-<?php echo esc_attr($atts['columns']); ?> ss-responsive <?php echo esc_attr($ss_design_classes); ?>"<?php echo $ss_design_attrs; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped attr-by-attr in data_attrs(). ?>> 43 25 <?php if (empty($plans)) : ?> 44 26 <p class="no-plans"><?php esc_html_e('No subscription plans available.', 'subscription-system'); ?></p> … … 47 29 <div class="ss-pricing-container ss-layout1"> 48 30 <div class="ss-pricing-plans"> 49 <?php 50 // Determine which plan should be featured (middle plan by default) 31 <?php 51 32 $featured_index = subs_sys_featured_plan_index($plans); 52 53 // Loop through plans 54 foreach ($plans as $index => $plan) : 33 34 foreach ($plans as $index => $plan) : 55 35 $is_featured = ($index === $featured_index) ? 'ss-featured' : ''; 36 include __DIR__ . '/components/pricing-plan-card.php'; 37 endforeach; 56 38 ?> 57 <div class="ss-pricing-plan <?php echo esc_attr($is_featured); ?>" data-ss-category="<?php echo esc_attr($plan->category_name ?? ''); ?>">58 <div class="ss-plan-header">59 <h3><?php echo esc_html($plan->plan_name); ?></h3>60 <div class="ss-price">$<?php echo esc_html(number_format($plan->price, 2)); ?><span><?php echo esc_html(subs_sys_plan_period_label($plan)); ?></span></div>61 </div>62 <div class="ss-plan-features">63 <ul>64 <?php65 // If plan has features, display them66 if (!empty($plan->description)) {67 // Clean up the description string - remove escaped quotes68 $clean_description = str_replace('\"', '"', $plan->description);69 70 // Try to decode the JSON71 $features = json_decode($clean_description);72 73 // If JSON decode failed, try other methods74 if ($features === null) {75 // Try to unserialize76 $features = maybe_unserialize($clean_description);77 78 // If still not an array, use as single item79 if (!is_array($features)) {80 $features = array($clean_description);81 }82 }83 84 // Display features85 if (is_array($features) || is_object($features)) {86 foreach ($features as $feature) {87 echo '<li>' . esc_html($feature) . '</li>';88 }89 } else {90 echo '<li>' . esc_html($clean_description) . '</li>';91 }92 } else {93 echo '<!-- Debug: Plan description is empty -->';94 }95 ?>96 </ul>97 </div>98 <div class="ss-plan-footer">99 <?php $ss_owned = is_user_logged_in() && class_exists('Subs_Sys_Subscription_Repository') && Subs_Sys_Subscription_Repository::owns_plan(get_current_user_id(), $plan->id); ?>100 <?php if ($ss_owned) : ?>101 <button class="ss-button subscribe-button ss-button--owned" disabled aria-disabled="true">102 <?php esc_html_e('Current Plan', 'subscription-system'); ?>103 </button>104 <?php else : ?>105 <button class="ss-button subscribe-button" data-plan-id="<?php echo esc_attr($plan->id); ?>">106 <?php echo esc_html(subs_sys_plan_button_label($plan)); ?>107 </button>108 <?php endif; ?>109 </div>110 </div>111 <?php endforeach; ?>112 39 </div> 113 40 </div> -
subscription-system/trunk/public/partials/pricing-plans-layout2.php
r3650727 r3651653 2 2 /** 3 3 * Template for displaying pricing plans (Layout 2) 4 * 5 * The "Ledger": one flat ruled sheet, left-aligned type, inverted featured column. Structure is shared with the other layouts via 6 * components/pricing-plan-card.php; this file only sets the wrapper. 4 7 * 5 8 * @package Subscription_System … … 11 14 } 12 15 13 // Enqueue the layout-specific CSS 14 wp_enqueue_style('subscription-pricing-layout2', plugin_dir_url(dirname(__FILE__)) . 'css/pricing-layout2.css', array('subs-sys-tokens'), '1.0.0', 'all'); 16 $layout = 'layout2'; 17 $ss_behavior = isset($ss_behavior) ? $ss_behavior : ''; 18 require __DIR__ . '/components/pricing-assets.php'; 15 19 16 // Add responsive viewport meta tag if not already present 17 18 19 // Enqueue Stripe JS 20 wp_enqueue_script('stripe-js', 'https://js.stripe.com/v3/', array(), null, true); 21 22 // Enqueue our custom JS 23 wp_enqueue_script('subscription-pricing-js', plugin_dir_url(dirname(__FILE__)) . 'js/subscription-pricing.js', array('jquery', 'stripe-js'), '1.0.0', true); 24 25 // Localize script with necessary data 26 wp_localize_script('subscription-pricing-js', 'subscription_data', array( 27 'ajax_url' => admin_url('admin-ajax.php'), 28 'stripe_publishable_key' => class_exists('Subs_Sys_Stripe_Sync') ? Subs_Sys_Stripe_Sync::get_publishable_key() : get_option('subscription_stripe_publishable_key'), 29 'checkout_nonce' => wp_create_nonce('subscription_checkout_nonce'), 30 'is_user_logged_in' => is_user_logged_in(), 31 'register_page_url' => get_permalink(get_option('register_page')), 32 'account_page_url' => get_permalink(get_option('account_page')), 33 'thank_you_page_url' => get_permalink(get_option('thank_you_page')), 34 'pricing_plans_page_url' => get_permalink(get_option('pricing_plans_page')), 35 'nonce' => wp_create_nonce('subscription_check_nonce'), 36 'subscribe_now_text' => esc_html__('Subscribe Now', 'subscription-system') 37 )); 20 $ss_design_classes = class_exists('Subs_Sys_Layout_Settings') ? Subs_Sys_Layout_Settings::wrapper_classes($ss_behavior) : ''; 21 $ss_design_attrs = class_exists('Subs_Sys_Layout_Settings') ? Subs_Sys_Layout_Settings::data_attrs() : ''; 38 22 ?> 39 23 40 <div class="subscription-pricing-plans layout2 <?php echo esc_attr($orientation_class); ?> columns-<?php echo esc_attr($atts['columns']); ?> ss-responsive ">24 <div class="subscription-pricing-plans layout2 <?php echo esc_attr($orientation_class); ?> columns-<?php echo esc_attr($atts['columns']); ?> ss-responsive <?php echo esc_attr($ss_design_classes); ?>"<?php echo $ss_design_attrs; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped attr-by-attr in data_attrs(). ?>> 41 25 <?php if (empty($plans)) : ?> 42 26 <p class="no-plans"><?php esc_html_e('No subscription plans available.', 'subscription-system'); ?></p> … … 44 28 <?php include __DIR__ . '/components/plan-filter-bar.php'; ?> 45 29 <div class="ss-pricing-container ss-layout2"> 46 < ?php47 // Determine which plan should be featured (middle plan by default)48 $featured_index = subs_sys_featured_plan_index($plans);30 <div class="ss-pricing-plans"> 31 <?php 32 $featured_index = subs_sys_featured_plan_index($plans); 49 33 50 foreach ($plans as $index => $plan) : 51 $is_featured = ($index === $featured_index) ? 'ss-featured' : ''; 52 ?> 53 <div class="ss-pricing-plan <?php echo esc_attr($is_featured); ?>" data-ss-category="<?php echo esc_attr($plan->category_name ?? ''); ?>"> 54 <div class="ss-plan-header"> 55 <h3><?php echo esc_html($plan->plan_name); ?></h3> 56 <div class="ss-price">$<?php echo esc_html(number_format($plan->price, 2)); ?><span><?php echo esc_html(subs_sys_plan_period_label($plan)); ?></span></div> 57 </div> 58 <div class="ss-plan-features"> 59 <ul> 60 <?php 61 // If plan has features, display them 62 if (!empty($plan->description)) { 63 // Clean up the description string - remove escaped quotes 64 $clean_description = str_replace('\"', '"', $plan->description); 65 66 // Try to decode the JSON 67 $features = json_decode($clean_description); 68 69 // If JSON decode failed, try other methods 70 if ($features === null) { 71 // Try to unserialize 72 $features = maybe_unserialize($clean_description); 73 74 // If still not an array, use as single item 75 if (!is_array($features)) { 76 $features = array($clean_description); 77 } 78 } 79 80 // Display features 81 if (is_array($features) || is_object($features)) { 82 foreach ($features as $feature) { 83 echo '<li>' . esc_html($feature) . '</li>'; 84 } 85 } else { 86 echo '<li>' . esc_html($clean_description) . '</li>'; 87 } 88 } 89 ?> 90 </ul> 91 </div> 92 <div class="ss-plan-footer"> 93 <?php $ss_owned = is_user_logged_in() && class_exists('Subs_Sys_Subscription_Repository') && Subs_Sys_Subscription_Repository::owns_plan(get_current_user_id(), $plan->id); ?> 94 <button class="ss-button subscribe-button<?php echo $ss_owned ? ' ss-button--owned' : ''; ?>"<?php echo $ss_owned ? ' disabled aria-disabled="true"' : ''; ?> data-plan-id="<?php echo esc_attr($plan->id); ?>"> 95 <?php echo $ss_owned ? esc_html__("Current Plan", "subscription-system") : esc_html(subs_sys_plan_button_label($plan)); ?> 96 </button> 97 </div> 98 </div> 99 <?php endforeach; ?> 34 foreach ($plans as $index => $plan) : 35 $is_featured = ($index === $featured_index) ? 'ss-featured' : ''; 36 include __DIR__ . '/components/pricing-plan-card.php'; 37 endforeach; 38 ?> 39 </div> 100 40 </div> 101 41 <?php endif; ?> 102 42 </div> 103 104 <script>105 jQuery(document).ready(function($) {106 // Initialize Stripe107 var stripe = Stripe('<?php echo esc_js( class_exists('Subs_Sys_Stripe_Sync') ? Subs_Sys_Stripe_Sync::get_publishable_key() : get_option('subscription_stripe_publishable_key') ); ?>');108 109 $('.subscribe-button').on('click', function() {110 var planId = $(this).data('plan-id');111 var button = $(this);112 var planName = button.closest('.ss-pricing-plan').find('h3').text();113 var planPrice = button.closest('.ss-pricing-plan').find('.ss-price').text().replace(/[^0-9.]/g, '');114 115 // Check if user is logged in116 <?php if (!is_user_logged_in()): ?>117 // User is not logged in, redirect to register page118 window.location.href = '<?php echo esc_js(get_permalink(get_option('register_page'))); ?>?plan_id=' + planId;119 return;120 <?php else: ?>121 // Check if user already has this subscription122 $.ajax({123 url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',124 type: 'POST',125 data: {126 action: 'check_user_subscription',127 plan_id: planId,128 nonce: '<?php echo esc_attr(wp_create_nonce('subscription_check_nonce')); ?>'129 },130 success: function(response) {131 if (response.success && response.data.has_subscription) {132 // User already has this subscription, redirect to account page133 window.location.href = '<?php echo esc_js(get_permalink(get_option('account_page'))); ?>?tab=subscription';134 } else if (response.success && response.data.has_other_plan) {135 // One active plan at a time: this click is a switch, not a purchase.136 var current = response.data.current_plan_name || 'your current plan';137 if (confirm('You are currently on "' + current + '". Switch to "' + planName + '" instead?\n\nUpgrades apply immediately with prorated billing. Downgrades keep your current benefits until the end of the period you already paid for.')) {138 proceedWithPayment(planId, button, planName, planPrice);139 }140 } else {141 // User is logged in but doesn't have this subscription, proceed with payment142 proceedWithPayment(planId, button, planName, planPrice);143 }144 },145 error: function() {146 // On error, proceed with payment flow147 proceedWithPayment(planId, button, planName, planPrice);148 }149 });150 <?php endif; ?>151 });152 153 function proceedWithPayment(planId, button, planName, planPrice) {154 // Remember this plan's own wording — each plan may override its button155 // label, so restoring a hardcoded "Subscribe Now" here would quietly156 // rewrite the label the site owner chose.157 var originalLabel = button.text();158 159 // Disable button and show loading state160 button.prop('disabled', true).addClass('loading').text('Processing...');161 162 // Ask our own server to create the Checkout Session. The Stripe secret key163 // never leaves the server - the browser only ever receives back a checkout_url.164 $.ajax({165 url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',166 type: 'POST',167 data: {168 action: 'create_checkout_session',169 plan_id: planId,170 nonce: '<?php echo esc_attr(wp_create_nonce('subscription_checkout_nonce')); ?>'171 },172 success: function(response) {173 if (response.success && response.data.checkout_url) {174 window.location.href = response.data.checkout_url;175 } else {176 var message = (response.data && response.data.message) ? response.data.message : 'Unknown error occurred';177 alert('Error: ' + message);178 button.prop('disabled', false).removeClass('loading').text(originalLabel);179 }180 },181 error: function() {182 alert('Error: Unable to start checkout. Please try again.');183 button.prop('disabled', false).removeClass('loading').text(originalLabel);184 }185 });186 }187 });188 </script> -
subscription-system/trunk/public/partials/pricing-plans-layout3.php
r3650727 r3651653 2 2 /** 3 3 * Template for displaying pricing plans (Layout 3) 4 * 5 * The "Spotlight": dark stage panel with one lit, elevated featured card. Structure is shared with the other layouts via 6 * components/pricing-plan-card.php; this file only sets the wrapper. 4 7 * 5 8 * @package Subscription_System … … 11 14 } 12 15 13 // Enqueue the layout-specific CSS 14 wp_enqueue_style('subscription-pricing-layout3', plugin_dir_url(dirname(__FILE__)) . 'css/pricing-layout3.css', array('subs-sys-tokens'), '1.0.0', 'all'); 16 $layout = 'layout3'; 17 $ss_behavior = isset($ss_behavior) ? $ss_behavior : ''; 18 require __DIR__ . '/components/pricing-assets.php'; 15 19 16 // Add responsive viewport meta tag if not already present 17 18 19 // Enqueue Stripe JS 20 wp_enqueue_script('stripe-js', 'https://js.stripe.com/v3/', array(), null, true); 21 22 // Enqueue our custom JS 23 wp_enqueue_script('subscription-pricing-js', plugin_dir_url(dirname(__FILE__)) . 'js/subscription-pricing.js', array('jquery', 'stripe-js'), '1.0.0', true); 24 25 // Localize script with necessary data 26 wp_localize_script('subscription-pricing-js', 'subscription_data', array( 27 'ajax_url' => admin_url('admin-ajax.php'), 28 'stripe_publishable_key' => class_exists('Subs_Sys_Stripe_Sync') ? Subs_Sys_Stripe_Sync::get_publishable_key() : get_option('subscription_stripe_publishable_key'), 29 'checkout_nonce' => wp_create_nonce('subscription_checkout_nonce'), 30 'is_user_logged_in' => is_user_logged_in(), 31 'register_page_url' => get_permalink(get_option('register_page')), 32 'account_page_url' => get_permalink(get_option('account_page')), 33 'thank_you_page_url' => get_permalink(get_option('thank_you_page')), 34 'pricing_plans_page_url' => get_permalink(get_option('pricing_plans_page')), 35 'nonce' => wp_create_nonce('subscription_check_nonce'), 36 'subscribe_now_text' => esc_html__('Subscribe Now', 'subscription-system') 37 )); 20 $ss_design_classes = class_exists('Subs_Sys_Layout_Settings') ? Subs_Sys_Layout_Settings::wrapper_classes($ss_behavior) : ''; 21 $ss_design_attrs = class_exists('Subs_Sys_Layout_Settings') ? Subs_Sys_Layout_Settings::data_attrs() : ''; 38 22 ?> 39 23 40 <div class="subscription-pricing-plans layout3 <?php echo esc_attr($orientation_class); ?> columns-<?php echo esc_attr($atts['columns']); ?> ss-responsive ">24 <div class="subscription-pricing-plans layout3 <?php echo esc_attr($orientation_class); ?> columns-<?php echo esc_attr($atts['columns']); ?> ss-responsive <?php echo esc_attr($ss_design_classes); ?>"<?php echo $ss_design_attrs; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped attr-by-attr in data_attrs(). ?>> 41 25 <?php if (empty($plans)) : ?> 42 26 <p class="no-plans"><?php esc_html_e('No subscription plans available.', 'subscription-system'); ?></p> … … 44 28 <?php include __DIR__ . '/components/plan-filter-bar.php'; ?> 45 29 <div class="ss-pricing-container ss-layout3"> 46 <?php 47 $featured_index = subs_sys_featured_plan_index($plans); // Middle plan is featured 48 foreach ($plans as $index => $plan) : 49 $is_featured = ($index === $featured_index) ? 'ss-featured' : ''; 50 ?> 51 <div class="ss-pricing-plan <?php echo esc_attr($is_featured); ?>" data-ss-category="<?php echo esc_attr($plan->category_name ?? ''); ?>"> 52 <div class="ss-plan-header"> 53 <h3><?php echo esc_html($plan->plan_name); ?></h3> 54 <div class="ss-price">$<?php echo esc_html(number_format($plan->price, 2)); ?><span><?php echo esc_html(subs_sys_plan_period_label($plan)); ?></span></div> 55 </div> 56 <div class="ss-plan-features"> 57 <ul> 58 <?php 59 60 // If plan has features, display them 61 if (!empty($plan->description)) { 62 try { 63 // Clean up the description string - remove escaped quotes 64 $clean_description = str_replace('\"', '"', $plan->description); 65 66 // Try to decode the JSON 67 $features = json_decode($clean_description); 68 69 // If JSON decode failed, try other methods 70 if ($features === null) { 71 // Try to unserialize 72 $features = maybe_unserialize($clean_description); 73 74 // If still not an array, use as single item 75 if (!is_array($features)) { 76 $features = array($clean_description); 77 } 78 } 79 80 // Display features without dashicons 81 if (is_array($features) || is_object($features)) { 82 foreach ($features as $feature) { 83 echo '<li>' . esc_html($feature) . '</li>'; 84 } 85 } else { 86 echo '<li>' . esc_html($clean_description) . '</li>'; 87 } 88 } catch (Exception $e) { 89 // Fallback if any error occurs 90 echo '<li>' . esc_html($plan->description) . '</li>'; 91 } 92 } 93 ?> 94 </ul> 95 </div> 96 <div class="ss-plan-footer"> 97 <?php $ss_owned = is_user_logged_in() && class_exists('Subs_Sys_Subscription_Repository') && Subs_Sys_Subscription_Repository::owns_plan(get_current_user_id(), $plan->id); ?> 98 <button class="ss-pricing-button subscribe-button<?php echo $ss_owned ? ' ss-button--owned' : ''; ?>"<?php echo $ss_owned ? ' disabled aria-disabled="true"' : ''; ?> data-plan-id="<?php echo esc_attr($plan->id); ?>"> 99 <?php echo $ss_owned ? esc_html__("Current Plan", "subscription-system") : esc_html(subs_sys_plan_button_label($plan)); ?> 100 </button> 101 </div> 102 </div> 103 <?php endforeach; ?> 30 <div class="ss-pricing-plans"> 31 <?php 32 $featured_index = subs_sys_featured_plan_index($plans); 33 34 foreach ($plans as $index => $plan) : 35 $is_featured = ($index === $featured_index) ? 'ss-featured' : ''; 36 include __DIR__ . '/components/pricing-plan-card.php'; 37 endforeach; 38 ?> 39 </div> 104 40 </div> 105 41 <?php endif; ?> 106 42 </div> 107 <script>108 jQuery(document).ready(function($) {109 // Initialize Stripe110 var stripe = Stripe('<?php echo esc_js( class_exists('Subs_Sys_Stripe_Sync') ? Subs_Sys_Stripe_Sync::get_publishable_key() : get_option('subscription_stripe_publishable_key') ); ?>');111 112 $('.subscribe-button').on('click', function() {113 var planId = $(this).data('plan-id');114 var button = $(this);115 var planName = button.closest('.ss-pricing-plan').find('h3').text();116 var planPrice = button.closest('.ss-pricing-plan').find('.ss-price').text().replace(/[^0-9.]/g, '');117 118 // Check if user is logged in119 <?php if (!is_user_logged_in()): ?>120 // User is not logged in, redirect to register page121 window.location.href = '<?php echo esc_js(get_permalink(get_option('register_page'))); ?>?plan_id=' + planId;122 return;123 <?php else: ?>124 // Check if user already has this subscription125 $.ajax({126 url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',127 type: 'POST',128 data: {129 action: 'check_user_subscription',130 plan_id: planId,131 nonce: '<?php echo esc_attr(wp_create_nonce('subscription_check_nonce')); ?>'132 },133 success: function(response) {134 if (response.success && response.data.has_subscription) {135 // User already has this subscription, redirect to account page136 window.location.href = '<?php echo esc_js(get_permalink(get_option('account_page'))); ?>?tab=subscription';137 } else if (response.success && response.data.has_other_plan) {138 // One active plan at a time: this click is a switch, not a purchase.139 var current = response.data.current_plan_name || 'your current plan';140 if (confirm('You are currently on "' + current + '". Switch to "' + planName + '" instead?\n\nUpgrades apply immediately with prorated billing. Downgrades keep your current benefits until the end of the period you already paid for.')) {141 proceedWithPayment(planId, button, planName, planPrice);142 }143 } else {144 // User is logged in but doesn't have this subscription, proceed with payment145 proceedWithPayment(planId, button, planName, planPrice);146 }147 },148 error: function() {149 // On error, proceed with payment flow150 proceedWithPayment(planId, button, planName, planPrice);151 }152 });153 <?php endif; ?>154 });155 156 function proceedWithPayment(planId, button, planName, planPrice) {157 // Remember this plan's own wording — each plan may override its button158 // label, so restoring a hardcoded "Subscribe Now" here would quietly159 // rewrite the label the site owner chose.160 var originalLabel = button.text();161 162 // Disable button and show loading state163 button.prop('disabled', true).addClass('loading').text('Processing...');164 165 // Ask our own server to create the Checkout Session. The Stripe secret key166 // never leaves the server - the browser only ever receives back a checkout_url.167 $.ajax({168 url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',169 type: 'POST',170 data: {171 action: 'create_checkout_session',172 plan_id: planId,173 nonce: '<?php echo esc_attr(wp_create_nonce('subscription_checkout_nonce')); ?>'174 },175 success: function(response) {176 if (response.success && response.data.checkout_url) {177 window.location.href = response.data.checkout_url;178 } else {179 var message = (response.data && response.data.message) ? response.data.message : '<?php esc_html_e('Unknown error occurred', 'subscription-system'); ?>';180 alert(message);181 button.prop('disabled', false).removeClass('loading').text(originalLabel);182 }183 },184 error: function() {185 alert('<?php esc_html_e('Error connecting to server. Please try again.', 'subscription-system'); ?>');186 button.prop('disabled', false).removeClass('loading').text(originalLabel);187 }188 });189 }190 });191 </script> -
subscription-system/trunk/subscription-system.php
r3650727 r3651653 17 17 * Plugin URI: https://mediahubster.in/subscription-system 18 18 * Description: A comprehensive subscription management system for WordPress. 19 * Version: 1.0.2 119 * Version: 1.0.22 20 20 * Author: Joe Youngblood 21 21 * Author URI: https://mediahubster.in … … 97 97 98 98 // Define plugin constants 99 define('SUBS_SYS_VERSION', '1.0.2 1');99 define('SUBS_SYS_VERSION', '1.0.22'); 100 100 define('SUBS_SYS_PLUGIN_DIR', plugin_dir_path(__FILE__)); 101 101 define('SUBS_SYS_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 227 227 228 228 return (int) ceil( $count / 2 ) - 1; 229 } 230 231 /** 232 * A plan's feature lines as a flat array of strings. 233 * 234 * The description column has held three shapes over the plugin's life: a JSON 235 * array, a PHP-serialized array, and free text. Each layout template used to 236 * carry its own copy of this decoding cascade; one copy lives here now so the 237 * card component (and anything else) always parses identically. 238 * 239 * @param object $plan Plan row. 240 * @return string[] Feature lines, possibly empty. 241 */ 242 function subs_sys_plan_features( $plan ) { 243 $raw = isset( $plan->description ) ? trim( (string) $plan->description ) : ''; 244 245 if ( '' === $raw ) { 246 return array(); 247 } 248 249 $clean = str_replace( '\\"', '"', $raw ); 250 $features = json_decode( $clean ); 251 252 if ( null === $features ) { 253 $features = maybe_unserialize( $clean ); 254 } 255 256 if ( ! is_array( $features ) && ! is_object( $features ) ) { 257 $features = array( $clean ); 258 } 259 260 $out = array(); 261 foreach ( (array) $features as $feature ) { 262 $feature = trim( (string) $feature ); 263 if ( '' !== $feature ) { 264 $out[] = $feature; 265 } 266 } 267 return $out; 229 268 } 230 269 … … 487 526 'finishing' => __( 'Setting up your subscription…', 'subscription-system' ), 488 527 'to_checkout' => __( 'Taking you to secure checkout…', 'subscription-system' ), 528 'done' => __( 'All set ✓', 'subscription-system' ), 489 529 'auth_required' => __( 'Please log in or create an account to continue.', 'subscription-system' ), 490 530 'login_subscribe' => __( 'Log in & Subscribe', 'subscription-system' ), -
subscription-system/trunk/uninstall.php
r3650727 r3651653 62 62 'ss_migration_completed_v1', 63 63 'ss_unified_webhook_secret', 64 'ss_paywall_teaser_word_count', // Removed paywall feature; may linger on old installs. 64 65 'enable_stripe', 65 66 'stripe_mode',
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)