Changeset 3692533
- Timestamp:
- 09/12/2026 09:48:37 AM (6 weeks ago)
- Location:
- vayapin/trunk
- Files:
-
- 14 added
- 16 edited
-
README.md (modified) (1 diff)
-
README.txt (modified) (3 diffs)
-
framework/ajax-methods.php (modified) (8 diffs)
-
framework/backend-ui/admin-ui.php (modified) (6 diffs)
-
framework/backend-ui/assets/tab-icons/group.svg (added)
-
framework/backend-ui/css/admin-redesign.css (modified) (1 diff)
-
framework/backend-ui/js/admin-redesign.js (modified) (2 diffs)
-
framework/backend-ui/js/connect-partners.js (added)
-
framework/backend-ui/partials/settings-page-redesign.php (modified) (3 diffs)
-
framework/backend-ui/webhook-logs.php (modified) (1 diff)
-
framework/connect (added)
-
framework/connect/connect-config.php (added)
-
framework/connect/dev-simulate.php (added)
-
framework/connect/label-intl.php (added)
-
framework/connect/partners.php (added)
-
framework/connect/record.php (added)
-
framework/connect/request-store.php (added)
-
framework/connect/vayapin-intl-label-template.php (added)
-
framework/frontend-ui/checkout-ui.php (modified) (1 diff)
-
framework/styles-scripts/styles-scripts.php (modified) (2 diffs)
-
framework/vayapin/api-methods.php (modified) (2 diffs)
-
framework/vayapin/connect-api.php (added)
-
framework/vayapin/connect-webhook.php (added)
-
includes/class-vayapin-activator.php (modified) (1 diff)
-
public/css/vayapin-sheet.css (added)
-
public/js/vayapin-public.js (modified) (7 diffs)
-
public/js/vayapin-select2.js (modified) (5 diffs)
-
public/js/vayapin-sheet.js (added)
-
uninstall.php (modified) (1 diff)
-
vayapin.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
vayapin/trunk/README.md
r3676127 r3692533 8 8 | **WooCommerce** | 3.0+ (tested up to 11.x) | 9 9 | **Requires PHP** | 7.4+ | 10 | **Stable tag** | 2. 1.2 |10 | **Stable tag** | 2.3.2 | 11 11 | **License** | GPLv2 or later | 12 12 -
vayapin/trunk/README.txt
r3676127 r3692533 4 4 Requires at least: 6.0 5 5 Tested up to: 7.1 6 Stable tag: 2. 1.26 Stable tag: 2.3.2 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 140 140 == Changelog == 141 141 142 = 2.1.2 = 143 * Compatibility: tested against WordPress 7.1 and WooCommerce 11.0. 144 * Compatibility: declares support for WooCommerce High-Performance Order Storage (HPOS), so stores can switch order storage with the plugin active. 145 * Housekeeping: added the "WC requires at least" and "WC tested up to" plugin headers. 142 = 2.3.2 = 143 * Change: the mobile sheet takes the widget's own background colour instead of white, so the panel and the pin field read as one surface. The colour follows the merchant's accent setting. 144 * Fix: the sheet's back arrow disappeared under storefront button styling (themes match plain buttons through [type=button], which ties on specificity and loads last). The sheet's own controls are now insulated from it. 145 146 = 2.3.1 = 147 * Fix: inside the mobile sheet the pin row lost its styling (oversized GO button, collapsed input) because the widget styles hang off a class that stayed behind on the original container. The row now matches the desktop layout exactly. 148 * Fix: the sheet's back button and search field no longer inherit storefront button and input styling. 149 150 = 2.3.0 = 151 * New: on phones the whole widget flow opens in a bottom sheet: pin entry, country picking with search and full-size rows, drag or flick down to dismiss. Desktop keeps the existing popup. 152 * Fix: the widget scripts relied on jQuery's removed `.load()` shortcut and only ran on sites that still load jQuery Migrate. 153 154 = 2.2.9 = 155 * Fix: typing and backspace were blocked in the VayaPin field after the country list had been opened. The list was hidden by class only, which left the country plugin's key handler bound and it cancelled every key press. 156 * Fix: the "Not found. Please enter a valid VayaPin." message stayed on screen after closing the popup. It is cleared on close, on reopen, and as soon as the code is edited. 157 158 = 2.2.8 = 159 * Fix: checkout autofill left "Street address" and "Apartment" empty for pins that store the street number or the building name in separate fields (JP pins among them). The street line is now built from road + house number, the second line from building name + addition. 160 * Fix: city and state fall back to their "_part" counterparts when the primary field is empty. 161 162 = 2.2.7 = 163 * Fix: checkout autofill now clears the "required field" validation errors on the fields it fills, instead of leaving them red until the field is touched manually. 164 * Fix: new VayaPin creation sent only the last name in the `name` field, which the API rejected with HTTP 422 ("name: must include a last name"). Full first + last name is sent now, per the /v1/pins contract. 165 * Fix: API validation errors were logged with an empty message, so a failed pin creation showed a bare HTTP_422 with no reason. The error code, message and per-field errors are parsed and stored now. 166 * Change: Pin Creation History CSV export includes the raw API response for failed attempts. 167 168 = 2.2.0 = 169 * New: VayaConnect — request a VayaPin address from any customer. Adds a Partners tab listing WooCommerce customers with their VayaPin status (verified / pending / none) and a "Request VayaPin address" button. 170 * New: inbound `pin.address_updated` webhook receiver (REST: /wp-json/vayapin/v1/webhooks) with HMAC-SHA256 verification and replay idempotency. The verified record (VayaPin ID, every script, GPS, QR) is written back to the customer/order automatically. 171 * New: VayaConnect record surface on the order detail screen and customer profile. 172 * New: carrier-ready international label (dual-script + QR) at /vayapin-intl-label/{order_id}. 173 * New: VayaConnect on/off toggle (off by default). The outbound request verb is behind a single contract seam pending the VayaCore backend. 146 174 147 175 = 2.1.1 = … … 161 189 == Upgrade Notice == 162 190 163 = 2.1.2 = 164 Compatibility release for WordPress 7.1 and WooCommerce 11. No functional changes; safe update for all stores. 191 = 2.3.2 = 192 Mobile sheet visual fixes: single tinted surface and a visible back arrow. 193 194 = 2.3.1 = 195 Fixes the pin row layout inside the mobile sheet. 196 197 = 2.3.0 = 198 Adds the mobile bottom sheet and fixes empty street fields after autofill. Test the checkout widget on a phone after updating. 199 200 = 2.2.9 = 201 Fixes the VayaPin input becoming uneditable after opening the country list, and the stale "Not found" message. 202 203 = 2.2.8 = 204 Fixes empty street address fields after autofill for pins with a separate house number or building name. 205 206 = 2.2.7 = 207 Fixes checkout validation errors sticking after autofill and VayaPin creation failing with HTTP 422. Recommended for all stores. 208 209 = 2.2.0 = 210 Adds VayaConnect (Partners tab + inbound address webhook). Disabled by default — enable it under the Partners tab and set a webhook secret. Re-save permalinks if the international label URL 404s. 165 211 166 212 = 2.1.1 = -
vayapin/trunk/framework/ajax-methods.php
r3531799 r3692533 171 171 } 172 172 173 // A pin splits its address across more fields than WooCommerce has lines: 174 // road + house_number + house (building) + addition, and settlement/region each 175 // have a "_part" sibling. Reading only address_road/address_addition drops the 176 // street entirely for pins that carry the number or the building name instead 177 // (e.g. JP pins: "Marunouchi 1 Chome 9-1" + "Gran Tokyo North Tower"). 178 $road = $get_field_value($data_source['address_road'] ?? ''); 179 $house_number = $get_field_value($data_source['address_house_number'] ?? ''); 180 $house = $get_field_value($data_source['address_house'] ?? ''); 181 $addition = $get_field_value($data_source['address_addition'] ?? ''); 182 $settlement = $get_field_value($data_source['address_settlement'] ?? ''); 183 $settlement_part = $get_field_value($data_source['address_settlement_part'] ?? ''); 184 $region = $get_field_value($data_source['address_region'] ?? ''); 185 $region_part = $get_field_value($data_source['address_region_part'] ?? ''); 186 187 $join = function( $parts, $glue ) { 188 $parts = array_filter( array_map( 'trim', $parts ), function ( $part ) { 189 return '' !== $part; 190 } ); 191 return implode( $glue, $parts ); 192 }; 193 194 $address_1 = $join( array( $road, $house_number ), ' ' ); 195 $address_2 = $join( array( $house, $addition ), ', ' ); 196 197 // Never leave line 1 empty while line 2 has the only street data. 198 if ( '' === $address_1 && '' !== $address_2 ) { 199 $address_1 = $address_2; 200 $address_2 = ''; 201 } 202 203 $city_value = '' !== $settlement ? $settlement : $settlement_part; 204 $state_value = '' !== $region ? $region : $region_part; 205 173 206 $data = [ 174 207 'company' => sanitize_text_field($title_value), … … 180 213 'email' => !empty($email_value) ? sanitize_email($email_value) : '', 181 214 'country' => sanitize_text_field(strtoupper($data_source['country'] ?? '')), 182 'city' => sanitize_text_field($ get_field_value($data_source['address_settlement'] ?? '')),183 'state' => sanitize_text_field($ get_field_value($data_source['address_region'] ?? '')),215 'city' => sanitize_text_field($city_value), 216 'state' => sanitize_text_field($state_value), 184 217 'postcode' => sanitize_text_field($get_field_value($data_source['address_postcode'] ?? '')), 185 218 'phone' => sanitize_text_field($get_field_value($data_source['phone'] ?? '')), 186 'address_1' => sanitize_text_field($ get_field_value($data_source['address_road'] ?? '')),187 'address_2' => sanitize_text_field($ get_field_value($data_source['address_addition'] ?? '')),219 'address_1' => sanitize_text_field($address_1), 220 'address_2' => sanitize_text_field($address_2), 188 221 'debug' => $debug_data 189 222 ]; … … 353 386 354 387 // Generate CSV content 355 $csv_content = "Timestamp,Order ID,Activity Type,Status,Error Code,Error Message,VayaPin ID \n";388 $csv_content = "Timestamp,Order ID,Activity Type,Status,Error Code,Error Message,VayaPin ID,API Response\n"; 356 389 357 390 foreach ($logs as $log) { … … 370 403 $vayapin_id = $log->vayapin_id ? $log->vayapin_id : ''; 371 404 405 // Raw body of the failed call: without it a 422 cannot be diagnosed outside the DB. 406 $raw_response = ''; 407 if ( 'failed' === $log->status && $log->api_response ) { 408 $raw_response = str_replace( 409 array( "\r", "\n", '"' ), 410 array( ' ', ' ', '""' ), 411 mb_substr( $log->api_response, 0, 2000 ) 412 ); 413 } 414 372 415 $csv_content .= sprintf( 373 '"%s","%s","%s","%s","%s","%s","%s" ' . "\n",416 '"%s","%s","%s","%s","%s","%s","%s","%s"' . "\n", 374 417 $timestamp, 375 418 $order_id, … … 378 421 $error_code, 379 422 $error_message, 380 $vayapin_id 423 $vayapin_id, 424 $raw_response 381 425 ); 382 426 } … … 465 509 $logs = $wpdb->get_results( $query ); 466 510 467 $csv_content = "Timestamp,Event Type,Webhook URL,Status,Response Code,Error Message \n";511 $csv_content = "Timestamp,Event Type,Webhook URL,Status,Response Code,Error Message,Payload\n"; 468 512 foreach ( $logs as $log ) { 469 513 $timestamp = date_i18n( 'Y-m-d H:i:s', strtotime( $log->timestamp ) ); … … 473 517 $resp_code = $log->response_code ? (string) $log->response_code : ''; 474 518 $err_message = $log->error_message ? str_replace( array( "\r", "\n", '"' ), array( ' ', ' ', '""' ), (string) $log->error_message ) : ''; 519 $payload = isset( $log->payload ) && '' !== (string) $log->payload ? str_replace( array( "\r", "\n", '"' ), array( ' ', ' ', '""' ), (string) $log->payload ) : ''; 475 520 476 521 $csv_content .= sprintf( 477 '"%s","%s","%s","%s","%s","%s" ' . "\n",522 '"%s","%s","%s","%s","%s","%s","%s"' . "\n", 478 523 $timestamp, 479 524 $event_type, … … 481 526 $status, 482 527 $resp_code, 483 $err_message 528 $err_message, 529 $payload 484 530 ); 485 531 } -
vayapin/trunk/framework/backend-ui/admin-ui.php
r3531799 r3692533 57 57 'analytics_enabled' => '1', 58 58 'analytics_weekly_email' => '1', 59 'vayaconnect_enabled' => '0', 59 60 'label_generation' => '1', 60 61 'order_confirmation_notice' => '1', … … 1606 1607 $new_input['analytics_enabled'] = $vp_on( 'analytics_enabled' ) ? '1' : '0'; 1607 1608 $new_input['analytics_weekly_email'] = $vp_on( 'analytics_weekly_email' ) ? '1' : '0'; 1609 $new_input['vayaconnect_enabled'] = $vp_on( 'vayaconnect_enabled' ) ? '1' : '0'; 1608 1610 1609 1611 $allowed_widget_positions = array( 'above_billing', 'below_billing', 'above_shipping', 'custom' ); … … 1687 1689 $new_input['webhook_secret'] = $options['webhook_secret']; 1688 1690 } 1691 } 1692 1693 // VayaConnect inbound webhook secret (separate from the outbound Notifications secret). 1694 if ( isset( $input['vayaconnect_webhook_secret'] ) ) { 1695 $vc_secret = sanitize_text_field( $input['vayaconnect_webhook_secret'] ); 1696 1697 $existing_encrypted = $options['vayaconnect_webhook_secret'] ?? ''; 1698 $existing_decrypted = vayapin_decrypt_api_key( $existing_encrypted ); 1699 $masked_existing = vayapin_mask_api_key( $existing_decrypted ); 1700 1701 if ( empty( $vc_secret ) ) { 1702 $new_input['vayaconnect_webhook_secret'] = ''; 1703 } elseif ( $vc_secret === $masked_existing && ! empty( $existing_encrypted ) ) { 1704 // Unchanged masked value — keep the stored encrypted secret. 1705 $new_input['vayaconnect_webhook_secret'] = $existing_encrypted; 1706 } else { 1707 $new_input['vayaconnect_webhook_secret'] = vayapin_encrypt_api_key( $vc_secret ); 1708 } 1709 } elseif ( isset( $options['vayaconnect_webhook_secret'] ) ) { 1710 $new_input['vayaconnect_webhook_secret'] = $options['vayaconnect_webhook_secret']; 1689 1711 } 1690 1712 … … 2170 2192 </td> 2171 2193 </tr> 2194 <?php if ( isset( $log->payload ) && '' !== (string) $log->payload ) : ?> 2195 <tr class="vp-log-payload-row"> 2196 <td colspan="6"> 2197 <details> 2198 <summary><?php esc_html_e( 'Payload', 'vayapin-woocommerce' ); ?></summary> 2199 <pre class="vp-log-payload"><?php echo esc_html( (string) $log->payload ); ?></pre> 2200 </details> 2201 </td> 2202 </tr> 2203 <?php endif; ?> 2172 2204 <?php endforeach; ?> 2173 2205 </tbody> … … 2258 2290 'resetAction' => 'vayapin_reset_settings_defaults', 2259 2291 'iconBaseUrl' => plugins_url( 'icon/', __FILE__ ), 2292 'connect' => array( 2293 'nonce' => wp_create_nonce( 'vayapin_connect' ), 2294 'listAction' => 'vayapin_connect_partners_list', 2295 'requestAction' => 'vayapin_connect_send_request', 2296 'createAction' => 'vayapin_connect_add_recipient', 2297 'deleteAction' => 'vayapin_connect_delete_request', 2298 'recordAction' => 'vayapin_connect_get_record', 2299 'i18n' => array( 2300 'requesting' => __( 'Sending…', 'vayapin-woocommerce' ), 2301 'removing' => __( 'Removing…', 'vayapin-woocommerce' ), 2302 'failed' => __( 'Failed. Try again.', 'vayapin-woocommerce' ), 2303 'emailInvalid' => __( 'Enter a valid email address.', 'vayapin-woocommerce' ), 2304 ), 2305 ), 2260 2306 'i18n' => array( 2261 2307 'resetError' => __( 'Something went wrong. Please try again.', 'vayapin-woocommerce' ), … … 2265 2311 ), 2266 2312 ) 2313 ); 2314 wp_enqueue_script( 2315 'vayapin-connect-partners', 2316 plugins_url( 'js/connect-partners.js', __FILE__ ), 2317 array( 'jquery', 'vayapin-admin-redesign' ), 2318 defined( 'VAYAPIN_VERSION' ) ? VAYAPIN_VERSION : '1.0.0', 2319 true 2267 2320 ); 2268 2321 } -
vayapin/trunk/framework/backend-ui/css/admin-redesign.css
r3531799 r3692533 3175 3175 } 3176 3176 } 3177 3178 /* ── VayaConnect · Partners tab ─────────────────────────────────── */ 3179 .status-pill.vp-pill-none { 3180 background: #f3f4f6; 3181 color: #6b7280; 3182 } 3183 .status-pill.vp-pill-none .status-pill-dot { 3184 background: #9ca3af; 3185 } 3186 3187 .vp-partners-toolbar { 3188 display: flex; 3189 gap: 0.625rem; 3190 flex-wrap: wrap; 3191 align-items: center; 3192 } 3193 .vp-partners-toolbar #vp_partners_search { 3194 flex: 1 1 220px; 3195 min-width: 180px; 3196 } 3197 .vp-partners-toolbar #vp_partners_filter { 3198 flex: 0 0 auto; 3199 min-width: 160px; 3200 } 3201 .vp-partners-toolbar .vp-partners-add-toggle { 3202 flex: 0 0 auto; 3203 margin-left: auto; 3204 } 3205 3206 .vp-partners-add-form { 3207 margin-top: 0.75rem; 3208 padding: 1rem; 3209 border: 1px solid var(--border, #e5e7eb); 3210 border-radius: 0.625rem; 3211 background: var(--surface-subtle, #f9fafb); 3212 } 3213 .vp-partners-add-grid { 3214 display: grid; 3215 grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); 3216 gap: 0.75rem; 3217 } 3218 .vp-partners-add-field { 3219 display: flex; 3220 flex-direction: column; 3221 gap: 0.25rem; 3222 font-size: 0.75rem; 3223 color: #6b7280; 3224 } 3225 .vp-partners-add-field > span { 3226 font-weight: 600; 3227 } 3228 .vp-req { 3229 color: #dc2626; 3230 } 3231 .vp-partners-add-actions { 3232 display: flex; 3233 justify-content: flex-end; 3234 gap: 0.5rem; 3235 margin-top: 0.75rem; 3236 } 3237 3238 .vp-partner-check-cell { 3239 width: 2.5rem; 3240 text-align: center; 3241 } 3242 .vp-partner-check, 3243 #vp_partners_check_all { 3244 width: 1rem; 3245 height: 1rem; 3246 cursor: pointer; 3247 } 3248 .vp-partners-bulkbar { 3249 display: flex; 3250 align-items: center; 3251 gap: 0.625rem; 3252 margin-top: 0.75rem; 3253 padding: 0.5rem 0.75rem; 3254 border: 1px solid var(--border, #e5e7eb); 3255 border-radius: 0.625rem; 3256 background: var(--surface-subtle, #f9fafb); 3257 } 3258 .vp-partners-bulkbar #vp_partners_bulk_count { 3259 margin-right: auto; 3260 } 3261 3262 .vp-log-payload-row > td { 3263 background: var(--surface-subtle, #f9fafb); 3264 } 3265 .vp-log-payload-row summary { 3266 cursor: pointer; 3267 font-size: 0.75rem; 3268 color: #6b7280; 3269 } 3270 .vp-log-payload { 3271 margin: 0.25rem 0 0; 3272 padding: 0.5rem 0.75rem; 3273 max-height: 20rem; 3274 overflow: auto; 3275 white-space: pre-wrap; 3276 word-break: break-all; 3277 font-size: 0.75rem; 3278 font-family: ui-monospace, Menlo, monospace; 3279 border: 1px solid var(--border, #e5e7eb); 3280 border-radius: 0.375rem; 3281 background: #fff; 3282 } 3283 3284 .vp-partners-table th, 3285 .vp-partners-table td { 3286 vertical-align: middle; 3287 } 3288 .vp-partner-name { 3289 font-weight: 600; 3290 color: var(--graphite-dark, #454545); 3291 } 3292 .vp-partner-email { 3293 font-size: 0.75rem; 3294 color: #6b7280; 3295 } 3296 .vp-partner-action-cell { 3297 white-space: nowrap; 3298 text-align: right; 3299 } 3300 .vp-partners-verified-note { 3301 font-family: ui-monospace, Menlo, monospace; 3302 font-size: 0.75rem; 3303 color: var(--green-dark, #0d9488); 3304 } 3305 .vp-partners-empty { 3306 text-align: center; 3307 color: #9ca3af; 3308 padding: 1.5rem 1rem !important; 3309 } 3310 .vp-muted { 3311 color: #9ca3af; 3312 } 3313 3314 .vp-partners-footer { 3315 display: flex; 3316 align-items: center; 3317 justify-content: space-between; 3318 margin-top: 0.75rem; 3319 gap: 0.625rem; 3320 flex-wrap: wrap; 3321 } 3322 .vp-partners-pager { 3323 display: flex; 3324 gap: 0.375rem; 3325 } 3326 3327 .vp-dev-badge { 3328 display: inline-block; 3329 font-size: 0.625rem; 3330 font-weight: 700; 3331 letter-spacing: 0.05em; 3332 color: #92400e; 3333 background: #fff8eb; 3334 border: 1px solid #f0deb6; 3335 border-radius: 4px; 3336 padding: 0.05rem 0.35rem; 3337 margin-right: 0.35rem; 3338 vertical-align: middle; 3339 } 3340 3341 /* ── VayaConnect · record modal + toast ─────────────────────────── */ 3342 .vayapin-settings-redesign .vp-record-modal__dialog { 3343 max-width: 30rem; 3344 } 3345 .vayapin-settings-redesign .vp-record-modal__head { 3346 display: flex; 3347 align-items: flex-start; 3348 justify-content: space-between; 3349 gap: 0.75rem; 3350 margin-bottom: 0.875rem; 3351 } 3352 .vayapin-settings-redesign .vp-record-modal__eyebrow { 3353 font-size: 0.625rem; 3354 font-weight: 700; 3355 letter-spacing: 0.06em; 3356 text-transform: uppercase; 3357 color: var(--green-dark, #0d9488); 3358 margin-bottom: 0.125rem; 3359 } 3360 .vayapin-settings-redesign .vp-record-modal__title { 3361 margin: 0; 3362 font-size: 1.125rem; 3363 } 3364 .vayapin-settings-redesign .vp-record-modal__body { 3365 margin-bottom: 1rem; 3366 } 3367 .vayapin-settings-redesign .vp-rec-table { 3368 border: 1px solid var(--border); 3369 border-radius: 0.625rem; 3370 overflow: hidden; 3371 } 3372 .vayapin-settings-redesign .vp-rec-row { 3373 display: flex; 3374 align-items: baseline; 3375 gap: 0.75rem; 3376 padding: 0.625rem 0.875rem; 3377 border-bottom: 1px solid var(--border); 3378 } 3379 .vayapin-settings-redesign .vp-rec-row:last-child { 3380 border-bottom: 0; 3381 } 3382 .vayapin-settings-redesign .vp-rec-key { 3383 flex: 0 0 8.5rem; 3384 font-size: 0.75rem; 3385 font-weight: 600; 3386 color: var(--graphite-mid, #6b7280); 3387 } 3388 .vayapin-settings-redesign .vp-rec-val { 3389 flex: 1; 3390 font-size: 0.8125rem; 3391 color: var(--graphite-dark, #454545); 3392 text-align: right; 3393 word-break: break-word; 3394 } 3395 .vayapin-settings-redesign .vp-rec-mono { 3396 font-family: ui-monospace, Menlo, monospace; 3397 font-size: 0.78rem; 3398 } 3399 .vayapin-settings-redesign .vp-rec-script { 3400 font-size: 0.85rem; 3401 } 3402 .vayapin-settings-redesign .vp-rec-qr { 3403 display: flex; 3404 justify-content: center; 3405 margin-top: 0.875rem; 3406 } 3407 .vayapin-settings-redesign .vp-rec-qr img { 3408 width: 7.5rem; 3409 height: 7.5rem; 3410 object-fit: contain; 3411 border: 1px solid var(--border); 3412 border-radius: 0.5rem; 3413 padding: 0.25rem; 3414 background: #fff; 3415 } 3416 .vayapin-settings-redesign .vp-rec-foot { 3417 margin: 0.875rem 0 0; 3418 font-size: 0.72rem; 3419 color: var(--graphite-light, #9ca3af); 3420 text-align: center; 3421 } 3422 .vayapin-settings-redesign .vp-toast-modal__dialog { 3423 max-width: 22rem; 3424 } 3425 3426 /* ── VayaConnect · verified action spacing + secret gate ────────── */ 3427 .vayapin-settings-redesign .vp-partner-action-cell .vp-partners-view-btn { 3428 margin-right: 0.75rem; 3429 } 3430 .vayapin-settings-redesign .vp-partner-action-cell .vp-partners-verified-note { 3431 margin-left: 0.25rem; 3432 white-space: nowrap; 3433 } 3434 3435 .vayapin-settings-redesign .vp-partners-gate { 3436 text-align: center; 3437 padding: 2rem 1.25rem; 3438 border: 1px dashed var(--border); 3439 border-radius: 0.75rem; 3440 background: var(--green-light, #f0fcf6); 3441 } 3442 .vayapin-settings-redesign .vp-partners-gate__icon { 3443 display: inline-flex; 3444 align-items: center; 3445 justify-content: center; 3446 width: 2.5rem; 3447 height: 2.5rem; 3448 border-radius: 50%; 3449 background: #fff; 3450 border: 1px solid var(--border); 3451 color: var(--green-dark, #0d9488); 3452 margin-bottom: 0.625rem; 3453 } 3454 .vayapin-settings-redesign .vp-partners-gate__icon svg { 3455 width: 1.125rem; 3456 height: 1.125rem; 3457 } 3458 .vayapin-settings-redesign .vp-partners-gate__title { 3459 font-size: 0.95rem; 3460 font-weight: 700; 3461 color: var(--graphite-dark, #454545); 3462 margin-bottom: 0.375rem; 3463 } 3464 .vayapin-settings-redesign .vp-partners-gate__text { 3465 margin: 0 auto; 3466 max-width: 28rem; 3467 font-size: 0.8125rem; 3468 color: var(--graphite-mid, #6b7280); 3469 line-height: 1.5; 3470 } 3471 3472 /* VayaConnect · pending "Request sent" as text, not a button */ 3473 .vayapin-settings-redesign .vp-partner-action-cell .vp-partners-sent-note { 3474 font-size: 0.8125rem; 3475 color: var(--graphite-light, #9ca3af); 3476 margin-right: 0.75rem; 3477 } -
vayapin/trunk/framework/backend-ui/js/admin-redesign.js
r3531799 r3692533 14 14 analytics: 1, 15 15 badge: 1, 16 partners: 1, 16 17 }; 17 18 var VP_TAB_PREF_CLASSES = [ … … 21 22 'vp-tab-pref-analytics', 22 23 'vp-tab-pref-badge', 24 'vp-tab-pref-partners', 23 25 ]; 24 26 -
vayapin/trunk/framework/backend-ui/partials/settings-page-redesign.php
r3578216 r3692533 174 174 } 175 175 var t = localStorage.getItem('vayapin_settings_active_tab'); 176 var ok = { connection: 1, checkout: 1, delivery: 1, notifications: 1, analytics: 1, badge: 1 };176 var ok = { connection: 1, checkout: 1, delivery: 1, notifications: 1, analytics: 1, badge: 1, partners: 1 }; 177 177 if (!t || !ok[t] || t === 'connection') { 178 178 return; … … 258 258 <button type="button" class="tab-btn" data-vtab="notifications"><?php echo vayapin_admin_inline_tab_icon_svg( 'mail', 'tab-icon' ); ?> <?php esc_html_e( 'Notifications', 'vayapin-woocommerce' ); ?></button> 259 259 <button type="button" class="tab-btn" data-vtab="analytics"><?php echo vayapin_admin_inline_tab_icon_svg( 'stats-report', 'tab-icon' ); ?> <?php esc_html_e( 'Analytics', 'vayapin-woocommerce' ); ?></button> 260 <button type="button" class="tab-btn" data-vtab="partners"><?php echo vayapin_admin_inline_tab_icon_svg( 'group', 'tab-icon' ); ?> <?php esc_html_e( 'Partners', 'vayapin-woocommerce' ); ?></button> 260 261 </div> 261 262 … … 871 872 </div> 872 873 874 <?php 875 $vayaconnect_enabled = '1' === vayapin_get_option( 'vayaconnect_enabled', '0' ); 876 $vc_receiver_url = function_exists( 'vayapin_connect_webhook_receiver_url' ) ? vayapin_connect_webhook_receiver_url() : ''; 877 $vc_secret_encrypted = vayapin_get_option( 'vayaconnect_webhook_secret', '' ); 878 $vc_secret_decrypted = function_exists( 'vayapin_connect_get_inbound_secret' ) ? vayapin_connect_get_inbound_secret() : ''; 879 $vc_secret_masked = ( '' !== $vc_secret_decrypted && function_exists( 'vayapin_mask_api_key' ) ) ? vayapin_mask_api_key( $vc_secret_decrypted ) : ''; 880 $vc_secret_display = '' !== $vc_secret_decrypted ? $vc_secret_masked : ''; 881 $vc_initial = ( $vayaconnect_enabled && '' !== (string) $vc_secret_decrypted && function_exists( 'vayapin_connect_get_partners' ) ) 882 ? vayapin_connect_get_partners( array( 'paged' => 1, 'per_page' => 15 ) ) 883 : array( 'rows' => array(), 'total' => 0, 'paged' => 1, 'per_page' => 15 ); 884 ?> 885 <div id="tab-partners" class="tab-panel"> 886 <div class="section-card"> 887 <div class="section-header"> 888 <div class="section-icon"><?php echo vayapin_admin_inline_tab_icon_svg( 'group' ); ?></div> 889 <div class="section-title-wrap"> 890 <div class="section-title"><?php esc_html_e( 'VayaConnect', 'vayapin-woocommerce' ); ?></div> 891 <div class="section-desc"><?php esc_html_e( 'Request a VayaPin address from any customer. They drop one GPS pin in their own language; the verified address returns to you automatically — no foreign characters typed.', 'vayapin-woocommerce' ); ?></div> 892 </div> 893 </div> 894 <div class="section-body"> 895 <div class="vp-checkbox-wrap"> 896 <input type="checkbox" class="vp-checkbox" name="vayapin_woocommerce_options[vayaconnect_enabled]" value="1" id="vp_chk_vayaconnect" <?php checked( $vayaconnect_enabled ); ?>> 897 <label class="vp-checkbox-text" for="vp_chk_vayaconnect"> 898 <div class="vp-checkbox-title"><?php esc_html_e( 'Enable VayaConnect', 'vayapin-woocommerce' ); ?></div> 899 <div class="vp-checkbox-desc"><?php esc_html_e( 'Turns on the partner list, the "Request VayaPin address" button, and the inbound address webhook receiver. Save after changing.', 'vayapin-woocommerce' ); ?></div> 900 </label> 901 </div> 902 <?php if ( $vc_receiver_url ) : ?> 903 <div class="field-row" style="margin-top:1rem;"> 904 <div> 905 <div class="field-label"><?php esc_html_e( 'Inbound webhook URL', 'vayapin-woocommerce' ); ?></div> 906 <div class="field-hint"><?php esc_html_e( 'Give this URL and the VayaConnect secret below to VayaPin so address updates are pushed back to your store.', 'vayapin-woocommerce' ); ?></div> 907 </div> 908 <div class="field-control"> 909 <input class="vp-input mono" type="text" value="<?php echo esc_attr( $vc_receiver_url ); ?>" readonly onclick="this.select();"> 910 </div> 911 </div> 912 <?php endif; ?> 913 <div class="field-row"> 914 <div> 915 <div class="field-label"><?php esc_html_e( 'VayaConnect webhook secret', 'vayapin-woocommerce' ); ?></div> 916 <div class="field-hint"><?php esc_html_e( 'Verifies HMAC-SHA256 signatures on inbound pin.address_updated payloads. Separate from the outbound Notifications secret. Required for the inbound webhook receiver.', 'vayapin-woocommerce' ); ?></div> 917 </div> 918 <div class="field-control"> 919 <div class="input-wrap"> 920 <input class="vp-input mono vp-input--with-actions" type="password" name="vayapin_woocommerce_options[vayaconnect_webhook_secret]" id="vp_vc_secret_input" value="<?php echo esc_attr( $vc_secret_display ); ?>" data-real="<?php echo esc_attr( $vc_secret_decrypted ); ?>" data-masked="<?php echo esc_attr( $vc_secret_masked ); ?>" placeholder="<?php esc_attr_e( 'Paste the secret from VayaPin', 'vayapin-woocommerce' ); ?>" autocomplete="off"> 921 </div> 922 <div class="vp-mono-helper" style="margin-top:0.5rem;"> 923 <span class="field-hint"><?php esc_html_e( 'Paste the signing secret VayaPin gave you for this endpoint, then click Save settings.', 'vayapin-woocommerce' ); ?></span> 924 </div> 925 </div> 926 </div> 927 </div> 928 </div> 929 930 <div class="section-card" style="margin-top:1rem;"> 931 <div class="section-header"> 932 <div class="section-icon"><?php echo vayapin_admin_inline_tab_icon_svg( 'package' ); ?></div> 933 <div class="section-title-wrap"> 934 <div class="section-title"><?php esc_html_e( 'Partners', 'vayapin-woocommerce' ); ?></div> 935 <div class="section-desc"><?php esc_html_e( 'Your WooCommerce customers and their VayaPin status. Send an address request to anyone who has no VayaPin yet.', 'vayapin-woocommerce' ); ?></div> 936 </div> 937 </div> 938 <div class="section-body"> 939 <?php 940 $vc_has_secret = '' !== (string) $vc_secret_decrypted; 941 if ( ! $vayaconnect_enabled ) : 942 ?> 943 <p class="vp-muted"><?php esc_html_e( 'Enable VayaConnect above and save to load your partner list.', 'vayapin-woocommerce' ); ?></p> 944 <?php elseif ( ! $vc_has_secret ) : ?> 945 <div class="vp-partners-gate"> 946 <div class="vp-partners-gate__icon"><?php echo vayapin_admin_inline_tab_icon_svg( 'lock' ); ?></div> 947 <div class="vp-partners-gate__title"><?php esc_html_e( 'Set a webhook secret to continue', 'vayapin-woocommerce' ); ?></div> 948 <p class="vp-partners-gate__text"><?php esc_html_e( 'Generate the VayaConnect webhook secret above and click Save settings. The partner list and address requests unlock once the inbound receiver can verify signatures.', 'vayapin-woocommerce' ); ?></p> 949 </div> 950 <?php else : ?> 951 <div class="vp-partners-toolbar"> 952 <input type="search" id="vp_partners_search" class="vp-input" placeholder="<?php esc_attr_e( 'Search name or email…', 'vayapin-woocommerce' ); ?>" autocomplete="off"> 953 <select id="vp_partners_filter" class="vp-select"> 954 <option value="all"><?php esc_html_e( 'All statuses', 'vayapin-woocommerce' ); ?></option> 955 <option value="none"><?php esc_html_e( 'No VayaPin', 'vayapin-woocommerce' ); ?></option> 956 <option value="pending"><?php esc_html_e( 'Pending', 'vayapin-woocommerce' ); ?></option> 957 <option value="verified"><?php esc_html_e( 'Verified', 'vayapin-woocommerce' ); ?></option> 958 </select> 959 <button type="button" class="btn btn-primary btn-sm vp-partners-add-toggle" id="vp_partners_add_toggle" aria-expanded="false" aria-controls="vp_partners_add_form">+ <?php esc_html_e( 'Add recipient', 'vayapin-woocommerce' ); ?></button> 960 </div> 961 <?php 962 $vc_base_country = ''; 963 $vc_countries = array(); 964 if ( function_exists( 'WC' ) && WC()->countries ) { 965 $vc_countries = WC()->countries->get_countries(); 966 $vc_base = function_exists( 'wc_get_base_location' ) ? wc_get_base_location() : array(); 967 $vc_base_country = isset( $vc_base['country'] ) ? $vc_base['country'] : ''; 968 } 969 ?> 970 <div id="vp_partners_add_form" class="vp-partners-add-form" hidden> 971 <div class="vp-partners-add-grid"> 972 <label class="vp-partners-add-field"> 973 <span><?php esc_html_e( 'Name', 'vayapin-woocommerce' ); ?></span> 974 <input type="text" id="vp_add_name" class="vp-input" autocomplete="off"> 975 </label> 976 <label class="vp-partners-add-field"> 977 <span><?php esc_html_e( 'Email', 'vayapin-woocommerce' ); ?> <span class="vp-req">*</span></span> 978 <input type="email" id="vp_add_email" class="vp-input" autocomplete="off"> 979 </label> 980 <label class="vp-partners-add-field"> 981 <span><?php esc_html_e( 'Phone', 'vayapin-woocommerce' ); ?></span> 982 <input type="text" id="vp_add_phone" class="vp-input" autocomplete="off" placeholder="+45…"> 983 </label> 984 <label class="vp-partners-add-field"> 985 <span><?php esc_html_e( 'Country', 'vayapin-woocommerce' ); ?></span> 986 <select id="vp_add_country" class="vp-select"> 987 <?php foreach ( $vc_countries as $vc_code => $vc_label ) : ?> 988 <option value="<?php echo esc_attr( $vc_code ); ?>" <?php selected( $vc_code, $vc_base_country ); ?>><?php echo esc_html( $vc_label ); ?></option> 989 <?php endforeach; ?> 990 </select> 991 </label> 992 <label class="vp-partners-add-field"> 993 <span><?php esc_html_e( 'Address line 1', 'vayapin-woocommerce' ); ?></span> 994 <input type="text" id="vp_add_address_1" class="vp-input" autocomplete="off"> 995 </label> 996 <label class="vp-partners-add-field"> 997 <span><?php esc_html_e( 'City', 'vayapin-woocommerce' ); ?></span> 998 <input type="text" id="vp_add_city" class="vp-input" autocomplete="off"> 999 </label> 1000 <label class="vp-partners-add-field"> 1001 <span><?php esc_html_e( 'ZIP / Postcode', 'vayapin-woocommerce' ); ?></span> 1002 <input type="text" id="vp_add_zip" class="vp-input" autocomplete="off"> 1003 </label> 1004 </div> 1005 <p class="field-hint" style="margin-top:0.5rem;"><?php esc_html_e( 'VayaPin needs a rough address (varies by country) to start the request; the recipient refines it into a precise pin. For existing customers this is pulled from their order automatically.', 'vayapin-woocommerce' ); ?></p> 1006 <div class="vp-partners-add-actions"> 1007 <button type="button" class="btn btn-ghost btn-sm" id="vp_partners_add_cancel"><?php esc_html_e( 'Cancel', 'vayapin-woocommerce' ); ?></button> 1008 <button type="button" class="btn btn-primary btn-sm" id="vp_partners_add_submit"><?php esc_html_e( 'Send request', 'vayapin-woocommerce' ); ?></button> 1009 </div> 1010 </div> 1011 <div class="vayapin-table-wrapper" style="margin-top:0.75rem;"> 1012 <div id="vp_partners_bulkbar" class="vp-partners-bulkbar" hidden><span id="vp_partners_bulk_count" class="vp-muted"></span><button type="button" class="btn btn-primary btn-sm" id="vp_partners_bulk_request"><?php esc_html_e( 'Request VayaPin address', 'vayapin-woocommerce' ); ?></button><button type="button" class="btn btn-ghost btn-sm" id="vp_partners_bulk_clear"><?php esc_html_e( 'Clear', 'vayapin-woocommerce' ); ?></button></div> 1013 <table class="vayapin-logs-table vp-partners-table"> 1014 <thead> 1015 <tr> 1016 <th class="vp-partner-check-cell"><input type="checkbox" id="vp_partners_check_all" aria-label="<?php esc_attr_e( 'Select all', 'vayapin-woocommerce' ); ?>"></th><th><?php esc_html_e( 'Customer', 'vayapin-woocommerce' ); ?></th> 1017 <th><?php esc_html_e( 'Phone', 'vayapin-woocommerce' ); ?></th> 1018 <th><?php esc_html_e( 'Status', 'vayapin-woocommerce' ); ?></th> 1019 <th><?php esc_html_e( 'Action', 'vayapin-woocommerce' ); ?></th> 1020 </tr> 1021 </thead> 1022 <tbody id="vp_partners_tbody"> 1023 <?php echo vayapin_connect_render_partner_rows( $vc_initial ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped within renderer. ?> 1024 </tbody> 1025 </table> 1026 </div> 1027 <div class="vp-partners-footer"> 1028 <span id="vp_partners_count" class="vp-muted"><?php echo esc_html( sprintf( /* translators: %d: total partner count */ _n( '%d customer', '%d customers', (int) $vc_initial['total'], 'vayapin-woocommerce' ), (int) $vc_initial['total'] ) ); ?></span> 1029 <div class="vp-partners-pager"> 1030 <button type="button" class="btn btn-ghost btn-sm" id="vp_partners_prev" disabled>← <?php esc_html_e( 'Prev', 'vayapin-woocommerce' ); ?></button> 1031 <button type="button" class="btn btn-ghost btn-sm" id="vp_partners_next"><?php esc_html_e( 'Next', 'vayapin-woocommerce' ); ?> →</button> 1032 </div> 1033 </div> 1034 <?php endif; ?> 1035 </div> 1036 </div> 1037 </div> 1038 1039 <div id="vp_record_modal" class="vp-reset-modal vp-record-modal" aria-hidden="true"> 1040 <div class="vp-reset-modal__backdrop" tabindex="-1" data-vp-record-close></div> 1041 <div class="vp-reset-modal__dialog vp-record-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="vp_record_modal_title"> 1042 <div class="vp-record-modal__head"> 1043 <div> 1044 <div class="vp-record-modal__eyebrow"><?php esc_html_e( 'Verified contact record', 'vayapin-woocommerce' ); ?></div> 1045 <h2 id="vp_record_modal_title" class="vp-reset-modal__title vp-record-modal__title"></h2> 1046 </div> 1047 <span class="status-pill connected"><span class="status-pill-dot" aria-hidden="true"></span><?php esc_html_e( 'VayaPin verified', 'vayapin-woocommerce' ); ?></span> 1048 </div> 1049 <div class="vp-record-modal__body" id="vp_record_modal_body"></div> 1050 <div class="vp-reset-modal__actions"> 1051 <button type="button" class="btn btn-secondary vp-record-modal__close" data-vp-record-close><?php esc_html_e( 'Close', 'vayapin-woocommerce' ); ?></button> 1052 </div> 1053 </div> 1054 </div> 1055 1056 <div id="vp_toast_modal" class="vp-reset-modal vp-toast-modal" aria-hidden="true"> 1057 <div class="vp-reset-modal__backdrop" tabindex="-1" data-vp-toast-close></div> 1058 <div class="vp-reset-modal__dialog vp-toast-modal__dialog" role="alertdialog" aria-modal="true" aria-labelledby="vp_toast_modal_title"> 1059 <h2 id="vp_toast_modal_title" class="vp-reset-modal__title"></h2> 1060 <p class="vp-reset-modal__text" id="vp_toast_modal_text"></p> 1061 <div class="vp-reset-modal__actions"> 1062 <button type="button" class="btn btn-primary vp-toast-modal__ok" data-vp-toast-close><?php esc_html_e( 'OK', 'vayapin-woocommerce' ); ?></button> 1063 </div> 1064 </div> 1065 </div> 1066 873 1067 <div class="save-footer-meta"> 874 1068 <div class="save-footer-version"><?php -
vayapin/trunk/framework/backend-ui/webhook-logs.php
r3531799 r3692533 304 304 </td> 305 305 </tr> 306 <?php if ( isset( $log->payload ) && '' !== (string) $log->payload ) : ?> 307 <tr class="vp-log-payload-row"> 308 <td colspan="6"> 309 <details> 310 <summary><?php esc_html_e( 'Payload', 'vayapin-woocommerce' ); ?></summary> 311 <pre class="vp-log-payload"><?php echo esc_html( (string) $log->payload ); ?></pre> 312 </details> 313 </td> 314 </tr> 315 <?php endif; ?> 306 316 <?php endforeach; ?> 307 317 </tbody> -
vayapin/trunk/framework/frontend-ui/checkout-ui.php
r3578216 r3692533 399 399 $pin_fields = array( 400 400 'country' => $order->get_billing_country(), 401 // POST /v1/pins expects `name` as "first last"; a single token is rejected with 402 // "name: must include a last name" (see openapi.json, /v1/pins request body). 401 403 'name' => trim($order->get_billing_first_name() . ' ' . $order->get_billing_last_name()), 402 404 'email' => $order->get_billing_email(), -
vayapin/trunk/framework/styles-scripts/styles-scripts.php
r3506381 r3692533 41 41 ); 42 42 43 // Mobile sheet: hosts the whole widget flow on phone-width screens. 44 $vayapin_sheet_css_path = plugin_dir_path( dirname( __DIR__ ) ) . 'public/css/vayapin-sheet.css'; 45 $vayapin_sheet_css_version = file_exists( $vayapin_sheet_css_path ) ? filemtime( $vayapin_sheet_css_path ) : '1.0.0'; 46 47 wp_enqueue_style( 48 'vayapin-sheet-css', 49 plugin_dir_url( dirname( __DIR__ ) ) . 'public/css/vayapin-sheet.css', 50 array( 'vayapin-country-select-css' ), 51 $vayapin_sheet_css_version 52 ); 53 54 $vayapin_sheet_js_path = plugin_dir_path( dirname( __DIR__ ) ) . 'public/js/vayapin-sheet.js'; 55 $vayapin_sheet_js_version = file_exists( $vayapin_sheet_js_path ) ? filemtime( $vayapin_sheet_js_path ) : '1.0.0'; 56 57 wp_enqueue_script( 58 'vayapin-sheet', 59 plugin_dir_url( dirname( __DIR__ ) ) . 'public/js/vayapin-sheet.js', 60 array( 'jquery', 'vayapin-country-select-js' ), 61 $vayapin_sheet_js_version, 62 true 63 ); 64 43 65 // Enqueue and localize custom JS for initializing Select2 on desired elements 44 66 $vayapin_select2_js_path = plugin_dir_path( dirname( __DIR__ ) ) . 'public/js/vayapin-select2.js'; … … 48 70 'vayapin-select2-init', 49 71 plugin_dir_url( dirname( __DIR__ ) ) . 'public/js/vayapin-select2.js', 50 array( 'jquery', 'select2-js' ),72 array( 'jquery', 'select2-js', 'vayapin-sheet' ), 51 73 $vayapin_select2_js_version, 52 74 true -
vayapin/trunk/framework/vayapin/api-methods.php
r3506381 r3692533 207 207 error_log($e->getMessage()); 208 208 } 209 } 210 211 /** 212 * Pull code and message out of a VayaPin API error body. 213 * 214 * The API answers a 422 with { error: { code, message }, data: { errors: { field: [...] } } }, 215 * older builds answered with a plain string in `error`. Reading only `$result['error']` 216 * yields an array on the current format, which silently logs an empty message. 217 * 218 * @param mixed $result Decoded response body. 219 * @param int $response_code HTTP status. 220 * @return array { code, message } 221 */ 222 function vayapin_extract_api_error( $result, $response_code ) { 223 $code = ''; 224 $message = ''; 225 226 if ( is_array( $result ) ) { 227 if ( isset( $result['error'] ) && is_array( $result['error'] ) ) { 228 $code = isset( $result['error']['code'] ) ? (string) $result['error']['code'] : ''; 229 $message = isset( $result['error']['message'] ) ? (string) $result['error']['message'] : ''; 230 } elseif ( isset( $result['error'] ) && is_string( $result['error'] ) ) { 231 $message = $result['error']; 232 } 233 234 if ( '' === $code && isset( $result['error_code'] ) && is_string( $result['error_code'] ) ) { 235 $code = $result['error_code']; 236 } 237 if ( '' === $message && isset( $result['message'] ) && is_string( $result['message'] ) ) { 238 $message = $result['message']; 239 } 240 241 // Per-field validation errors: { data: { errors: { phone_number: ["can't be blank"] } } }. 242 $field_errors = isset( $result['data']['errors'] ) ? $result['data']['errors'] : null; 243 if ( is_array( $field_errors ) ) { 244 $parts = array(); 245 foreach ( $field_errors as $field => $messages ) { 246 $messages = is_array( $messages ) ? $messages : array( $messages ); 247 $messages = array_filter( array_map( 'strval', $messages ) ); 248 if ( $messages ) { 249 $parts[] = $field . ': ' . implode( ', ', $messages ); 250 } 251 } 252 if ( $parts ) { 253 $joined = implode( '; ', $parts ); 254 $message = '' !== $message ? $message . ' (' . $joined . ')' : $joined; 255 } 256 } 257 } 258 259 if ( '' === $code ) { 260 $code = 'HTTP_' . (int) $response_code; 261 } 262 if ( '' === $message ) { 263 $message = 'Unknown error (HTTP ' . (int) $response_code . ')'; 264 } 265 266 return array( 267 'code' => $code, 268 'message' => $message, 269 ); 209 270 } 210 271 … … 293 354 } else { 294 355 // Failed 295 $error_code = isset($result['error_code']) ? $result['error_code'] : 'HTTP_' . $response_code; 296 $error_message = isset($result['error']) ? $result['error'] : (isset($result['message']) ? $result['message'] : 'Unknown error'); 297 vayapin_log_pin_creation($order_id, 'failed', $error_code, $error_message, null, $result); 298 return new WP_Error('api_error', $error_message, array('status' => $response_code, 'response' => $result)); 299 } 300 } 356 $error = vayapin_extract_api_error($result, $response_code); 357 // Keep the raw body when it is not JSON, otherwise the reason is lost for good. 358 $logged_response = is_array($result) ? $result : array('raw_body' => (string) $body, 'http_code' => (int) $response_code); 359 vayapin_log_pin_creation($order_id, 'failed', $error['code'], $error['message'], null, $logged_response); 360 return new WP_Error('api_error', $error['message'], array('status' => $response_code, 'response' => $result)); 361 } 362 } -
vayapin/trunk/includes/class-vayapin-activator.php
r3531799 r3692533 33 33 Vayapin_Activator::create_pin_logs_table(); 34 34 Vayapin_Activator::create_webhook_logs_table(); 35 36 // VayaConnect request-state table (function loaded from framework/connect/request-store.php). 37 if ( function_exists( 'vayapin_connect_create_requests_table' ) ) { 38 vayapin_connect_create_requests_table(); 39 } 40 41 // Flush rewrites so the /vayapin-intl-label/{id} route resolves. 42 flush_rewrite_rules(); 35 43 } 36 44 -
vayapin/trunk/public/js/vayapin-public.js
r3578216 r3692533 4 4 var allowClose = true; 5 5 6 $(window).load(function () { 6 /** 7 * Close the country list through the plugin instance. Hiding the list by class 8 * alone leaves countrySelect's document-level keydown handler bound, and that 9 * handler calls preventDefault() on every key, which blocks typing and backspace 10 * in the VayaPin input. 11 */ 12 function closeCountryList() { 13 var instance = $("#country_selector").data("plugin_countrySelect"); 14 15 if (instance && typeof instance._closeDropdown === "function") { 16 instance._closeDropdown(); 17 return; 18 } 19 20 $(".vayapin-pin-country-wrapper .country-list") 21 .addClass("hide") 22 .removeClass("v-hide"); 23 $(".vayapin-pin-country-wrapper .selected-flag .arrow").removeClass("up"); 24 } 25 26 function closeVayapinPopup() { 27 if (window.vayapinSheet && window.vayapinSheet.isOpen()) { 28 window.vayapinSheet.close(0); 29 $(".vayapin-checkout-button").attr("aria-expanded", "false"); 30 return; 31 } 32 33 closeCountryList(); 34 $(".vayapin-overlay").fadeOut(100); 35 $(".vayapin-checkout-dropdown").fadeOut(100); 36 $(".vayapin-checkout-button").attr("aria-expanded", "false"); 37 // Stale "Not found" from the previous attempt must not greet the next one. 38 $(".vayapin-error-text").hide(); 39 } 40 41 // jQuery 3 removed the .load() shortcut; it only still runs on sites that kept 42 // jQuery Migrate, and silently kills this file on the ones that dropped it. 43 $(window).on("load", function () { 7 44 $(".vayapin-checkout-button").on("click", function (e) { 8 45 e.stopPropagation(); 46 $(".vayapin-error-text").hide(); 47 48 // Phones get the whole flow in a bottom sheet, not a popup over the form. 49 if (window.vayapinSheet && window.vayapinSheet.shouldUse()) { 50 $(this).attr("aria-expanded", "true"); 51 window.vayapinSheet.open(); 52 return; 53 } 9 54 $(".vayapin-overlay").fadeIn(100); 10 55 $(".vayapin-checkout-dropdown").fadeIn(100); … … 14 59 $(".vayapin-checkout-dropdown-close").on("click", function (e) { 15 60 e.preventDefault(); 16 $(".vayapin-overlay").fadeOut(100); 17 $(".vayapin-checkout-dropdown").fadeOut(100); 18 $(".vayapin-checkout-button").attr("aria-expanded", "false"); 61 closeVayapinPopup(); 19 62 }); 20 63 … … 22 65 if (e.key === "Escape" || e.keyCode === 27) { 23 66 if ($(".vayapin-checkout-dropdown").is(":visible")) { 24 $(".vayapin-overlay").fadeOut(100); 25 $(".vayapin-checkout-dropdown").fadeOut(100); 26 $(".vayapin-checkout-button").attr("aria-expanded", "false"); 67 closeVayapinPopup(); 27 68 } 28 69 } … … 40 81 // it bubbles up the country list is already hidden, so .has() misses it 41 82 // and the whole popup would wrongly close (notably on mobile/touch). 42 !$(e.target).closest(".country-select, .country-list").length 83 !$(e.target).closest(".country-select, .country-list").length && 84 // The mobile country sheet lives at body level, so any tap inside it 85 // counts as "outside" the popup unless excluded here. 86 !$(e.target).closest(".vp-sheet").length 43 87 ) { 44 $(".vayapin-overlay").fadeOut(100); 45 $dropdown.fadeOut(100); 46 $(".vayapin-checkout-button").attr("aria-expanded", "false"); 88 closeVayapinPopup(); 47 89 } 48 90 }); … … 64 106 $(document).on( 65 107 "touchstart mousedown", 66 ".country-select, .country-list ",108 ".country-select, .country-list, .vp-sheet", 67 109 function () { 68 110 allowClose = false; … … 75 117 $(document).on( 76 118 "touchend mouseup", 77 ".country-select, .country-list ",119 ".country-select, .country-list, .vp-sheet", 78 120 function () { 79 121 if (countryCloseTimer) { … … 92 134 // sees this click because we stopPropagation() below. 93 135 if (!$(e.target).closest(".country-select").length) { 94 $(".vayapin-pin-country-wrapper .country-list") 95 .addClass("hide") 96 .removeClass("v-hide"); 97 $(".vayapin-pin-country-wrapper .selected-flag .arrow").removeClass( 98 "up" 99 ); 136 closeCountryList(); 100 137 } 101 138 e.stopPropagation(); -
vayapin/trunk/public/js/vayapin-select2.js
r3506381 r3692533 2 2 "use strict"; 3 3 4 $(window).load(function () { 4 // jQuery 3 removed the .load() shortcut; it only still runs on sites that kept 5 // jQuery Migrate, and silently kills this file on the ones that dropped it. 6 $(window).on("load", function () { 5 7 /** 6 8 * Initialize select2 … … 161 163 e.stopPropagation(); 162 164 165 // Phone width gets the sheet's country screen instead of the anchored dropdown. 166 if (window.vayapinSheet && window.vayapinSheet.shouldUse()) { 167 window.vayapinSheet.openCountryView(); 168 return; 169 } 170 163 171 var $list = instance.countryList; 164 172 if (!$list || !$list.length) return; … … 192 200 }); 193 201 202 // countrySelect binds a document-level keydown that preventDefault()s every key 203 // while its list is open. Without stopping the bubble here, typing and backspace 204 // in the VayaPin field are swallowed and the text cannot be edited. 205 $("#vayapin_input").on("keydown", function (e) { 206 e.stopPropagation(); 207 }); 208 194 209 // Set the vaya pin in local storage and auto-set country 195 210 $("#vayapin_input").on("input", function (e) { 211 // Editing the code means the previous "Not found" no longer applies. 212 $(".vayapin-error-text").hide(); 213 196 214 var value = $(this).val(); 197 215 $(this).val(value.toUpperCase()); … … 253 271 }, 254 272 success: function (response) { 273 if (window.vayapinSheet && window.vayapinSheet.isOpen()) { 274 window.vayapinSheet.close(0); 275 } 255 276 $(".vayapin-overlay").fadeOut(100); 256 277 $(".vayapin-checkout-dropdown").fadeOut(100); … … 281 302 282 303 /** 304 * Write a value into a checkout field and let WooCommerce re-run its own 305 * validation for it. WooCommerce binds validate_field to "input validate change", 306 * so without a trigger a "required field" error raised before the autofill keeps 307 * showing on a field that now has a value. 308 */ 309 function setCheckoutField($field, value) { 310 if (!$field.length) return; 311 312 $field.val(value); 313 314 // Empty value: leave whatever validation state the field already had. 315 if (value === undefined || value === null || value === "") return; 316 317 $field.trigger("input").trigger("change").trigger("validate"); 318 } 319 320 /** 321 * Remove the summary block from a previous failed "Place order" attempt once no 322 * field is flagged invalid anymore. Only the checkout notice group is touched, 323 * so coupon/payment notices stay. 324 */ 325 function clearStaleCheckoutNotices() { 326 var $form = $("form.checkout"); 327 if (!$form.length) return; 328 if ($form.find(".woocommerce-invalid").length) return; 329 330 $(".woocommerce-NoticeGroup-checkout").remove(); 331 } 332 333 /** 283 334 * Fill out fields in the checkout form 284 335 */ 285 336 function fillOutFields(data) { 286 // Fill first name field (from title) 287 $("#billing_first_name").val(data.title); 288 289 // Fill last name field (from subtitle) 290 $("#billing_last_name").val(data.subtitle); 291 292 // Fill company field (from description) 293 $("#billing_company").val(data.description); 294 295 // Fill email field 296 $("#billing_email").val(data.email); 297 298 // Fill phone field 299 $("#billing_phone").val(data.phone); 300 301 // Fill address fields 302 $("#billing_address_1").val(data.address_1); 303 304 $("#billing_address_2").val(data.address_2); 305 306 // Fill city field 307 $("#billing_city").val(data.city); 308 309 // Fill postcode field 310 $("#billing_postcode").val(data.postcode); 311 312 // Set country first and wait for state field to update 313 $("#billing_country").val(data.country).trigger("change"); 337 setCheckoutField($("#billing_first_name"), data.title); 338 setCheckoutField($("#billing_last_name"), data.subtitle); 339 setCheckoutField($("#billing_company"), data.description); 340 setCheckoutField($("#billing_email"), data.email); 341 setCheckoutField($("#billing_phone"), data.phone); 342 setCheckoutField($("#billing_address_1"), data.address_1); 343 setCheckoutField($("#billing_address_2"), data.address_2); 344 setCheckoutField($("#billing_city"), data.city); 345 setCheckoutField($("#billing_postcode"), data.postcode); 346 347 // Country rebuilds the state field, so it goes first and on its own. 348 var $country = $("#billing_country"); 349 if ($country.length && data.country) { 350 $country.val(data.country).trigger("change").trigger("validate"); 351 } 352 353 clearStaleCheckoutNotices(); 314 354 315 355 // Wait for country change to complete and state field to reload 316 356 setTimeout(function () { 317 357 var $stateField = $("#billing_state"); 318 if ( 319 $stateField.length && 320 $stateField.hasClass("select2-hidden-accessible") 321 ) { 322 var stateValue = data.state; 323 var stateText = data.state; 324 325 // Create option if it doesn't exist 326 var $existingOption = $stateField.find( 327 "option[value='" + stateValue + "']" 328 ); 329 if ($existingOption.length === 0) { 330 var $newOption = new Option(stateText, stateValue, true, true); 331 $stateField.append($newOption); 358 359 if ($stateField.length && data.state) { 360 if ($stateField.is("select")) { 361 var stateValue = data.state; 362 var $existingOption = $stateField.find( 363 "option[value='" + stateValue + "']" 364 ); 365 366 if ($existingOption.length === 0) { 367 $stateField.append(new Option(stateValue, stateValue, true, true)); 368 } else { 369 $existingOption.prop("selected", true); 370 } 371 372 $stateField.val(stateValue).trigger("change").trigger("validate"); 373 374 if ($stateField.hasClass("select2-hidden-accessible")) { 375 $stateField.trigger("select2:select", { 376 params: { data: { id: stateValue, text: stateValue } }, 377 }); 378 } 332 379 } else { 333 $existingOption.prop("selected", true); 334 } 335 336 // Modern Select2 approach 337 $stateField.val(stateValue).trigger("change"); 338 339 // Alternative: trigger select2:select event 340 $stateField.trigger("select2:select", { 341 params: { data: { id: stateValue, text: stateText } }, 342 }); 343 } 380 setCheckoutField($stateField, data.state); 381 } 382 } 383 384 clearStaleCheckoutNotices(); 344 385 }, 500); 345 386 } -
vayapin/trunk/uninstall.php
r3531799 r3692533 36 36 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}vayapin_notification_logs" ); 37 37 38 // Drop VayaConnect request-state table (added in 2.2.0). 39 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}vayapin_connect_requests" ); 40 38 41 // Strip removed option keys from the main options array so re-installs start clean. 39 42 $vp_options = get_option( 'vayapin_woocommerce_options' ); -
vayapin/trunk/vayapin.php
r3676127 r3692533 17 17 * Plugin URI: https://vayapin.com 18 18 * Description: Add VayaPin checkout autofill and delivery QR stickers to your WooCommerce store. 19 * Version: 2. 1.219 * Version: 2.3.2 20 20 * Author: Vayapin 21 21 * Author URI: https://vayapin.com/ … … 38 38 * Rename this for your plugin and update it as you release new versions. 39 39 */ 40 define( 'VAYAPIN_VERSION', '2. 1.2' );40 define( 'VAYAPIN_VERSION', '2.3.2' ); 41 41 42 42 if ( ! defined( 'VAYAPIN_PLUGIN_FILE' ) ) { … … 102 102 103 103 /** 104 * VayaConnect: outbound request + inbound webhook receiver + Partners surface. 105 * Contract-dependent pieces are stubbed behind framework/connect/connect-config.php. 106 */ 107 require_once 'framework/connect/connect-config.php'; 108 require_once 'framework/connect/request-store.php'; 109 require_once 'framework/connect/partners.php'; 110 require_once 'framework/connect/record.php'; 111 require_once 'framework/connect/label-intl.php'; 112 require_once 'framework/vayapin/connect-api.php'; 113 require_once 'framework/vayapin/connect-webhook.php'; 114 115 /** 104 116 * Begins execution of the plugin. 105 117 *
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)