Changeset 3758537
- Timestamp:
- 10/21/2026 02:20:13 PM (13 days ago)
- Location:
- klamra-paycal-for-aspaclaria
- Files:
-
- 90 added
- 34 edited
-
assets/screenshot-1.jpg (added)
-
assets/screenshot-2.png (modified) ( previous)
-
assets/screenshot-3.jpg (added)
-
assets/screenshot-4.jpg (added)
-
tags/1.4.4 (added)
-
tags/1.4.4/assets (added)
-
tags/1.4.4/assets/admin.css (added)
-
tags/1.4.4/assets/admin.js (added)
-
tags/1.4.4/assets/front.css (added)
-
tags/1.4.4/assets/front.js (added)
-
tags/1.4.4/assets/images (added)
-
tags/1.4.4/assets/images/bugs-runner.png (added)
-
tags/1.4.4/assets/images/paycal-baroque-lock-clean-117.png (added)
-
tags/1.4.4/assets/images/paycal-baroque-lock.png (added)
-
tags/1.4.4/assets/images/paycal-dragon-shield-clean-117.png (added)
-
tags/1.4.4/assets/images/paycal-dragon-shield.png (added)
-
tags/1.4.4/includes (added)
-
tags/1.4.4/includes/Admin (added)
-
tags/1.4.4/includes/Admin/Page_Bit.php (added)
-
tags/1.4.4/includes/Admin/Page_Connector.php (added)
-
tags/1.4.4/includes/Admin/Page_Dashboard.php (added)
-
tags/1.4.4/includes/Admin/Page_Invoices.php (added)
-
tags/1.4.4/includes/Admin/Page_License.php (added)
-
tags/1.4.4/includes/Admin/Page_Marketplace.php (added)
-
tags/1.4.4/includes/Admin/Page_Payments.php (added)
-
tags/1.4.4/includes/Admin/Page_Permissions.php (added)
-
tags/1.4.4/includes/Admin/Page_Wizard.php (added)
-
tags/1.4.4/includes/Connector (added)
-
tags/1.4.4/includes/Connector/ConnectorStore.php (added)
-
tags/1.4.4/includes/Connector/DashboardClient.php (added)
-
tags/1.4.4/includes/Connector/EventClient.php (added)
-
tags/1.4.4/includes/Connector/MerchantDashboard.php (added)
-
tags/1.4.4/includes/Connector/ProviderRegistry.php (added)
-
tags/1.4.4/includes/Connector/SyncClient.php (added)
-
tags/1.4.4/includes/Core (added)
-
tags/1.4.4/includes/Core/AdminMenu.php (added)
-
tags/1.4.4/includes/Core/Assets.php (added)
-
tags/1.4.4/includes/Core/Notices.php (added)
-
tags/1.4.4/includes/Core/PermissionFallback.php (added)
-
tags/1.4.4/includes/Core/Plugin.php (added)
-
tags/1.4.4/includes/Gateway (added)
-
tags/1.4.4/includes/Gateway/HostedGateway.php (added)
-
tags/1.4.4/includes/Gateway/WC_Gateway_PayCal_Hosted.php (added)
-
tags/1.4.4/includes/Licensing (added)
-
tags/1.4.4/includes/Licensing/Cron.php (added)
-
tags/1.4.4/includes/Licensing/Gate.php (added)
-
tags/1.4.4/includes/Licensing/LicenseClient.php (added)
-
tags/1.4.4/includes/Licensing/LicenseStore.php (added)
-
tags/1.4.4/includes/Modules (added)
-
tags/1.4.4/includes/Modules/Bit (added)
-
tags/1.4.4/includes/Modules/Bit/BitAdminActions.php (added)
-
tags/1.4.4/includes/Modules/Bit/BitCheckoutUI.php (added)
-
tags/1.4.4/includes/Modules/Bit/BitGatewayLoader.php (added)
-
tags/1.4.4/includes/Modules/Bit/BitModule.php (added)
-
tags/1.4.4/includes/Modules/Bit/BitOrderStatus.php (added)
-
tags/1.4.4/includes/Modules/Bit/BitSettings.php (added)
-
tags/1.4.4/includes/Modules/Bit/BitThankYouText.php (added)
-
tags/1.4.4/includes/Modules/Bit/Gateway (added)
-
tags/1.4.4/includes/Modules/Bit/Gateway/assets (added)
-
tags/1.4.4/includes/Modules/Bit/Gateway/assets/css (added)
-
tags/1.4.4/includes/Modules/Bit/Gateway/assets/css/style.css (added)
-
tags/1.4.4/includes/Modules/Bit/Gateway/assets/js (added)
-
tags/1.4.4/includes/Modules/Bit/Gateway/assets/js/asp.js (added)
-
tags/1.4.4/includes/Modules/Bit/Gateway/class-wc-gateway-asp-bit.php (added)
-
tags/1.4.4/includes/Modules/Invoices (added)
-
tags/1.4.4/includes/Modules/Invoices/InvoicesModule.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/README.txt (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/admin (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/admin/admin.css (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/admin/admin.js (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/admin/settings.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/aspaclaria-invoices.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes/cpt.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes/email.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes/numbering.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes/order-hook.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes/pdf (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes/pdf/download.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/includes/render.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/paycal-invoices.php (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/templates (added)
-
tags/1.4.4/includes/Modules/Invoices/Legacy/templates/invoice.php (added)
-
tags/1.4.4/includes/Permissions (added)
-
tags/1.4.4/includes/Permissions/Access.php (added)
-
tags/1.4.4/includes/Permissions/PermissionsStore.php (added)
-
tags/1.4.4/klamra-paycal-for-aspaclaria.php (added)
-
tags/1.4.4/readme.txt (added)
-
tags/1.4.4/uninstall.php (added)
-
trunk/assets/admin.css (modified) (1 diff)
-
trunk/assets/admin.js (modified) (2 diffs)
-
trunk/includes/Admin/Page_Connector.php (modified) (3 diffs)
-
trunk/includes/Admin/Page_Dashboard.php (modified) (11 diffs)
-
trunk/includes/Admin/Page_License.php (modified) (5 diffs)
-
trunk/includes/Admin/Page_Marketplace.php (modified) (6 diffs)
-
trunk/includes/Admin/Page_Payments.php (modified) (3 diffs)
-
trunk/includes/Admin/Page_Permissions.php (modified) (5 diffs)
-
trunk/includes/Admin/Page_Wizard.php (modified) (7 diffs)
-
trunk/includes/Connector/ConnectorStore.php (modified) (1 diff)
-
trunk/includes/Connector/DashboardClient.php (modified) (9 diffs)
-
trunk/includes/Connector/EventClient.php (modified) (3 diffs)
-
trunk/includes/Connector/MerchantDashboard.php (modified) (1 diff)
-
trunk/includes/Connector/ProviderRegistry.php (added)
-
trunk/includes/Connector/SyncClient.php (modified) (5 diffs)
-
trunk/includes/Core/AdminMenu.php (modified) (1 diff)
-
trunk/includes/Core/PermissionFallback.php (modified) (1 diff)
-
trunk/includes/Core/Plugin.php (modified) (4 diffs)
-
trunk/includes/Gateway/WC_Gateway_PayCal_Hosted.php (modified) (29 diffs)
-
trunk/includes/Licensing/LicenseClient.php (modified) (4 diffs)
-
trunk/includes/Modules/Bit/BitOrderStatus.php (modified) (1 diff)
-
trunk/includes/Modules/Invoices/InvoicesModule.php (modified) (1 diff)
-
trunk/includes/Modules/Invoices/Legacy/admin/settings.php (modified) (1 diff)
-
trunk/includes/Modules/Invoices/Legacy/aspaclaria-invoices.php (modified) (2 diffs)
-
trunk/includes/Modules/Invoices/Legacy/includes/cpt.php (modified) (3 diffs)
-
trunk/includes/Modules/Invoices/Legacy/includes/numbering.php (modified) (1 diff)
-
trunk/includes/Modules/Invoices/Legacy/includes/order-hook.php (modified) (2 diffs)
-
trunk/includes/Modules/Invoices/Legacy/includes/pdf/download.php (modified) (1 diff)
-
trunk/includes/Modules/Invoices/Legacy/includes/render.php (modified) (2 diffs)
-
trunk/includes/Modules/Invoices/Legacy/paycal-invoices.php (modified) (3 diffs)
-
trunk/includes/Modules/Invoices/Legacy/templates/invoice.php (modified) (2 diffs)
-
trunk/includes/Permissions/PermissionsStore.php (modified) (1 diff)
-
trunk/klamra-paycal-for-aspaclaria.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
klamra-paycal-for-aspaclaria/trunk/assets/admin.css
r3565039 r3758537 1778 1778 } 1779 1779 } 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 1 1 (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 17 2 function setupCopy(){ 18 3 document.querySelectorAll('[data-copy]').forEach(function(btn){ … … 34 19 35 20 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 47 21 setupCopy(); 48 22 }); -
klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Connector.php
r3661556 r3758537 70 70 <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> 71 71 <?php if (!$platform_compatible) : ?> 72 <div class="notice notice-warning"><p><?php echo esc_html__('PayCal Connector 1.3. 1is 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> 73 73 <?php endif; ?> 74 74 <?php if ($no_migrated_settings_found) : ?> … … 84 84 <tr><th>Plugin version</th><td><?php echo esc_html(defined('PAYCAL_VERSION') ? PAYCAL_VERSION : 'unknown'); ?></td></tr> 85 85 <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> 87 87 <tr><th>PHP runtime</th><td><?php echo esc_html(PHP_VERSION . ' (target: 8.3.31)'); ?></td></tr> 88 88 <tr><th>WooCommerce gateway classes</th><td><?php echo esc_html($woocommerce_ok ? 'available' : 'unavailable'); ?></td></tr> … … 101 101 <tr><th>Hosted checkout</th><td><?php echo esc_html($connector_ready ? 'enabled' : 'disabled'); ?></td></tr> 102 102 <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> 105 105 <tr><th>Merchant isolation</th><td><?php echo esc_html($connector_ready ? 'enabled' : 'disabled'); ?></td></tr> 106 106 <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> 108 108 </tbody> 109 109 </table> -
klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Dashboard.php
r3661556 r3758537 7 7 use PayCal\Licensing\Gate; 8 8 use PayCal\Licensing\LicenseStore; 9 use PayCal\Connector\ProviderRegistry; 9 10 10 11 class Page_Dashboard { … … 14 15 $data = LicenseStore::get(); 15 16 $mods = array_values(array_filter((array) ($data['modules'] ?? []))); 17 $providers = ProviderRegistry::all(); 16 18 $license_label = Gate::status_label(); 17 19 $license_active = !empty($data['valid']); … … 22 24 $license_display = $license_active || $license_in_grace ? $license_label : 'לא מחובר'; 23 25 24 $bit_enabled = Gate::has('bit');25 26 $invoice_settings = get_option('paycal_invoice_settings', []); 26 27 $invoices_enabled = !is_array($invoice_settings) || !isset($invoice_settings['enabled']) || (string) $invoice_settings['enabled'] === 'yes'; 27 28 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');41 29 $setup_link = Access::can('setup') ? admin_url('admin.php?page=paycal-setup') : ''; 42 30 $market_link = Access::can('bundles') ? admin_url('admin.php?page=paycal-bundles') : ''; 43 31 $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') : '';45 32 $invoice_link = Access::can('invoices') ? admin_url('admin.php?page=paycal-invoices') : ''; 46 33 … … 64 51 echo ' <strong>' . esc_html((string) count($mods)) . '</strong>'; 65 52 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>';71 53 echo ' </div>'; 72 54 echo ' </div>'; … … 97 79 echo ' </article>'; 98 80 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">';104 81 echo ' <span class="paycal-stat-label">Invoices</span>'; 105 82 echo ' <strong class="paycal-stat-value">' . ($invoices_enabled ? 'פעיל' : 'לא פעיל') . '</strong>'; … … 111 88 echo ' <p class="paycal-stat-meta">אשף התקנה, רישוי ומודולים במקום אחד</p>'; 112 89 echo ' </article>'; 90 self::render_connection_card($providers); 113 91 echo '</section>'; 114 92 … … 118 96 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>'; 119 97 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>';121 98 echo ' <span class="paycal-pill"><span class="paycal-dot"></span>מודולים: <strong>' . esc_html((string) count($mods)) . '</strong></span>'; 122 99 echo ' </div>'; … … 127 104 echo ' <div><strong>רישוי</strong><span>רישיון מסחרי לא חובר. מודולים שאינם דורשים רישיון ימשיכו לעבוד.</span></div>'; 128 105 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>';135 106 echo ' </div>'; 136 107 } … … 151 122 if ($market_link) echo ' <a class="paycal-action-item" href="' . esc_url($market_link) . '"><strong>Marketplace</strong><span>ניהול הרחבות ומודולים זמינים</span></a>'; 152 123 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 }156 124 echo ' </div>'; 157 125 echo ' </div>'; … … 160 128 echo ' <ul class="paycal-checklist">'; 161 129 echo ' <li>' . ($license_active ? 'הרישיון מחובר והמערכת יכולה לטעון מודולים בהתאם.' : 'רישיון נדרש רק להפעלת מודולים מסחריים אופציונליים.') . '</li>'; 162 echo ' <li>' . ($bit_enabled ? 'מודול Bit זמין. מומלץ לעבור על הזמנות ממתינות.' : 'להפעיל את Bit אם רוצים זרימת תשלומים מהירה בווקומרס.') . '</li>';163 130 echo ' <li>' . ($invoices_enabled ? 'מודול החשבוניות פעיל וזמין להגדרות.' : 'להפעיל Invoices אם צריך מסמכים וניהול פיננסי מתוך המערכת.') . '</li>'; 164 131 echo ' </ul>'; … … 169 136 echo '</div>'; 170 137 } 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 } 171 169 } -
klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_License.php
r3676169 r3758537 16 16 $err = ''; 17 17 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') { 20 19 check_admin_referer('paycal_save_license'); 21 20 … … 41 40 'grace_until' => time() + (7 * DAY_IN_SECONDS), 42 41 ]); 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'); 44 43 } 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.'; 46 45 } 47 46 } … … 63 62 } 64 63 } 64 $key_val = esc_attr($stored_key); 65 65 $raw_status = class_exists(Gate::class) ? Gate::status_label() : 'לא מחובר'; 66 66 $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'] ?? ''); 68 69 69 70 echo '<div class="wrap paycal-wrap">'; … … 75 76 echo '<div class="paycal-card">'; 76 77 echo '<h2>רישוי</h2>'; 77 echo '<p><strong>' . esc_html($status _text) . '</strong></p>';78 echo '<p><strong>' . esc_html($status) . '</strong></p>'; 78 79 if ($raw_status === 'Grace period') { 79 80 echo '<p class="description">Optional commercial modules are not fully licensed. Core connector and admin access remain available.</p>'; … … 87 88 echo '<div class="paycal-field" style="max-width:560px">'; 88 89 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_" />'; 90 91 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>'; 91 92 echo '</div>'; -
klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Marketplace.php
r3565039 r3758537 18 18 'tag' => 'להתחלה', 19 19 'features' => [ 20 'Bit בחינם',21 20 'תשלום בכרטיס אשראי לפי החבילה', 22 21 'חשבוניות כלולות בחבילה', … … 30 29 'tag' => 'מומלץ', 31 30 'features' => [ 32 'Bit בחינם',33 31 'תשלום בכרטיס אשראי לפי החבילה', 34 32 'חשבוניות כלולות בחבילה', … … 42 40 'tag' => 'לסוכנויות', 43 41 'features' => [ 44 'Bit בחינם',45 42 'תשלום בכרטיס אשראי לפי החבילה', 46 43 'חשבוניות כלולות בחבילה', … … 57 54 echo ' <div class="paycal-market-hero__text">'; 58 55 echo ' <h1>PayCal - ערכות</h1>'; 59 echo ' <p>בחרי את החבילה המתאימה לך. Bit ניתן בחינם,תשלום בכרטיס אשראי זמין לפי החבילה, וחשבוניות כלולות בתוך הערכות.</p>';56 echo ' <p>בחרי את החבילה המתאימה לך. תשלום בכרטיס אשראי זמין לפי החבילה, וחשבוניות כלולות בתוך הערכות.</p>'; 60 57 echo ' </div>'; 61 58 echo ' </div>'; … … 64 61 echo '<div class="paycal-bundles-intro">'; 65 62 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>'; 67 64 echo '</div>'; 68 65 … … 94 91 95 92 echo ' <div class="paycal-bundle-meta">'; 96 echo ' <span><strong>Bit:</strong> חינם</span>';97 93 echo ' <span><strong>אשראי:</strong> לפי החבילה</span>'; 98 94 echo ' <span><strong>חשבוניות:</strong> כלול</span>'; -
klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Payments.php
r3676169 r3758537 40 40 echo ' <span class="paycal-payments-eyebrow">PAYCAL MERCHANT PAYMENTS</span>'; 41 41 echo ' <h1>PayCal Payments</h1>'; 42 echo ' <p> תצוגת תשלומים ו־settlements לקריאה בלבד מתוך PayCal Platform.</p>';42 echo ' <p>רשומות PayCal לעיון בלבד. אין כאן אישור סליקה או הכנסות Live מאומתות. הרשומות עשויות לכלול הדגמות ונתונים היסטוריים.</p>'; 43 43 echo ' <div class="paycal-payments-meta">'; 44 44 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>'; 46 46 echo ' <span>' . esc_html($currency) . '</span>'; 47 47 echo ' </div>'; … … 64 64 65 65 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')); 72 69 echo '</section>'; 73 70 74 71 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>'; 76 73 self::payments_table($payments); 77 74 echo '</article>'; 78 75 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>'; 80 77 self::reconciliation_card($reconciliation); 81 78 echo '</article>'; 82 79 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>'; 84 81 self::settlements_table($settlements); 85 82 echo '</article>'; … … 90 87 91 88 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'); 93 90 } 94 91 -
klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Permissions.php
r3676169 r3758537 10 10 public static function render(): void { 11 11 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 } 12 17 13 18 self::maybe_handle_setup(); … … 189 194 } 190 195 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']) : ''; 193 198 194 199 self::validate_secret_or_redirect($secret, $confirm); … … 206 211 } 207 212 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']) : ''; 209 214 if (!PermissionsStore::verify_secret($secret)) { 210 215 self::redirect_with_notice('bad_secret'); … … 237 242 } 238 243 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']) : ''; 241 246 242 247 self::validate_secret_or_redirect($secret, $confirm); … … 254 259 } 255 260 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') : []; 258 263 259 264 PermissionsStore::save([ -
klamra-paycal-for-aspaclaria/trunk/includes/Admin/Page_Wizard.php
r3676169 r3758537 15 15 Access::enforce('setup'); 16 16 17 $step = isset($_GET['step']) ? sanitize_key(wp_unslash($_GET['step'])) : 'welcome';17 $step = sanitize_key($_GET['step'] ?? 'welcome'); 18 18 $msg = ''; 19 19 $err = ''; 20 20 21 if ( isset($_POST['paycal_wizard_action'])) {21 if (!empty($_POST['paycal_wizard_action'])) { 22 22 check_admin_referer('paycal_wizard'); 23 23 $action = sanitize_key(wp_unslash($_POST['paycal_wizard_action'])); … … 56 56 $err = 'זה נראה כמו נתון חיבור של PayCal, לא מפתח רישוי. יש להזין אותו בהגדרות Merchant / Terminal.'; 57 57 } 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'); 59 59 } 60 60 } … … 63 63 wp_safe_redirect(admin_url('admin.php?page=paycal-setup&step=modules')); 64 64 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 }83 65 } 84 66 … … 122 104 $stored_key = (string) ($data['license_key'] ?? ''); 123 105 $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); 125 107 126 108 if ($looks_like_connector) { … … 148 130 149 131 if ($step === 'modules') { 150 $mods = LicenseStore::entitlements();132 $mods = array_values(array_diff(LicenseStore::entitlements(), ['bit'])); 151 133 echo '<h2>Modules</h2>'; 152 134 if (empty($mods)) { … … 157 139 echo '</ul>'; 158 140 } 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>'; 191 142 } 192 143 … … 211 162 'license' => 'License - optional', 212 163 'modules' => 'Modules', 213 'bit' => 'Bit Setup',214 164 'finish' => 'Finish', 215 165 ]; -
klamra-paycal-for-aspaclaria/trunk/includes/Connector/ConnectorStore.php
r3676169 r3758537 9 9 private const BASENAME_OPTION = 'paycal_connector_plugin_basename'; 10 10 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 } 11 31 12 32 public static function defaults(): array { -
klamra-paycal-for-aspaclaria/trunk/includes/Connector/DashboardClient.php
r3676169 r3758537 6 6 class DashboardClient { 7 7 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'; 9 9 10 10 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 } 11 14 $settings = ConnectorStore::get(); 12 15 $credentials = self::credentials($settings); … … 45 48 $url = add_query_arg('_paycal_refresh', (string) time(), $url); 46 49 } 47 $response = wp_remote_get($url, [50 $response = ConnectorStore::remote_get($url, [ 48 51 'timeout' => 12, 52 'limit_response_size' => 1048576, 49 53 'headers' => [ 50 54 'Accept' => 'application/json', … … 57 61 58 62 if (is_wp_error($response)) { 59 return self::unavailable( $response->get_error_message(), $settings);63 return self::unavailable('PayCal Platform is unavailable.', $settings); 60 64 } 61 65 62 66 $code = (int) wp_remote_retrieve_response_code($response); 63 67 if ($code === 401 || $code === 403) { 68 delete_transient($cache_key); 69 delete_option(self::LAST_GOOD_OPTION); 64 70 return [ 65 71 'ok' => false, … … 77 83 78 84 $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)) { 80 86 return self::unavailable('PayCal Platform returned an unreadable dashboard response.', $settings); 81 87 } 82 88 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 } 83 95 $data = self::clean_payload($decoded); 84 96 $now = self::fetch_time($data); … … 102 114 103 115 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']); 105 117 return ['ready' => $ready]; 106 118 } … … 130 142 131 143 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'] ?? ''))); 133 145 } 134 146 … … 164 176 'settlement_status', 165 177 'settlement_reference', 166 'invoice_url',167 178 ]), 168 179 'recent_settlements' => self::clean_rows((array) ($payload['recent_settlements'] ?? []), [ … … 188 199 'created_at', 189 200 ]), 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']) : '', 191 202 ]; 192 203 } … … 206 217 foreach ($keys as $key) { 207 218 $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 } 208 225 $clean[$key] = is_scalar($value) ? sanitize_text_field((string) $value) : ''; 209 226 } -
klamra-paycal-for-aspaclaria/trunk/includes/Connector/EventClient.php
r3676169 r3758537 15 15 } 16 16 17 if (!function_exists('wp_ remote_post')) {17 if (!function_exists('wp_safe_remote_post')) { 18 18 \paycal_debug_log('[PayCal] Connector event skipped: WordPress HTTP API unavailable.'); 19 19 return ['ok' => false, 'message' => 'WordPress HTTP API unavailable']; … … 27 27 set_transient($rate_key, 1, 5 * MINUTE_IN_SECONDS); 28 28 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', [ 30 30 'timeout' => 10, 31 31 'headers' => [ … … 39 39 40 40 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.'); 42 42 return ['ok' => false, 'message' => $response->get_error_message()]; 43 43 } -
klamra-paycal-for-aspaclaria/trunk/includes/Connector/MerchantDashboard.php
r3557616 r3758537 12 12 if (!is_user_logged_in()) { 13 13 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>'; 14 18 } 15 19 -
klamra-paycal-for-aspaclaria/trunk/includes/Connector/SyncClient.php
r3676169 r3758537 18 18 $url = rtrim($settings['platform_url'], '/') . '/wp-json/paycal/v1/sync'; 19 19 20 if (!function_exists('wp_ remote_post')) {20 if (!function_exists('wp_safe_remote_post')) { 21 21 $result = ['ok' => false, 'message' => 'WordPress HTTP API unavailable.', 'synced_at' => current_time('mysql')]; 22 22 update_option(self::OPTION, $result, false); … … 35 35 } 36 36 37 $response = wp_remote_post($url, [37 $response = ConnectorStore::remote_post($url, [ 38 38 'timeout' => 15, 39 39 'headers' => $headers, … … 42 42 'terminal_id' => $settings['terminal_id'] ?? '', 43 43 'site_url' => home_url('/'), 44 'plugin_version' => defined('PAYCAL_VERSION') ? PAYCAL_VERSION : ' unknown',44 'plugin_version' => defined('PAYCAL_VERSION') ? PAYCAL_VERSION : '1.3.1', 45 45 ]), 46 46 ]); … … 49 49 $result = ['ok' => false, 'message' => $response->get_error_message()]; 50 50 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.'); 52 52 return $result; 53 53 } … … 61 61 62 62 $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)), 64 64 'code' => $code, 65 65 'body' => is_array($body) ? $body : [], -
klamra-paycal-for-aspaclaria/trunk/includes/Core/AdminMenu.php
r3676169 r3758537 42 42 'slug' => 'paycal-bundles', 43 43 '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'],50 44 ], 51 45 'invoices' => [ -
klamra-paycal-for-aspaclaria/trunk/includes/Core/PermissionFallback.php
r3661556 r3758537 15 15 'license' => 'גישה למסך רישיון', 16 16 'bundles' => 'גישה לערכות', 17 'bit' => 'גישה למסך ביט',18 17 'invoices' => 'גישה למסך חשבוניות', 19 18 'setup' => 'גישה למסך התקנה', -
klamra-paycal-for-aspaclaria/trunk/includes/Core/Plugin.php
r3676169 r3758537 47 47 } 48 48 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(); 51 52 } 52 53 … … 67 68 68 69 self::safe_require('includes/Connector/ConnectorStore.php', 'Connector store'); 70 self::safe_require('includes/Connector/ProviderRegistry.php', 'Provider catalogue'); 69 71 self::safe_require('includes/Connector/EventClient.php', 'Connector event client'); 70 72 self::safe_require('includes/Gateway/HostedGateway.php', 'Hosted gateway'); … … 72 74 self::safe_require('includes/Connector/MerchantDashboard.php', 'Merchant dashboard'); 73 75 self::safe_require('includes/Connector/DashboardClient.php', 'Dashboard client'); 76 self::safe_require('includes/Admin/Page_Payments.php', 'Payments dashboard'); 74 77 75 78 $permissions_store_loaded = self::safe_require('includes/Permissions/PermissionsStore.php', 'Permissions store'); … … 87 90 self::safe_require('includes/Admin/Page_Permissions.php', 'Permissions page'); 88 91 self::safe_require('includes/Admin/Page_Connector.php', 'Connector page'); 89 self::safe_require('includes/Admin/Page_Payments.php', 'Payments dashboard page');90 92 91 self::safe_require('includes/Admin/Page_Bit.php', 'Bit page');92 93 self::safe_require('includes/Admin/Page_Invoices.php', 'Invoices page'); 93 94 94 self::safe_require('includes/Modules/Bit/Bit Module.php', 'Bit module');95 self::safe_require('includes/Modules/Bit/BitOrderStatus.php', 'Historical order status'); 95 96 self::safe_require('includes/Modules/Invoices/InvoicesModule.php', 'Invoices module'); 96 97 } -
klamra-paycal-for-aspaclaria/trunk/includes/Gateway/WC_Gateway_PayCal_Hosted.php
r3676169 r3758537 11 11 12 12 add_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.14 13 if (!empty($_GET['wc-api']) && sanitize_key(wp_unslash($_GET['wc-api'])) === 'paycal_hosted_return') { 15 14 return; … … 47 46 48 47 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 } 50 53 $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; 52 55 $order_key = isset($_GET['paycal_order_key']) ? sanitize_text_field(wp_unslash($_GET['paycal_order_key'])) : ''; 53 // phpcs:enable WordPress.Security.NonceVerification.Recommended54 56 55 57 return $paid === '1' && $order_id > 0 && $order_key !== ''; … … 65 67 } 66 68 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']); 69 70 $order = wc_get_order($order_id); 70 71 … … 74 75 } 75 76 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'])); 78 82 79 83 if (!hash_equals($order->get_order_key(), $order_key)) { … … 102 106 $redirect_debug = self::return_redirect_debug($order); 103 107 $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')); 105 109 self::render_success_transition($redirect_url, $order, $redirect_debug); 106 110 } elseif ($redirect_after) { … … 120 124 $this->init_settings(); 121 125 122 $this->title = $this->get_option('title', ' Apple Pay / Credit Card');126 $this->title = $this->get_option('title', 'PayCal'); 123 127 $this->description = $this->get_option('description', 'Secure payment through PayCal.'); 124 128 $this->enabled = $this->get_option('enabled', 'no'); … … 138 142 'title' => 'Title', 139 143 'type' => 'text', 140 'default' => ' Apple Pay / Credit Card',144 'default' => 'PayCal', 141 145 ], 142 146 'description' => [ … … 165 169 $settings = ConnectorStore::get(); 166 170 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'])) { 168 172 return false; 169 173 } … … 178 182 wc_add_notice('Payment error: order not found.', 'error'); 179 183 return ['result' => 'failure']; 184 } 185 186 if ($order->is_paid()) { 187 return ['result' => 'success', 'redirect' => $this->get_return_url($order)]; 180 188 } 181 189 … … 204 212 } 205 213 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 206 219 \paycal_debug_log('[PayCal] Server verification started for order ' . (string) $order->get_id() . '.'); 207 220 … … 217 230 } 218 231 219 if (!function_exists('wp_ remote_post')) {232 if (!function_exists('wp_safe_remote_post')) { 220 233 \paycal_debug_log('[PayCal] Payment verification failed: WordPress HTTP API unavailable.'); 221 234 return false; … … 223 236 224 237 $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', [ 226 239 'timeout' => 15, 227 240 'headers' => [ … … 242 255 243 256 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.'); 245 258 return false; 246 259 } … … 249 262 $raw = wp_remote_retrieve_body($response); 250 263 $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); 252 265 self::$last_verification_response[(int) $order->get_id()] = is_array($body) ? $body : []; 253 266 if ($paid && !self::verification_response_matches_order($body, $order, $session_id)) { … … 261 274 } 262 275 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; 265 281 } 266 282 … … 274 290 $verification = self::$last_verification_response[(int) $order->get_id()] ?? []; 275 291 $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.')); 277 293 } 278 294 … … 392 408 } 393 409 394 $lines = ['PayCal payment verified successfully.', 'Gateway: Apple Pay'];410 $lines = ['PayCal payment verified successfully.', 'Gateway: PayCal Hosted Checkout']; 395 411 $amount = self::scalar_text($verification['amount'] ?? ''); 396 412 $currency = self::scalar_text($verification['currency'] ?? $order->get_currency()); … … 460 476 foreach ((array) $notes as $note) { 461 477 $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) { 463 479 return true; 464 480 } … … 468 484 469 485 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. 470 491 if (!function_exists('wc_get_order_notes')) { 471 492 return ''; … … 508 529 return false; 509 530 } 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) { 511 532 return false; 512 533 } … … 559 580 $lines = [ 560 581 'PayCal hosted return redirect debug.', 561 'Redirect URL: ' . self::scalar_text($debug['redirect_url'] ?? ''),562 582 'Has order key: ' . self::scalar_text($debug['has_order_key'] ?? 'no'), 563 583 'Return source: ' . self::scalar_text($debug['return_source'] ?? 'checkout_fallback'), … … 568 588 'Final response emitted: ' . self::scalar_text($debug['final_response_emitted'] ?? 'unknown'), 569 589 ]; 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 }580 590 581 591 $order->add_order_note(implode("\n", $lines)); … … 750 760 751 761 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'])) { 753 763 \paycal_debug_log('[PayCal] Generic checkout session skipped: connector API settings are incomplete.'); 754 764 return ''; … … 761 771 ], $this->get_return_url($order)); 762 772 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', [ 764 774 'timeout' => 15, 765 775 'headers' => [ … … 783 793 784 794 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'; 788 796 \paycal_debug_log('[PayCal] Generic checkout session creation failed. ' . $diagnostic); 789 797 $order->add_order_note('PayCal generic checkout session create failed. ' . $diagnostic . '. Existing hosted checkout fallback used.'); … … 795 803 $body = json_decode($raw, true); 796 804 $valid_json = json_last_error() === JSON_ERROR_NONE && is_array($body); 797 $session_id = is_array($body) ? s anitize_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']) : ''; 799 807 $checkout_host = strtolower((string) wp_parse_url($checkout_url, PHP_URL_HOST)); 800 808 $checkout_scheme = strtolower((string) wp_parse_url($checkout_url, PHP_URL_SCHEME)); 801 809 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') { 803 811 $diagnostic = $this->checkout_session_response_diagnostic((int) $code, $body, $valid_json, $session_id, $checkout_url); 804 812 \paycal_debug_log('[PayCal] Generic checkout session creation returned an invalid response. ' . $diagnostic . ', body_hash=' . hash('sha256', (string) $raw)); … … 807 815 } 808 816 817 $order->update_meta_data('_paycal_checkout_session_id', $session_id); 818 $order->save(); 809 819 $order->add_order_note('PayCal generic checkout session created: ' . $session_id); 810 820 \paycal_debug_log('[PayCal] Generic checkout session created for order ' . (string) $order->get_id() . ': ' . $session_id); … … 813 823 814 824 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) : '';820 825 $data_status = $valid_json && isset($body['data']['status']) && is_scalar($body['data']['status']) ? absint($body['data']['status']) : 0; 821 826 … … 823 828 'http_status=' . $http_code, 824 829 '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'),828 830 'data_status=' . ($data_status ?: 'none'), 829 831 'session_id_exists=' . ($session_id !== '' ? 'yes' : 'no'), -
klamra-paycal-for-aspaclaria/trunk/includes/Licensing/LicenseClient.php
r3676169 r3758537 24 24 } 25 25 26 if (!function_exists('wp_ remote_post')) {26 if (!function_exists('wp_safe_remote_post')) { 27 27 \paycal_debug_log('[PayCal] WordPress HTTP API unavailable during license validation.'); 28 28 return ['ok' => false, 'error' => 'WordPress HTTP API unavailable', 'reason' => 'transport']; … … 30 30 31 31 $endpoint = self::endpoint(); 32 $response = wp_remote_post($endpoint, [32 $response = ConnectorStore::remote_post($endpoint, [ 33 33 'timeout' => 15, 34 34 'headers' => ['Accept' => 'application/json'], … … 43 43 44 44 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.'); 46 46 return ['ok' => false, 'error' => $response->get_error_message(), 'reason' => 'transport']; 47 47 } … … 56 56 } 57 57 58 if ( empty($json['valid'])) {58 if (!in_array($json['valid'] ?? null, [true, 1, '1'], true)) { 59 59 return ['ok' => false, 'error' => 'License invalid', 'reason' => 'invalid']; 60 60 } -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Bit/BitOrderStatus.php
r3676169 r3758537 17 17 'show_in_admin_all_list' => true, 18 18 'show_in_admin_status_list' => true, 19 / * translators: %s: number of Bit-pending orders. */19 // translators: %s: Number of historical orders awaiting Bit payment. 20 20 'label_count' => _n_noop('ממתין לתשלום בביט <span class="count">(%s)</span>', 'ממתין לתשלום בביט <span class="count">(%s)</span>', 'klamra-paycal-for-aspaclaria') 21 21 ]); -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/InvoicesModule.php
r3676169 r3758537 12 12 if (!defined('PAYCAL_INV_URL')) define('PAYCAL_INV_URL', PAYCAL_URL . 'includes/Modules/Invoices/Legacy/'); 13 13 14 // Back-compat constants used in legacy files. 15 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound 14 // Back-compat constants used in legacy files 16 15 if (!defined('ASP_INV_PATH')) define('ASP_INV_PATH', PAYCAL_INV_PATH); 17 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound18 16 if (!defined('ASP_INV_URL')) define('ASP_INV_URL', PAYCAL_INV_URL); 19 17 -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/admin/settings.php
r3676169 r3758537 51 51 if ($paycal_inv_action === 'save') { 52 52 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 } 53 58 $trigger_status = sanitize_key(wp_unslash($_POST['trigger_status'] ?? 'completed')); 54 59 -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/aspaclaria-invoices.php
r3676169 r3758537 7 7 if (!defined('ABSPATH')) exit; 8 8 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'; 10 if (!file_exists($loader)) { 11 \paycal_debug_log('[PayCal Invoices] Missing compatibility loader target: ' . $loader); 12 12 add_action('admin_notices', function () { 13 13 if (current_user_can('manage_options')) { … … 19 19 20 20 try { 21 require_once $ paycal_invoice_loader;21 require_once $loader; 22 22 } catch (Throwable $e) { 23 23 \paycal_debug_log('[PayCal Invoices] Compatibility loader failed: ' . $e->getMessage()); -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/cpt.php
r3676169 r3758537 9 9 'menu_icon' => 'dashicons-media-text', 10 10 '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'), 11 18 ]); 12 19 }); … … 159 166 } 160 167 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.162 168 $invoice_ids = get_posts([ 163 169 'post_type' => 'asp_invoice', … … 168 174 'posts_per_page' => 1, 169 175 ]); 170 // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value171 176 172 177 $invoice_id = !empty($invoice_ids) ? absint($invoice_ids[0]) : 0; -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/numbering.php
r3676169 r3758537 2 2 if (!defined('ABSPATH')) exit; 3 3 4 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy invoice numbering API kept for backward compatibility.5 4 function asp_get_next_invoice_number() { 6 5 $n = (int) get_option('asp_invoice_number', 1000); -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/order-hook.php
r3676169 r3758537 18 18 19 19 // 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.21 20 $existing = get_posts([ 22 21 'post_type' => 'asp_invoice', … … 27 26 'posts_per_page' => 1 28 27 ]); 29 // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value30 28 if (!empty($existing)) return; 31 29 -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/pdf/download.php
r3676169 r3758537 15 15 } 16 16 17 $invoice_id = isset($_GET['invoice_id']) ? absint(wp_unslash($_GET['invoice_id'])) : 0;17 $invoice_id = absint($_GET['invoice_id'] ?? 0); 18 18 if (!$invoice_id) { 19 19 paycal_invoice_download_not_found(); -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/includes/render.php
r3555025 r3758537 36 36 $map = [ 37 37 '{{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)), 39 39 '{{order_id}}' => (string)$order->get_id(), 40 '{{order_number}}' => (string)$order->get_order_number(),40 '{{order_number}}' => esc_html((string)$order->get_order_number()), 41 41 '{{order_date}}' => esc_html($order->get_date_created() ? $order->get_date_created()->date_i18n('d/m/Y') : ''), 42 42 '{{total}}' => wp_kses_post(wc_price($order->get_total())), … … 194 194 <div> 195 195 <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> 197 197 </div>' 198 198 . ($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 11 11 define('PAYCAL_INV_URL', plugin_dir_url(__FILE__)); 12 12 13 // Back-compat constants (older code). 14 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound 13 // Back-compat constants (older code) 15 14 if (!defined('ASP_INV_PATH')) define('ASP_INV_PATH', PAYCAL_INV_PATH); 16 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound17 15 if (!defined('ASP_INV_URL')) define('ASP_INV_URL', PAYCAL_INV_URL); 18 16 … … 33 31 add_action('admin_notices', function () use ($paycal_invoice_file) { 34 32 if (current_user_can('manage_options')) { 35 /* translators: %s: missing PayCal Invoices file path. */33 /* translators: %s: Missing invoice module filename. */ 36 34 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>'; 37 35 } … … 46 44 add_action('admin_notices', function () use ($paycal_invoice_file) { 47 45 if (current_user_can('manage_options')) { 48 /* translators: %s: failed PayCal Invoices file path. */46 /* translators: %s: Invoice module filename. */ 49 47 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>'; 50 48 } -
klamra-paycal-for-aspaclaria/trunk/includes/Modules/Invoices/Legacy/templates/invoice.php
r3676169 r3758537 2 2 if (!defined('ABSPATH')) exit; 3 3 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; 5 if (!$invoice_id && isset($_GET['invoice_id'])) { 6 $invoice_id = absint($_GET['invoice_id']); 7 7 if (!is_user_logged_in() || !function_exists('paycal_invoice_get_authorized_order_for_download')) { 8 8 wp_die(esc_html__('Access denied.', 'klamra-paycal-for-aspaclaria'), '', ['response' => 403]); 9 9 } 10 10 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)) { 13 13 wp_die(esc_html__('Access denied.', 'klamra-paycal-for-aspaclaria'), '', ['response' => 403]); 14 14 } 15 15 16 $order = paycal_invoice_get_authorized_order_for_download($ paycal_invoice_template_id);16 $order = paycal_invoice_get_authorized_order_for_download($invoice_id); 17 17 if (is_wp_error($order)) { 18 18 $status = (int) ($order->get_error_data()['status'] ?? 403); … … 25 25 } 26 26 27 echo wp_kses_post(paycal_invoice_render_html($ paycal_invoice_template_id));27 echo wp_kses_post(paycal_invoice_render_html($invoice_id)); -
klamra-paycal-for-aspaclaria/trunk/includes/Permissions/PermissionsStore.php
r3498130 r3758537 14 14 'license' => 'גישה למסך רישיון', 15 15 'bundles' => 'גישה לערכות', 16 'bit' => 'גישה למסך ביט',17 16 'invoices' => 'גישה למסך חשבוניות', 18 17 'setup' => 'גישה למסך התקנה', -
klamra-paycal-for-aspaclaria/trunk/klamra-paycal-for-aspaclaria.php
r3676169 r3758537 2 2 /** 3 3 * Plugin Name: Klamra Paycal for Aspaclaria 4 * Plugin URI: https:// paycal.online/4 * Plugin URI: https://wordpress.org/plugins/klamra-paycal-for-aspaclaria/ 5 5 * Description: PayCal branded checkout and payment-management connector for WooCommerce, synced with PayCal Platform and approved payment providers. 6 * Version: 1.4. 26 * Version: 1.4.4 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.0 … … 15 15 if (!defined('ABSPATH')) exit; 16 16 17 define('PAYCAL_VERSION', '1.4. 2');17 define('PAYCAL_VERSION', '1.4.4'); 18 18 define('PAYCAL_PLUGIN_FILE', __FILE__); 19 19 define('PAYCAL_PATH', plugin_dir_path(__FILE__)); 20 20 define('PAYCAL_URL', plugin_dir_url(__FILE__)); 21 22 add_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 }); 21 27 22 28 add_action('before_woocommerce_init', function () { … … 31 37 return; 32 38 } 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']); 35 43 } 36 44 } 37 45 38 46 if (!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.'); 40 48 add_action('admin_notices', function () { 41 49 if (current_user_can('manage_options')) { … … 49 57 require_once PAYCAL_PATH . 'includes/Core/Plugin.php'; 50 58 } 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()); 52 60 add_action('admin_notices', function () { 53 61 if (current_user_can('manage_options')) { -
klamra-paycal-for-aspaclaria/trunk/readme.txt
r3676169 r3758537 3 3 Tags: apple pay, woocommerce, checkout, payments, payment gateway 4 4 Requires at least: 6.0 5 Tested up to: 7.1 5 Tested up to: 7.1.3 6 6 Requires PHP: 8.0 7 Stable tag: 1.4. 27 Stable tag: 1.4.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 12 12 13 13 == Description == 14 15 Maintenance 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. 14 16 15 17 **PayCal** is a branded checkout and payment-management platform for WooCommerce stores built for creative businesses. … … 57 59 == Screenshots == 58 60 59 1. PayCal settings inside WooCommerce60 2. PayCal payment option at WooCommerce checkout61 3. Guided merchant connection and License Token setup62 4. PayCal Payments read-only Merchant Dashboard with recent payments and settlement totals63 5. WooCommerce admin menu entry for PayCal Payments61 1. Klamra PayCal dashboard and module navigation in a staging installation. 62 2. PayCal Hosted Checkout gateway settings in WooCommerce, before activation. 63 3. PayCal connector configuration in Sandbox, with credential fields empty. 64 4. Invoice layout settings with demonstration business details in a staging installation. 65 5. 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. 64 66 65 67 == 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 66 78 67 79 = 1.4.2 = … … 76 88 77 89 = 1.4.0 = 78 * Added WooCommerce →PayCal Payments read-only merchant dashboard.90 * Added WooCommerce PayCal Payments read-only merchant dashboard. 79 91 * Pulls Ledger, Settlement and Reconciliation data from PayCal Platform through the authenticated merchant dashboard API. 80 92 * Adds short-lived dashboard caching and last-good fallback when PayCal Platform is temporarily unavailable. 81 93 * 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 82 111 83 112 = 1.3.1 = … … 222 251 223 252 == 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. 253 Version 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.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)