Plugin Directory

Changeset 3758537


Ignore:
Timestamp:
10/21/2026 02:20:13 PM (13 days ago)
Author:
klamra22
Message:

security_update_stripe_sandbox

Location:
klamra-paycal-for-aspaclaria
Files:
90 added
34 edited

Legend:

Unmodified
Added
Removed
  • klamra-paycal-for-aspaclaria/trunk/assets/admin.css

    r3565039 r3758537  
    17781778  }
    17791779}
     1780
     1781/* PayCal 1.4.4: display-only provider inventory. */
     1782.paycal-provider-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(215px,1fr));gap:12px}
     1783.paycal-provider-card{background:#f7fbff;border:1px solid #dfeaf4;border-radius:14px;padding:15px;min-width:0}
     1784.paycal-provider-card-head{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:10px}
     1785.paycal-provider-card strong{font-size:16px;color:#102e50}
     1786.paycal-provider-card small{display:block;color:#60748b;line-height:1.5}
     1787.paycal-provider-status{display:inline-block;font-weight:600;font-size:12px;border-radius:20px;padding:4px 9px;background:#e9eef4;color:#43566d}
     1788.paycal-provider-status.is-configured{background:#fff2d6;color:#895807}
     1789.paycal-provider-status.is-verified{background:#def6e8;color:#16643b}
     1790.paycal-provider-footnote{font-size:12px;margin:14px 0 0}
     1791
     1792/* Compact connection status is the fourth KPI card, not a public provider catalogue. */
     1793.paycal-stats-grid { grid-template-columns: repeat(4,minmax(0,1fr)); }
     1794.paycal-connections-summary { min-width:0; }
     1795.paycal-connections-summary .paycal-stat-value { font-size:clamp(20px,1.9vw,30px);line-height:1.35; }
     1796.paycal-connection-lines { list-style:none;margin:10px 0 0;padding:0;display:flex;flex-direction:column;gap:7px; }
     1797.paycal-connection-lines li { display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap;font-size:13px;color:#526d89; }
     1798.paycal-connection-lines li strong { color:#0b294a; }
     1799@media(max-width:1180px){ .paycal-stats-grid { grid-template-columns:repeat(2,minmax(0,1fr)); } }
     1800@media(max-width:782px){ .paycal-stats-grid { grid-template-columns:1fr; } }
  • klamra-paycal-for-aspaclaria/trunk/assets/admin.js

    r3469131 r3758537  
    11(function($){
    2   function renderBitQR(){
    3     var box = document.getElementById('paycal_qr_box');
    4     if(!box) return;
    5 
    6     var phone = (box.dataset.phone || '').trim();
    7     if(!phone){
    8       box.innerHTML = '<span style="color:#64748B">הזן מספר טלפון כדי לראות QR</span>';
    9       return;
    10     }
    11 
    12     var payload = 'BIT:' + phone;
    13     var url = 'https://chart.googleapis.com/chart?chs=220x220&cht=qr&chl=' + encodeURIComponent(payload);
    14     box.innerHTML = '<img alt="QR" src="'+url+'" style="width:220px;height:220px;border-radius:14px" />';
    15   }
    16 
    172  function setupCopy(){
    183    document.querySelectorAll('[data-copy]').forEach(function(btn){
    … …  
    3419
    3520  document.addEventListener('DOMContentLoaded', function(){
    36     renderBitQR();
    37 
    38     var phoneInput = document.getElementById('paycal_bit_phone');
    39     if(phoneInput){
    40       phoneInput.addEventListener('input', function(){
    41         var box = document.getElementById('paycal_qr_box');
    42         if(box) box.dataset.phone = phoneInput.value.replace(/\D+/g,'');
    43         renderBitQR();
    44       });
    45     }
    46 
    4721    setupCopy();
    4822  });
  • klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Connector.php

    r3661556 r3758537  
    7070      <div class="notice notice-info"><p><?php echo esc_html__('PayCal connects this WooCommerce store to branded checkout, payment-management tools and approved payment-provider configurations. PayCal does not replace the merchant payment provider and does not act as a bank or seller of record.', 'klamra-paycal-for-aspaclaria'); ?></p></div>
    7171      <?php if (!$platform_compatible) : ?>
    72         <div class="notice notice-warning"><p><?php echo esc_html__('PayCal Connector 1.3.1 is designed for PayCal Platform 1.2.7 or newer. Please update the Platform plugin when possible. Checkout will continue to use server verification and will fail safely if an incompatible response is returned.', 'klamra-paycal-for-aspaclaria'); ?></p></div>
     72        <div class="notice notice-warning"><p><?php echo esc_html__('PayCal Connector 1.3.2 is designed for PayCal Platform 1.2.7 or newer. Please update the Platform plugin when possible. Checkout will continue to use server verification and will fail safely if an incompatible response is returned.', 'klamra-paycal-for-aspaclaria'); ?></p></div>
    7373      <?php endif; ?>
    7474      <?php if ($no_migrated_settings_found) : ?>
    … …  
    8484            <tr><th>Plugin version</th><td><?php echo esc_html(defined('PAYCAL_VERSION') ? PAYCAL_VERSION : 'unknown'); ?></td></tr>
    8585            <tr><th>Platform version</th><td><?php echo esc_html($platform_version !== '' ? $platform_version : 'unknown until sync'); ?></td></tr>
    86             <tr><th>WordPress compatibility</th><td><?php echo esc_html('Tested up to 7.1'); ?></td></tr>
     86            <tr><th>WordPress compatibility</th><td><?php echo esc_html('Requires 6.0 or newer; see release validation report'); ?></td></tr>
    8787            <tr><th>PHP runtime</th><td><?php echo esc_html(PHP_VERSION . ' (target: 8.3.31)'); ?></td></tr>
    8888            <tr><th>WooCommerce gateway classes</th><td><?php echo esc_html($woocommerce_ok ? 'available' : 'unavailable'); ?></td></tr>
    … …  
    101101            <tr><th>Hosted checkout</th><td><?php echo esc_html($connector_ready ? 'enabled' : 'disabled'); ?></td></tr>
    102102            <tr><th>Server-side verify</th><td><?php echo esc_html($verify_ready ? 'enabled' : 'disabled'); ?></td></tr>
    103             <tr><th>Session expiry</th><td><?php echo esc_html('enabled'); ?></td></tr>
    104             <tr><th>One-time session use</th><td><?php echo esc_html('enabled'); ?></td></tr>
     103            <tr><th>Session expiry</th><td><?php echo esc_html('Managed by PayCal Platform; not verified locally'); ?></td></tr>
     104            <tr><th>One-time session use</th><td><?php echo esc_html('Managed by PayCal Platform; not verified locally'); ?></td></tr>
    105105            <tr><th>Merchant isolation</th><td><?php echo esc_html($connector_ready ? 'enabled' : 'disabled'); ?></td></tr>
    106106            <tr><th>Debug visibility</th><td><?php echo esc_html('admin-only'); ?></td></tr>
    107             <tr><th>Webhook idempotency</th><td><?php echo esc_html('enabled'); ?></td></tr>
     107            <tr><th>Webhook idempotency</th><td><?php echo esc_html('Managed by PayCal Platform; not verified locally'); ?></td></tr>
    108108          </tbody>
    109109        </table>
  • klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Dashboard.php

    r3661556 r3758537  
    77use PayCal\Licensing\Gate;
    88use PayCal\Licensing\LicenseStore;
     9use PayCal\Connector\ProviderRegistry;
    910
    1011class Page_Dashboard {
    … …  
    1415    $data = LicenseStore::get();
    1516    $mods = array_values(array_filter((array) ($data['modules'] ?? [])));
     17    $providers = ProviderRegistry::all();
    1618    $license_label = Gate::status_label();
    1719    $license_active = !empty($data['valid']);
    … …  
    2224    $license_display = $license_active || $license_in_grace ? $license_label : 'לא מחובר';
    2325
    24     $bit_enabled = Gate::has('bit');
    2526    $invoice_settings = get_option('paycal_invoice_settings', []);
    2627    $invoices_enabled = !is_array($invoice_settings) || !isset($invoice_settings['enabled']) || (string) $invoice_settings['enabled'] === 'yes';
    2728
    28     $bit_pending_count = 0;
    29     if (class_exists('\\WooCommerce') && $bit_enabled && function_exists('wc_get_orders')) {
    30       $ids = wc_get_orders([
    31         'status' => ['paycal-bit-pending'],
    32         'limit' => -1,
    33         'return' => 'ids',
    34       ]);
    35       $bit_pending_count = is_array($ids) ? count($ids) : 0;
    36     }
    37 
    38     $orders_link = class_exists('\Automattic\WooCommerce\Utilities\OrderUtil') && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()
    39       ? admin_url('admin.php?page=wc-orders&status=wc-paycal-bit-pending')
    40       : admin_url('edit.php?post_type=shop_order&post_status=wc-paycal-bit-pending');
    4129    $setup_link = Access::can('setup') ? admin_url('admin.php?page=paycal-setup') : '';
    4230    $market_link = Access::can('bundles') ? admin_url('admin.php?page=paycal-bundles') : '';
    4331    $license_link = Access::can('license') ? admin_url('admin.php?page=paycal-license') : '';
    44     $bit_link = Access::can('bit') ? admin_url('admin.php?page=paycal-bit') : '';
    4532    $invoice_link = Access::can('invoices') ? admin_url('admin.php?page=paycal-invoices') : '';
    4633
    … …  
    6451    echo '      <strong>' . esc_html((string) count($mods)) . '</strong>';
    6552    echo '      <small>' . esc_html($mods ? implode(' • ', $mods) : 'עדיין לא הופעלו מודולים') . '</small>';
    66     echo '    </div>';
    67     echo '    <div class="paycal-hero-stat">';
    68     echo '      <span class="paycal-hero-label">Bit Pending</span>';
    69     echo '      <strong>' . esc_html((string) $bit_pending_count) . '</strong>';
    70     echo '      <small>הזמנות שממתינות לאישור תשלום</small>';
    7153    echo '    </div>';
    7254    echo '  </div>';
    … …  
    9779    echo '  </article>';
    9880    echo '  <article class="paycal-stat-card">';
    99     echo '    <span class="paycal-stat-label">Bit</span>';
    100     echo '    <strong class="paycal-stat-value">' . ($bit_enabled ? 'פעיל' : 'לא פעיל') . '</strong>';
    101     echo '    <p class="paycal-stat-meta">ממתינות: ' . esc_html((string) $bit_pending_count) . '</p>';
    102     echo '  </article>';
    103     echo '  <article class="paycal-stat-card">';
    10481    echo '    <span class="paycal-stat-label">Invoices</span>';
    10582    echo '    <strong class="paycal-stat-value">' . ($invoices_enabled ? 'פעיל' : 'לא פעיל') . '</strong>';
    … …  
    11188    echo '    <p class="paycal-stat-meta">אשף התקנה, רישוי ומודולים במקום אחד</p>';
    11289    echo '  </article>';
     90    self::render_connection_card($providers);
    11391    echo '</section>';
    11492
    … …  
    11896    echo '      <span class="paycal-pill ' . ($license_active ? 'is-active' : '') . '" data-status="' . ($license_active ? 'active' : 'inactive') . '"><span class="paycal-dot"></span>רישוי: ' . esc_html($license_display) . '</span>';
    11997    echo '      <span class="paycal-pill ' . ($invoices_enabled ? 'is-active' : '') . '" data-status="' . ($invoices_enabled ? 'active' : 'inactive') . '"><span class="paycal-dot"></span>חשבוניות: ' . ($invoices_enabled ? 'פעיל' : 'לא פעיל') . '</span>';
    120     echo '      <span class="paycal-pill ' . ($bit_enabled ? 'is-active' : '') . '" data-status="' . ($bit_enabled ? 'active' : 'inactive') . '"><span class="paycal-dot"></span>Bit: ' . ($bit_enabled ? 'פעיל' : 'לא פעיל') . '</span>';
    12198    echo '      <span class="paycal-pill"><span class="paycal-dot"></span>מודולים: <strong>' . esc_html((string) count($mods)) . '</strong></span>';
    12299    echo '    </div>';
    … …  
    127104      echo '        <div><strong>רישוי</strong><span>רישיון מסחרי לא חובר. מודולים שאינם דורשים רישיון ימשיכו לעבוד.</span></div>';
    128105      echo '        <div class="paycal-module-actions"><span class="paycal-badge ' . ($license_active ? 'is-success' : 'is-warning') . '">' . esc_html($license_display) . '</span><a class="paycal-step" href="' . esc_url($license_link) . '">פתח</a></div>';
    129       echo '      </div>';
    130     }
    131     if ($bit_link) {
    132       echo '      <div class="paycal-module-row">';
    133       echo '        <div><strong>Bit</strong><span>ניהול הגדרות ותשלומים ממתינים</span></div>';
    134       echo '        <div class="paycal-module-actions"><span class="paycal-badge ' . ($bit_enabled ? 'is-success' : 'is-muted') . '">' . ($bit_enabled ? 'פעיל' : 'כבוי') . '</span><a class="paycal-step" href="' . esc_url($bit_link) . '">הגדרות Bit</a></div>';
    135106      echo '      </div>';
    136107    }
    … …  
    151122    if ($market_link) echo '        <a class="paycal-action-item" href="' . esc_url($market_link) . '"><strong>Marketplace</strong><span>ניהול הרחבות ומודולים זמינים</span></a>';
    152123    if ($license_link) echo '        <a class="paycal-action-item" href="' . esc_url($license_link) . '"><strong>License</strong><span>חיבור, עדכון ובדיקת רישיון</span></a>';
    153     if ($bit_enabled && $bit_link) {
    154       echo '        <a class="paycal-action-item" href="' . esc_url($orders_link) . '"><strong>Bit Pending</strong><span>מעבר מהיר להזמנות ממתינות</span></a>';
    155     }
    156124    echo '      </div>';
    157125    echo '    </div>';
    … …  
    160128    echo '      <ul class="paycal-checklist">';
    161129    echo '        <li>' . ($license_active ? 'הרישיון מחובר והמערכת יכולה לטעון מודולים בהתאם.' : 'רישיון נדרש רק להפעלת מודולים מסחריים אופציונליים.') . '</li>';
    162     echo '        <li>' . ($bit_enabled ? 'מודול Bit זמין. מומלץ לעבור על הזמנות ממתינות.' : 'להפעיל את Bit אם רוצים זרימת תשלומים מהירה בווקומרס.') . '</li>';
    163130    echo '        <li>' . ($invoices_enabled ? 'מודול החשבוניות פעיל וזמין להגדרות.' : 'להפעיל Invoices אם צריך מסמכים וניהול פיננסי מתוך המערכת.') . '</li>';
    164131    echo '      </ul>';
    … …  
    169136    echo '</div>';
    170137  }
     138
     139  /** Dashboard summary only. No public catalogue or inferred provider connections. */
     140  private static function render_connection_card(array $providers): void {
     141    $visible = array_values(array_filter($providers, static function ($provider): bool {
     142      return in_array((string) ($provider['status'] ?? ''), [
     143        'sandbox_configured', 'live_configured', 'verified_sandbox', 'verified_live', 'error'
     144      ], true);
     145    }));
     146    $labels = [
     147      'sandbox_configured' => 'Sandbox בהגדרה',
     148      'live_configured' => 'Live בהגדרה',
     149      'verified_sandbox' => 'Sandbox מאומת',
     150      'verified_live' => 'Live מאומת',
     151      'error' => 'דורש בדיקה',
     152    ];
     153    echo '<article class="paycal-stat-card paycal-connections-summary" dir="rtl">';
     154    echo '<span class="paycal-stat-label">מרכז חיבורים</span>';
     155    if (!$visible) {
     156      echo '<strong class="paycal-stat-value">אין חיבור מזוהה</strong>';
     157      echo '<p class="paycal-stat-meta">לא זוהה חיבור תשלום מוגדר או מאומת בהתקנה זו.</p>';
     158    } else {
     159      echo '<strong class="paycal-stat-value">' . esc_html((string) count($visible)) . ' חיבורים במעקב</strong>';
     160      echo '<ul class="paycal-connection-lines">';
     161      foreach ($visible as $provider) {
     162        $status = (string) $provider['status'];
     163        echo '<li><strong>' . esc_html((string) $provider['name']) . '</strong><span>' . esc_html($labels[$status] ?? 'דורש בדיקה') . '</span></li>';
     164      }
     165      echo '</ul>';
     166    }
     167    echo '</article>';
     168  }
    171169}
  • klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_License.php

    r3676169 r3758537  
    1616    $err = '';
    1717
    18     $paycal_action = isset($_POST['paycal_action']) ? sanitize_key(wp_unslash($_POST['paycal_action'])) : '';
    19     if ($paycal_action === 'save_license') {
     18    if (!empty($_POST['paycal_action']) && $_POST['paycal_action'] === 'save_license') {
    2019      check_admin_referer('paycal_save_license');
    2120
    … …  
    4140            'grace_until' => time() + (7 * DAY_IN_SECONDS),
    4241          ]);
    43           $err = 'License Token verification failed: ' . sanitize_text_field((string) ($res['error'] ?? 'Unknown error'));
     42          $err = 'License Token verification failed: ' . esc_html($res['error'] ?? 'Unknown error');
    4443        } else {
    45           $err = 'License Token verification could not be completed: ' . sanitize_text_field((string) ($res['error'] ?? 'Unknown error')) . '. Existing connection status was preserved.';
     44          $err = 'License Token verification could not be completed: ' . esc_html($res['error'] ?? 'Unknown error') . '. Existing connection status was preserved.';
    4645        }
    4746      }
    … …  
    6362      }
    6463    }
     64    $key_val = esc_attr($stored_key);
    6565    $raw_status = class_exists(Gate::class) ? Gate::status_label() : 'לא מחובר';
    6666    $status_text = in_array($raw_status, ['Pending verification', 'Disabled'], true) ? 'לא מחובר' : $raw_status;
    67     $expires = (string) ($data['expires_at'] ?? '');
     67    $status = esc_html($status_text);
     68    $expires = esc_html($data['expires_at'] ?? '');
    6869
    6970    echo '<div class="wrap paycal-wrap">';
    … …  
    7576    echo '<div class="paycal-card">';
    7677    echo '<h2>רישוי</h2>';
    77     echo '<p><strong>' . esc_html($status_text) . '</strong></p>';
     78    echo '<p><strong>' . esc_html($status) . '</strong></p>';
    7879    if ($raw_status === 'Grace period') {
    7980      echo '<p class="description">Optional commercial modules are not fully licensed. Core connector and admin access remain available.</p>';
    … …  
    8788    echo '<div class="paycal-field" style="max-width:560px">';
    8889    echo '<label for="license_key">License Token</label>';
    89     echo '<input id="license_key" name="license_key" type="text" value="' . esc_attr($stored_key) . '" placeholder="lic_... only, not mrc_/trm_/pk_" />';
     90    echo '<input id="license_key" name="license_key" type="text" value="' . esc_attr($key_val) . '" placeholder="lic_... only, not mrc_/trm_/pk_" />';
    9091    echo '<p class="description">License Tokens are only for optional commercial modules. Merchant ID, Terminal ID and Public Key belong in Merchant / Terminal settings.</p>';
    9192    echo '</div>';
  • klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Marketplace.php

    r3565039 r3758537  
    1818        'tag'   => 'להתחלה',
    1919        'features' => [
    20           'Bit בחינם',
    2120          'תשלום בכרטיס אשראי לפי החבילה',
    2221          'חשבוניות כלולות בחבילה',
    … …  
    3029        'tag'   => 'מומלץ',
    3130        'features' => [
    32           'Bit בחינם',
    3331          'תשלום בכרטיס אשראי לפי החבילה',
    3432          'חשבוניות כלולות בחבילה',
    … …  
    4240        'tag'   => 'לסוכנויות',
    4341        'features' => [
    44           'Bit בחינם',
    4542          'תשלום בכרטיס אשראי לפי החבילה',
    4643          'חשבוניות כלולות בחבילה',
    … …  
    5754    echo '    <div class="paycal-market-hero__text">';
    5855    echo '      <h1>PayCal - ערכות</h1>';
    59     echo '      <p>בחרי את החבילה המתאימה לך. Bit ניתן בחינם, תשלום בכרטיס אשראי זמין לפי החבילה, וחשבוניות כלולות בתוך הערכות.</p>';
     56    echo '      <p>בחרי את החבילה המתאימה לך. תשלום בכרטיס אשראי זמין לפי החבילה, וחשבוניות כלולות בתוך הערכות.</p>';
    6057    echo '    </div>';
    6158    echo '  </div>';
    … …  
    6461    echo '<div class="paycal-bundles-intro">';
    6562    echo '<span class="paycal-current-plan">החבילה הנוכחית שלך: ' . esc_html(ucfirst($current_plan)) . '</span>';
    66     echo '<div class="paycal-bundle-note"><strong>מה כלול?</strong> Bit בחינם בכל החבילות • סליקת אשראי לפי החבילה • חשבוניות כחלק מהחבילה</div>';
     63    echo '<div class="paycal-bundle-note"><strong>מה כלול?</strong> סליקת אשראי לפי החבילה • חשבוניות כחלק מהחבילה</div>';
    6764    echo '</div>';
    6865
    … …  
    9491
    9592      echo '  <div class="paycal-bundle-meta">';
    96       echo '    <span><strong>Bit:</strong> חינם</span>';
    9793      echo '    <span><strong>אשראי:</strong> לפי החבילה</span>';
    9894      echo '    <span><strong>חשבוניות:</strong> כלול</span>';
  • klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Payments.php

    r3676169 r3758537  
    4040    echo '    <span class="paycal-payments-eyebrow">PAYCAL MERCHANT PAYMENTS</span>';
    4141    echo '    <h1>PayCal Payments</h1>';
    42     echo '    <p>תצוגת תשלומים ו־settlements לקריאה בלבד מתוך PayCal Platform.</p>';
     42    echo '    <p>רשומות PayCal לעיון בלבד. אין כאן אישור סליקה או הכנסות Live מאומתות. הרשומות עשויות לכלול הדגמות ונתונים היסטוריים.</p>';
    4343    echo '    <div class="paycal-payments-meta">';
    4444    echo '      <span>' . esc_html(self::text($merchant['business_name'] ?? 'Connected merchant')) . '</span>';
    45     echo '      <span>' . esc_html(self::text($merchant['mode'] ?? '')) . '</span>';
     45    echo '      <span>' . esc_html('Configured environment: ' . self::text($merchant['mode'] ?? '')) . '</span>';
    4646    echo '      <span>' . esc_html($currency) . '</span>';
    4747    echo '    </div>';
    … …  
    6464
    6565    echo '<section class="paycal-payments-stats">';
    66     self::metric('Gross', self::money($summary['gross_total'] ?? '0.00', $currency));
    67     self::metric('Fee', self::money($summary['fee_total'] ?? '0.00', $currency));
    68     self::metric('Net', self::money($summary['net_total'] ?? '0.00', $currency));
    69     self::metric('Pending Settlement', self::money($summary['pending_settlement'] ?? '0.00', $currency));
    70     self::metric('Settled Settlement', self::money($summary['settled_settlement'] ?? '0.00', $currency));
    71     self::metric('Ledger Entries', self::text($summary['ledger_entry_count'] ?? '0'));
     66    self::metric('Verified Live revenue', 'Not available');
     67    self::metric('Verified settlements', 'Not available');
     68    self::metric('Historical records reported by Platform', self::text($summary['ledger_entry_count'] ?? '0'));
    7269    echo '</section>';
    7370
    7471    echo '<section class="paycal-payments-grid">';
    75     echo '<article class="paycal-panel paycal-panel-wide"><div class="paycal-panel-head"><h2>Recent PayCal Payments</h2><span>' . esc_html((string) count($payments)) . '</span></div>';
     72    echo '<article class="paycal-panel paycal-panel-wide"><div class="paycal-panel-head"><h2>Unverified payment history</h2><span>' . esc_html((string) count($payments)) . '</span></div>';
    7673    self::payments_table($payments);
    7774    echo '</article>';
    7875
    79     echo '<article class="paycal-panel"><div class="paycal-panel-head"><h2>Reconciliation</h2></div>';
     76    echo '<article class="paycal-panel"><div class="paycal-panel-head"><h2>Unverified reconciliation history</h2></div>';
    8077    self::reconciliation_card($reconciliation);
    8178    echo '</article>';
    8279
    83     echo '<article class="paycal-panel paycal-panel-wide"><div class="paycal-panel-head"><h2>Settlements</h2><span>' . esc_html(self::text($summary['settlement_batch_count'] ?? '0')) . '</span></div>';
     80    echo '<article class="paycal-panel paycal-panel-wide"><div class="paycal-panel-head"><h2>Unverified settlement history</h2><span>' . esc_html(self::text($summary['settlement_batch_count'] ?? '0')) . '</span></div>';
    8481    self::settlements_table($settlements);
    8582    echo '</article>';
    … …  
    9087
    9188  private static function can_view(): bool {
    92     return current_user_can('manage_options') || current_user_can('manage_woocommerce') || current_user_can('edit_shop_orders');
     89    return current_user_can('manage_options') || current_user_can('manage_woocommerce');
    9390  }
    9491
  • klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Permissions.php

    r3676169 r3758537  
    1010  public static function render(): void {
    1111    Access::enforce('permissions');
     12    foreach (['paycal_permissions_secret', 'paycal_permissions_secret_confirm', 'paycal_permissions_unlock_secret', 'paycal_permissions_new_secret', 'paycal_permissions_new_secret_confirm', 'paycal_user_ids'] as $field) {
     13      if (isset($_POST[$field]) && !is_string($_POST[$field])) {
     14        wp_die(esc_html__('Invalid permission settings.', 'klamra-paycal-for-aspaclaria'), '', ['response'=>400]);
     15      }
     16    }
    1217
    1318    self::maybe_handle_setup();
    … …  
    189194    }
    190195
    191     $secret = isset($_POST['paycal_permissions_secret']) ? sanitize_text_field(wp_unslash($_POST['paycal_permissions_secret'])) : '';
    192     $confirm = isset($_POST['paycal_permissions_secret_confirm']) ? sanitize_text_field(wp_unslash($_POST['paycal_permissions_secret_confirm'])) : '';
     196    $secret = isset($_POST['paycal_permissions_secret']) && is_string($_POST['paycal_permissions_secret']) ? wp_unslash($_POST['paycal_permissions_secret']) : '';
     197    $confirm = isset($_POST['paycal_permissions_secret_confirm']) && is_string($_POST['paycal_permissions_secret_confirm']) ? wp_unslash($_POST['paycal_permissions_secret_confirm']) : '';
    193198
    194199    self::validate_secret_or_redirect($secret, $confirm);
    … …  
    206211    }
    207212
    208     $secret = isset($_POST['paycal_permissions_unlock_secret']) ? sanitize_text_field(wp_unslash($_POST['paycal_permissions_unlock_secret'])) : '';
     213    $secret = isset($_POST['paycal_permissions_unlock_secret']) && is_string($_POST['paycal_permissions_unlock_secret']) ? wp_unslash($_POST['paycal_permissions_unlock_secret']) : '';
    209214    if (!PermissionsStore::verify_secret($secret)) {
    210215      self::redirect_with_notice('bad_secret');
    … …  
    237242    }
    238243
    239     $secret = isset($_POST['paycal_permissions_new_secret']) ? sanitize_text_field(wp_unslash($_POST['paycal_permissions_new_secret'])) : '';
    240     $confirm = isset($_POST['paycal_permissions_new_secret_confirm']) ? sanitize_text_field(wp_unslash($_POST['paycal_permissions_new_secret_confirm'])) : '';
     244    $secret = isset($_POST['paycal_permissions_new_secret']) && is_string($_POST['paycal_permissions_new_secret']) ? wp_unslash($_POST['paycal_permissions_new_secret']) : '';
     245    $confirm = isset($_POST['paycal_permissions_new_secret_confirm']) && is_string($_POST['paycal_permissions_new_secret_confirm']) ? wp_unslash($_POST['paycal_permissions_new_secret_confirm']) : '';
    241246
    242247    self::validate_secret_or_redirect($secret, $confirm);
    … …  
    254259    }
    255260
    256     $roles = isset($_POST['paycal_roles']) ? map_deep(wp_unslash($_POST['paycal_roles']), 'sanitize_text_field') : [];
    257     $users = isset($_POST['paycal_users']) ? map_deep(wp_unslash($_POST['paycal_users']), 'sanitize_text_field') : [];
     261    $roles = isset($_POST['paycal_roles']) && is_array($_POST['paycal_roles']) ? map_deep(wp_unslash($_POST['paycal_roles']), 'sanitize_text_field') : [];
     262    $users = isset($_POST['paycal_users']) && is_array($_POST['paycal_users']) ? map_deep(wp_unslash($_POST['paycal_users']), 'sanitize_text_field') : [];
    258263
    259264    PermissionsStore::save([
  • klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Wizard.php

    r3676169 r3758537  
    1515    Access::enforce('setup');
    1616
    17     $step = isset($_GET['step']) ? sanitize_key(wp_unslash($_GET['step'])) : 'welcome';
     17    $step = sanitize_key($_GET['step'] ?? 'welcome');
    1818    $msg = '';
    1919    $err = '';
    2020
    21     if (isset($_POST['paycal_wizard_action'])) {
     21    if (!empty($_POST['paycal_wizard_action'])) {
    2222      check_admin_referer('paycal_wizard');
    2323      $action = sanitize_key(wp_unslash($_POST['paycal_wizard_action']));
    … …  
    5656          $err = 'זה נראה כמו נתון חיבור של PayCal, לא מפתח רישוי. יש להזין אותו בהגדרות Merchant / Terminal.';
    5757        } else {
    58           $err = 'License verification failed: ' . sanitize_text_field((string) ($res['error'] ?? 'Unknown error'));
     58          $err = 'License verification failed: ' . esc_html($res['error'] ?? 'Unknown error');
    5959        }
    6060      }
    … …  
    6363        wp_safe_redirect(admin_url('admin.php?page=paycal-setup&step=modules'));
    6464        exit;
    65       }
    66 
    67       if ($action === 'save_bit') {
    68         $phone = preg_replace('/\D+/', '', sanitize_text_field(wp_unslash($_POST['bit_phone'] ?? '')));
    69         $display = sanitize_text_field(wp_unslash($_POST['bit_display_name'] ?? ''));
    70         $note = sanitize_textarea_field(wp_unslash($_POST['bit_note'] ?? ''));
    71 
    72         if (strlen($phone) < 9) {
    73           $err = 'The Bit phone number looks too short. Check that you entered a valid phone number.';
    74         } else {
    75           \PayCal\Modules\Bit\BitSettings::set([
    76             'phone' => $phone,
    77             'display_name' => $display,
    78             'note' => $note,
    79           ]);
    80           wp_safe_redirect(admin_url('admin.php?page=paycal-setup&step=finish'));
    81           exit;
    82         }
    8365      }
    8466
    … …  
    122104      $stored_key = (string) ($data['license_key'] ?? '');
    123105      $looks_like_connector = $stored_key !== '' && LicenseClient::looks_like_connector_credential($stored_key);
    124       $key_val = $looks_like_connector ? '' : $stored_key;
     106      $key_val = $looks_like_connector ? '' : esc_attr($stored_key);
    125107
    126108      if ($looks_like_connector) {
    … …  
    148130
    149131    if ($step === 'modules') {
    150       $mods = LicenseStore::entitlements();
     132      $mods = array_values(array_diff(LicenseStore::entitlements(), ['bit']));
    151133      echo '<h2>Modules</h2>';
    152134      if (empty($mods)) {
    … …  
    157139        echo '</ul>';
    158140      }
    159       echo '<p><a class="button button-primary" href="' . esc_url(admin_url('admin.php?page=paycal-setup&step=bit')) . '">Continue to Bit Setup</a> ';
    160       echo '<a class="button" href="' . esc_url(admin_url('admin.php?page=paycal-setup&step=finish')) . '">Skip Bit Setup</a></p>';
    161     }
    162 
    163     if ($step === 'bit') {
    164       if (!Gate::has('bit')) {
    165         echo '<h2>Bit Setup</h2>';
    166         echo '<p class="paycal-muted">Bit is an optional commercial module and is not currently licensed.</p>';
    167         echo '<p><a class="button button-primary" href="' . esc_url(admin_url('admin.php?page=paycal-setup&step=finish')) . '">Continue to finish</a> ';
    168         echo '<a class="button" href="'.esc_url(admin_url('admin.php?page=paycal-bundles')).'">Marketplace</a></p>';
    169       } else {
    170         $s = \PayCal\Modules\Bit\BitSettings::get();
    171         $phone = (string) ($s['phone'] ?? '');
    172         $display = (string) ($s['display_name'] ?? '');
    173         $note = (string) ($s['note'] ?? 'Transfer with Bit according to the order total. After payment, the order will be reviewed.');
    174 
    175         echo '<h2>Bit Setup</h2>';
    176         echo '<div class="paycal-two-col">';
    177         echo '<div class="paycal-card">';
    178         echo '<h2>Payment receiving details</h2>';
    179         echo '<form method="post">';
    180         wp_nonce_field('paycal_wizard');
    181         echo '<input type="hidden" name="paycal_wizard_action" value="save_bit" />';
    182         echo '<div class="paycal-field" style="margin-bottom:12px"><label>Bit phone</label><input name="bit_phone" id="paycal_bit_phone" type="text" value="'.esc_attr($phone).'" placeholder="05XXXXXXXX" /></div>';
    183         echo '<div class="paycal-field" style="margin-bottom:12px"><label>Display name</label><input name="bit_display_name" id="paycal_bit_display" type="text" value="'.esc_attr($display).'" /></div>';
    184         echo '<div class="paycal-field" style="margin-bottom:12px"><label>Customer instructions</label><textarea name="bit_note" rows="4" id="paycal_bit_note">'.esc_textarea($note).'</textarea></div>';
    185         echo '<p class="submit" style="margin-top:14px"><button class="button button-primary">Save and continue</button></p>';
    186         echo '</form>';
    187         echo '</div>';
    188         echo '<div class="paycal-card"><h2>Preview</h2><div class="paycal-qr"><div id="paycal_qr_box" data-phone="'.esc_attr($phone).'"></div></div></div>';
    189         echo '</div>';
    190       }
     141      echo '<a class="button" href="' . esc_url(admin_url('admin.php?page=paycal-setup&step=finish')) . '">Continue to finish</a></p>';
    191142    }
    192143
    … …  
    211162      'license' => 'License - optional',
    212163      'modules' => 'Modules',
    213       'bit' => 'Bit Setup',
    214164      'finish' => 'Finish',
    215165    ];
  • klamra-paycal-for-aspaclaria/trunk/includes/Connector/ConnectorStore.php

    r3676169 r3758537  
    99  private const BASENAME_OPTION = 'paycal_connector_plugin_basename';
    1010  private const DIAGNOSTICS_OPTION = 'paycal_connector_update_diagnostics';
     11
     12  public static function remote_post(string $url, array $args) {
     13    $parts = wp_parse_url($url);
     14    if (!is_array($parts) || strtolower($parts['scheme'] ?? '') !== 'https' || empty($parts['host']) || isset($parts['user']) || isset($parts['pass'])) {
     15      return new \WP_Error('paycal_https_required', 'PayCal API requests require an HTTPS URL without embedded credentials.');
     16    }
     17    $args['redirection'] = 0;
     18    $args['sslverify'] = true;
     19    return wp_safe_remote_post($url, $args);
     20  }
     21
     22  public static function remote_get(string $url, array $args) {
     23    $parts = wp_parse_url($url);
     24    if (!is_array($parts) || strtolower($parts['scheme'] ?? '') !== 'https' || empty($parts['host']) || isset($parts['user']) || isset($parts['pass'])) {
     25      return new \WP_Error('paycal_https_required', 'PayCal API requests require an HTTPS URL without embedded credentials.');
     26    }
     27    $args['redirection'] = 0;
     28    $args['sslverify'] = true;
     29    return wp_safe_remote_get($url, $args);
     30  }
    1131
    1232  public static function defaults(): array {
  • klamra-paycal-for-aspaclaria/trunk/includes/Connector/DashboardClient.php

    r3676169 r3758537  
    66class DashboardClient {
    77  private const TRANSIENT_TTL = 180;
    8   private const LAST_GOOD_OPTION = 'paycal_merchant_dashboard_last_good';
     8  private const LAST_GOOD_OPTION = 'paycal_merchant_dashboard_review_v2_last_good';
    99
    1010  public static function get(bool $force_refresh = false): array {
     11    if (!current_user_can('manage_options') && !current_user_can('manage_woocommerce')) {
     12      return ['ok'=>false, 'auth_error'=>true, 'stale'=>false, 'data'=>[], 'message'=>'Dashboard access denied.', 'last_updated'=>''];
     13    }
    1114    $settings = ConnectorStore::get();
    1215    $credentials = self::credentials($settings);
    … …  
    4548      $url = add_query_arg('_paycal_refresh', (string) time(), $url);
    4649    }
    47     $response = wp_remote_get($url, [
     50    $response = ConnectorStore::remote_get($url, [
    4851      'timeout' => 12,
     52      'limit_response_size' => 1048576,
    4953      'headers' => [
    5054        'Accept' => 'application/json',
    … …  
    5761
    5862    if (is_wp_error($response)) {
    59       return self::unavailable($response->get_error_message(), $settings);
     63      return self::unavailable('PayCal Platform is unavailable.', $settings);
    6064    }
    6165
    6266    $code = (int) wp_remote_retrieve_response_code($response);
    6367    if ($code === 401 || $code === 403) {
     68      delete_transient($cache_key);
     69      delete_option(self::LAST_GOOD_OPTION);
    6470      return [
    6571        'ok' => false,
    … …  
    7783
    7884    $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
    79     if (!is_array($decoded) || empty($decoded['ok'])) {
     85    if (!is_array($decoded) || !in_array($decoded['ok'] ?? null, [true, 1, '1'], true)) {
    8086      return self::unavailable('PayCal Platform returned an unreadable dashboard response.', $settings);
    8187    }
    8288
     89    $merchant = $decoded['merchant'] ?? null;
     90    if (!is_array($merchant) || !is_string($merchant['uid'] ?? null) || empty($settings['merchant_id']) || !hash_equals((string) $settings['merchant_id'], $merchant['uid']) || ($merchant['mode'] ?? '') !== ($settings['mode'] ?? '')) {
     91      delete_transient($cache_key);
     92      delete_option(self::LAST_GOOD_OPTION);
     93      return ['ok'=>false, 'auth_error'=>true, 'stale'=>false, 'data'=>[], 'message'=>'Dashboard merchant or environment mismatch.', 'last_updated'=>''];
     94    }
    8395    $data = self::clean_payload($decoded);
    8496    $now = self::fetch_time($data);
    … …  
    102114
    103115  private static function credentials(array $settings): array {
    104     $ready = !empty($settings['platform_url']) && !empty($settings['public_key']) && !empty($settings['access_token']);
     116    $ready = !empty($settings['platform_url']) && !empty($settings['public_key']) && !empty($settings['access_token']) && !empty($settings['merchant_id']);
    105117    return ['ready' => $ready];
    106118  }
    … …  
    130142
    131143  private static function cache_key(array $settings): string {
    132     return 'paycal_merchant_dashboard_' . md5((string) ($settings['platform_url'] ?? '') . '|' . (string) ($settings['public_key'] ?? '') . '|' . hash('sha256', (string) ($settings['access_token'] ?? '')));
     144    return 'paycal_dashboard_review_v2_' . md5((string) ($settings['merchant_id'] ?? '') . '|' . (string) ($settings['terminal_id'] ?? '') . '|' . (string) ($settings['mode'] ?? '') . '|' . (string) ($settings['platform_url'] ?? '') . '|' . (string) ($settings['public_key'] ?? '') . '|' . hash('sha256', (string) ($settings['access_token'] ?? '')));
    133145  }
    134146
    … …  
    164176        'settlement_status',
    165177        'settlement_reference',
    166         'invoice_url',
    167178      ]),
    168179      'recent_settlements' => self::clean_rows((array) ($payload['recent_settlements'] ?? []), [
    … …  
    188199        'created_at',
    189200      ]),
    190       'server_time' => sanitize_text_field((string) ($payload['server_time'] ?? '')),
     201      'server_time' => is_scalar($payload['server_time'] ?? null) ? sanitize_text_field((string) $payload['server_time']) : '',
    191202    ];
    192203  }
    … …  
    206217    foreach ($keys as $key) {
    207218      $value = $row[$key] ?? '';
     219      if ($key === 'status') {
     220        $value = in_array($value, ['demo_approved', 'test_approved'], true) ? 'demo_or_test_unverified' : 'legacy_unverified';
     221      }
     222      if ($key === 'settlement_status' || $key === 'match_status') {
     223        $value = 'unverified';
     224      }
    208225      $clean[$key] = is_scalar($value) ? sanitize_text_field((string) $value) : '';
    209226    }
  • klamra-paycal-for-aspaclaria/trunk/includes/Connector/EventClient.php

    r3676169 r3758537  
    1515    }
    1616
    17     if (!function_exists('wp_remote_post')) {
     17    if (!function_exists('wp_safe_remote_post')) {
    1818      \paycal_debug_log('[PayCal] Connector event skipped: WordPress HTTP API unavailable.');
    1919      return ['ok' => false, 'message' => 'WordPress HTTP API unavailable'];
    … …  
    2727    set_transient($rate_key, 1, 5 * MINUTE_IN_SECONDS);
    2828
    29     $response = wp_remote_post(rtrim($settings['platform_url'], '/') . '/wp-json/paycal/v1/connector-event', [
     29    $response = ConnectorStore::remote_post(rtrim($settings['platform_url'], '/') . '/wp-json/paycal/v1/connector-event', [
    3030      'timeout' => 10,
    3131      'headers' => [
    … …  
    3939
    4040    if (is_wp_error($response)) {
    41       \paycal_debug_log('[PayCal] Connector event failed: ' . $response->get_error_message());
     41      \paycal_debug_log('[PayCal] Connector event failed: HTTP transport error.');
    4242      return ['ok' => false, 'message' => $response->get_error_message()];
    4343    }
  • klamra-paycal-for-aspaclaria/trunk/includes/Connector/MerchantDashboard.php

    r3557616 r3758537  
    1212    if (!is_user_logged_in()) {
    1313      return '<div class="paycal-front-card"><p>יש להתחבר כדי לצפות בדשבורד הסוחר.</p></div>';
     14    }
     15
     16    if (!current_user_can('manage_options') && !current_user_can('manage_woocommerce')) {
     17      return '<div class="paycal-front-card"><p>' . esc_html__('Access denied.', 'klamra-paycal-for-aspaclaria') . '</p></div>';
    1418    }
    1519
  • klamra-paycal-for-aspaclaria/trunk/includes/Connector/SyncClient.php

    r3676169 r3758537  
    1818    $url = rtrim($settings['platform_url'], '/') . '/wp-json/paycal/v1/sync';
    1919
    20     if (!function_exists('wp_remote_post')) {
     20    if (!function_exists('wp_safe_remote_post')) {
    2121      $result = ['ok' => false, 'message' => 'WordPress HTTP API unavailable.', 'synced_at' => current_time('mysql')];
    2222      update_option(self::OPTION, $result, false);
    … …  
    3535    }
    3636
    37     $response = wp_remote_post($url, [
     37    $response = ConnectorStore::remote_post($url, [
    3838      'timeout' => 15,
    3939      'headers' => $headers,
    … …  
    4242        'terminal_id' => $settings['terminal_id'] ?? '',
    4343        'site_url' => home_url('/'),
    44         'plugin_version' => defined('PAYCAL_VERSION') ? PAYCAL_VERSION : 'unknown',
     44        'plugin_version' => defined('PAYCAL_VERSION') ? PAYCAL_VERSION : '1.3.1',
    4545      ]),
    4646    ]);
    … …  
    4949      $result = ['ok' => false, 'message' => $response->get_error_message()];
    5050      update_option(self::OPTION, $result, false);
    51       \paycal_debug_log('[PayCal] Sync failed: ' . $response->get_error_message());
     51      \paycal_debug_log('[PayCal] Sync failed: HTTP transport error.');
    5252      return $result;
    5353    }
    … …  
    6161
    6262    $result = [
    63       'ok' => $code >= 200 && $code < 300,
     63      'ok' => $code >= 200 && $code < 300 && is_array($body) && !empty($body) && (!array_key_exists('ok', $body) || in_array($body['ok'], [true, 1, '1'], true)),
    6464      'code' => $code,
    6565      'body' => is_array($body) ? $body : [],
  • klamra-paycal-for-aspaclaria/trunk/includes/Core/AdminMenu.php

    r3676169 r3758537  
    4242        'slug' => 'paycal-bundles',
    4343        'callback' => [\PayCal\Admin\Page_Marketplace::class, 'render'],
    44       ],
    45       'bit' => [
    46         'page_title' => 'ביט',
    47         'menu_title' => 'ביט',
    48         'slug' => 'paycal-bit',
    49         'callback' => [\PayCal\Admin\Page_Bit::class, 'render'],
    5044      ],
    5145      'invoices' => [
  • klamra-paycal-for-aspaclaria/trunk/includes/Core/PermissionFallback.php

    r3661556 r3758537  
    1515      'license'     => 'גישה למסך רישיון',
    1616      'bundles'     => 'גישה לערכות',
    17       'bit'         => 'גישה למסך ביט',
    1817      'invoices'    => 'גישה למסך חשבוניות',
    1918      'setup'       => 'גישה למסך התקנה',
  • klamra-paycal-for-aspaclaria/trunk/includes/Core/Plugin.php

    r3676169 r3758537  
    4747    }
    4848
    49     if (class_exists(\PayCal\Modules\Bit\BitModule::class)) {
    50       add_action('plugins_loaded', [\PayCal\Modules\Bit\BitModule::class, 'init'], 20);
     49    // Keep historical order statuses visible without loading a payment gateway.
     50    if (class_exists(\PayCal\Modules\Bit\BitOrderStatus::class)) {
     51      \PayCal\Modules\Bit\BitOrderStatus::init();
    5152    }
    5253
    … …  
    6768
    6869    self::safe_require('includes/Connector/ConnectorStore.php', 'Connector store');
     70    self::safe_require('includes/Connector/ProviderRegistry.php', 'Provider catalogue');
    6971    self::safe_require('includes/Connector/EventClient.php', 'Connector event client');
    7072    self::safe_require('includes/Gateway/HostedGateway.php', 'Hosted gateway');
    … …  
    7274    self::safe_require('includes/Connector/MerchantDashboard.php', 'Merchant dashboard');
    7375    self::safe_require('includes/Connector/DashboardClient.php', 'Dashboard client');
     76    self::safe_require('includes/Admin/Page_Payments.php', 'Payments dashboard');
    7477
    7578    $permissions_store_loaded = self::safe_require('includes/Permissions/PermissionsStore.php', 'Permissions store');
    … …  
    8790    self::safe_require('includes/Admin/Page_Permissions.php', 'Permissions page');
    8891    self::safe_require('includes/Admin/Page_Connector.php', 'Connector page');
    89     self::safe_require('includes/Admin/Page_Payments.php', 'Payments dashboard page');
    9092
    91     self::safe_require('includes/Admin/Page_Bit.php', 'Bit page');
    9293    self::safe_require('includes/Admin/Page_Invoices.php', 'Invoices page');
    9394
    94     self::safe_require('includes/Modules/Bit/BitModule.php', 'Bit module');
     95    self::safe_require('includes/Modules/Bit/BitOrderStatus.php', 'Historical order status');
    9596    self::safe_require('includes/Modules/Invoices/InvoicesModule.php', 'Invoices module');
    9697  }
  • klamra-paycal-for-aspaclaria/trunk/includes/Gateway/WC_Gateway_PayCal_Hosted.php

    r3676169 r3758537  
    1111
    1212add_action('template_redirect', function () {
    13   // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public WooCommerce return route; authenticated by order key and server-side PayCal verification below.
    1413  if (!empty($_GET['wc-api']) && sanitize_key(wp_unslash($_GET['wc-api'])) === 'paycal_hosted_return') {
    1514    return;
    … …  
    4746
    4847  public static function is_paycal_return_request(): bool {
    49     // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Public hosted checkout return; adding a nonce would change the PayCal return contract.
     48    foreach (['paycal_paid', 'paycal_order_id', 'paycal_order_key'] as $key) {
     49      if (!isset($_GET[$key]) || !is_scalar($_GET[$key])) {
     50        return false;
     51      }
     52    }
    5053    $paid = isset($_GET['paycal_paid']) ? sanitize_text_field(wp_unslash($_GET['paycal_paid'])) : '';
    51     $order_id = isset($_GET['paycal_order_id']) ? absint(wp_unslash($_GET['paycal_order_id'])) : 0;
     54    $order_id = isset($_GET['paycal_order_id']) ? absint($_GET['paycal_order_id']) : 0;
    5255    $order_key = isset($_GET['paycal_order_key']) ? sanitize_text_field(wp_unslash($_GET['paycal_order_key'])) : '';
    53     // phpcs:enable WordPress.Security.NonceVerification.Recommended
    5456
    5557    return $paid === '1' && $order_id > 0 && $order_key !== '';
    … …  
    6567  }
    6668
    67   // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Return was identified above; order key and server verification protect the flow.
    68   $order_id = isset($_GET['paycal_order_id']) ? absint(wp_unslash($_GET['paycal_order_id'])) : 0;
     69  $order_id = absint($_GET['paycal_order_id']);
    6970  $order = wc_get_order($order_id);
    7071
    … …  
    7475  }
    7576
    76   // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public hosted checkout return; validated against the WooCommerce order key.
    77   $order_key = isset($_GET['paycal_order_key']) ? sanitize_text_field(wp_unslash($_GET['paycal_order_key'])) : '';
     77  if ($order->get_payment_method() !== 'paycal_hosted') {
     78    return;
     79  }
     80
     81  $order_key = sanitize_text_field(wp_unslash($_GET['paycal_order_key']));
    7882
    7983  if (!hash_equals($order->get_order_key(), $order_key)) {
    … …  
    102106      $redirect_debug = self::return_redirect_debug($order);
    103107      $redirect_url = $redirect_debug['redirect_url'];
    104       \paycal_debug_log('[PayCal] Hosted return redirecting customer after verification. order_id=' . (string) $order->get_id() . ', redirect_url=' . $redirect_url . ', has_key=' . (strpos($redirect_url, 'key=') !== false ? 'yes' : 'no'));
     108      \paycal_debug_log('[PayCal] Hosted return redirecting customer after verification. order_id=' . (string) $order->get_id() . ', has_key=' . (strpos($redirect_url, 'key=') !== false ? 'yes' : 'no'));
    105109      self::render_success_transition($redirect_url, $order, $redirect_debug);
    106110    } elseif ($redirect_after) {
    … …  
    120124    $this->init_settings();
    121125
    122     $this->title = $this->get_option('title', 'Apple Pay / Credit Card');
     126    $this->title = $this->get_option('title', 'PayCal');
    123127    $this->description = $this->get_option('description', 'Secure payment through PayCal.');
    124128    $this->enabled = $this->get_option('enabled', 'no');
    … …  
    138142        'title' => 'Title',
    139143        'type' => 'text',
    140         'default' => 'Apple Pay / Credit Card',
     144        'default' => 'PayCal',
    141145      ],
    142146      'description' => [
    … …  
    165169    $settings = ConnectorStore::get();
    166170
    167     if (empty($settings['platform_url']) || empty($settings['merchant_id']) || empty($settings['public_key'])) {
     171    if (empty($settings['platform_url']) || empty($settings['merchant_id']) || empty($settings['public_key']) || empty($settings['access_token'])) {
    168172      return false;
    169173    }
    … …  
    178182      wc_add_notice('Payment error: order not found.', 'error');
    179183      return ['result' => 'failure'];
     184    }
     185
     186    if ($order->is_paid()) {
     187      return ['result' => 'success', 'redirect' => $this->get_return_url($order)];
    180188    }
    181189
    … …  
    204212    }
    205213
     214    self::$last_verification_response[(int) $order->get_id()] = [];
     215    if (!method_exists($order, 'get_payment_method') || $order->get_payment_method() !== 'paycal_hosted') {
     216      return false;
     217    }
     218
    206219    \paycal_debug_log('[PayCal] Server verification started for order ' . (string) $order->get_id() . '.');
    207220
    … …  
    217230    }
    218231
    219     if (!function_exists('wp_remote_post')) {
     232    if (!function_exists('wp_safe_remote_post')) {
    220233      \paycal_debug_log('[PayCal] Payment verification failed: WordPress HTTP API unavailable.');
    221234      return false;
    … …  
    223236
    224237    $session_id = self::latest_checkout_session_id_from_notes($order);
    225     $response = wp_remote_post(rtrim((string) $settings['platform_url'], '/') . '/wp-json/paycal/v1/payments/verify', [
     238    $response = ConnectorStore::remote_post(rtrim((string) $settings['platform_url'], '/') . '/wp-json/paycal/v1/payments/verify', [
    226239      'timeout' => 15,
    227240      'headers' => [
    … …  
    242255
    243256    if (is_wp_error($response)) {
    244       \paycal_debug_log('[PayCal] Payment verification failed: ' . $response->get_error_message());
     257      \paycal_debug_log('[PayCal] Payment verification failed: HTTP transport error.');
    245258      return false;
    246259    }
    … …  
    249262    $raw = wp_remote_retrieve_body($response);
    250263    $body = json_decode($raw, true);
    251     $paid = $code >= 200 && $code < 300 && is_array($body) && !empty($body['paid']);
     264    $paid = $code >= 200 && $code < 300 && is_array($body) && in_array($body['paid'] ?? null, [true, 1, '1'], true);
    252265    self::$last_verification_response[(int) $order->get_id()] = is_array($body) ? $body : [];
    253266    if ($paid && !self::verification_response_matches_order($body, $order, $session_id)) {
    … …  
    261274    }
    262275
    263     \paycal_debug_log('[PayCal] Server verification succeeded for order ' . (string) $order->get_id() . '.');
    264     return true;
     276    // The current v1 response cannot prove provider settlement. No option, filter,
     277    // response flag or environment label may bypass this local safety hold.
     278    self::$last_verification_response[(int) $order->get_id()] = ['code' => 'provider_verification_unavailable'];
     279    \paycal_debug_log('[PayCal] Provider verification unavailable. Order remains unpaid.');
     280    return false;
    265281  }
    266282
    … …  
    274290    $verification = self::$last_verification_response[(int) $order->get_id()] ?? [];
    275291    $code = is_array($verification) ? self::scalar_text($verification['code'] ?? '') : '';
    276     $order->add_order_note($code === 'checkout_session_expired' ? 'PayCal session expired.' : 'PayCal verification failed.');
     292    $order->add_order_note($code === 'checkout_session_expired' ? 'PayCal session expired.' : ($code === 'provider_verification_unavailable' ? 'PayCal provider verification unavailable. No payment confirmation was applied.' : 'PayCal verification failed.'));
    277293  }
    278294
    … …  
    392408    }
    393409
    394     $lines = ['PayCal payment verified successfully.', 'Gateway: Apple Pay'];
     410    $lines = ['PayCal payment verified successfully.', 'Gateway: PayCal Hosted Checkout'];
    395411    $amount = self::scalar_text($verification['amount'] ?? '');
    396412    $currency = self::scalar_text($verification['currency'] ?? $order->get_currency());
    … …  
    460476    foreach ((array) $notes as $note) {
    461477      $content = is_object($note) && isset($note->content) ? (string) $note->content : '';
    462       if (strpos($content, 'PayCal verification failed.') !== false || strpos($content, 'PayCal session expired.') !== false || strpos($content, 'PayCal hosted checkout return received, but server-to-server verification did not confirm payment.') !== false) {
     478      if (strpos($content, 'PayCal provider verification unavailable.') !== false || strpos($content, 'PayCal verification failed.') !== false || strpos($content, 'PayCal session expired.') !== false || strpos($content, 'PayCal hosted checkout return received, but server-to-server verification did not confirm payment.') !== false) {
    463479        return true;
    464480      }
    … …  
    468484
    469485  private static function latest_checkout_session_id_from_notes($order): string {
     486    $stored = (string) $order->get_meta('_paycal_checkout_session_id', true);
     487    if (preg_match('/^pcs_[a-z0-9_-]{20,64}$/D', $stored)) {
     488      return $stored;
     489    }
     490    // Compatibility for sessions created before 1.3.2.
    470491    if (!function_exists('wc_get_order_notes')) {
    471492      return '';
    … …  
    508529      return false;
    509530    }
    510     if ($response_amount === '' || abs((float) $response_amount - (float) $order->get_total()) >= 0.0001) {
     531    if ((!is_string($body['amount'] ?? null) && !is_int($body['amount'] ?? null) && !is_float($body['amount'] ?? null)) || !preg_match('/^[0-9]+(?:\.[0-9]+)?$/D', $response_amount) || !is_finite((float) $response_amount) || abs((float) $response_amount - (float) $order->get_total()) >= 0.0001) {
    511532      return false;
    512533    }
    … …  
    559580    $lines = [
    560581      'PayCal hosted return redirect debug.',
    561       'Redirect URL: ' . self::scalar_text($debug['redirect_url'] ?? ''),
    562582      'Has order key: ' . self::scalar_text($debug['has_order_key'] ?? 'no'),
    563583      'Return source: ' . self::scalar_text($debug['return_source'] ?? 'checkout_fallback'),
    … …  
    568588      'Final response emitted: ' . self::scalar_text($debug['final_response_emitted'] ?? 'unknown'),
    569589    ];
    570 
    571     $order_received_url = self::scalar_text($debug['order_received_url'] ?? '');
    572     if ($order_received_url !== '') {
    573       $lines[] = 'Order received URL: ' . $order_received_url;
    574     }
    575 
    576     $gateway_return_url = self::scalar_text($debug['gateway_return_url'] ?? '');
    577     if ($gateway_return_url !== '') {
    578       $lines[] = 'Gateway return URL: ' . $gateway_return_url;
    579     }
    580590
    581591    $order->add_order_note(implode("\n", $lines));
    … …  
    750760
    751761  private function create_checkout_session($order, array $settings): string {
    752     if (!function_exists('wp_remote_post') || empty($settings['platform_url']) || empty($settings['public_key']) || empty($settings['access_token'])) {
     762    if (!function_exists('wp_safe_remote_post') || empty($settings['platform_url']) || empty($settings['public_key']) || empty($settings['access_token'])) {
    753763      \paycal_debug_log('[PayCal] Generic checkout session skipped: connector API settings are incomplete.');
    754764      return '';
    … …  
    761771    ], $this->get_return_url($order));
    762772
    763     $response = wp_remote_post(rtrim((string) $settings['platform_url'], '/') . '/wp-json/paycal/v1/checkout-sessions/create', [
     773    $response = ConnectorStore::remote_post(rtrim((string) $settings['platform_url'], '/') . '/wp-json/paycal/v1/checkout-sessions/create', [
    764774      'timeout' => 15,
    765775      'headers' => [
    … …  
    783793
    784794    if (is_wp_error($response)) {
    785       $error_code = sanitize_text_field((string) $response->get_error_code());
    786       $error_message = sanitize_text_field((string) $response->get_error_message());
    787       $diagnostic = 'transport_error=yes, error_code=' . ($error_code ?: 'unknown') . ', message=' . ($error_message ?: 'unknown');
     795      $diagnostic = 'transport_error=yes';
    788796      \paycal_debug_log('[PayCal] Generic checkout session creation failed. ' . $diagnostic);
    789797      $order->add_order_note('PayCal generic checkout session create failed. ' . $diagnostic . '. Existing hosted checkout fallback used.');
    … …  
    795803    $body = json_decode($raw, true);
    796804    $valid_json = json_last_error() === JSON_ERROR_NONE && is_array($body);
    797     $session_id = is_array($body) ? sanitize_text_field((string) ($body['session_id'] ?? '')) : '';
    798     $checkout_url = is_array($body) ? esc_url_raw((string) ($body['checkout_url'] ?? '')) : '';
     805    $session_id = is_array($body) ? self::scalar_text($body['session_id'] ?? '') : '';
     806    $checkout_url = is_array($body) && is_string($body['checkout_url'] ?? null) ? esc_url_raw($body['checkout_url']) : '';
    799807    $checkout_host = strtolower((string) wp_parse_url($checkout_url, PHP_URL_HOST));
    800808    $checkout_scheme = strtolower((string) wp_parse_url($checkout_url, PHP_URL_SCHEME));
    801809
    802     if ($code < 200 || $code >= 300 || empty($body['ok']) || !preg_match('/^pcs_[a-z0-9_-]{20,64}$/', $session_id) || $checkout_scheme !== 'https' || $checkout_host !== 'checkout.paycal.online') {
     810    if ($code < 200 || $code >= 300 || !is_array($body) || !in_array($body['ok'] ?? null, [true, 1, '1'], true) || !preg_match('/^pcs_[a-z0-9_-]{20,64}$/', $session_id) || $checkout_scheme !== 'https' || $checkout_host !== 'checkout.paycal.online') {
    803811      $diagnostic = $this->checkout_session_response_diagnostic((int) $code, $body, $valid_json, $session_id, $checkout_url);
    804812      \paycal_debug_log('[PayCal] Generic checkout session creation returned an invalid response. ' . $diagnostic . ', body_hash=' . hash('sha256', (string) $raw));
    … …  
    807815    }
    808816
     817    $order->update_meta_data('_paycal_checkout_session_id', $session_id);
     818    $order->save();
    809819    $order->add_order_note('PayCal generic checkout session created: ' . $session_id);
    810820    \paycal_debug_log('[PayCal] Generic checkout session created for order ' . (string) $order->get_id() . ': ' . $session_id);
    … …  
    813823
    814824  private function checkout_session_response_diagnostic(int $http_code, $body, bool $valid_json, string $session_id, string $checkout_url): string {
    815     $keys = $valid_json ? array_slice(array_map('sanitize_key', array_keys($body)), 0, 12) : [];
    816     $error_value = $valid_json ? ($body['code'] ?? ($body['error'] ?? '')) : '';
    817     $message_value = $valid_json ? ($body['message'] ?? '') : '';
    818     $error_code = is_scalar($error_value) ? sanitize_text_field((string) $error_value) : '';
    819     $message = is_scalar($message_value) ? sanitize_text_field((string) $message_value) : '';
    820825    $data_status = $valid_json && isset($body['data']['status']) && is_scalar($body['data']['status']) ? absint($body['data']['status']) : 0;
    821826
    … …  
    823828      'http_status=' . $http_code,
    824829      'valid_json=' . ($valid_json ? 'yes' : 'no'),
    825       'top_level_keys=' . ($keys ? implode('|', $keys) : 'none'),
    826       'error_code=' . ($error_code !== '' ? substr($error_code, 0, 80) : 'none'),
    827       'message=' . ($message !== '' ? substr($message, 0, 160) : 'none'),
    828830      'data_status=' . ($data_status ?: 'none'),
    829831      'session_id_exists=' . ($session_id !== '' ? 'yes' : 'no'),
  • klamra-paycal-for-aspaclaria/trunk/includes/Licensing/LicenseClient.php

    r3676169 r3758537  
    2424    }
    2525
    26     if (!function_exists('wp_remote_post')) {
     26    if (!function_exists('wp_safe_remote_post')) {
    2727      \paycal_debug_log('[PayCal] WordPress HTTP API unavailable during license validation.');
    2828      return ['ok' => false, 'error' => 'WordPress HTTP API unavailable', 'reason' => 'transport'];
    … …  
    3030
    3131    $endpoint = self::endpoint();
    32     $response = wp_remote_post($endpoint, [
     32    $response = ConnectorStore::remote_post($endpoint, [
    3333      'timeout' => 15,
    3434      'headers' => ['Accept' => 'application/json'],
    … …  
    4343
    4444    if (is_wp_error($response)) {
    45       \paycal_debug_log('[PayCal] License validation failed: ' . $response->get_error_message());
     45      \paycal_debug_log('[PayCal] License validation failed: HTTP transport error.');
    4646      return ['ok' => false, 'error' => $response->get_error_message(), 'reason' => 'transport'];
    4747    }
    … …  
    5656    }
    5757
    58     if (empty($json['valid'])) {
     58    if (!in_array($json['valid'] ?? null, [true, 1, '1'], true)) {
    5959      return ['ok' => false, 'error' => 'License invalid', 'reason' => 'invalid'];
    6060    }
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Bit/BitOrderStatus.php

    r3676169 r3758537  
    1717      'show_in_admin_all_list'    => true,
    1818      'show_in_admin_status_list' => true,
    19       /* translators: %s: number of Bit-pending orders. */
     19      // translators: %s: Number of historical orders awaiting Bit payment.
    2020      'label_count'               => _n_noop('ממתין לתשלום בביט <span class="count">(%s)</span>', 'ממתין לתשלום בביט <span class="count">(%s)</span>', 'klamra-paycal-for-aspaclaria')
    2121    ]);
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/InvoicesModule.php

    r3676169 r3758537  
    1212    if (!defined('PAYCAL_INV_URL'))  define('PAYCAL_INV_URL',  PAYCAL_URL .  'includes/Modules/Invoices/Legacy/');
    1313
    14     // Back-compat constants used in legacy files.
    15     // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
     14    // Back-compat constants used in legacy files
    1615    if (!defined('ASP_INV_PATH')) define('ASP_INV_PATH', PAYCAL_INV_PATH);
    17     // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
    1816    if (!defined('ASP_INV_URL'))  define('ASP_INV_URL',  PAYCAL_INV_URL);
    1917
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/admin/settings.php

    r3676169 r3758537  
    5151  if ($paycal_inv_action === 'save') {
    5252    check_admin_referer('paycal_inv_save');
     53    foreach (['layout', 'primary_color', 'accent_color', 'logo_id', 'business_name', 'business_details', 'footer_html', 'trigger_status', 'custom_html', 'israel_allocation_threshold_2025', 'israel_allocation_threshold_2026_h1', 'israel_allocation_threshold_current', 'israel_allocation_current_from', 'enabled'] as $field) {
     54      if (isset($_POST[$field]) && !is_string($_POST[$field])) {
     55        wp_die(esc_html__('Invalid invoice settings.', 'klamra-paycal-for-aspaclaria'), '', ['response'=>400]);
     56      }
     57    }
    5358    $trigger_status = sanitize_key(wp_unslash($_POST['trigger_status'] ?? 'completed'));
    5459
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/aspaclaria-invoices.php

    r3676169 r3758537  
    77if (!defined('ABSPATH')) exit;
    88
    9 $paycal_invoice_loader = __DIR__ . '/paycal-invoices.php';
    10 if (!file_exists($paycal_invoice_loader)) {
    11     \paycal_debug_log('[PayCal Invoices] Missing compatibility loader target: ' . $paycal_invoice_loader);
     9$loader = __DIR__ . '/paycal-invoices.php';
     10if (!file_exists($loader)) {
     11    \paycal_debug_log('[PayCal Invoices] Missing compatibility loader target: ' . $loader);
    1212    add_action('admin_notices', function () {
    1313        if (current_user_can('manage_options')) {
    … …  
    1919
    2020try {
    21     require_once $paycal_invoice_loader;
     21    require_once $loader;
    2222} catch (Throwable $e) {
    2323    \paycal_debug_log('[PayCal Invoices] Compatibility loader failed: ' . $e->getMessage());
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/cpt.php

    r3676169 r3758537  
    99    'menu_icon' => 'dashicons-media-text',
    1010    'supports' => ['title'],
     11    'map_meta_cap' => false,
     12    'capabilities' => array_fill_keys([
     13      'edit_post', 'read_post', 'delete_post', 'edit_posts', 'edit_others_posts',
     14      'publish_posts', 'read_private_posts', 'delete_posts', 'delete_private_posts',
     15      'delete_published_posts', 'delete_others_posts', 'edit_private_posts',
     16      'edit_published_posts', 'create_posts',
     17    ], 'manage_woocommerce'),
    1118  ]);
    1219});
    … …  
    159166  }
    160167
    161   // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Narrow admin lookup for one invoice linked to the current WooCommerce order.
    162168  $invoice_ids = get_posts([
    163169    'post_type' => 'asp_invoice',
    … …  
    168174    'posts_per_page' => 1,
    169175  ]);
    170   // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
    171176
    172177  $invoice_id = !empty($invoice_ids) ? absint($invoice_ids[0]) : 0;
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/numbering.php

    r3676169 r3758537  
    22if (!defined('ABSPATH')) exit;
    33
    4 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy invoice numbering API kept for backward compatibility.
    54function asp_get_next_invoice_number() {
    65    $n = (int) get_option('asp_invoice_number', 1000);
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/order-hook.php

    r3676169 r3758537  
    1818
    1919  // prevent duplicates
    20   // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Duplicate guard for the WooCommerce order currently changing status.
    2120  $existing = get_posts([
    2221    'post_type' => 'asp_invoice',
    … …  
    2726    'posts_per_page' => 1
    2827  ]);
    29   // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
    3028  if (!empty($existing)) return;
    3129
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/pdf/download.php

    r3676169 r3758537  
    1515    }
    1616
    17     $invoice_id = isset($_GET['invoice_id']) ? absint(wp_unslash($_GET['invoice_id'])) : 0;
     17    $invoice_id = absint($_GET['invoice_id'] ?? 0);
    1818    if (!$invoice_id) {
    1919        paycal_invoice_download_not_found();
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/render.php

    r3555025 r3758537  
    3636  $map = [
    3737    '{{invoice_id}}' => (string)$invoice_id,
    38     '{{invoice_number}}' => get_post_field('post_title', $invoice_id),
     38    '{{invoice_number}}' => esc_html(get_post_field('post_title', $invoice_id)),
    3939    '{{order_id}}' => (string)$order->get_id(),
    40     '{{order_number}}' => (string)$order->get_order_number(),
     40    '{{order_number}}' => esc_html((string)$order->get_order_number()),
    4141    '{{order_date}}' => esc_html($order->get_date_created() ? $order->get_date_created()->date_i18n('d/m/Y') : ''),
    4242    '{{total}}' => wp_kses_post(wc_price($order->get_total())),
    … …  
    194194      <div>
    195195        <div style="font-size:20px;font-weight:900;margin:0">'.$biz.'</div>
    196         <div style="opacity:.9;color:#E6F4FF;margin-top:6px">חשבונית • '.$order->get_order_number().' • '.($order->get_date_created() ? $order->get_date_created()->date_i18n('d/m/Y') : '').'</div>
     196        <div style="opacity:.9;color:#E6F4FF;margin-top:6px">חשבונית • '.esc_html((string)$order->get_order_number()).' • '.esc_html($order->get_date_created() ? $order->get_date_created()->date_i18n('d/m/Y') : '').'</div>
    197197      </div>'
    198198      . ($logo_url ? '<img src="'.esc_url($logo_url).'" alt="logo" style="max-height:52px;border-radius:10px" />' : '') .
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/paycal-invoices.php

    r3676169 r3758537  
    1111define('PAYCAL_INV_URL', plugin_dir_url(__FILE__));
    1212
    13 // Back-compat constants (older code).
    14 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
     13// Back-compat constants (older code)
    1514if (!defined('ASP_INV_PATH')) define('ASP_INV_PATH', PAYCAL_INV_PATH);
    16 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
    1715if (!defined('ASP_INV_URL'))  define('ASP_INV_URL',  PAYCAL_INV_URL);
    1816
    … …  
    3331        add_action('admin_notices', function () use ($paycal_invoice_file) {
    3432            if (current_user_can('manage_options')) {
    35                 /* translators: %s: missing PayCal Invoices file path. */
     33                /* translators: %s: Missing invoice module filename. */
    3634                echo '<div class="notice notice-error"><p>' . esc_html(sprintf(__('PayCal Invoices could not load %s. Reinstall the plugin package.', 'klamra-paycal-for-aspaclaria'), $paycal_invoice_file)) . '</p></div>';
    3735            }
    … …  
    4644        add_action('admin_notices', function () use ($paycal_invoice_file) {
    4745            if (current_user_can('manage_options')) {
    48                 /* translators: %s: failed PayCal Invoices file path. */
     46                /* translators: %s: Invoice module filename. */
    4947                echo '<div class="notice notice-error"><p>' . esc_html(sprintf(__('PayCal Invoices failed to load %s safely. Check the server error log.', 'klamra-paycal-for-aspaclaria'), $paycal_invoice_file)) . '</p></div>';
    5048            }
  • klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/templates/invoice.php

    r3676169 r3758537  
    22if (!defined('ABSPATH')) exit;
    33
    4 $paycal_invoice_template_id = isset($invoice_id) ? absint($invoice_id) : 0;
    5 if (!$paycal_invoice_template_id && isset($_GET['invoice_id'])) {
    6   $paycal_invoice_template_id = absint(wp_unslash($_GET['invoice_id']));
     4$invoice_id = isset($invoice_id) ? absint($invoice_id) : 0;
     5if (!$invoice_id && isset($_GET['invoice_id'])) {
     6  $invoice_id = absint($_GET['invoice_id']);
    77  if (!is_user_logged_in() || !function_exists('paycal_invoice_get_authorized_order_for_download')) {
    88    wp_die(esc_html__('Access denied.', 'klamra-paycal-for-aspaclaria'), '', ['response' => 403]);
    99  }
    1010
    11   $paycal_invoice_nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? ''));
    12   if (!$paycal_invoice_nonce || !wp_verify_nonce($paycal_invoice_nonce, 'paycal_invoice_download_' . $paycal_invoice_template_id)) {
     11  $nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? ''));
     12  if (!$nonce || !wp_verify_nonce($nonce, 'paycal_invoice_download_' . $invoice_id)) {
    1313    wp_die(esc_html__('Access denied.', 'klamra-paycal-for-aspaclaria'), '', ['response' => 403]);
    1414  }
    1515
    16   $order = paycal_invoice_get_authorized_order_for_download($paycal_invoice_template_id);
     16  $order = paycal_invoice_get_authorized_order_for_download($invoice_id);
    1717  if (is_wp_error($order)) {
    1818    $status = (int) ($order->get_error_data()['status'] ?? 403);
    … …  
    2525}
    2626
    27 echo wp_kses_post(paycal_invoice_render_html($paycal_invoice_template_id));
     27echo wp_kses_post(paycal_invoice_render_html($invoice_id));
  • klamra-paycal-for-aspaclaria/trunk/includes/Permissions/PermissionsStore.php

    r3498130 r3758537  
    1414    'license'     => 'גישה למסך רישיון',
    1515    'bundles'     => 'גישה לערכות',
    16     'bit'         => 'גישה למסך ביט',
    1716    'invoices'    => 'גישה למסך חשבוניות',
    1817    'setup'       => 'גישה למסך התקנה',
  • klamra-paycal-for-aspaclaria/trunk/klamra-paycal-for-aspaclaria.php

    r3676169 r3758537  
    22/**
    33 * Plugin Name: Klamra Paycal for Aspaclaria
    4  * Plugin URI: https://paycal.online/
     4 * Plugin URI: https://wordpress.org/plugins/klamra-paycal-for-aspaclaria/
    55 * Description: PayCal branded checkout and payment-management connector for WooCommerce, synced with PayCal Platform and approved payment providers.
    6  * Version: 1.4.2
     6 * Version: 1.4.4
    77 * Requires at least: 6.0
    88 * Requires PHP: 8.0
    … …  
    1515if (!defined('ABSPATH')) exit;
    1616
    17 define('PAYCAL_VERSION', '1.4.2');
     17define('PAYCAL_VERSION', '1.4.4');
    1818define('PAYCAL_PLUGIN_FILE', __FILE__);
    1919define('PAYCAL_PATH', plugin_dir_path(__FILE__));
    2020define('PAYCAL_URL', plugin_dir_url(__FILE__));
     21
     22add_action('admin_notices', function () {
     23    if (current_user_can('manage_woocommerce') || current_user_can('manage_options')) {
     24        echo '<div class="notice notice-warning"><p>' . esc_html__('PayCal automatic payment completion is blocked until verified payment-provider integration is available. Demo checkout does not mark orders paid.', 'klamra-paycal-for-aspaclaria') . '</p></div>';
     25    }
     26});
    2127
    2228add_action('before_woocommerce_init', function () {
    … …  
    3137            return;
    3238        }
    33 
    34         wc_get_logger()->debug(sanitize_text_field($message), ['source' => 'paycal']);
     39        // Log only the fixed event prefix, never interpolated payloads, URLs or identifiers.
     40        $prefixes = ['[PayCal]', '[PayCal Invoices]'];
     41        $prefix = strpos($message, $prefixes[1]) === 0 ? $prefixes[1] : $prefixes[0];
     42        wc_get_logger()->debug($prefix . ' Diagnostic event. Sensitive details omitted.', ['source' => 'paycal']);
    3543    }
    3644}
    3745
    3846if (!file_exists(PAYCAL_PATH . 'includes/Core/Plugin.php')) {
    39     paycal_debug_log('[PayCal] Missing core plugin loader.');
     47    \paycal_debug_log('[PayCal] Missing core plugin loader.');
    4048    add_action('admin_notices', function () {
    4149        if (current_user_can('manage_options')) {
    … …  
    4957    require_once PAYCAL_PATH . 'includes/Core/Plugin.php';
    5058} catch (Throwable $e) {
    51     paycal_debug_log('[PayCal] Failed loading core plugin loader: ' . $e->getMessage());
     59    \paycal_debug_log('[PayCal] Failed loading core plugin loader: ' . $e->getMessage());
    5260    add_action('admin_notices', function () {
    5361        if (current_user_can('manage_options')) {
  • klamra-paycal-for-aspaclaria/trunk/readme.txt

    r3676169 r3758537  
    33Tags: apple pay, woocommerce, checkout, payments, payment gateway
    44Requires at least: 6.0
    5 Tested up to: 7.1
     5Tested up to: 7.1.3
    66Requires PHP: 8.0
    7 Stable tag: 1.4.2
     7Stable tag: 1.4.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1212
    1313== Description ==
     14
     15Maintenance review candidate: automatic WooCommerce payment completion is blocked in this version because the existing PayCal v1 response does not establish verified provider payment. Hosted checkout and Demo requests remain available, but must not be used as evidence that an order was paid. Do not install on a live store before separate approval and provider integration testing.
    1416
    1517**PayCal** is a branded checkout and payment-management platform for WooCommerce stores built for creative businesses.
    … …  
    5759== Screenshots ==
    5860
    59 1. PayCal settings inside WooCommerce
    60 2. PayCal payment option at WooCommerce checkout
    61 3. Guided merchant connection and License Token setup
    62 4. PayCal Payments read-only Merchant Dashboard with recent payments and settlement totals
    63 5. WooCommerce admin menu entry for PayCal Payments
     611. Klamra PayCal dashboard and module navigation in a staging installation.
     622. PayCal Hosted Checkout gateway settings in WooCommerce, before activation.
     633. PayCal connector configuration in Sandbox, with credential fields empty.
     644. Invoice layout settings with demonstration business details in a staging installation.
     655. PayCal Payments dashboard displaying merchant payment history, ledger entries, settlement summaries, reconciliation status, and recent transactions in a read-only interface. Historical figures are not verified Live payment revenue.
    6466
    6567== Changelog ==
     68
     69= 1.4.3 =
     70* Fail closed on automatic payment completion, including legacy checkout fallback; positive Platform flags cannot substitute for verified provider integration.
     71* Merge the read-only payments dashboard, refresh cache and form improvements from 1.4.2 onto the security-maintained 1.3.2 baseline.
     72* Preserve strict payment parsing, WooCommerce CRUD session metadata and removal of the active Bit integration.
     73* Restrict dashboard access, require safe HTTPS transport, bind cache to merchant/environment and omit untrusted invoice links.
     74* Label all Platform ledger history as unverified; verified Live revenue and settlement totals are unavailable under the existing API.
     75* Gate diagnostic logging behind WP_DEBUG and omit dynamic sensitive details.
     76* Staging review candidate. No PayCal Suite, external checkout or Stripe integration changes.
     77
    6678
    6779= 1.4.2 =
    … …  
    7688
    7789= 1.4.0 =
    78 * Added WooCommerce → PayCal Payments read-only merchant dashboard.
     90* Added WooCommerce PayCal Payments read-only merchant dashboard.
    7991* Pulls Ledger, Settlement and Reconciliation data from PayCal Platform through the authenticated merchant dashboard API.
    8092* Adds short-lived dashboard caching and last-good fallback when PayCal Platform is temporarily unavailable.
    8193* Does not create local Ledger or Settlement tables and does not change checkout, payment verification, refunds or money movement.
     94
     95= 1.3.2 =
     96* Update screenshot captions for planned staging captures. Replacement screenshots must be captured and reviewed before directory publication.
     97* Harden payment verification types, amount validation and payment-method isolation.
     98* Preserve new checkout session IDs using the WooCommerce order metadata API.
     99* Require HTTPS and validated destinations for API requests; do not follow credential-bearing redirects.
     100* Stop recording order-key URLs in new return diagnostics.
     101* Restrict invoice administration and merchant dashboard access.
     102* Remove the Bit gateway, settings, payment instructions and marketing UI. Preserve historical order status registration and all existing order data.
     103* Reject malformed or explicitly unsuccessful sync responses.
     104* Correct unverified security-status claims in the connector screen.
     105* Retain existing environment selection and credentials; no Sandbox/Production routing changes.
     106* Use a neutral PayCal label for new gateway configurations and payment notes.
     107* Escape invoice and admin output at the rendering boundary.
     108* Local regression tests: WordPress 7.1.3, WooCommerce 11.2.1, HPOS and classic order storage. Provider responses were mocked; live wallets and provider webhooks require separate staging validation.
     109* Maintenance candidate: see the separate validation report before production installation. Historical testing claims below are inherited, not re-certified.
     110
    82111
    83112= 1.3.1 =
    … …  
    222251
    223252== Hosted Checkout Gateway ==
    224 Version 1.3.1 includes the customer-side hosted WooCommerce gateway. Apple Pay provisioning remains in PayCal Platform and is synced by Merchant ID, Terminal ID, Public Key and Token.
     253Version 1.3.2 includes the customer-side hosted WooCommerce gateway. Apple Pay provisioning remains in PayCal Platform and is synced by Merchant ID, Terminal ID, Public Key and Token.
     254
     255== 1.4.4 Review changes ==
     256* Added display-only payment provider catalogue to dashboard (Stripe, Bit, Isracard, MAX, CAL, Tranzila, PayPlus).
     257* Provider statuses default to not configured; Bit is marked paused. Verified state may be supplied by trusted PHP integrations via paycal_provider_verified_states filter.
     258* Preserved INVOICES, license, system dashboard cards and all legacy invoice sources.
     259* No provider gateway or automatic order completion was enabled.
Note: See TracChangeset for help on using the changeset viewer.