Plugin Directory

Changeset 3780965


Ignore:
Timestamp:
11/02/2026 11:50:24 PM (4 hours ago)
Author:
yatoon
Message:

Update 4.10.2 and fix appointment language selector

Location:
yatoon-booking-system
Files:
22 edited

Legend:

Unmodified
Added
Removed
  • yatoon-booking-system/tags/4.10.2/admin/views/bookings.php

    r3780403 r3780965  
    367367                        if ( $summary_end && ( ! $get || $summary_end > $get ) ) { $get = $summary_end; }
    368368                    }
     369                    // 4.10.2: label each expanded row with its day when the group covers more than one day (cancelled rows included).
     370                    $yatoon_group_days = array();
     371                    foreach ( $gbks as $yatoon_day_row ) {
     372                        $yatoon_day_start = yatoon_booking_display_dt( $yatoon_day_row, 'start_time', $_sbs_local_tz, $_sbs_utc_tz );
     373                        if ( $yatoon_day_start ) { $yatoon_group_days[ $yatoon_day_start->format( 'Y-m-d' ) ] = true; }
     374                    }
     375                    $yatoon_group_multi_day = count( $yatoon_group_days ) > 1;
    369376                    $total_price = array_sum(array_column($summary_bookings, 'total_price'));
    370377                    $group_statuses = array_unique( array_map( static fn( $b ) => (string) $b->status, $summary_bookings ) );
    … …  
    432439                    <td><span style="font-size:12px;"><?php echo esc_html( $staff_names ); ?></span></td>
    433440                    <td>
     441                        <?php // 4.10.2: a cart can hold appointments on different days; show the date range, not one long block. ?>
     442                        <?php if ( $gst->format( 'Y-m-d' ) === $get->format( 'Y-m-d' ) ) : ?>
    434443                        <strong><?php echo esc_html( $gst->format('M j, Y') ); ?></strong><br>
    435444                        <small><?php echo esc_html( $gst->format('g:i A') ); ?> - <?php echo esc_html( $get->format('g:i A') ); ?></small>
     445                        <?php else : ?>
     446                        <strong><?php echo esc_html( $gst->format( 'Y' ) === $get->format( 'Y' ) ? $gst->format( 'M j' ) . ' – ' . $get->format( 'M j, Y' ) : $gst->format( 'M j, Y' ) . ' – ' . $get->format( 'M j, Y' ) ); ?></strong><br>
     447                        <small><?php esc_html_e( 'Several days. Expand for each time.', 'yatoon-booking-system' ); ?></small>
     448                        <?php endif; ?>
    436449                    </td>
    437450                    <td><?php echo esc_html( $yatoon_admin_money( $total_price ) ); if ( $cancelled_count ) { echo '<br><small>' . esc_html( $active_bookings ? __( 'Excludes cancelled items', 'yatoon-booking-system' ) : __( 'Cancelled service total', 'yatoon-booking-system' ) ) . '</small>'; } echo wp_kses_post(YATOON_Gift_Cards::summary_html($summary_bookings)); ?></td>
    … …  
    493506                    <td><?php echo esc_html($yatoon_booking_row->staff_name ?: 'N/A'); ?></td>
    494507                    <td>
    495                         <small style="color:#0369a1;font-weight:600;"><?php echo esc_html( $yatoon_booking_start_time->format('g:i A') ); ?></small>
     508                        <small style="color:#0369a1;font-weight:600;"><?php echo esc_html( ( $yatoon_group_multi_day ? $yatoon_booking_start_time->format( 'M j' ) . ' · ' : '' ) . $yatoon_booking_start_time->format('g:i A') ); ?></small>
    496509                        <small> - <?php echo esc_html( $yatoon_booking_end_time->format('g:i A') ); ?></small>
    497510                    </td>
  • yatoon-booking-system/tags/4.10.2/assets/css/manage-booking.css

    r3729808 r3780965  
    737737 .yatoon-manage-wrap .yatoon-rebook-btn { width:100%; }
    738738}
     739/* Customer appointment management language menu. */
     740.yatoon-manage-wrap .sbs-header-language{margin:0;position:relative;font-family:inherit}
     741.yatoon-manage-wrap .sbs-language-picker{position:relative;margin:0;padding:0;border:0}
     742.yatoon-manage-wrap .sbs-language-trigger{display:flex;align-items:center;gap:6px;min-height:40px;padding:7px 9px;border:0;border-radius:999px;background:transparent;color:#111827;font-family:inherit;font-size:13px;font-weight:600;line-height:1.4;cursor:pointer;list-style:none}
     743.yatoon-manage-wrap .sbs-language-trigger::-webkit-details-marker{display:none}.yatoon-manage-wrap .sbs-language-trigger::marker{content:''}.yatoon-manage-wrap .sbs-language-trigger::before,.yatoon-manage-wrap .sbs-language-trigger::after{content:none!important}
     744.yatoon-manage-wrap .sbs-language-flag{display:inline-flex!important;flex:0 0 21px;width:21px!important;height:21px!important;overflow:hidden;border-radius:50%;border:1px solid rgba(128,128,128,.22);box-sizing:border-box}
     745.yatoon-manage-wrap .sbs-language-flag svg{display:block;width:100%!important;height:100%!important;max-width:none!important}
     746.yatoon-manage-wrap .sbs-language-chevron{width:12px;height:12px;flex:0 0 12px}.yatoon-manage-wrap .sbs-language-picker[open] .sbs-language-chevron{transform:rotate(180deg)}
     747.yatoon-manage-wrap .sbs-language-panel{position:absolute;z-index:10004;right:0;top:calc(100% + 5px);box-sizing:border-box;width:min(220px,calc(100vw - 40px));max-height:65vh;overflow:auto;padding:5px;border:1px solid #e5e7eb;border-radius:12px;background:#fff;box-shadow:0 8px 28px rgba(0,0,0,.16)}
     748.yatoon-manage-wrap .sbs-language-picker:not([open]) .sbs-language-panel{display:none!important}
     749.yatoon-manage-wrap .sbs-language-choice{display:flex!important;align-items:center;gap:9px;width:100%;min-height:42px;margin:0;padding:9px;border:0!important;border-radius:8px;background:transparent!important;color:#111827!important;box-shadow:none!important;font-family:inherit;font-size:13px;font-weight:500;line-height:1.4;text-align:left;text-transform:none;cursor:pointer}
     750.yatoon-manage-wrap .sbs-language-choice:hover,.yatoon-manage-wrap .sbs-language-choice[aria-current=true],.yatoon-manage-wrap .sbs-language-trigger:hover{background:#f3f4f6!important}.yatoon-manage-wrap .sbs-language-check{margin-left:auto}.yatoon-manage-wrap .sbs-language-trigger:focus-visible,.yatoon-manage-wrap .sbs-language-choice:focus-visible{outline:2px solid #2563eb;outline-offset:2px}
  • yatoon-booking-system/tags/4.10.2/assets/css/manage-booking.min.css

    r3767282 r3780965  
    11/*! Yatoon Booking System 4.9.0 */.yatoon-manage-wrap{--yatoon-primary-light:#fdf2f8;--yatoon-text:#1f2937;--yatoon-radius:12px;color:var(--yatoon-text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;margin:0 auto;max-width:520px;padding:20px 16px 40px}.yatoon-manage-card{background:#fff;border:1px solid var(--yatoon-border);box-shadow:0 4px 24px rgba(0,0,0,.07)}.yatoon-manage-header{background:var(--yatoon-primary);color:#fff;padding:24px 28px 20px}.yatoon-manage-header h2{font-size:20px;font-weight:700}.yatoon-manage-subtitle{opacity:.85}.yatoon-manage-details{border-bottom:1px solid var(--yatoon-border);padding:20px 28px}.yatoon-manage-row{align-items:baseline;border-bottom:1px solid #f3f4f6;padding:7px 0}.ymr-label{flex:0 0 90px;letter-spacing:.04em;text-transform:uppercase}.yatoon-manage-actions{gap:12px;padding:24px 28px 4px}.yatoon-btn{border:2px solid transparent;border-radius:8px;font-size:14px;font-weight:600;padding:10px 20px;transition:all .15s}.yatoon-btn:disabled{opacity:.6}.yatoon-btn-primary:hover:not(:disabled){filter:brightness(1.1)}.yatoon-btn-outline{background:transparent;border-color:var(--yatoon-primary)}.yatoon-btn-ghost{background:transparent;border-color:var(--yatoon-border)}.yatoon-btn-ghost:hover:not(:disabled){background:#f9fafb}.yatoon-btn-sm{padding:7px 14px}.yatoon-reschedule-panel{background:#fafafa;border-top:1px solid var(--yatoon-border);padding:20px 28px}.yatoon-reschedule-panel h3{font-size:15px}.yatoon-reschedule-date-row{align-items:center;display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.yatoon-reschedule-date-row label{color:var(--yatoon-muted);font-size:13px;font-weight:600}.yatoon-reschedule-date-row input[type=date]{border:1.5px solid var(--yatoon-border);border-radius:7px;outline:none;padding:8px 10px}.yatoon-slots-list{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}.yatoon-slot-chip{background:#fff;border:1.5px solid var(--yatoon-border);border-radius:20px;cursor:pointer;font-weight:600;padding:8px 14px;transition:all .15s}.yatoon-reschedule-confirm-row{display:flex;gap:10px;margin-top:4px}.yatoon-change-services-help{color:var(--yatoon-muted);font-size:13px;line-height:1.45;margin:-4px 0 14px}.yatoon-change-services-status{border-radius:10px;font-size:14px;line-height:1.5;margin:12px 0;padding:12px 14px}.yatoon-change-services-panel .yatoon-reschedule-confirm-row{flex-wrap:wrap}.yatoon-change-services-panel .yatoon-reschedule-confirm-row .yatoon-btn{flex:1 1 150px}.yatoon-service-change-row{align-items:center;display:grid;gap:10px;grid-template-columns:92px minmax(0,1fr);margin-bottom:12px}.yatoon-service-change-row label{color:var(--yatoon-muted);font-size:13px;font-weight:700}.yatoon-service-change-select,.yatoon-service-change-staff{background:#fff;border:1px solid var(--yatoon-border);border-radius:10px;color:var(--yatoon-text);font-size:14px;min-height:42px;padding:8px 10px;width:100%}.yatoon-service-change-staff-note{color:var(--yatoon-muted);font-size:12px;grid-column:2;margin-top:-4px}.yatoon-service-remove-row{background:#fff;border:1px solid var(--yatoon-border);border-radius:999px;color:#dc2626;cursor:pointer;font-weight:700;grid-column:2;min-height:38px;padding:7px 12px}.yatoon-manage-notice{border-radius:8px;margin:16px 28px;padding:12px 16px}.yatoon-notice-success{background:#d1fae5;color:#065f46}.yatoon-notice-warning{background:#fef3c7;color:#92400e}.yatoon-notice-info{color:#1e40af}.yatoon-manage-error{background:#fee2e2;border-radius:8px;color:#991b1b;padding:14px 18px}.yatoon-manage-footer{border-top:1px solid var(--yatoon-border);padding:16px 28px;text-align:center}.yatoon-after-cancel-actions{display:flex;flex-wrap:wrap;gap:10px;padding:0 28px 18px}.yatoon-after-cancel-actions .yatoon-btn{flex:1 1 180px;justify-content:center}@media (max-width:480px){.yatoon-manage-wrap{padding:12px 8px 32px}.yatoon-manage-actions,.yatoon-manage-details,.yatoon-manage-header,.yatoon-manage-notice,.yatoon-reschedule-panel{padding-left:18px;padding-right:18px}.yatoon-manage-actions{flex-direction:column}.yatoon-after-cancel-actions{flex-direction:column;padding-left:18px;padding-right:18px}.yatoon-btn{justify-content:center}.yatoon-service-change-row{grid-template-columns:1fr}.yatoon-service-change-staff-note,.yatoon-service-remove-row{grid-column:1}}.yatoon-manage-footer .yatoon-manage-return{color:var(--yatoon-text);display:inline-flex;text-decoration:none}.yatoon-manage-footer .yatoon-manage-return:hover{text-decoration:none}.yatoon-manage-wrap{--yatoon-border:#e5e7eb;--yatoon-radius:16px}.yatoon-manage-card{box-shadow:0 14px 36px rgba(15,23,42,.08)}.yatoon-manage-header h2{letter-spacing:0}.ymr-value{color:#111827}.yatoon-btn{border-width:1px;min-height:42px}.yatoon-btn-primary{box-shadow:0 10px 22px rgba(236,72,153,.16)}.yatoon-btn-ghost,.yatoon-btn-outline{background:#fff}.yatoon-reschedule-panel{background:#f8fafc}.yatoon-reschedule-date-row input[type=date]{border-width:1px;min-height:40px}.yatoon-reschedule-date-row input[type=date]:focus{box-shadow:0 0 0 3px rgba(236,72,153,.12)}.yatoon-slot-chip{border-width:1px}.yatoon-manage-wrap{--yatoon-primary:#ec4899;--yatoon-primary-light:rgba(236,72,153,.1);--yatoon-text:#111827;--yatoon-muted:#6b7280;--yatoon-border:rgba(0,0,0,.08);--yatoon-surface:#fff;--yatoon-surface2:#f8fafc;--yatoon-radius:20px;--yatoon-ease:cubic-bezier(.4,0,.2,1);-webkit-font-smoothing:antialiased;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,Arial,sans-serif}.yatoon-manage-card{background:var(--yatoon-surface);border:1px solid rgba(0,0,0,.08);border-radius:var(--yatoon-radius);box-shadow:0 16px 48px rgba(15,23,42,.11),inset 0 1px 0 hsla(0,0%,100%,.9);overflow:hidden}.yatoon-manage-header{background:#111827;padding:28px 30px 22px}.yatoon-manage-header h2{border:none;color:#fff;font-size:22px;font-weight:900;letter-spacing:-.03em;margin:0 0 4px}.yatoon-manage-subtitle{color:hsla(0,0%,100%,.65);font-size:13px;margin:0}.yatoon-manage-details{border-bottom:1px solid rgba(0,0,0,.06);padding:22px 30px}.yatoon-manage-row{align-items:flex-start;border-bottom:1px solid rgba(0,0,0,.05);display:flex;font-size:14px;gap:16px;justify-content:space-between;padding:8px 0;transition:background .15s}.yatoon-manage-row:last-child{border-bottom:none}.ymr-label{color:var(--yatoon-muted);flex:0 0 100px;font-size:12px;font-weight:700;letter-spacing:0;text-transform:none}.ymr-value{color:var(--yatoon-text);font-weight:600;text-align:right}.yatoon-manage-actions{display:flex;flex-wrap:wrap;gap:10px;padding:22px 30px 8px}.yatoon-btn{align-items:center;border:1px solid transparent;border-radius:999px;cursor:pointer;display:inline-flex;font-size:15px;font-weight:700;gap:6px;letter-spacing:-.01em;min-height:44px;padding:10px 22px;text-decoration:none;transition:all .18s var(--yatoon-ease)}.yatoon-btn:hover{transform:translateY(-1px)}.yatoon-btn:active{transform:scale(.97)}.yatoon-btn:disabled{cursor:not-allowed;opacity:.5;transform:none}.yatoon-btn-primary{background:var(--yatoon-primary);border-color:var(--yatoon-primary);box-shadow:0 6px 18px rgba(236,72,153,.25);color:#fff}.yatoon-btn-primary:hover:not(:disabled){box-shadow:0 10px 26px rgba(236,72,153,.32);filter:brightness(1.06)}.yatoon-btn-outline{background:var(--yatoon-surface);border-color:rgba(236,72,153,.3);color:var(--yatoon-primary)}.yatoon-btn-outline:hover:not(:disabled){background:var(--yatoon-primary-light);border-color:var(--yatoon-primary)}.yatoon-btn-ghost{background:var(--yatoon-surface);border-color:rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.06);color:var(--yatoon-muted)}.yatoon-btn-ghost:hover:not(:disabled){background:var(--yatoon-surface2);color:var(--yatoon-text)}.yatoon-btn-sm{font-size:13px;min-height:36px;padding:7px 16px}.yatoon-reschedule-panel{background:var(--yatoon-surface2);border-top:1px solid rgba(0,0,0,.07);padding:22px 30px}.yatoon-reschedule-panel h3{color:var(--yatoon-text);font-size:16px;font-weight:800;letter-spacing:-.015em;margin:0 0 16px}.yatoon-reschedule-date-row input[type=date]{background:var(--yatoon-surface);border:1px solid rgba(0,0,0,.14);border-radius:10px;font-size:14px;min-height:42px;transition:border-color .18s,box-shadow .18s}.yatoon-reschedule-date-row input[type=date]:focus{border-color:var(--yatoon-primary);box-shadow:0 0 0 4px rgba(236,72,153,.12);outline:none}.yatoon-slot-chip{background:var(--yatoon-surface);border:1px solid rgba(0,0,0,.1);border-radius:999px;box-shadow:0 1px 3px rgba(0,0,0,.06);color:var(--yatoon-text);font-size:13px;font-weight:700;padding:9px 16px;transition:all .15s var(--yatoon-ease)}.yatoon-slot-chip:hover{border-color:var(--yatoon-primary);box-shadow:0 3px 10px rgba(236,72,153,.15);color:var(--yatoon-primary);transform:translateY(-1px)}.yatoon-slot-chip.selected{background:var(--yatoon-primary);border-color:var(--yatoon-primary);box-shadow:0 4px 14px rgba(236,72,153,.28);color:#fff}.yatoon-manage-notice{border-radius:12px;font-size:14px;font-weight:500;line-height:1.5;margin:14px 30px;padding:13px 16px}.yatoon-notice-success{background:#dcfce7;border-left:3px solid #22c55e;color:#15803d}.yatoon-notice-error{background:#fee2e2;border-left:3px solid #ef4444;color:#991b1b}.yatoon-notice-warning{background:#fef9c3;border-left:3px solid #f59e0b;color:#a16207}.yatoon-notice-info{background:#eff6ff;border-left:3px solid #3b82f6;color:#1d4ed8}.yatoon-manage-footer{background:var(--yatoon-surface2);border-top:1px solid rgba(0,0,0,.06);color:var(--yatoon-muted);font-size:13px;padding:16px 30px}.yatoon-manage-footer a{color:var(--yatoon-primary);font-weight:600;text-decoration:none}.yatoon-manage-footer a:hover{text-decoration:underline}.yatoon-status-pending{color:#d97706;font-weight:700}.yatoon-status-confirmed{color:#059669;font-weight:700}.yatoon-status-completed{color:#6b7280;font-weight:700}.yatoon-status-cancelled,.yatoon-status-cancelled_by_customer{color:#dc2626;font-weight:700}.yatoon-cancel-choice{align-items:flex-start;cursor:pointer;display:inline-flex;gap:10px;justify-content:flex-end}.yatoon-cancel-choice input{flex:0 0 auto;margin-top:3px}.yatoon-cancel-choice span{display:block}@media (max-width:480px){.yatoon-manage-wrap{padding:10px 6px 28px}.yatoon-manage-actions,.yatoon-manage-details,.yatoon-manage-footer,.yatoon-manage-header,.yatoon-manage-notice,.yatoon-reschedule-panel{padding-left:18px;padding-right:18px}.yatoon-manage-actions{flex-direction:column}.yatoon-btn{justify-content:center}}#yatoon-after-cancel-actions.yatoon-after-cancel-actions{align-items:center;display:flex;flex-wrap:wrap;gap:10px;padding:0 30px 18px}#yatoon-after-cancel-actions.yatoon-after-cancel-actions .yatoon-btn{border-radius:14px;box-shadow:none;flex:1 1 180px;font-size:14px;height:auto;line-height:1.2;max-height:48px;min-height:44px;padding:10px 16px}#yatoon-manage-overlay :where(button,a,input,select,textarea,[tabindex]):focus-visible,.yatoon-manage-wrap :where(button,a,input,select,textarea):focus-visible{outline:3px solid #2563eb;outline-offset:3px}@media (prefers-reduced-motion:reduce){#yatoon-manage-overlay *,.yatoon-manage-wrap *{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}@media (max-width:480px){#yatoon-after-cancel-actions.yatoon-after-cancel-actions{flex-direction:column;padding-left:18px;padding-right:18px}#yatoon-after-cancel-actions.yatoon-after-cancel-actions .yatoon-btn{flex:0 0 auto;width:100%}}.yatoon-manage-wrap{box-sizing:border-box;min-width:0}.yatoon-manage-wrap .ymr-value{min-width:0;overflow-wrap:anywhere}.yatoon-manage-wrap .yatoon-manage-row{gap:14px}#yatoon-manage-overlay button,.yatoon-manage-wrap :is(.yatoon-btn,input:not([type=checkbox]),select,textarea){border-radius:12px;box-sizing:border-box;line-height:1.35;min-height:44px}.yatoon-manage-wrap .yatoon-btn{text-align:center;white-space:normal}@media(max-width:640px){.yatoon-manage-wrap :is(input:not([type=checkbox]),select,textarea){font-size:16px}.yatoon-manage-wrap .yatoon-manage-row{align-items:flex-start}.yatoon-manage-wrap .ymr-label{flex-basis:85px}}.yatoon-manage-wrap #yatoon-reschedule-btn{background:#15803d!important;border-color:#15803d!important;color:#fff!important}.yatoon-manage-wrap #yatoon-change-services-btn{background:#1d4ed8!important;border-color:#1d4ed8!important;color:#fff!important}.yatoon-manage-wrap #yatoon-cancel-btn{background:#b91c1c!important;border-color:#b91c1c!important;color:#fff!important}.yatoon-manage-wrap #yatoon-reschedule-btn:hover{background:#166534!important}.yatoon-manage-wrap #yatoon-change-services-btn:hover{background:#1e40af!important}.yatoon-manage-wrap #yatoon-cancel-btn:hover{background:#991b1b!important}.yatoon-manage-wrap .yatoon-slot-chip{background:#fff!important;border:1px solid #cbd5e1!important;color:#111827!important;min-height:44px}.yatoon-manage-wrap .yatoon-slot-chip:hover{background:#f1f5f9!important}.yatoon-manage-wrap .yatoon-slot-chip.selected{background:#fce7f3!important;border:2px solid #be185d!important;color:#111827!important}.yatoon-manage-wrap .yatoon-manage-rebook{margin:0;padding:0 30px 18px}.yatoon-manage-wrap .yatoon-rebook-btn{background:#15803d!important;border-color:#15803d!important;color:#fff!important;justify-content:center;min-height:44px!important;text-decoration:none!important}.yatoon-manage-wrap .yatoon-rebook-btn:hover{background:#166534!important}@media(max-width:480px){.yatoon-manage-wrap .yatoon-manage-rebook{padding-inline:18px}.yatoon-manage-wrap .yatoon-rebook-btn{width:100%}}
     2
     3/* Customer appointment management language menu. */
     4.yatoon-manage-wrap .sbs-header-language{margin:0;position:relative;font-family:inherit}
     5.yatoon-manage-wrap .sbs-language-picker{position:relative;margin:0;padding:0;border:0}
     6.yatoon-manage-wrap .sbs-language-trigger{display:flex;align-items:center;gap:6px;min-height:40px;padding:7px 9px;border:0;border-radius:999px;background:transparent;color:#111827;font-family:inherit;font-size:13px;font-weight:600;line-height:1.4;cursor:pointer;list-style:none}
     7.yatoon-manage-wrap .sbs-language-trigger::-webkit-details-marker{display:none}.yatoon-manage-wrap .sbs-language-trigger::marker{content:''}.yatoon-manage-wrap .sbs-language-trigger::before,.yatoon-manage-wrap .sbs-language-trigger::after{content:none!important}
     8.yatoon-manage-wrap .sbs-language-flag{display:inline-flex!important;flex:0 0 21px;width:21px!important;height:21px!important;overflow:hidden;border-radius:50%;border:1px solid rgba(128,128,128,.22);box-sizing:border-box}
     9.yatoon-manage-wrap .sbs-language-flag svg{display:block;width:100%!important;height:100%!important;max-width:none!important}
     10.yatoon-manage-wrap .sbs-language-chevron{width:12px;height:12px;flex:0 0 12px}.yatoon-manage-wrap .sbs-language-picker[open] .sbs-language-chevron{transform:rotate(180deg)}
     11.yatoon-manage-wrap .sbs-language-panel{position:absolute;z-index:10004;right:0;top:calc(100% + 5px);box-sizing:border-box;width:min(220px,calc(100vw - 40px));max-height:65vh;overflow:auto;padding:5px;border:1px solid #e5e7eb;border-radius:12px;background:#fff;box-shadow:0 8px 28px rgba(0,0,0,.16)}
     12.yatoon-manage-wrap .sbs-language-picker:not([open]) .sbs-language-panel{display:none!important}
     13.yatoon-manage-wrap .sbs-language-choice{display:flex!important;align-items:center;gap:9px;width:100%;min-height:42px;margin:0;padding:9px;border:0!important;border-radius:8px;background:transparent!important;color:#111827!important;box-shadow:none!important;font-family:inherit;font-size:13px;font-weight:500;line-height:1.4;text-align:left;text-transform:none;cursor:pointer}
     14.yatoon-manage-wrap .sbs-language-choice:hover,.yatoon-manage-wrap .sbs-language-choice[aria-current=true],.yatoon-manage-wrap .sbs-language-trigger:hover{background:#f3f4f6!important}.yatoon-manage-wrap .sbs-language-check{margin-left:auto}.yatoon-manage-wrap .sbs-language-trigger:focus-visible,.yatoon-manage-wrap .sbs-language-choice:focus-visible{outline:2px solid #2563eb;outline-offset:2px}
  • yatoon-booking-system/tags/4.10.2/assets/js/frontend-checkout.js

    r3780403 r3780965  
    988988                        })
    989989                        .catch(function(error) {
     990                            // 4.10.2: the Stripe form could not open, so nothing was charged.
     991                            // On the first appointment, let the customer simply try again.
     992                            if (idx === 0 && error && error.data && error.data.preflight) {
     993                                self._showToast(error.message || __('Stripe payment could not be started.'), 'error');
     994                                restoreSubmitButtons();
     995                                return;
     996                            }
    990997                            self._showBookingRecovery({reference:payload.client_reference,failedIndex:idx,message:error.message,uncertain:true});
    991998                            self._showToast(error && error.message ? error.message : __('Stripe payment could not be started.'), 'error');
  • yatoon-booking-system/tags/4.10.2/assets/js/frontend-checkout.min.js

    r3780403 r3780965  
    1 !function(e,t){"use strict";const o=e.wp?.i18n?.__?t=>e.wp.i18n.__(t,"yatoon-booking-system"):e=>e,{ysSiteNow:n,ysSiteOffsetMin:s,ysFormatMoney:a,ysPriceVaries:i,ysMoneyFrom:r,ysCalcDeposit:c,validateBookingPhone:d,validateUSPhone:l,showFieldError:u,showPhoneError:m,validateCustomerName:g,validateCustomerEmail:f,ysFocusTrap:p}=e.YatoonBookingHelpers;class _{_paidCheckoutKey(e){return"yatoon-checkout:"+location.pathname+":"+e}_forgetPaidCheckout(e){if(e)try{sessionStorage.removeItem(this._paidCheckoutKey(e))}catch(e){}}_unresolvedPaidCheckout(e){const t=this._paidCheckoutKey("");try{for(let o=0;o<sessionStorage.length;o++){const n=sessionStorage.key(o);if(!n||0!==n.indexOf(t))continue;const s=n.slice(t.length);if(!s||s===e)continue;const a=JSON.parse(sessionStorage.getItem(n)||"null");if(a&&a.payment&&a.payment.id&&!a.resolved)return s}}catch(e){}return""}_showPaidRecoveryNotice(e,n){const s=this,a=t("#sbs-booking-recovery");a.length&&(a.addClass("sbs-booking-recovery-paid"),a.find("h3").first().text(o("Payment received, appointment not confirmed yet")),a.find(".sbs-booking-recovery-guidance").text(o('Your payment went through, but this appointment could not be confirmed. Please do not pay again. Use "Check payment and appointment status" to finish it, or contact the salon with the reference below so they can confirm another time or refund you.')),n&&!a.find(".sbs-paid-recovery-resolved").length&&t("<button/>",{type:"button",class:"sbs-paid-recovery-resolved",text:o("The salon has resolved this payment")}).appendTo(a.find(".sbs-booking-recovery-actions")).on("click",function(){try{const t=s._paidCheckoutKey(e),o=JSON.parse(sessionStorage.getItem(t)||"null");o&&(o.resolved=!0,sessionStorage.setItem(t,JSON.stringify(o)))}catch(e){}s._clearBookingRecovery(),s._saveSession()}))}submitBooking(){const n=this;if(this._handleBookingRecoveryAttempt())return;if(this._paymentsConfigured()&&"function"!=typeof this._ensurePaymentSdk){if(this._submissionChunkPromise)return;const e=t(".sbs-btn-submit").prop("disabled",!0).text(o("Loading secure payment..."));return void(this._submissionChunkPromise=this._loadFeatureChunk("payments").then(function(){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n.submitBooking()}).catch(function(t){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n._showToast(t.message||o("The secure payment component could not be loaded."),"error")}))}if(this._isSubmitting)return;if(this.designUploadInProgress)return void this._showToast(o("Please wait for the reference photo to finish uploading."),"info");if(!this._paymentsConfigured()&&(!this._turnstileTokenFetched||Date.now()>(this._turnstileTokenExpires||0))&&"function"==typeof e.yatoonGetTurnstileToken)return this._turnstileTokenFetched=!0,void e.yatoonGetTurnstileToken().then(function(t){e.yatoonTurnstileToken=t,n._turnstileTokenFetched=!0,n._turnstileTokenExpires=Date.now()+9e4,n.submitBooking()}).catch(function(){n._turnstileTokenFetched=!1,n._turnstileTokenExpires=0,n._showToast(o("Verification could not finish. Please try again."),"error")});const s=document.getElementById("sbs-otp-section"),a="undefined"!=typeof yatoonData&&"1"===yatoonData.verifyEmailOtp,i="undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp;if(s&&(a||i)){const e=(document.getElementById("sbs-customer-email")||{}).value||"",t=(document.getElementById("sbs-customer-phone")||{}).value||"",a=e.trim()+"|"+t.trim();s.dataset.sentContact&&s.dataset.sentContact!==a&&(s.dataset.sent="",s.dataset.sentContact="");const r="1"===s.dataset.verified&&s.dataset.verifiedContact===a;"1"!==s.dataset.verified||r||(s.dataset.verified="",s.dataset.verifiedKey="");const c=function(e){const t=String(e||"").replace(/\D/g,"");return 11===t.length&&"1"===t.charAt(0)?t.slice(1):t},l=i?""!==c(s.dataset.verifiedPhone)&&c(t)===c(s.dataset.verifiedPhone):!!s.dataset.verifiedEmail&&e.trim().toLowerCase()===s.dataset.verifiedEmail.toLowerCase(),g=s.dataset.deviceVerified===a;if(!r&&!l&&!g){const r=f(e);if(r){u("sbs-customer-email",r);const e=document.getElementById("sbs-customer-email");return void(e&&e.scrollIntoView({behavior:"smooth",block:"center"}))}if(i){const e=d(t);if(e){m(e);const t=document.getElementById("sbs-phone-display")||document.getElementById("sbs-customer-phone");return void(t&&t.scrollIntoView({behavior:"smooth",block:"center"}))}}if(s.dataset.deviceChecked!==a)return void jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_otp_device_check",nonce:yatoonData.nonce,email:e,phone:t},function(e){s.dataset.deviceChecked=a,e&&e.success&&e.data&&e.data.verified&&(s.dataset.deviceVerified=a),n.submitBooking()}).fail(function(){s.dataset.deviceChecked=a,n.submitBooking()});const c=(document.getElementById("sbs-customer-name")||{}).value||"",l=i?"sms":"email";s.style.display="block";const g=document.getElementById("sbs-otp-remember");if(g&&"1"===g.dataset.regionAuto&&!g.dataset.regionChecked){g.dataset.regionChecked="1";let e="";try{e=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(t){e=""}const t=/^Europe\/(Moscow|Minsk|Kaliningrad|Samara|Volgograd|Kirov|Astrakhan|Saratov|Ulyanovsk|Simferopol|Istanbul|Kiev|Kyiv|Uzhgorod|Zaporozhye|Chisinau|Tirane|Skopje|Sarajevo|Belgrade|Podgorica)$/;(/^Europe\//.test(e)&&!t.test(e)||/^Atlantic\/(Reykjavik|Madeira|Azores|Canary)$/.test(e))&&(g.checked=!1)}if(document.getElementById("sbs-otp-prompt").textContent="sms"===l?o("We are sending a verification code to your phone. Please enter it below."):o("We are sending a verification code to")+" "+e+". "+o("Please enter it below."),!s.dataset.sent){s.dataset.sent="1",s.dataset.sentContact=a;const n=document.getElementById("sbs-otp-msg"),i=document.getElementById("sbs-otp-resend-btn");n&&(n.textContent=o("Sending verification code..."),n.style.color=""),i&&(i.disabled=!0),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_send_otp",nonce:yatoonData.nonce,channel:l,email:e,phone:t,name:c},function(e){const t=!(!e||!e.success);n&&(n.textContent=t?e.data&&e.data.message?e.data.message:o("Verification code sent."):e&&e.data&&e.data.message?e.data.message:o("Failed to send code.")),i&&(i.style.display=""),i&&(i.disabled=!1),t||(s.dataset.sent="",s.dataset.sentContact="",n&&(n.style.color="#dc2626"))}).fail(function(){s.dataset.sent="",s.dataset.sentContact="",n&&(n.textContent=o("Could not send the verification code. Please try again."),n.style.color="#dc2626"),i&&(i.style.display="",i.disabled=!1)})}const p=document.getElementById("sbs-otp-verify-btn");p&&!p.dataset.bound&&(p.dataset.bound="1",p.addEventListener("click",function(){const e=(document.getElementById("sbs-otp-input")||{}).value||"",t=document.getElementById("sbs-otp-msg"),a=(document.getElementById("sbs-customer-email")||{}).value||"",i=(document.getElementById("sbs-customer-phone")||{}).value||"",r=a.trim()+"|"+i.trim();e.trim()?(p.disabled=!0,p.dataset.pending="1",t&&(t.textContent=o("Verifying..."),t.style.color=""),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_verify_otp",nonce:yatoonData.nonce,channel:l,email:a,phone:i,code:e,remember:document.getElementById("sbs-otp-remember")&&document.getElementById("sbs-otp-remember").checked?1:0},function(e){e.success?(s.dataset.verified="1",s.dataset.verifiedKey=e.data&&e.data.verified_key?e.data.verified_key:"",n._giftSubmissionQuote=null,n._dynamicQuoteKey="",s.dataset.verifiedContact=r,s.style.display="none",n.submitBooking()):(t&&(t.textContent=e.data&&e.data.message?e.data.message:o("Incorrect code.")),t&&(t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending="")}).fail(function(){t&&(t.textContent=o("Could not verify the code. Please try again."),t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending=""})):t&&(t.textContent=o("Enter the verification code first."),t.style.color="#dc2626")}));const _=document.getElementById("sbs-otp-resend-btn");return void(_&&!_.dataset.bound&&(_.dataset.bound="1",_.addEventListener("click",function(){if(_.disabled)return;s.dataset.sent="",s.dataset.sentContact="",s.dataset.verified="",s.dataset.verifiedKey="",s.dataset.verifiedContact="";const e=document.getElementById("sbs-otp-msg");e&&(e.textContent=""),n.submitBooking()})))}}if(this._isSubmitting=!0,this._clearBookingRecovery(),0===this.bookingCart.length&&this.selectedDateTime&&(this.pendingServices.length>0||this.selectedService)&&this.addToCart(),0===this.bookingCart.length)return this._isSubmitting=!1,void this._showToast(o("Please add at least one service to your booking."),"error");const r=t(".sbs-btn-submit"),c=t("#sbs-cart-next-btn"),l=t("#sbs-sidebar-next-btn"),g=r.text(),p=c.text(),_=l.text(),h=()=>{this._isSubmitting=!1,this._turnstileTokenFetched=!1,r.prop("disabled",!1).text(g),c.prop("disabled",!1).removeClass("sbs-submitting").text(p||o("Confirm Booking")),l.prop("disabled",!1).removeClass("sbs-submitting").text(_||o("Confirm Booking")),this._applyConfirmBtn(),this._updateMobileCartBar()};r.prop("disabled",!0).text(o("Confirming your booking...")),c.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),l.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),this._updateLegalBoxes();const y={customer_name:t("#sbs-customer-name").val(),customer_email:t("#sbs-customer-email").val(),customer_phone:t("#sbs-customer-phone").val(),client_timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"",notes:t("#sbs-customer-notes").val(),coupon_code:t("#sbs-coupon-code").length?t("#sbs-coupon-code").val():"",marketing_email_optin:t("#sbs-marketing-email-optin").is(":checked")?1:0,marketing_sms_optin:t("#sbs-marketing-sms-optin").is(":checked")?1:0,consent_accepted:t("#sbs-consent-accepted").length&&t("#sbs-consent-accepted").is(":checked")?1:0,consent_signature:t("#sbs-consent-signature").length?t("#sbs-consent-signature").val():"",policy_acknowledged:t("#sbs-policy-acknowledged").length&&t("#sbs-policy-acknowledged").is(":checked")?1:0,tip_amount:parseFloat(t("#sbs-tip-amount").val())||0,gift_card_code:t("#sbs-gift-card-code-confirmed").val()||"",custom_fields:function(){const e={};return t('[name^="cf["]').each(function(){const o=t(this).attr("name").replace(/^cf\[|\]$/g,"");t(this).is(":checkbox")||t(this).is(":radio")?t(this).is(":checked")&&(e[o]=t(this).val()):e[o]=t(this).val()}),JSON.stringify(e)}()},b=this._collectGuestContacts(),k=f(y.customer_email);if(k)return h(),u("sbs-customer-email",k),void(document.getElementById("sbs-customer-email")&&document.getElementById("sbs-customer-email").scrollIntoView({behavior:"smooth",block:"center"}));"undefined"!=typeof yatoonData&&yatoonData.phoneMode&&yatoonData.phoneMode;if(y.customer_phone){const e=d(y.customer_phone);if(e)return h(),m(e),void(document.getElementById("sbs-customer-phone")&&document.getElementById("sbs-customer-phone").scrollIntoView({behavior:"smooth",block:"center"}))}if(this._needsPolicyAck()&&!y.policy_acknowledged)return h(),this._showToast(o("Please review and acknowledge the cancellation / no-show policy before booking."),"error"),void this._focusBookingValidation(t("#sbs-policy-acknowledged"),t(".sbs-policy-box:visible").first());if(this._needsConsent()&&(!y.consent_accepted||!y.consent_signature.trim())){h(),this._showToast(o("Please read and sign the consent form before booking."),"error");const e=y.consent_accepted?t("#sbs-consent-signature"):t("#sbs-consent-accepted");return void this._focusBookingValidation(e,t(".sbs-consent-box:visible").first())}const v=this.bookingCart.slice(),C=[],w=JSON.stringify(v.map(e=>({start:e.dateTime&&e.dateTime.start_at,guest:e.guestLabel||"Me",services:(e.services||[{service:e.service,options:e.options,staff:e.staff}]).map(t=>({service:t.service&&t.service.id,options:(t.options||[]).map(e=>e.id),staff:t.staff&&t.staff.id||0,guest:t.guestLabel||e.guestLabel||"Me"}))}))),T=this._newBookingReference(w),I="undefined"!=typeof yatoonData&&(yatoonData.ajax_url||yatoonData.ajaxUrl)?yatoonData.ajax_url||yatoonData.ajaxUrl:"";if(!I)return h(),void this._showToast(o("Booking could not start because the WordPress AJAX URL is missing. Please refresh and try again."),"error");if(!v.length)return h(),void this._showToast(o("Please choose a service and time before confirming."),"error");const S=v.some(e=>e&&e.services&&e.services.length>1),P=v.length>1||S?"yatoon-"+T:"";let x=this.designUploadAttachmentId||0,B=this.designUploadToken||"";const D=o=>{const s=v[o],a=s.services||[{service:s.service,options:s.options,staff:s.staff}],i=s.dateTime&&s.dateTime.staff_map?s.dateTime.staff_map:null,r="string"==typeof i?function(){try{return JSON.parse(i)}catch(e){return null}}():i,c={},d={};a.forEach(function(e,t){const o=void 0!==e._originalIndex?e._originalIndex:t;s.dateTime.local_staff_map&&Number(s.dateTime.local_staff_map[o])>0&&(d[t]=Number(s.dateTime.local_staff_map[o])),r&&void 0!==r[o]&&(c[t]=r[o])});const l=Object.keys(c).length?JSON.stringify(c):i?"string"==typeof i?i:JSON.stringify(i):"",u=s.guestLabel||a[0]&&a[0].guestLabel||"Me",m=b[u]||{};let g=y.notes||"";if("Me"!==u){const e=[`Guest: ${u}`];m.name&&e.push(`Name: ${m.name}`),m.phone&&e.push(`Phone: ${m.phone}`),g=`${g}${g?"\n\n":""}${e.join(" - ")}`}const f={},p=new Date(s.dateTime.start_at).getTime(),_=a.map(e=>{const t=e.guestLabel||s.guestLabel||"Me",o=f[t]||0,a=Number.isFinite(p)?new Date(p+6e4*o).toISOString():s.dateTime.start_at;return f[t]=o+n._serviceDuration(e),{service_id:e.service.id,staff_id:e.staff?e.staff.id:0,gender_preference:n._genderPreferenceFor(e),option_ids:e.options.map(e=>e.id),guest_label:t,start_at:a,design_attachment_id:parseInt(e.designAttachmentId||0,10)||0,design_upload_token:String(e.designUploadToken||"")}});return{action:"yatoon_create_booking",nonce:yatoonData.nonce,client_reference:`${T}-${o+1}`,start_at:s.dateTime.start_at,local_staff_map_json:JSON.stringify(d),staff_map_json:l,services_json:JSON.stringify(_),service_id:a[0].service.id,staff_id:a[0].staff?a[0].staff.id:0,gender_preference:n._genderPreferenceFor(a[0]),selected_options:a[0].options.map(e=>e.id),notes:g,guest_label:u,guest_name:m.name||"",guest_phone:m.phone||"",booking_group_id:P,coupon_code:y.coupon_code,tip_amount:0===o?y.tip_amount:0,gift_card_code:n._giftSubmissionQuote?.cart[o]?.gift_card_amount>0?y.gift_card_code||n._giftCode:"",gift_card_amount:n._giftSubmissionQuote?.cart[o]?.gift_card_amount||0,custom_fields:y.custom_fields,customer_name:y.customer_name,customer_email:y.customer_email,customer_phone:y.customer_phone,otp_channel:"undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp?"sms":"email",otp_verified_key:document.getElementById("sbs-otp-section")&&document.getElementById("sbs-otp-section").dataset.verifiedKey?document.getElementById("sbs-otp-section").dataset.verifiedKey:"",marketing_email_optin:y.marketing_email_optin,marketing_sms_optin:y.marketing_sms_optin,consent_accepted:y.consent_accepted,consent_signature:y.consent_signature,policy_acknowledged:y.policy_acknowledged,client_timezone:y.client_timezone,design_attachment_id:x||"",design_upload_token:B||"",website:t("#sbs-website").val()||"",form_rendered_at:t("#sbs-form-rendered-at").val()||"",cf_turnstile_response:void 0!==e.yatoonTurnstileToken?e.yatoonTurnstileToken:"",yatoon_test_booking:t(".sbs-booking-container").hasClass("yatoon-test-mode")?"1":""}},E=()=>{v.length<2||!n._paymentsConfigured()?F(0):n._loadFeatureChunk("recovery").then(async()=>{for(let e=0;e<v.length;e++)try{await n._prepareCheckout(D(e))}catch(t){throw t.cartIndex=e,t}}).then(()=>F(0)).catch(e=>{const t=e&&"number"==typeof e.cartIndex?e.cartIndex:0;if(e&&e.data&&e.data.preflight)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:`${T}-${t+1}`,failedIndex:t,confirmedCount:0,message:e&&e.message||o("This appointment is no longer available. Please choose another time."),uncertain:!1}),h()})},F=s=>{if(s>=v.length){if(C.every(e=>e&&e.success)){const s=C.find(e=>e&&e.success&&e.data&&e.data.woocommerce_checkout_url);if(s&&s.data.woocommerce_checkout_url)return this.bookingConfirmed=!0,this._clearSession(),void e.location.assign(s.data.woocommerce_checkout_url);this.bookingConfirmed=!0,t(".sbs-form-step").removeClass("active"),t(".sbs-step-success").show(),this._updateStepAccordion(),this._markBookingFlowComplete(),t("#sbs-success-count").text(v.length);const a=1!==v.length;t("#sbs-success-booking-word").text(o(a?"Bookings":"Booking"));const i=this._confirmedBookingItems(v,C);this._renderSuccessBookingSummary(i);const r=C.filter(e=>e&&e.success&&e.data&&e.data.manage_url&&String(e.data.manage_url).length),d=r.length&&r[0].data?r[0].data.manage_url:"",u=C.find(e=>e&&e.success&&e.data&&e.data.calendar_url&&String(e.data.calendar_url).length),m=u&&u.data?u.data.calendar_url:"",g=yatoonData.portalUrl||"",f=yatoonData.portalLabel||o("My Appointments"),p=t("#sbs-success-actions").empty();if(t("<div/>",{class:"sbs-success-action-note",text:yatoonData.successNote||o("Your appointment is booked. Save this page or use the manage link below if you need to change services, reschedule, or cancel.")}).appendTo(p),d&&1===r.length&&t("<a/>",{class:"sbs-success-action-link primary",href:d,target:"_blank",rel:"noopener",text:"undefined"!=typeof yatoonData&&yatoonData.isPro?o("Change services, reschedule, or cancel"):o("Reschedule or cancel")}).appendTo(p),r.length>1){const e=t("<div/>",{class:"sbs-success-manage-list"}).appendTo(p),n=v.some(e=>e.guestLabel&&"Me"!==e.guestLabel);r.forEach((s,a)=>{const i=v[a],r=i&&i.guestLabel&&"Me"!==i.guestLabel?i.guestLabel:null;let c;if(r)c=o("Change or cancel for")+" "+r;else if(n)c=o("Change or cancel my appointment");else{const e=s.data&&s.data.service_name?s.data.service_name:o("Appointment")+" "+(a+1);c=o("Change or cancel:")+" "+e}t("<a/>",{class:"sbs-success-action-link compact",href:s.data.manage_url,target:"_blank",rel:"noopener",text:c}).appendTo(e)})}if(m&&!t("#sbs-success-cal-row").length&&t("<a/>",{class:"sbs-success-action-link",href:m,text:o("Add to calendar")}).appendTo(p),g&&t("<a/>",{class:"sbs-success-action-link",href:g,text:f}).appendTo(p),t("<button/>",{type:"button",class:"sbs-success-action-link sbs-success-book-again"+(d?"":" primary"),text:o("Book again")}).appendTo(p),p.show(),m){t("#sbs-cal-ics").attr("href",m);const e=C.find(e=>e&&e.success&&e.data&&(e.data.start_at||e.data.start_time)),n=i[0]||null,s=n&&n.dateTime?n.dateTime.start_at:"",a=n?this._getItemVisitDuration(n):0,r=s?new Date(s):null,c=r&&!isNaN(r.getTime())&&a?new Date(r.getTime()+6e4*a).toISOString():"",d=e&&e.data&&(e.data.start_at||e.data.start_time)||s,l=e&&e.data&&(e.data.end_at||e.data.end_time)||c;if(d&&l){const e=function(e){const t=new Date(e);return isNaN(t.getTime())?"":t.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}Z$/,"Z")},s=(n?n.services||[{service:n.service}]:[]).map(e=>e.service&&e.service.name).filter(Boolean).join(", "),a=encodeURIComponent((yatoonData.businessName||o("Appointment"))+(s?" - "+s:"")),i=encodeURIComponent(e(d)),r=encodeURIComponent(e(l)),c=encodeURIComponent(yatoonData&&yatoonData.siteTimezone?yatoonData.siteTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone);i&&r?t("#sbs-cal-google").attr("href","https://calendar.google.com/calendar/render?action=TEMPLATE&text="+a+"&dates="+i+"/"+r+"&ctz="+c):t("#sbs-cal-google").attr("href",m)}else t("#sbs-cal-google").attr("href",m);t("#sbs-success-cal-row").show()}yatoonData.rebookingNudgeText&&(t("#sbs-rebooking-nudge .sbs-rebooking-text").text(yatoonData.rebookingNudgeText),t("#sbs-rebooking-nudge .sbs-rebooking-btn").text(yatoonData.rebookingNudgeBtn||o("Book next visit")),setTimeout(function(){t("#sbs-rebooking-nudge").fadeIn(400)},1200));const _=C.find(e=>e&&e.success&&e.data&&e.data.booking_id);if(_&&_.data.manage_token&&t("#sbs-recurring-setup").length){const e=_.data.booking_id,n=_.data.manage_token;let s=2;setTimeout(function(){t("#sbs-recurring-setup").fadeIn(400)},2e3),t(document).on("click",".sbs-freq-btn",function(){t(".sbs-freq-btn").removeClass("active"),t(this).addClass("active"),s=parseInt(t(this).data("weeks"))}),t(document).on("click","#sbs-recurring-confirm",function(){const a=t(this).prop("disabled",!0).text(o("Setting up...")),i=parseInt(t("#sbs-recurring-count").val())||6;t.post(ajaxurl,{action:"yatoon_setup_recurring",nonce:yatoonData.nonce,yatoon_location_id:parseInt(yatoonData.locationId||0,10),booking_id:e,manage_token:n,frequency_weeks:s,total_count:i},function(e){const n=t("#sbs-recurring-result");e.success?(a.text(o("Done")),n.removeClass("error").text(e.data.message).show()):(a.prop("disabled",!1).text(o("Try again")),n.addClass("error").text(e.data&&e.data.message||o("Could not set up recurring.")).show())}).fail(function(){a.prop("disabled",!1).text(o("Try again")),t("#sbs-recurring-result").addClass("error").text(o("Network error. Please try again.")).show()})})}n.bookingCart=[],n.pendingServices=[],n._clearSession(),n._clearBookingRecovery(),n._bookingAttemptReference="",n._bookingAttemptFingerprint="",n._giftCode="",n._giftSubmissionQuote=null,t("#sbs-gift-card-code-confirmed").val(""),n._hideMobileCartBar(),n._closeDrawer(),n._isSubmitting=!1,h(),c.hide(),l.hide()}else{const e=C.filter(e=>!e||!e.success),t=e.map(e=>e&&e.data&&e.data.message?e.data.message:"").filter(Boolean).join(" "),s=t?`\n\nDetails: ${t}`:"",a=C.length-e.length;n._showToast(`${a} booking(s) confirmed, but ${e.length} failed. Please contact us.${s}`,"error");const i=e[0]&&e[0].data?e[0].data:{};n._showBookingRecovery({reference:i.reference||T,failedIndex:a,confirmedCount:a,message:t||o("One or more appointments were not confirmed."),safeToRetry:!0===i.retry_safe}),h()}return}const a=D(s),i=document.getElementById("sbs-design-reference"),r=i&&i.files&&i.files[0]?i.files[0]:null;let d=a,u=!0,m="application/x-www-form-urlencoded; charset=UTF-8";r&&!x&&(d=new FormData,Object.keys(a).forEach(function(e){const t=a[e];Array.isArray(t)?t.forEach(function(t){d.append(e+"[]",t)}):d.append(e,t)}),d.append("design_reference",r),u=!1,m=!1);const g=function(){t.ajax({url:I,type:"POST",timeout:9e4,data:d,processData:u,contentType:m,success:function(e,i,r){try{if("string"==typeof e){const t=e.indexOf("{"),o=e.lastIndexOf("}"),n=t>=0&&o>t?e.slice(t,o+1):e;e=JSON.parse(n)}if(C.push(e),!e||!e.success){const t=e&&e.data&&e.data.message?e.data.message:"",i=t?`\n\nDetails: ${t}`:"",r=C.length-1;n._showToast(`${r} booking(s) confirmed, but appointment ${s+1} failed. Please contact us.${i}`,"error");const c=e&&e.data?e.data:{},l=!!function(){const e=["deposit_intent_id","paypal_order_id","square_payment_id"];for(let t=0;t<e.length;t++){const o=d instanceof FormData?d.get(e[t]):d[e[t]];if(o)return String(o)}return""}();return n._showBookingRecovery({reference:a.client_reference||c.reference||`${T}-${s+1}`,failedIndex:s,confirmedCount:r,message:t||o("The appointment was not confirmed."),safeToRetry:!0===c.retry_safe&&!l,uncertain:!0===c.outcome_uncertain||l}),l&&n._showPaidRecoveryNotice(a.client_reference),void h()}n._forgetPaidCheckout(a.client_reference),e.data&&e.data.design_attachment_id&&(x=parseInt(e.data.design_attachment_id,10)||x,n.designUploadAttachmentId=x),e.data&&e.data.design_upload_token&&(B=String(e.data.design_upload_token||""),n.designUploadToken=B),F(s+1)}catch(a){if(console.error(o("Yatoon booking submit failed after server response:"),a),n.bookingConfirmed||t(".sbs-step-success:visible").length>0)return;const i=r&&r.responseText?String(r.responseText):"string"==typeof e?e:"";if(i&&-1!==i.indexOf('"success":true'))return C.push({success:!0,data:{}}),void F(s+1);n._showToast(o("We could not read the booking confirmation response. Please check the booking list before trying again."),"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:C.filter(e=>e&&e.success).length,message:o("We could not verify the server response."),uncertain:!0}),h()}},error:function(e,t){C.push({success:!1});const a=C.length-1,i="timeout"===t?"the request timed out":"a network error";n._showToast(`${a} booking(s) confirmed, but appointment ${s+1} failed due to ${i}. Please contact us.`,"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:a,message:o("timeout"===t?"The request timed out.":"The network connection was interrupted."),uncertain:!0}),h()}})},f=function(){const e=(e,t)=>{n._rememberCheckoutPayment&&n._rememberCheckoutPayment(a.client_reference,e,t),n._giftSubmissionQuote&&(n._giftSubmissionQuote.payments=n._giftSubmissionQuote.payments||{},n._giftSubmissionQuote.payments[s]={field:e,id:t},n._saveSession())},t=n._checkoutSavedPayment||n._giftSubmissionQuote?.payments?.[s];n._checkoutSavedPayment=null;const i=t?"":n._unresolvedPaidCheckout(a.client_reference);if(i)return n._showBookingRecovery({reference:i,failedIndex:s,confirmedCount:s,message:o("A payment for an earlier attempt was received, but that appointment is not confirmed yet."),uncertain:!0}),n._showPaidRecoveryNotice(i,!0),void h();t?(d instanceof FormData?d.set(t.field,t.id):d[t.field]=t.id,g()):a.gift_card_code&&0===n._giftSubmissionQuote?.cart[s]?.amount_due?g():n._squarePreparedPaymentId&&n._squarePreparedPaymentReference===a.client_reference?(e("square_payment_id",n._squarePreparedPaymentId),d instanceof FormData?d.set("square_payment_id",n._squarePreparedPaymentId):d.square_payment_id=n._squarePreparedPaymentId,n._squarePreparedPaymentId="",n._squarePreparedPaymentReference="",g()):n._paymentProviderEnabled("square")?n._ensurePaymentSdk("square").then(function(){return n._createSquarePaymentForPayload(a)}).then(function(t){e("square_payment_id",t),d instanceof FormData?d.set("square_payment_id",t):d.square_payment_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Square payment could not be started."),"error"),h()}):n._paymentProviderEnabled("paypal")?n._ensurePaymentSdk("paypal").then(function(){return n._createPayPalPaymentForPayload(a)}).then(function(t){e("paypal_order_id",t),d instanceof FormData?d.set("paypal_order_id",t):d.paypal_order_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("PayPal Checkout could not be started."),"error"),h()}):n._paymentProviderEnabled("stripe")?n._ensurePaymentSdk("stripe").then(function(){return n._createStripePaymentForPayload(a)}).then(function(t){e("deposit_intent_id",t),d instanceof FormData?d.set("deposit_intent_id",t):d.deposit_intent_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Stripe payment could not be started."),"error"),h()}):g()};n._paymentsConfigured()?n._loadFeatureChunk("recovery").then(()=>n._prepareCheckout(a)).then(e=>{d instanceof FormData?d.set("checkout_token",a.checkout_token):d.checkout_token=a.checkout_token,n._checkoutSavedPayment=e.payment||null,f()}).catch(e=>{const t=!!(e&&e.data&&e.data.preflight);if(t&&0===s)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:a.client_reference,failedIndex:s,confirmedCount:s,message:e.message,uncertain:!t}),h()}):f()};try{if(y.gift_card_code||n._giftCode){(n._giftSubmissionQuote?Promise.resolve(n._giftSubmissionQuote):n._fetchGiftQuote(parseFloat(y.tip_amount)||0)).then(e=>{n._giftSubmissionQuote=e,n._saveSession(),n._renderGiftQuote(e),E()}).catch(e=>{n._showToast(e.message,"error"),n._isSubmitting=!1,h()})}else E()}catch(e){console.error(o("Yatoon booking submit error:"),e),this._showToast(o("Something went wrong before the booking could be sent. Please refresh and try again."),"error"),h()}}}Object.getOwnPropertyNames(_.prototype).filter(e=>"constructor"!==e).forEach(t=>e.YatoonSquareBookingForm.prototype[t]=_.prototype[t])}(window,window.jQuery);
     1!function(e,t){"use strict";const o=e.wp?.i18n?.__?t=>e.wp.i18n.__(t,"yatoon-booking-system"):e=>e,{ysSiteNow:n,ysSiteOffsetMin:s,ysFormatMoney:a,ysPriceVaries:i,ysMoneyFrom:r,ysCalcDeposit:c,validateBookingPhone:d,validateUSPhone:l,showFieldError:u,showPhoneError:m,validateCustomerName:g,validateCustomerEmail:f,ysFocusTrap:p}=e.YatoonBookingHelpers;class _{_paidCheckoutKey(e){return"yatoon-checkout:"+location.pathname+":"+e}_forgetPaidCheckout(e){if(e)try{sessionStorage.removeItem(this._paidCheckoutKey(e))}catch(e){}}_unresolvedPaidCheckout(e){const t=this._paidCheckoutKey("");try{for(let o=0;o<sessionStorage.length;o++){const n=sessionStorage.key(o);if(!n||0!==n.indexOf(t))continue;const s=n.slice(t.length);if(!s||s===e)continue;const a=JSON.parse(sessionStorage.getItem(n)||"null");if(a&&a.payment&&a.payment.id&&!a.resolved)return s}}catch(e){}return""}_showPaidRecoveryNotice(e,n){const s=this,a=t("#sbs-booking-recovery");a.length&&(a.addClass("sbs-booking-recovery-paid"),a.find("h3").first().text(o("Payment received, appointment not confirmed yet")),a.find(".sbs-booking-recovery-guidance").text(o('Your payment went through, but this appointment could not be confirmed. Please do not pay again. Use "Check payment and appointment status" to finish it, or contact the salon with the reference below so they can confirm another time or refund you.')),n&&!a.find(".sbs-paid-recovery-resolved").length&&t("<button/>",{type:"button",class:"sbs-paid-recovery-resolved",text:o("The salon has resolved this payment")}).appendTo(a.find(".sbs-booking-recovery-actions")).on("click",function(){try{const t=s._paidCheckoutKey(e),o=JSON.parse(sessionStorage.getItem(t)||"null");o&&(o.resolved=!0,sessionStorage.setItem(t,JSON.stringify(o)))}catch(e){}s._clearBookingRecovery(),s._saveSession()}))}submitBooking(){const n=this;if(this._handleBookingRecoveryAttempt())return;if(this._paymentsConfigured()&&"function"!=typeof this._ensurePaymentSdk){if(this._submissionChunkPromise)return;const e=t(".sbs-btn-submit").prop("disabled",!0).text(o("Loading secure payment..."));return void(this._submissionChunkPromise=this._loadFeatureChunk("payments").then(function(){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n.submitBooking()}).catch(function(t){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n._showToast(t.message||o("The secure payment component could not be loaded."),"error")}))}if(this._isSubmitting)return;if(this.designUploadInProgress)return void this._showToast(o("Please wait for the reference photo to finish uploading."),"info");if(!this._paymentsConfigured()&&(!this._turnstileTokenFetched||Date.now()>(this._turnstileTokenExpires||0))&&"function"==typeof e.yatoonGetTurnstileToken)return this._turnstileTokenFetched=!0,void e.yatoonGetTurnstileToken().then(function(t){e.yatoonTurnstileToken=t,n._turnstileTokenFetched=!0,n._turnstileTokenExpires=Date.now()+9e4,n.submitBooking()}).catch(function(){n._turnstileTokenFetched=!1,n._turnstileTokenExpires=0,n._showToast(o("Verification could not finish. Please try again."),"error")});const s=document.getElementById("sbs-otp-section"),a="undefined"!=typeof yatoonData&&"1"===yatoonData.verifyEmailOtp,i="undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp;if(s&&(a||i)){const e=(document.getElementById("sbs-customer-email")||{}).value||"",t=(document.getElementById("sbs-customer-phone")||{}).value||"",a=e.trim()+"|"+t.trim();s.dataset.sentContact&&s.dataset.sentContact!==a&&(s.dataset.sent="",s.dataset.sentContact="");const r="1"===s.dataset.verified&&s.dataset.verifiedContact===a;"1"!==s.dataset.verified||r||(s.dataset.verified="",s.dataset.verifiedKey="");const c=function(e){const t=String(e||"").replace(/\D/g,"");return 11===t.length&&"1"===t.charAt(0)?t.slice(1):t},l=i?""!==c(s.dataset.verifiedPhone)&&c(t)===c(s.dataset.verifiedPhone):!!s.dataset.verifiedEmail&&e.trim().toLowerCase()===s.dataset.verifiedEmail.toLowerCase(),g=s.dataset.deviceVerified===a;if(!r&&!l&&!g){const r=f(e);if(r){u("sbs-customer-email",r);const e=document.getElementById("sbs-customer-email");return void(e&&e.scrollIntoView({behavior:"smooth",block:"center"}))}if(i){const e=d(t);if(e){m(e);const t=document.getElementById("sbs-phone-display")||document.getElementById("sbs-customer-phone");return void(t&&t.scrollIntoView({behavior:"smooth",block:"center"}))}}if(s.dataset.deviceChecked!==a)return void jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_otp_device_check",nonce:yatoonData.nonce,email:e,phone:t},function(e){s.dataset.deviceChecked=a,e&&e.success&&e.data&&e.data.verified&&(s.dataset.deviceVerified=a),n.submitBooking()}).fail(function(){s.dataset.deviceChecked=a,n.submitBooking()});const c=(document.getElementById("sbs-customer-name")||{}).value||"",l=i?"sms":"email";s.style.display="block";const g=document.getElementById("sbs-otp-remember");if(g&&"1"===g.dataset.regionAuto&&!g.dataset.regionChecked){g.dataset.regionChecked="1";let e="";try{e=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(t){e=""}const t=/^Europe\/(Moscow|Minsk|Kaliningrad|Samara|Volgograd|Kirov|Astrakhan|Saratov|Ulyanovsk|Simferopol|Istanbul|Kiev|Kyiv|Uzhgorod|Zaporozhye|Chisinau|Tirane|Skopje|Sarajevo|Belgrade|Podgorica)$/;(/^Europe\//.test(e)&&!t.test(e)||/^Atlantic\/(Reykjavik|Madeira|Azores|Canary)$/.test(e))&&(g.checked=!1)}if(document.getElementById("sbs-otp-prompt").textContent="sms"===l?o("We are sending a verification code to your phone. Please enter it below."):o("We are sending a verification code to")+" "+e+". "+o("Please enter it below."),!s.dataset.sent){s.dataset.sent="1",s.dataset.sentContact=a;const n=document.getElementById("sbs-otp-msg"),i=document.getElementById("sbs-otp-resend-btn");n&&(n.textContent=o("Sending verification code..."),n.style.color=""),i&&(i.disabled=!0),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_send_otp",nonce:yatoonData.nonce,channel:l,email:e,phone:t,name:c},function(e){const t=!(!e||!e.success);n&&(n.textContent=t?e.data&&e.data.message?e.data.message:o("Verification code sent."):e&&e.data&&e.data.message?e.data.message:o("Failed to send code.")),i&&(i.style.display=""),i&&(i.disabled=!1),t||(s.dataset.sent="",s.dataset.sentContact="",n&&(n.style.color="#dc2626"))}).fail(function(){s.dataset.sent="",s.dataset.sentContact="",n&&(n.textContent=o("Could not send the verification code. Please try again."),n.style.color="#dc2626"),i&&(i.style.display="",i.disabled=!1)})}const p=document.getElementById("sbs-otp-verify-btn");p&&!p.dataset.bound&&(p.dataset.bound="1",p.addEventListener("click",function(){const e=(document.getElementById("sbs-otp-input")||{}).value||"",t=document.getElementById("sbs-otp-msg"),a=(document.getElementById("sbs-customer-email")||{}).value||"",i=(document.getElementById("sbs-customer-phone")||{}).value||"",r=a.trim()+"|"+i.trim();e.trim()?(p.disabled=!0,p.dataset.pending="1",t&&(t.textContent=o("Verifying..."),t.style.color=""),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_verify_otp",nonce:yatoonData.nonce,channel:l,email:a,phone:i,code:e,remember:document.getElementById("sbs-otp-remember")&&document.getElementById("sbs-otp-remember").checked?1:0},function(e){e.success?(s.dataset.verified="1",s.dataset.verifiedKey=e.data&&e.data.verified_key?e.data.verified_key:"",n._giftSubmissionQuote=null,n._dynamicQuoteKey="",s.dataset.verifiedContact=r,s.style.display="none",n.submitBooking()):(t&&(t.textContent=e.data&&e.data.message?e.data.message:o("Incorrect code.")),t&&(t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending="")}).fail(function(){t&&(t.textContent=o("Could not verify the code. Please try again."),t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending=""})):t&&(t.textContent=o("Enter the verification code first."),t.style.color="#dc2626")}));const _=document.getElementById("sbs-otp-resend-btn");return void(_&&!_.dataset.bound&&(_.dataset.bound="1",_.addEventListener("click",function(){if(_.disabled)return;s.dataset.sent="",s.dataset.sentContact="",s.dataset.verified="",s.dataset.verifiedKey="",s.dataset.verifiedContact="";const e=document.getElementById("sbs-otp-msg");e&&(e.textContent=""),n.submitBooking()})))}}if(this._isSubmitting=!0,this._clearBookingRecovery(),0===this.bookingCart.length&&this.selectedDateTime&&(this.pendingServices.length>0||this.selectedService)&&this.addToCart(),0===this.bookingCart.length)return this._isSubmitting=!1,void this._showToast(o("Please add at least one service to your booking."),"error");const r=t(".sbs-btn-submit"),c=t("#sbs-cart-next-btn"),l=t("#sbs-sidebar-next-btn"),g=r.text(),p=c.text(),_=l.text(),h=()=>{this._isSubmitting=!1,this._turnstileTokenFetched=!1,r.prop("disabled",!1).text(g),c.prop("disabled",!1).removeClass("sbs-submitting").text(p||o("Confirm Booking")),l.prop("disabled",!1).removeClass("sbs-submitting").text(_||o("Confirm Booking")),this._applyConfirmBtn(),this._updateMobileCartBar()};r.prop("disabled",!0).text(o("Confirming your booking...")),c.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),l.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),this._updateLegalBoxes();const y={customer_name:t("#sbs-customer-name").val(),customer_email:t("#sbs-customer-email").val(),customer_phone:t("#sbs-customer-phone").val(),client_timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"",notes:t("#sbs-customer-notes").val(),coupon_code:t("#sbs-coupon-code").length?t("#sbs-coupon-code").val():"",marketing_email_optin:t("#sbs-marketing-email-optin").is(":checked")?1:0,marketing_sms_optin:t("#sbs-marketing-sms-optin").is(":checked")?1:0,consent_accepted:t("#sbs-consent-accepted").length&&t("#sbs-consent-accepted").is(":checked")?1:0,consent_signature:t("#sbs-consent-signature").length?t("#sbs-consent-signature").val():"",policy_acknowledged:t("#sbs-policy-acknowledged").length&&t("#sbs-policy-acknowledged").is(":checked")?1:0,tip_amount:parseFloat(t("#sbs-tip-amount").val())||0,gift_card_code:t("#sbs-gift-card-code-confirmed").val()||"",custom_fields:function(){const e={};return t('[name^="cf["]').each(function(){const o=t(this).attr("name").replace(/^cf\[|\]$/g,"");t(this).is(":checkbox")||t(this).is(":radio")?t(this).is(":checked")&&(e[o]=t(this).val()):e[o]=t(this).val()}),JSON.stringify(e)}()},b=this._collectGuestContacts(),k=f(y.customer_email);if(k)return h(),u("sbs-customer-email",k),void(document.getElementById("sbs-customer-email")&&document.getElementById("sbs-customer-email").scrollIntoView({behavior:"smooth",block:"center"}));"undefined"!=typeof yatoonData&&yatoonData.phoneMode&&yatoonData.phoneMode;if(y.customer_phone){const e=d(y.customer_phone);if(e)return h(),m(e),void(document.getElementById("sbs-customer-phone")&&document.getElementById("sbs-customer-phone").scrollIntoView({behavior:"smooth",block:"center"}))}if(this._needsPolicyAck()&&!y.policy_acknowledged)return h(),this._showToast(o("Please review and acknowledge the cancellation / no-show policy before booking."),"error"),void this._focusBookingValidation(t("#sbs-policy-acknowledged"),t(".sbs-policy-box:visible").first());if(this._needsConsent()&&(!y.consent_accepted||!y.consent_signature.trim())){h(),this._showToast(o("Please read and sign the consent form before booking."),"error");const e=y.consent_accepted?t("#sbs-consent-signature"):t("#sbs-consent-accepted");return void this._focusBookingValidation(e,t(".sbs-consent-box:visible").first())}const v=this.bookingCart.slice(),C=[],w=JSON.stringify(v.map(e=>({start:e.dateTime&&e.dateTime.start_at,guest:e.guestLabel||"Me",services:(e.services||[{service:e.service,options:e.options,staff:e.staff}]).map(t=>({service:t.service&&t.service.id,options:(t.options||[]).map(e=>e.id),staff:t.staff&&t.staff.id||0,guest:t.guestLabel||e.guestLabel||"Me"}))}))),T=this._newBookingReference(w),S="undefined"!=typeof yatoonData&&(yatoonData.ajax_url||yatoonData.ajaxUrl)?yatoonData.ajax_url||yatoonData.ajaxUrl:"";if(!S)return h(),void this._showToast(o("Booking could not start because the WordPress AJAX URL is missing. Please refresh and try again."),"error");if(!v.length)return h(),void this._showToast(o("Please choose a service and time before confirming."),"error");const I=v.some(e=>e&&e.services&&e.services.length>1),P=v.length>1||I?"yatoon-"+T:"";let x=this.designUploadAttachmentId||0,B=this.designUploadToken||"";const D=o=>{const s=v[o],a=s.services||[{service:s.service,options:s.options,staff:s.staff}],i=s.dateTime&&s.dateTime.staff_map?s.dateTime.staff_map:null,r="string"==typeof i?function(){try{return JSON.parse(i)}catch(e){return null}}():i,c={},d={};a.forEach(function(e,t){const o=void 0!==e._originalIndex?e._originalIndex:t;s.dateTime.local_staff_map&&Number(s.dateTime.local_staff_map[o])>0&&(d[t]=Number(s.dateTime.local_staff_map[o])),r&&void 0!==r[o]&&(c[t]=r[o])});const l=Object.keys(c).length?JSON.stringify(c):i?"string"==typeof i?i:JSON.stringify(i):"",u=s.guestLabel||a[0]&&a[0].guestLabel||"Me",m=b[u]||{};let g=y.notes||"";if("Me"!==u){const e=[`Guest: ${u}`];m.name&&e.push(`Name: ${m.name}`),m.phone&&e.push(`Phone: ${m.phone}`),g=`${g}${g?"\n\n":""}${e.join(" - ")}`}const f={},p=new Date(s.dateTime.start_at).getTime(),_=a.map(e=>{const t=e.guestLabel||s.guestLabel||"Me",o=f[t]||0,a=Number.isFinite(p)?new Date(p+6e4*o).toISOString():s.dateTime.start_at;return f[t]=o+n._serviceDuration(e),{service_id:e.service.id,staff_id:e.staff?e.staff.id:0,gender_preference:n._genderPreferenceFor(e),option_ids:e.options.map(e=>e.id),guest_label:t,start_at:a,design_attachment_id:parseInt(e.designAttachmentId||0,10)||0,design_upload_token:String(e.designUploadToken||"")}});return{action:"yatoon_create_booking",nonce:yatoonData.nonce,client_reference:`${T}-${o+1}`,start_at:s.dateTime.start_at,local_staff_map_json:JSON.stringify(d),staff_map_json:l,services_json:JSON.stringify(_),service_id:a[0].service.id,staff_id:a[0].staff?a[0].staff.id:0,gender_preference:n._genderPreferenceFor(a[0]),selected_options:a[0].options.map(e=>e.id),notes:g,guest_label:u,guest_name:m.name||"",guest_phone:m.phone||"",booking_group_id:P,coupon_code:y.coupon_code,tip_amount:0===o?y.tip_amount:0,gift_card_code:n._giftSubmissionQuote?.cart[o]?.gift_card_amount>0?y.gift_card_code||n._giftCode:"",gift_card_amount:n._giftSubmissionQuote?.cart[o]?.gift_card_amount||0,custom_fields:y.custom_fields,customer_name:y.customer_name,customer_email:y.customer_email,customer_phone:y.customer_phone,otp_channel:"undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp?"sms":"email",otp_verified_key:document.getElementById("sbs-otp-section")&&document.getElementById("sbs-otp-section").dataset.verifiedKey?document.getElementById("sbs-otp-section").dataset.verifiedKey:"",marketing_email_optin:y.marketing_email_optin,marketing_sms_optin:y.marketing_sms_optin,consent_accepted:y.consent_accepted,consent_signature:y.consent_signature,policy_acknowledged:y.policy_acknowledged,client_timezone:y.client_timezone,design_attachment_id:x||"",design_upload_token:B||"",website:t("#sbs-website").val()||"",form_rendered_at:t("#sbs-form-rendered-at").val()||"",cf_turnstile_response:void 0!==e.yatoonTurnstileToken?e.yatoonTurnstileToken:"",yatoon_test_booking:t(".sbs-booking-container").hasClass("yatoon-test-mode")?"1":""}},E=()=>{v.length<2||!n._paymentsConfigured()?F(0):n._loadFeatureChunk("recovery").then(async()=>{for(let e=0;e<v.length;e++)try{await n._prepareCheckout(D(e))}catch(t){throw t.cartIndex=e,t}}).then(()=>F(0)).catch(e=>{const t=e&&"number"==typeof e.cartIndex?e.cartIndex:0;if(e&&e.data&&e.data.preflight)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:`${T}-${t+1}`,failedIndex:t,confirmedCount:0,message:e&&e.message||o("This appointment is no longer available. Please choose another time."),uncertain:!1}),h()})},F=s=>{if(s>=v.length){if(C.every(e=>e&&e.success)){const s=C.find(e=>e&&e.success&&e.data&&e.data.woocommerce_checkout_url);if(s&&s.data.woocommerce_checkout_url)return this.bookingConfirmed=!0,this._clearSession(),void e.location.assign(s.data.woocommerce_checkout_url);this.bookingConfirmed=!0,t(".sbs-form-step").removeClass("active"),t(".sbs-step-success").show(),this._updateStepAccordion(),this._markBookingFlowComplete(),t("#sbs-success-count").text(v.length);const a=1!==v.length;t("#sbs-success-booking-word").text(o(a?"Bookings":"Booking"));const i=this._confirmedBookingItems(v,C);this._renderSuccessBookingSummary(i);const r=C.filter(e=>e&&e.success&&e.data&&e.data.manage_url&&String(e.data.manage_url).length),d=r.length&&r[0].data?r[0].data.manage_url:"",u=C.find(e=>e&&e.success&&e.data&&e.data.calendar_url&&String(e.data.calendar_url).length),m=u&&u.data?u.data.calendar_url:"",g=yatoonData.portalUrl||"",f=yatoonData.portalLabel||o("My Appointments"),p=t("#sbs-success-actions").empty();if(t("<div/>",{class:"sbs-success-action-note",text:yatoonData.successNote||o("Your appointment is booked. Save this page or use the manage link below if you need to change services, reschedule, or cancel.")}).appendTo(p),d&&1===r.length&&t("<a/>",{class:"sbs-success-action-link primary",href:d,target:"_blank",rel:"noopener",text:"undefined"!=typeof yatoonData&&yatoonData.isPro?o("Change services, reschedule, or cancel"):o("Reschedule or cancel")}).appendTo(p),r.length>1){const e=t("<div/>",{class:"sbs-success-manage-list"}).appendTo(p),n=v.some(e=>e.guestLabel&&"Me"!==e.guestLabel);r.forEach((s,a)=>{const i=v[a],r=i&&i.guestLabel&&"Me"!==i.guestLabel?i.guestLabel:null;let c;if(r)c=o("Change or cancel for")+" "+r;else if(n)c=o("Change or cancel my appointment");else{const e=s.data&&s.data.service_name?s.data.service_name:o("Appointment")+" "+(a+1);c=o("Change or cancel:")+" "+e}t("<a/>",{class:"sbs-success-action-link compact",href:s.data.manage_url,target:"_blank",rel:"noopener",text:c}).appendTo(e)})}if(m&&!t("#sbs-success-cal-row").length&&t("<a/>",{class:"sbs-success-action-link",href:m,text:o("Add to calendar")}).appendTo(p),g&&t("<a/>",{class:"sbs-success-action-link",href:g,text:f}).appendTo(p),t("<button/>",{type:"button",class:"sbs-success-action-link sbs-success-book-again"+(d?"":" primary"),text:o("Book again")}).appendTo(p),p.show(),m){t("#sbs-cal-ics").attr("href",m);const e=C.find(e=>e&&e.success&&e.data&&(e.data.start_at||e.data.start_time)),n=i[0]||null,s=n&&n.dateTime?n.dateTime.start_at:"",a=n?this._getItemVisitDuration(n):0,r=s?new Date(s):null,c=r&&!isNaN(r.getTime())&&a?new Date(r.getTime()+6e4*a).toISOString():"",d=e&&e.data&&(e.data.start_at||e.data.start_time)||s,l=e&&e.data&&(e.data.end_at||e.data.end_time)||c;if(d&&l){const e=function(e){const t=new Date(e);return isNaN(t.getTime())?"":t.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}Z$/,"Z")},s=(n?n.services||[{service:n.service}]:[]).map(e=>e.service&&e.service.name).filter(Boolean).join(", "),a=encodeURIComponent((yatoonData.businessName||o("Appointment"))+(s?" - "+s:"")),i=encodeURIComponent(e(d)),r=encodeURIComponent(e(l)),c=encodeURIComponent(yatoonData&&yatoonData.siteTimezone?yatoonData.siteTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone);i&&r?t("#sbs-cal-google").attr("href","https://calendar.google.com/calendar/render?action=TEMPLATE&text="+a+"&dates="+i+"/"+r+"&ctz="+c):t("#sbs-cal-google").attr("href",m)}else t("#sbs-cal-google").attr("href",m);t("#sbs-success-cal-row").show()}yatoonData.rebookingNudgeText&&(t("#sbs-rebooking-nudge .sbs-rebooking-text").text(yatoonData.rebookingNudgeText),t("#sbs-rebooking-nudge .sbs-rebooking-btn").text(yatoonData.rebookingNudgeBtn||o("Book next visit")),setTimeout(function(){t("#sbs-rebooking-nudge").fadeIn(400)},1200));const _=C.find(e=>e&&e.success&&e.data&&e.data.booking_id);if(_&&_.data.manage_token&&t("#sbs-recurring-setup").length){const e=_.data.booking_id,n=_.data.manage_token;let s=2;setTimeout(function(){t("#sbs-recurring-setup").fadeIn(400)},2e3),t(document).on("click",".sbs-freq-btn",function(){t(".sbs-freq-btn").removeClass("active"),t(this).addClass("active"),s=parseInt(t(this).data("weeks"))}),t(document).on("click","#sbs-recurring-confirm",function(){const a=t(this).prop("disabled",!0).text(o("Setting up...")),i=parseInt(t("#sbs-recurring-count").val())||6;t.post(ajaxurl,{action:"yatoon_setup_recurring",nonce:yatoonData.nonce,yatoon_location_id:parseInt(yatoonData.locationId||0,10),booking_id:e,manage_token:n,frequency_weeks:s,total_count:i},function(e){const n=t("#sbs-recurring-result");e.success?(a.text(o("Done")),n.removeClass("error").text(e.data.message).show()):(a.prop("disabled",!1).text(o("Try again")),n.addClass("error").text(e.data&&e.data.message||o("Could not set up recurring.")).show())}).fail(function(){a.prop("disabled",!1).text(o("Try again")),t("#sbs-recurring-result").addClass("error").text(o("Network error. Please try again.")).show()})})}n.bookingCart=[],n.pendingServices=[],n._clearSession(),n._clearBookingRecovery(),n._bookingAttemptReference="",n._bookingAttemptFingerprint="",n._giftCode="",n._giftSubmissionQuote=null,t("#sbs-gift-card-code-confirmed").val(""),n._hideMobileCartBar(),n._closeDrawer(),n._isSubmitting=!1,h(),c.hide(),l.hide()}else{const e=C.filter(e=>!e||!e.success),t=e.map(e=>e&&e.data&&e.data.message?e.data.message:"").filter(Boolean).join(" "),s=t?`\n\nDetails: ${t}`:"",a=C.length-e.length;n._showToast(`${a} booking(s) confirmed, but ${e.length} failed. Please contact us.${s}`,"error");const i=e[0]&&e[0].data?e[0].data:{};n._showBookingRecovery({reference:i.reference||T,failedIndex:a,confirmedCount:a,message:t||o("One or more appointments were not confirmed."),safeToRetry:!0===i.retry_safe}),h()}return}const a=D(s),i=document.getElementById("sbs-design-reference"),r=i&&i.files&&i.files[0]?i.files[0]:null;let d=a,u=!0,m="application/x-www-form-urlencoded; charset=UTF-8";r&&!x&&(d=new FormData,Object.keys(a).forEach(function(e){const t=a[e];Array.isArray(t)?t.forEach(function(t){d.append(e+"[]",t)}):d.append(e,t)}),d.append("design_reference",r),u=!1,m=!1);const g=function(){t.ajax({url:S,type:"POST",timeout:9e4,data:d,processData:u,contentType:m,success:function(e,i,r){try{if("string"==typeof e){const t=e.indexOf("{"),o=e.lastIndexOf("}"),n=t>=0&&o>t?e.slice(t,o+1):e;e=JSON.parse(n)}if(C.push(e),!e||!e.success){const t=e&&e.data&&e.data.message?e.data.message:"",i=t?`\n\nDetails: ${t}`:"",r=C.length-1;n._showToast(`${r} booking(s) confirmed, but appointment ${s+1} failed. Please contact us.${i}`,"error");const c=e&&e.data?e.data:{},l=!!function(){const e=["deposit_intent_id","paypal_order_id","square_payment_id"];for(let t=0;t<e.length;t++){const o=d instanceof FormData?d.get(e[t]):d[e[t]];if(o)return String(o)}return""}();return n._showBookingRecovery({reference:a.client_reference||c.reference||`${T}-${s+1}`,failedIndex:s,confirmedCount:r,message:t||o("The appointment was not confirmed."),safeToRetry:!0===c.retry_safe&&!l,uncertain:!0===c.outcome_uncertain||l}),l&&n._showPaidRecoveryNotice(a.client_reference),void h()}n._forgetPaidCheckout(a.client_reference),e.data&&e.data.design_attachment_id&&(x=parseInt(e.data.design_attachment_id,10)||x,n.designUploadAttachmentId=x),e.data&&e.data.design_upload_token&&(B=String(e.data.design_upload_token||""),n.designUploadToken=B),F(s+1)}catch(a){if(console.error(o("Yatoon booking submit failed after server response:"),a),n.bookingConfirmed||t(".sbs-step-success:visible").length>0)return;const i=r&&r.responseText?String(r.responseText):"string"==typeof e?e:"";if(i&&-1!==i.indexOf('"success":true'))return C.push({success:!0,data:{}}),void F(s+1);n._showToast(o("We could not read the booking confirmation response. Please check the booking list before trying again."),"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:C.filter(e=>e&&e.success).length,message:o("We could not verify the server response."),uncertain:!0}),h()}},error:function(e,t){C.push({success:!1});const a=C.length-1,i="timeout"===t?"the request timed out":"a network error";n._showToast(`${a} booking(s) confirmed, but appointment ${s+1} failed due to ${i}. Please contact us.`,"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:a,message:o("timeout"===t?"The request timed out.":"The network connection was interrupted."),uncertain:!0}),h()}})},f=function(){const e=(e,t)=>{n._rememberCheckoutPayment&&n._rememberCheckoutPayment(a.client_reference,e,t),n._giftSubmissionQuote&&(n._giftSubmissionQuote.payments=n._giftSubmissionQuote.payments||{},n._giftSubmissionQuote.payments[s]={field:e,id:t},n._saveSession())},t=n._checkoutSavedPayment||n._giftSubmissionQuote?.payments?.[s];n._checkoutSavedPayment=null;const i=t?"":n._unresolvedPaidCheckout(a.client_reference);if(i)return n._showBookingRecovery({reference:i,failedIndex:s,confirmedCount:s,message:o("A payment for an earlier attempt was received, but that appointment is not confirmed yet."),uncertain:!0}),n._showPaidRecoveryNotice(i,!0),void h();t?(d instanceof FormData?d.set(t.field,t.id):d[t.field]=t.id,g()):a.gift_card_code&&0===n._giftSubmissionQuote?.cart[s]?.amount_due?g():n._squarePreparedPaymentId&&n._squarePreparedPaymentReference===a.client_reference?(e("square_payment_id",n._squarePreparedPaymentId),d instanceof FormData?d.set("square_payment_id",n._squarePreparedPaymentId):d.square_payment_id=n._squarePreparedPaymentId,n._squarePreparedPaymentId="",n._squarePreparedPaymentReference="",g()):n._paymentProviderEnabled("square")?n._ensurePaymentSdk("square").then(function(){return n._createSquarePaymentForPayload(a)}).then(function(t){e("square_payment_id",t),d instanceof FormData?d.set("square_payment_id",t):d.square_payment_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Square payment could not be started."),"error"),h()}):n._paymentProviderEnabled("paypal")?n._ensurePaymentSdk("paypal").then(function(){return n._createPayPalPaymentForPayload(a)}).then(function(t){e("paypal_order_id",t),d instanceof FormData?d.set("paypal_order_id",t):d.paypal_order_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("PayPal Checkout could not be started."),"error"),h()}):n._paymentProviderEnabled("stripe")?n._ensurePaymentSdk("stripe").then(function(){return n._createStripePaymentForPayload(a)}).then(function(t){e("deposit_intent_id",t),d instanceof FormData?d.set("deposit_intent_id",t):d.deposit_intent_id=t,g()}).catch(function(e){if(0===s&&e&&e.data&&e.data.preflight)return n._showToast(e.message||o("Stripe payment could not be started."),"error"),void h();n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Stripe payment could not be started."),"error"),h()}):g()};n._paymentsConfigured()?n._loadFeatureChunk("recovery").then(()=>n._prepareCheckout(a)).then(e=>{d instanceof FormData?d.set("checkout_token",a.checkout_token):d.checkout_token=a.checkout_token,n._checkoutSavedPayment=e.payment||null,f()}).catch(e=>{const t=!!(e&&e.data&&e.data.preflight);if(t&&0===s)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:a.client_reference,failedIndex:s,confirmedCount:s,message:e.message,uncertain:!t}),h()}):f()};try{if(y.gift_card_code||n._giftCode){(n._giftSubmissionQuote?Promise.resolve(n._giftSubmissionQuote):n._fetchGiftQuote(parseFloat(y.tip_amount)||0)).then(e=>{n._giftSubmissionQuote=e,n._saveSession(),n._renderGiftQuote(e),E()}).catch(e=>{n._showToast(e.message,"error"),n._isSubmitting=!1,h()})}else E()}catch(e){console.error(o("Yatoon booking submit error:"),e),this._showToast(o("Something went wrong before the booking could be sent. Please refresh and try again."),"error"),h()}}}Object.getOwnPropertyNames(_.prototype).filter(e=>"constructor"!==e).forEach(t=>e.YatoonSquareBookingForm.prototype[t]=_.prototype[t])}(window,window.jQuery);
  • yatoon-booking-system/tags/4.10.2/assets/js/frontend-payments.js

    r3780403 r3780965  
    8787            return new Promise(function(resolve, reject) {
    8888                if (!self._stripePaymentsReady()) {
    89                     reject(new Error(__('Stripe payment form is not ready. Please refresh and try again.')));
     89                    const notReady = new Error(__('Stripe payment form is not ready. Please refresh and try again.'));
     90                    notReady.data = { preflight: true };
     91                    reject(notReady);
    9092                    return;
    9193                }
    … …  
    126128                        paymentElement.mount('#sbs-stripe-payment-element');
    127129                        const $status = $('#sbs-stripe-payment-status');
     130                        const payLabel = yatoonData.nativePaymentMode === 'full' ? __('Pay total') : __('Pay deposit');
     131                        // 4.10.2: the box is reused for the next appointment in a cart.
     132                        // Start each payment with a fresh, clickable button and no old status.
     133                        $status.text('');
     134                        $('#sbs-stripe-pay-button').prop('disabled', false).text(payLabel);
    128135                        $('#sbs-stripe-pay-button').off('.yatoonStripe').on('click.yatoonStripe', function() {
    129136                            const $button = $(this).prop('disabled', true).text(__('Processing...'));
    130137                            $status.text(__('Confirming your payment securely...'));
     138                            // 4.10.2: a Stripe error (no payment method chosen, card declined,
     139                            // failed verification) means nothing was charged. Keep the customer
     140                            // in this payment box so they can fix it and pay again, instead of
     141                            // ending the checkout and sending them to the booking-status panel.
     142                            const retryHere = function(message) {
     143                                $button.prop('disabled', false).text(payLabel);
     144                                $status.text(message);
     145                                self._showToast(message, 'error');
     146                            };
    131147                            stripe.confirmPayment({
    132148                                elements: elements,
    133149                                redirect: 'if_required'
    134150                            }).then(function(result) {
    135                                 if (result.error) throw new Error(result.error.message || __('Stripe could not complete the payment.'));
     151                                let intent = result.paymentIntent || null;
     152                                // A repeat click after a lost response: Stripe reports the intent
     153                                // is already paid. Treat that as success, not as an error.
     154                                if (result.error && result.error.payment_intent) {
     155                                    const already = result.error.payment_intent.status;
     156                                    if (already === 'succeeded' || already === 'processing') intent = result.error.payment_intent;
     157                                }
     158                                if (result.error && !intent) {
     159                                    retryHere(result.error.message || __('Stripe could not complete the payment.'));
     160                                    return;
     161                                }
    136162                                // 4.10.2: bank debits stay "processing" for a few days. The
    137163                                // appointment is booked now and marked paid when the bank clears.
    138                                 const intentStatus = result.paymentIntent ? result.paymentIntent.status : '';
     164                                const intentStatus = intent ? intent.status : '';
    139165                                if (intentStatus !== 'succeeded' && intentStatus !== 'processing') {
    140                                     throw new Error(__('Stripe payment is not complete.'));
     166                                    retryHere(__('Stripe payment is not complete. Please check your payment details and try again.'));
     167                                    return;
    141168                                }
    142169                                $status.text(intentStatus === 'processing'
    … …  
    144171                                    : __('Payment approved. Confirming your appointment...'));
    145172                                $box.hide();
    146                                 resolve(result.paymentIntent.id);
     173                                resolve(intent.id);
    147174                            }).catch(function(error) {
    148                                 $button.prop('disabled', false).text(yatoonData.nativePaymentMode === 'full' ? __('Pay total') : __('Pay deposit'));
    149                                 $status.text(error.message || __('Stripe payment failed.'));
    150                                 self._showToast(error.message || __('Stripe payment failed.'), 'error');
    151                                 reject(error);
     175                                // Stripe.js itself failed (network). The charge state is unknown,
     176                                // so let the customer press Pay again: Stripe answers a repeat
     177                                // confirm of a paid intent with its paid status (handled above).
     178                                retryHere((error && error.message) || __('We could not reach Stripe. Please check your connection and press Pay again.'));
    152179                            });
    153180                        });
    154181                        $box.show();
    155182                        if ($box[0] && $box[0].scrollIntoView) $box[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
    156                     }).catch(reject);
     183                    }).catch(function(error) {
     184                        // The payment form could not be opened, so nothing was charged.
     185                        if (error && typeof error === 'object') error.data = Object.assign({}, error.data, { preflight: true });
     186                        reject(error);
     187                    });
    157188            });
    158189        }
  • yatoon-booking-system/tags/4.10.2/assets/js/frontend-payments.min.js

    r3780403 r3780965  
    1 !function(e,t){"use strict";const a=e.wp&&e.wp.i18n&&"function"==typeof e.wp.i18n.__?function(t){return e.wp.i18n.__(t,"yatoon-booking-system")}:function(e){return e},n=e.YatoonSquareBookingForm;if(n&&t){class o{_squarePaymentsReady(){return!!("undefined"!=typeof yatoonData&&yatoonData.squarePaymentsEnabled&&yatoonData.squareApplicationId&&yatoonData.squareLocationId&&e.Square&&"function"==typeof e.Square.payments)}_paymentProviderEnabled(e){return"undefined"!=typeof yatoonData&&("square"===e?!!yatoonData.squarePaymentsEnabled:"paypal"===e?!!yatoonData.paypalPaymentsEnabled:"stripe"===e&&!!yatoonData.depositEnabled)}_ensurePaymentSdk(e){const t={square:()=>this._squarePaymentsReady(),paypal:()=>this._paypalPaymentsReady(),stripe:()=>this._stripePaymentsReady()}[e],n={square:yatoonData.squareSdkUrl,paypal:yatoonData.paypalSdkUrl,stripe:yatoonData.stripeSdkUrl}[e];return t&&t()?Promise.resolve():n?(this._paymentSdkPromises=this._paymentSdkPromises||{},this._paymentSdkPromises[e]||(this._paymentSdkPromises[e]=new Promise(function(o,r){const s=document.createElement("script");s.src=n,s.async=!0,s.dataset.yatoonPaymentSdk=e,s.onload=function(){t&&t()?o():r(new Error(a("The secure payment service did not initialize.")))},s.onerror=function(){r(new Error(a("The secure payment service could not be loaded. Check your connection and try again.")))},document.head.appendChild(s)}).catch(t=>{throw delete this._paymentSdkPromises[e],t})),this._paymentSdkPromises[e]):Promise.reject(new Error(a("The secure payment service is not configured.")))}_paypalPaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.paypalPaymentsEnabled||!e.paypal||"function"!=typeof e.paypal.Buttons)}_stripePaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.depositEnabled||!yatoonData.stripePublishableKey||"function"!=typeof e.Stripe)}_createStripePaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._stripePaymentsReady())return void s(new Error(a("Stripe payment form is not ready. Please refresh and try again.")));const i=new FormData;Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){i.append(e+"[]",t)}):i.append(e,null==t?"":t)}),i.set("action","yatoon_stripe_create_intent"),i.set("nonce",yatoonData.nonce),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:i,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(n){if(n?.success&&n.data?.already_paid&&n.data.intent_id)return void r(n.data.intent_id);if(!(n&&n.success&&n.data&&n.data.client_secret))throw new Error(n&&n.data&&n.data.message?n.data.message:a("Could not start the Stripe payment."));let i=t("#sbs-stripe-payment-box");if(!i.length){i=t(`\n                                <div id="sbs-stripe-payment-box" style="margin:16px 0;padding:16px;border:1px solid #ddd6fe;border-radius:14px;background:#f5f3ff;">\n                                    <div style="font-size:14px;font-weight:800;color:#2e1065;margin-bottom:6px;">Secure Stripe Payment</div>\n                                    <div id="sbs-stripe-payment-amount" style="font-size:13px;color:#475569;margin-bottom:12px;"></div>\n                                    <div id="sbs-stripe-payment-element"></div>\n                                    <button type="button" id="sbs-stripe-pay-button" style="width:100%;min-height:46px;margin-top:14px;border:0;border-radius:9px;background:#635bff;color:#fff;font-weight:800;cursor:pointer;">${"full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit")}</button>\n                                    <div id="sbs-stripe-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                                </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(i):t(".sbs-step-5").prepend(i)}t("#sbs-stripe-payment-element").empty(),t("#sbs-stripe-payment-amount").text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+(n.data.deposit_display||""));const p=e.Stripe(yatoonData.stripePublishableKey),d=p.elements({clientSecret:n.data.client_secret});d.create("payment").mount("#sbs-stripe-payment-element");const u=t("#sbs-stripe-payment-status");t("#sbs-stripe-pay-button").off(".yatoonStripe").on("click.yatoonStripe",function(){const e=t(this).prop("disabled",!0).text(a("Processing..."));u.text(a("Confirming your payment securely...")),p.confirmPayment({elements:d,redirect:"if_required"}).then(function(e){if(e.error)throw new Error(e.error.message||a("Stripe could not complete the payment."));const t=e.paymentIntent?e.paymentIntent.status:"";if("succeeded"!==t&&"processing"!==t)throw new Error(a("Stripe payment is not complete."));u.text(a("processing"===t?"Bank payment submitted. Confirming your appointment...":"Payment approved. Confirming your appointment...")),i.hide(),r(e.paymentIntent.id)}).catch(function(t){e.prop("disabled",!1).text("full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit")),u.text(t.message||a("Stripe payment failed.")),o._showToast(t.message||a("Stripe payment failed."),"error"),s(t)})}),i.show(),i[0]&&i[0].scrollIntoView&&i[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(s)})}_createPayPalPaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._paypalPaymentsReady())return void s(new Error(a("PayPal Checkout is not ready. Please refresh and try again.")));let i=t("#sbs-paypal-payment-box");if(!i.length){i=t(`\n                        <div id="sbs-paypal-payment-box" style="margin:16px 0;padding:16px;border:1px solid #bfdbfe;border-radius:14px;background:#eff6ff;">\n                            <div style="font-size:14px;font-weight:800;color:#172554;margin-bottom:6px;">Secure PayPal Payment</div>\n                            <div style="font-size:13px;color:#475569;margin-bottom:12px;">${"full"===yatoonData.nativePaymentMode?a("Pay the full appointment total with PayPal to confirm your booking."):a("Choose PayPal below to pay the deposit and confirm your appointment.")}</div>\n                            <div id="sbs-paypal-button-container"></div>\n                            <div id="sbs-paypal-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                        </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(i):t(".sbs-step-5").prepend(i)}const p=t("#sbs-paypal-payment-status"),d=yatoonData.ajax_url||yatoonData.ajaxUrl;function u(e,t){const o=new FormData;return Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){o.append(e+"[]",t)}):o.append(e,null==t?"":t)}),o.set("action",e),o.set("nonce",yatoonData.nonce),Object.keys(t||{}).forEach(function(e){o.set(e,t[e])}),fetch(d,{method:"POST",body:o,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data)throw new Error(e&&e.data&&e.data.message?e.data.message:a("PayPal payment failed."));return e.data})}t("#sbs-paypal-button-container").empty(),e.paypal.Buttons({style:{layout:"vertical",shape:"rect",label:"paypal"},createOrder:function(){return p.text(a("Preparing secure PayPal Checkout...")),u("yatoon_paypal_create_order").then(function(e){return e.amount_display&&p.text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+e.amount_display),e.order_id})},onApprove:function(e){return p.text(a("Confirming your PayPal payment...")),u("yatoon_paypal_capture_order",{order_id:e.orderID}).then(function(t){p.text(a("Payment approved. Confirming your appointment...")),i.hide(),r(t.order_id||e.orderID)}).catch(function(e){p.text(e.message||a("PayPal could not capture the payment.")),o._showToast(e.message||a("PayPal could not capture the payment."),"error"),s(e)})},onCancel:function(){const e=new Error(a("PayPal Checkout was cancelled."));p.text(e.message),s(e)},onError:function(e){p.text(a("PayPal Checkout could not be opened. Please try again.")),o._showToast(e&&e.message?e.message:a("PayPal Checkout failed."),"error"),s(e instanceof Error?e:new Error(a("PayPal Checkout failed.")))}}).render("#sbs-paypal-button-container").then(function(){i.show(),i[0]&&i[0].scrollIntoView&&i[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(s)})}_squareMethods(){const e=yatoonData&&Array.isArray(yatoonData.squarePaymentMethods)?yatoonData.squarePaymentMethods:["card"];return e.length?e:["card"]}_previewSquarePayment(e){const t=new FormData;return Object.keys(e).forEach(function(a){const n=e[a];Array.isArray(n)?n.forEach(function(e){t.append(a+"[]",e)}):t.append(a,null==n?"":n)}),t.set("action","yatoon_square_preview_payment"),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:t,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data){const t=e&&e.data&&e.data.message?e.data.message:a("Could not calculate the Square payment amount.");throw new Error(t)}return e.data})}_buildSquarePaymentRequest(e,t){return e.paymentRequest({countryCode:(yatoonData.squareCountryCode||"US").toUpperCase(),currencyCode:(t.currency||yatoonData.currency||"USD").toUpperCase(),total:{amount:t.amount||"0.00",label:"full"===yatoonData.squarePaymentMode?a("Appointment total"):a("Booking deposit")}})}_ensureSquarePaymentCard(n){const o=this,r=JSON.stringify({services_json:n.services_json||"",service_id:n.service_id||"",selected_options:n.selected_options||[],coupon_code:n.coupon_code||"",customer_email:n.customer_email||"",customer_phone:n.customer_phone||"",client_reference:n.client_reference||"",checkout_token:n.checkout_token||"",start_at:n.start_at||"",location:n.yatoon_location_id||0,tip_amount:n.tip_amount||0,gift_card_code:n.gift_card_code||""});return this._squareCardPromise&&this._squarePaymentPayloadKey===r||(this._squarePaymentPayloadKey=r,this._squareCardPromise=new Promise(function(r,s){if(!o._squarePaymentsReady())return void s(new Error(a("Square payment form is not ready. Please refresh and try again.")));let i=t("#sbs-square-payment-box");if(!i.length){const e="full"===yatoonData.squarePaymentMode?a("Pay securely now to confirm your appointment."):a("Pay your deposit securely now to confirm your appointment.");i=t(`\n                            <div id="sbs-square-payment-box" class="sbs-square-payment-box" style="margin:16px 0;padding:16px;border:1px solid #dbeafe;border-radius:14px;background:#eff6ff;">\n                                <div style="font-size:14px;font-weight:800;color:#1e3a8a;margin-bottom:6px;">Secure Square Payment</div>\n                                <div style="font-size:13px;color:#475569;margin-bottom:12px;">${e}</div>\n                                <div id="sbs-square-payment-amount" style="font-size:13px;font-weight:800;color:#0f172a;margin-bottom:12px;"></div>\n                                <div id="sbs-square-method-tabs" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;"></div>\n                                <button id="sbs-square-apple-pay-button" type="button" class="sbs-square-method-panel" data-method-panel="apple_pay" style="display:none;width:100%;min-height:44px;border-radius:8px;border:0;background:#000;color:#fff;font-weight:800;">Apple Pay</button>\n                                <div id="sbs-square-google-pay" class="sbs-square-method-panel" data-method-panel="google_pay" style="display:none;"></div>\n                                <div id="sbs-square-cash-app-pay" class="sbs-square-method-panel" data-method-panel="cash_app_pay" style="display:none;"></div>\n                                <div id="sbs-square-gift-card-container" class="sbs-square-method-panel" data-method-panel="gift_card" style="display:none;"></div>\n                                <div id="sbs-square-card-container"></div>\n                                <div id="sbs-square-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                            </div>\n                        `);const n=t("#sbs-booking-form");n.length?n.prepend(i):t(".sbs-step-5").prepend(i)}t("#sbs-square-payment-status").text(a("Loading secure Square card form...")),o._previewSquarePayment(n).then(function(s){return t("#sbs-square-payment-amount").text(("full"===s.payment_mode?a("Pay now:"):a("Deposit due now:"))+" "+s.amount_display),Promise.resolve(e.Square.payments(yatoonData.squareApplicationId,yatoonData.squareLocationId)).then(function(i){const p=o._squareMethods(),d={payments:i,preview:s,methods:{},selected:p[0]||"card"},u=o._buildSquarePaymentRequest(i,s),c={card:"Card",apple_pay:a("Apple Pay"),google_pay:a("Google Pay"),cash_app_pay:a("Cash App"),gift_card:a("Gift Card")},y=t("#sbs-square-method-tabs").empty();function l(e){d.selected=e,t(".sbs-square-method-tab").each(function(){const a=t(this).data("method")===e;t(this).css({background:a?"#1d4ed8":"#fff",color:a?"#fff":"#1e3a8a"})}),t(".sbs-square-method-panel").hide(),t("#sbs-square-card-container").toggle("card"===e),t('[data-method-panel="'+e+'"]').show(),t("#sbs-square-payment-status").text(a("apple_pay"===e||"cash_app_pay"===e?"Tap the wallet button above to authorize payment, then booking will continue automatically.":"Payment details are encrypted by Square."))}p.forEach(function(e){t("<button/>",{type:"button",class:"sbs-square-method-tab","data-method":e,text:c[e]||e}).css({border:"1px solid #bfdbfe",background:e===d.selected?"#1d4ed8":"#fff",color:e===d.selected?"#fff":"#1e3a8a",borderRadius:"999px",padding:"8px 12px",fontWeight:"800",cursor:"pointer"}).appendTo(y)}),t(document).off("click.squareMethod").on("click.squareMethod",".sbs-square-method-tab",function(){l(t(this).data("method"))});const m=[];return p.includes("card")&&m.push(i.card().then(function(e){return d.methods.card=e,e.attach("#sbs-square-card-container")})),p.includes("gift_card")&&"function"==typeof i.giftCard&&m.push(i.giftCard().then(function(e){return d.methods.gift_card=e,e.attach("#sbs-square-gift-card-container")}).catch(function(){})),p.includes("apple_pay")&&"function"==typeof i.applePay&&m.push(i.applePay(u).then(function(e){d.methods.apple_pay=e,t("#sbs-square-apple-pay-button").off("click.squareApple").on("click.squareApple",function(t){t.preventDefault(),o._handleSquareWalletButton(n,e,"apple_pay")})}).catch(function(){t('[data-method="apple_pay"], [data-method-panel="apple_pay"]').hide()})),p.includes("google_pay")&&"function"==typeof i.googlePay&&m.push(i.googlePay(u).then(function(e){return d.methods.google_pay=e,e.attach("#sbs-square-google-pay")}).catch(function(){t('[data-method="google_pay"], [data-method-panel="google_pay"]').hide()})),p.includes("cash_app_pay")&&"function"==typeof i.cashAppPay&&m.push(i.cashAppPay(u,{redirectURL:e.location.href,referenceId:"yatoon-"+Date.now()}).then(function(e){return d.methods.cash_app_pay=e,e.addEventListener("ontokenization",function(e){const t=e.detail||{};t.error?o._showToast(a("Cash App Pay could not be authorized."),"error"):t.tokenResult&&"OK"===t.tokenResult.status&&o._finishSquareTokenPayment(n,t.tokenResult.token,"cash_app_pay")}),e.attach("#sbs-square-cash-app-pay")}).catch(function(){t('[data-method="cash_app_pay"], [data-method-panel="cash_app_pay"]').hide()})),Promise.all(m).then(function(){o._squarePaymentState=d,l(d.methods[d.selected]?d.selected:d.methods.card?"card":Object.keys(d.methods)[0]),r(d)})})}).catch(function(e){o._squareCardPromise=null,s(e)})})),this._squareCardPromise}_finishSquareTokenPayment(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Completing")+" "+(o||a("Square"))+" "+a("payment...")),this._sendSquareTokenPayment(e,n).then(function(n){return r._squarePreparedPaymentId=n,r._squarePreparedPaymentReference=e.client_reference,r._rememberCheckoutPayment&&r._rememberCheckoutPayment(e.client_reference,"square_payment_id",n),t("#sbs-square-payment-status").text(a("Payment approved by Square. Confirming your booking...")),r._isSubmitting=!1,r.submitBooking(),n}).catch(function(e){throw r._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e})}_handleSquareWalletButton(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Opening")+" "+a("apple_pay"===o?"Apple Pay":"Square wallet")+"..."),n.tokenize().then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("The wallet payment was not authorized.");throw new Error(e)}return r._finishSquareTokenPayment(e,t.token,o)}).catch(function(e){t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Wallet payment failed.")),r._showToast(e&&e.message?e.message:a("Wallet payment failed. Please try again."),"error")})}_sendSquareTokenPayment(e,t){const n=new FormData;return Object.keys(e).forEach(function(t){const a=e[t];Array.isArray(a)?a.forEach(function(e){n.append(t+"[]",e)}):n.append(t,null==a?"":a)}),n.set("action","yatoon_square_create_payment"),n.set("source_id",t),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:n,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!(e&&e.success&&e.data&&e.data.payment_id)){const t=e&&e.data&&e.data.message?e.data.message:a("Square payment failed. Please try again.");throw new Error(t)}return e.data.payment_id})}_createSquarePaymentForPayload(e){const n=this;return this._squarePaymentsReady()?this._ensureSquarePaymentCard(e).then(function(e){t("#sbs-square-payment-box").show();const n=document.getElementById("sbs-square-payment-box");n&&n.scrollIntoView({behavior:"smooth",block:"center"});const o=e.selected||"card",r=e.methods[o];if(!r)throw new Error(a("This Square payment method is not available on this device. Please choose another method."));return"cash_app_pay"===o?(t("#sbs-square-payment-status").text(a("Tap the Cash App Pay button above to authorize payment.")),new Promise(function(){})):(t("#sbs-square-payment-status").text(a("Securing your payment with Square...")),r.tokenize())}).then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("Please check your card details and try again.");throw new Error(e)}return n._sendSquareTokenPayment(e,t.token)}).then(function(e){return t("#sbs-square-payment-status").text(a("Payment approved by Square.")),e}).catch(function(e){throw t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Square payment failed.")),n._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e}):Promise.reject(new Error(a("Square payment is enabled but the secure payment form is unavailable.")))}}Object.getOwnPropertyNames(o.prototype).forEach(function(e){"constructor"!==e&&Object.defineProperty(n.prototype,e,Object.getOwnPropertyDescriptor(o.prototype,e))})}}(window,window.jQuery);
     1!function(e,t){"use strict";const a=e.wp&&e.wp.i18n&&"function"==typeof e.wp.i18n.__?function(t){return e.wp.i18n.__(t,"yatoon-booking-system")}:function(e){return e},n=e.YatoonSquareBookingForm;if(n&&t){class o{_squarePaymentsReady(){return!!("undefined"!=typeof yatoonData&&yatoonData.squarePaymentsEnabled&&yatoonData.squareApplicationId&&yatoonData.squareLocationId&&e.Square&&"function"==typeof e.Square.payments)}_paymentProviderEnabled(e){return"undefined"!=typeof yatoonData&&("square"===e?!!yatoonData.squarePaymentsEnabled:"paypal"===e?!!yatoonData.paypalPaymentsEnabled:"stripe"===e&&!!yatoonData.depositEnabled)}_ensurePaymentSdk(e){const t={square:()=>this._squarePaymentsReady(),paypal:()=>this._paypalPaymentsReady(),stripe:()=>this._stripePaymentsReady()}[e],n={square:yatoonData.squareSdkUrl,paypal:yatoonData.paypalSdkUrl,stripe:yatoonData.stripeSdkUrl}[e];return t&&t()?Promise.resolve():n?(this._paymentSdkPromises=this._paymentSdkPromises||{},this._paymentSdkPromises[e]||(this._paymentSdkPromises[e]=new Promise(function(o,r){const s=document.createElement("script");s.src=n,s.async=!0,s.dataset.yatoonPaymentSdk=e,s.onload=function(){t&&t()?o():r(new Error(a("The secure payment service did not initialize.")))},s.onerror=function(){r(new Error(a("The secure payment service could not be loaded. Check your connection and try again.")))},document.head.appendChild(s)}).catch(t=>{throw delete this._paymentSdkPromises[e],t})),this._paymentSdkPromises[e]):Promise.reject(new Error(a("The secure payment service is not configured.")))}_paypalPaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.paypalPaymentsEnabled||!e.paypal||"function"!=typeof e.paypal.Buttons)}_stripePaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.depositEnabled||!yatoonData.stripePublishableKey||"function"!=typeof e.Stripe)}_createStripePaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._stripePaymentsReady()){const e=new Error(a("Stripe payment form is not ready. Please refresh and try again."));return e.data={preflight:!0},void s(e)}const i=new FormData;Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){i.append(e+"[]",t)}):i.append(e,null==t?"":t)}),i.set("action","yatoon_stripe_create_intent"),i.set("nonce",yatoonData.nonce),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:i,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(n){if(n?.success&&n.data?.already_paid&&n.data.intent_id)return void r(n.data.intent_id);if(!(n&&n.success&&n.data&&n.data.client_secret))throw new Error(n&&n.data&&n.data.message?n.data.message:a("Could not start the Stripe payment."));let s=t("#sbs-stripe-payment-box");if(!s.length){s=t(`\n                                <div id="sbs-stripe-payment-box" style="margin:16px 0;padding:16px;border:1px solid #ddd6fe;border-radius:14px;background:#f5f3ff;">\n                                    <div style="font-size:14px;font-weight:800;color:#2e1065;margin-bottom:6px;">Secure Stripe Payment</div>\n                                    <div id="sbs-stripe-payment-amount" style="font-size:13px;color:#475569;margin-bottom:12px;"></div>\n                                    <div id="sbs-stripe-payment-element"></div>\n                                    <button type="button" id="sbs-stripe-pay-button" style="width:100%;min-height:46px;margin-top:14px;border:0;border-radius:9px;background:#635bff;color:#fff;font-weight:800;cursor:pointer;">${"full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit")}</button>\n                                    <div id="sbs-stripe-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                                </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(s):t(".sbs-step-5").prepend(s)}t("#sbs-stripe-payment-element").empty(),t("#sbs-stripe-payment-amount").text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+(n.data.deposit_display||""));const i=e.Stripe(yatoonData.stripePublishableKey),p=i.elements({clientSecret:n.data.client_secret});p.create("payment").mount("#sbs-stripe-payment-element");const d=t("#sbs-stripe-payment-status"),c="full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit");d.text(""),t("#sbs-stripe-pay-button").prop("disabled",!1).text(c),t("#sbs-stripe-pay-button").off(".yatoonStripe").on("click.yatoonStripe",function(){const e=t(this).prop("disabled",!0).text(a("Processing..."));d.text(a("Confirming your payment securely..."));const n=function(t){e.prop("disabled",!1).text(c),d.text(t),o._showToast(t,"error")};i.confirmPayment({elements:p,redirect:"if_required"}).then(function(e){let t=e.paymentIntent||null;if(e.error&&e.error.payment_intent){const a=e.error.payment_intent.status;"succeeded"!==a&&"processing"!==a||(t=e.error.payment_intent)}if(e.error&&!t)return void n(e.error.message||a("Stripe could not complete the payment."));const o=t?t.status:"";"succeeded"===o||"processing"===o?(d.text(a("processing"===o?"Bank payment submitted. Confirming your appointment...":"Payment approved. Confirming your appointment...")),s.hide(),r(t.id)):n(a("Stripe payment is not complete. Please check your payment details and try again."))}).catch(function(e){n(e&&e.message||a("We could not reach Stripe. Please check your connection and press Pay again."))})}),s.show(),s[0]&&s[0].scrollIntoView&&s[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(function(e){e&&"object"==typeof e&&(e.data=Object.assign({},e.data,{preflight:!0})),s(e)})})}_createPayPalPaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._paypalPaymentsReady())return void s(new Error(a("PayPal Checkout is not ready. Please refresh and try again.")));let i=t("#sbs-paypal-payment-box");if(!i.length){i=t(`\n                        <div id="sbs-paypal-payment-box" style="margin:16px 0;padding:16px;border:1px solid #bfdbfe;border-radius:14px;background:#eff6ff;">\n                            <div style="font-size:14px;font-weight:800;color:#172554;margin-bottom:6px;">Secure PayPal Payment</div>\n                            <div style="font-size:13px;color:#475569;margin-bottom:12px;">${"full"===yatoonData.nativePaymentMode?a("Pay the full appointment total with PayPal to confirm your booking."):a("Choose PayPal below to pay the deposit and confirm your appointment.")}</div>\n                            <div id="sbs-paypal-button-container"></div>\n                            <div id="sbs-paypal-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                        </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(i):t(".sbs-step-5").prepend(i)}const p=t("#sbs-paypal-payment-status"),d=yatoonData.ajax_url||yatoonData.ajaxUrl;function c(e,t){const o=new FormData;return Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){o.append(e+"[]",t)}):o.append(e,null==t?"":t)}),o.set("action",e),o.set("nonce",yatoonData.nonce),Object.keys(t||{}).forEach(function(e){o.set(e,t[e])}),fetch(d,{method:"POST",body:o,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data)throw new Error(e&&e.data&&e.data.message?e.data.message:a("PayPal payment failed."));return e.data})}t("#sbs-paypal-button-container").empty(),e.paypal.Buttons({style:{layout:"vertical",shape:"rect",label:"paypal"},createOrder:function(){return p.text(a("Preparing secure PayPal Checkout...")),c("yatoon_paypal_create_order").then(function(e){return e.amount_display&&p.text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+e.amount_display),e.order_id})},onApprove:function(e){return p.text(a("Confirming your PayPal payment...")),c("yatoon_paypal_capture_order",{order_id:e.orderID}).then(function(t){p.text(a("Payment approved. Confirming your appointment...")),i.hide(),r(t.order_id||e.orderID)}).catch(function(e){p.text(e.message||a("PayPal could not capture the payment.")),o._showToast(e.message||a("PayPal could not capture the payment."),"error"),s(e)})},onCancel:function(){const e=new Error(a("PayPal Checkout was cancelled."));p.text(e.message),s(e)},onError:function(e){p.text(a("PayPal Checkout could not be opened. Please try again.")),o._showToast(e&&e.message?e.message:a("PayPal Checkout failed."),"error"),s(e instanceof Error?e:new Error(a("PayPal Checkout failed.")))}}).render("#sbs-paypal-button-container").then(function(){i.show(),i[0]&&i[0].scrollIntoView&&i[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(s)})}_squareMethods(){const e=yatoonData&&Array.isArray(yatoonData.squarePaymentMethods)?yatoonData.squarePaymentMethods:["card"];return e.length?e:["card"]}_previewSquarePayment(e){const t=new FormData;return Object.keys(e).forEach(function(a){const n=e[a];Array.isArray(n)?n.forEach(function(e){t.append(a+"[]",e)}):t.append(a,null==n?"":n)}),t.set("action","yatoon_square_preview_payment"),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:t,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data){const t=e&&e.data&&e.data.message?e.data.message:a("Could not calculate the Square payment amount.");throw new Error(t)}return e.data})}_buildSquarePaymentRequest(e,t){return e.paymentRequest({countryCode:(yatoonData.squareCountryCode||"US").toUpperCase(),currencyCode:(t.currency||yatoonData.currency||"USD").toUpperCase(),total:{amount:t.amount||"0.00",label:"full"===yatoonData.squarePaymentMode?a("Appointment total"):a("Booking deposit")}})}_ensureSquarePaymentCard(n){const o=this,r=JSON.stringify({services_json:n.services_json||"",service_id:n.service_id||"",selected_options:n.selected_options||[],coupon_code:n.coupon_code||"",customer_email:n.customer_email||"",customer_phone:n.customer_phone||"",client_reference:n.client_reference||"",checkout_token:n.checkout_token||"",start_at:n.start_at||"",location:n.yatoon_location_id||0,tip_amount:n.tip_amount||0,gift_card_code:n.gift_card_code||""});return this._squareCardPromise&&this._squarePaymentPayloadKey===r||(this._squarePaymentPayloadKey=r,this._squareCardPromise=new Promise(function(r,s){if(!o._squarePaymentsReady())return void s(new Error(a("Square payment form is not ready. Please refresh and try again.")));let i=t("#sbs-square-payment-box");if(!i.length){const e="full"===yatoonData.squarePaymentMode?a("Pay securely now to confirm your appointment."):a("Pay your deposit securely now to confirm your appointment.");i=t(`\n                            <div id="sbs-square-payment-box" class="sbs-square-payment-box" style="margin:16px 0;padding:16px;border:1px solid #dbeafe;border-radius:14px;background:#eff6ff;">\n                                <div style="font-size:14px;font-weight:800;color:#1e3a8a;margin-bottom:6px;">Secure Square Payment</div>\n                                <div style="font-size:13px;color:#475569;margin-bottom:12px;">${e}</div>\n                                <div id="sbs-square-payment-amount" style="font-size:13px;font-weight:800;color:#0f172a;margin-bottom:12px;"></div>\n                                <div id="sbs-square-method-tabs" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;"></div>\n                                <button id="sbs-square-apple-pay-button" type="button" class="sbs-square-method-panel" data-method-panel="apple_pay" style="display:none;width:100%;min-height:44px;border-radius:8px;border:0;background:#000;color:#fff;font-weight:800;">Apple Pay</button>\n                                <div id="sbs-square-google-pay" class="sbs-square-method-panel" data-method-panel="google_pay" style="display:none;"></div>\n                                <div id="sbs-square-cash-app-pay" class="sbs-square-method-panel" data-method-panel="cash_app_pay" style="display:none;"></div>\n                                <div id="sbs-square-gift-card-container" class="sbs-square-method-panel" data-method-panel="gift_card" style="display:none;"></div>\n                                <div id="sbs-square-card-container"></div>\n                                <div id="sbs-square-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                            </div>\n                        `);const n=t("#sbs-booking-form");n.length?n.prepend(i):t(".sbs-step-5").prepend(i)}t("#sbs-square-payment-status").text(a("Loading secure Square card form...")),o._previewSquarePayment(n).then(function(s){return t("#sbs-square-payment-amount").text(("full"===s.payment_mode?a("Pay now:"):a("Deposit due now:"))+" "+s.amount_display),Promise.resolve(e.Square.payments(yatoonData.squareApplicationId,yatoonData.squareLocationId)).then(function(i){const p=o._squareMethods(),d={payments:i,preview:s,methods:{},selected:p[0]||"card"},c=o._buildSquarePaymentRequest(i,s),u={card:"Card",apple_pay:a("Apple Pay"),google_pay:a("Google Pay"),cash_app_pay:a("Cash App"),gift_card:a("Gift Card")},y=t("#sbs-square-method-tabs").empty();function l(e){d.selected=e,t(".sbs-square-method-tab").each(function(){const a=t(this).data("method")===e;t(this).css({background:a?"#1d4ed8":"#fff",color:a?"#fff":"#1e3a8a"})}),t(".sbs-square-method-panel").hide(),t("#sbs-square-card-container").toggle("card"===e),t('[data-method-panel="'+e+'"]').show(),t("#sbs-square-payment-status").text(a("apple_pay"===e||"cash_app_pay"===e?"Tap the wallet button above to authorize payment, then booking will continue automatically.":"Payment details are encrypted by Square."))}p.forEach(function(e){t("<button/>",{type:"button",class:"sbs-square-method-tab","data-method":e,text:u[e]||e}).css({border:"1px solid #bfdbfe",background:e===d.selected?"#1d4ed8":"#fff",color:e===d.selected?"#fff":"#1e3a8a",borderRadius:"999px",padding:"8px 12px",fontWeight:"800",cursor:"pointer"}).appendTo(y)}),t(document).off("click.squareMethod").on("click.squareMethod",".sbs-square-method-tab",function(){l(t(this).data("method"))});const m=[];return p.includes("card")&&m.push(i.card().then(function(e){return d.methods.card=e,e.attach("#sbs-square-card-container")})),p.includes("gift_card")&&"function"==typeof i.giftCard&&m.push(i.giftCard().then(function(e){return d.methods.gift_card=e,e.attach("#sbs-square-gift-card-container")}).catch(function(){})),p.includes("apple_pay")&&"function"==typeof i.applePay&&m.push(i.applePay(c).then(function(e){d.methods.apple_pay=e,t("#sbs-square-apple-pay-button").off("click.squareApple").on("click.squareApple",function(t){t.preventDefault(),o._handleSquareWalletButton(n,e,"apple_pay")})}).catch(function(){t('[data-method="apple_pay"], [data-method-panel="apple_pay"]').hide()})),p.includes("google_pay")&&"function"==typeof i.googlePay&&m.push(i.googlePay(c).then(function(e){return d.methods.google_pay=e,e.attach("#sbs-square-google-pay")}).catch(function(){t('[data-method="google_pay"], [data-method-panel="google_pay"]').hide()})),p.includes("cash_app_pay")&&"function"==typeof i.cashAppPay&&m.push(i.cashAppPay(c,{redirectURL:e.location.href,referenceId:"yatoon-"+Date.now()}).then(function(e){return d.methods.cash_app_pay=e,e.addEventListener("ontokenization",function(e){const t=e.detail||{};t.error?o._showToast(a("Cash App Pay could not be authorized."),"error"):t.tokenResult&&"OK"===t.tokenResult.status&&o._finishSquareTokenPayment(n,t.tokenResult.token,"cash_app_pay")}),e.attach("#sbs-square-cash-app-pay")}).catch(function(){t('[data-method="cash_app_pay"], [data-method-panel="cash_app_pay"]').hide()})),Promise.all(m).then(function(){o._squarePaymentState=d,l(d.methods[d.selected]?d.selected:d.methods.card?"card":Object.keys(d.methods)[0]),r(d)})})}).catch(function(e){o._squareCardPromise=null,s(e)})})),this._squareCardPromise}_finishSquareTokenPayment(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Completing")+" "+(o||a("Square"))+" "+a("payment...")),this._sendSquareTokenPayment(e,n).then(function(n){return r._squarePreparedPaymentId=n,r._squarePreparedPaymentReference=e.client_reference,r._rememberCheckoutPayment&&r._rememberCheckoutPayment(e.client_reference,"square_payment_id",n),t("#sbs-square-payment-status").text(a("Payment approved by Square. Confirming your booking...")),r._isSubmitting=!1,r.submitBooking(),n}).catch(function(e){throw r._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e})}_handleSquareWalletButton(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Opening")+" "+a("apple_pay"===o?"Apple Pay":"Square wallet")+"..."),n.tokenize().then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("The wallet payment was not authorized.");throw new Error(e)}return r._finishSquareTokenPayment(e,t.token,o)}).catch(function(e){t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Wallet payment failed.")),r._showToast(e&&e.message?e.message:a("Wallet payment failed. Please try again."),"error")})}_sendSquareTokenPayment(e,t){const n=new FormData;return Object.keys(e).forEach(function(t){const a=e[t];Array.isArray(a)?a.forEach(function(e){n.append(t+"[]",e)}):n.append(t,null==a?"":a)}),n.set("action","yatoon_square_create_payment"),n.set("source_id",t),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:n,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!(e&&e.success&&e.data&&e.data.payment_id)){const t=e&&e.data&&e.data.message?e.data.message:a("Square payment failed. Please try again.");throw new Error(t)}return e.data.payment_id})}_createSquarePaymentForPayload(e){const n=this;return this._squarePaymentsReady()?this._ensureSquarePaymentCard(e).then(function(e){t("#sbs-square-payment-box").show();const n=document.getElementById("sbs-square-payment-box");n&&n.scrollIntoView({behavior:"smooth",block:"center"});const o=e.selected||"card",r=e.methods[o];if(!r)throw new Error(a("This Square payment method is not available on this device. Please choose another method."));return"cash_app_pay"===o?(t("#sbs-square-payment-status").text(a("Tap the Cash App Pay button above to authorize payment.")),new Promise(function(){})):(t("#sbs-square-payment-status").text(a("Securing your payment with Square...")),r.tokenize())}).then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("Please check your card details and try again.");throw new Error(e)}return n._sendSquareTokenPayment(e,t.token)}).then(function(e){return t("#sbs-square-payment-status").text(a("Payment approved by Square.")),e}).catch(function(e){throw t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Square payment failed.")),n._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e}):Promise.reject(new Error(a("Square payment is enabled but the secure payment form is unavailable.")))}}Object.getOwnPropertyNames(o.prototype).forEach(function(e){"constructor"!==e&&Object.defineProperty(n.prototype,e,Object.getOwnPropertyDescriptor(o.prototype,e))})}}(window,window.jQuery);
  • yatoon-booking-system/tags/4.10.2/assets/js/yatoon-asset-manifest.json

    r3780403 r3780965  
    1 {
    2   "version": "4.10.0",
    3   "builder": "terser-5.50.0 + postcss-8.5.6/cssnano-7.1.2/rtlcss-4.3.0",
    4   "inputs": {
    5     "yatoon-category-bar-423-path16.js": {
    6       "sha256": "d9d21e398c540a08d8e07c291b54a5f670616e5c5da35fbff9f3b579b7424cc7",
    7       "bytes": 11121
    8     },
    9     "frontend.js": {
    10       "sha256": "338ebe2b74415737c7616e1e36b62a28028dae5aa2037f6a4b1a5b2bc68a7ded",
    11       "bytes": 182985
    12     },
    13     "manage-booking.js": {
    14       "sha256": "aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7",
    15       "bytes": 36499
    16     },
    17     "customer-portal.js": {
    18       "sha256": "c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707",
    19       "bytes": 83590
    20     },
    21     "admin-services.js": {
    22       "sha256": "7f8c293589fc1f2a4d9be24b19c78668e9b73106ec1eb8cd111f31b5ff88f376",
    23       "bytes": 79799
    24     },
    25     "admin.js": {
    26       "sha256": "4b0851842309bc4e794d67c903f8ae777f93f455a6b69e1729d2f813a88e4b9a",
    27       "bytes": 23364
    28     },
    29     "admin-settings.js": {
    30       "sha256": "0f6928ef9a7c2967f5759ff846bc2492af4faf8490352b9d01f7af4491dc2a64",
    31       "bytes": 58129
    32     },
    33     "admin-option-groups.js": {
    34       "sha256": "dab46594bdf73d1eea2a85e79f976570c34185d9434633483bb086facc2c3f9c",
    35       "bytes": 15069
    36     },
    37     "frontend-payments.js": {
    38       "sha256": "17b903150a7629e803d3c8664642a7a4c3b693f7473bff24216e35375a70cf5c",
    39       "bytes": 35380
    40     },
    41     "yatoon-concierge-4766.js": {
    42       "sha256": "079493bf51ced34a809eb3fbc06079f507070627e247d1955019297acbcb59a1",
    43       "bytes": 131985
    44     },
    45     "admin-calendar.js": {
    46       "sha256": "46ecdfa78ad656102c720dd6deb0e0ef97ca94e758c1b56f5ba9c33b38ce7c9a",
    47       "bytes": 36565
    48     },
    49     "admin-clients.js": {
    50       "sha256": "a42717f6b0c4f1f6515040a218cfa681be7e7d44d82261f6a7e1402c1cb3b2ce",
    51       "bytes": 29292
    52     },
    53     "admin-bookings.js": {
    54       "sha256": "c1f8b151b6d7e097443634572aad2714aeb36cda507f525f7fe5842a6f1d467f",
    55       "bytes": 41934
    56     },
    57     "admin-staff.js": {
    58       "sha256": "b6b6e56b538ed9f2b6768104c7b7cc2bda2d94ffa48ff00932e964b1fe454b89",
    59       "bytes": 54358
    60     },
    61     "staff-portal.js": {
    62       "sha256": "2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68",
    63       "bytes": 220988
    64     },
    65     "frontend-availability.js": {
    66       "sha256": "4a271e0ecb0db8a4385331c67ba27776c3c7212aafe4e29e9f9ae49eace017c8",
    67       "bytes": 70358
    68     },
    69     "frontend-checkout.js": {
    70       "sha256": "44d9771ecd3505f72066e6eae910998ff38a2cb5dd4ff8363276f71549e062d2",
    71       "bytes": 68665
    72     },
    73     "frontend-recovery.js": {
    74       "sha256": "f96d9a84be0724732eb6baf4292fa0f5a81a6523f53456de5e880c7cca1ccef8",
    75       "bytes": 4566
    76     },
    77     "frontend-glass.js": {
    78       "sha256": "595c0c6c539d1495470f9597465a863e913fe81f726c7c7824c4f92d5051d66d",
    79       "bytes": 1416
    80     },
    81     "client-merge-ui.js": {
    82       "sha256": "a4f80bea59a75459b0625f295ba486753fd266c8e410d847a640b7f2d9fb31fe",
    83       "bytes": 7635
    84     },
    85     "staff-hours-editor.js": {
    86       "sha256": "9c8f00b26da3dd8c26b9160f5e8e3dd55507743aebaaf84895faa584fce27a96",
    87       "bytes": 8209
    88     },
    89     "admin-tools.js": {
    90       "sha256": "d084fac962bd24d074f636ae04a3e084bf6e22ff4458ba79f57854647cd9c206",
    91       "bytes": 6818
    92     },
    93     "yatoon-app-shell.js": {
    94       "sha256": "801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877",
    95       "bytes": 9793
    96     },
    97     "yatoon-category-bar-4721.js": {
    98       "sha256": "3596279a0fb230ed54ae6fc8ba7f7f0dc207c81891ea345b758bc9397085ff68",
    99       "bytes": 14196
    100     },
    101     "frontend-flow.js": {
    102       "sha256": "1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392",
    103       "bytes": 233386
    104     },
    105     "frontend-cart.js": {
    106       "sha256": "24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4",
    107       "bytes": 120622
    108     },
    109     "frontend-boot.js": {
    110       "sha256": "c0d60031a8f1da307ea5e896a747f96616a76de18fa6021e5ddcb8d74fe8ae37",
    111       "bytes": 30947
    112     },
    113     "yatoon-booking-header.js": {
    114       "sha256": "2bb689d5bf4e08a80d27582ee5a4474c029c45b67a7c12da1401bb8e3bdc8aaf",
    115       "bytes": 11913
    116     },
    117     "yatoon-language-order.js": {
    118       "sha256": "599194bba4daf0a4258843f43df0082d3aae7f78c0e0432efc1f281d6527e9d4",
    119       "bytes": 2125
    120     }
    121   },
    122   "outputs": {
    123     "frontend.min.js": {
    124       "sha256": "2358e461fd346486cc1d89f8702c1bca3e762ffd68863288166a93c03dd38773",
    125       "bytes": 78688
    126     },
    127     "manage-booking.min.js": {
    128       "sha256": "aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7",
    129       "bytes": 36499
    130     },
    131     "customer-portal.min.js": {
    132       "sha256": "c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707",
    133       "bytes": 83590
    134     },
    135     "admin-services.min.js": {
    136       "sha256": "18c487c0411571490cf721d0b30b49f2eff8e60d3d6c662e36d3920b7120c32f",
    137       "bytes": 46201
    138     },
    139     "admin.min.js": {
    140       "sha256": "ff2b431a9d6dc0f5e8c391ca762c27172461acfcf4ff85773bc865e5e9f75c37",
    141       "bytes": 10612
    142     },
    143     "admin-settings.min.js": {
    144       "sha256": "38db0164f533a4edfe41d999727e1f67705d498a8f50f50af0532e10e656ccb0",
    145       "bytes": 31390
    146     },
    147     "admin-option-groups.min.js": {
    148       "sha256": "7bc60b7c3208f3dc3d6380207ed551ed1619d00924c5a62cafa071958fbcef3e",
    149       "bytes": 11420
    150     },
    151     "frontend-payments.min.js": {
    152       "sha256": "e2c70a3d8bde13b32d4016a02835d6a2b2dc63e0171f74ef06ce829609f40925",
    153       "bytes": 18861
    154     },
    155     "frontend-availability.min.js": {
    156       "sha256": "aaa225d5ee3a114a82e1af57be8180d866d140f0ddbab2f81c3059f7c4fdeefb",
    157       "bytes": 29644
    158     },
    159     "yatoon-category-bar-423-path16.min.js": {
    160       "sha256": "a5608fd4415c1dbf4e851a4f207e60c8dd48e96f8ff9e7da3b396f6c71673b7d",
    161       "bytes": 6115
    162     },
    163     "yatoon-concierge-4766.min.js": {
    164       "sha256": "091e689701686070f21841c298121cd67163b6a0214b39a002d4bf96aa5e7652",
    165       "bytes": 51279
    166     },
    167     "admin-calendar.min.js": {
    168       "sha256": "ff82d1459da6b1f422dfd4437df106919891ed8fcc05e756c708513c87cb54d7",
    169       "bytes": 18956
    170     },
    171     "admin-clients.min.js": {
    172       "sha256": "9dea7f478d7b3d9310a1f9c0375bf18ccde5857ce0402a712407d48227dd7dda",
    173       "bytes": 17378
    174     },
    175     "admin-bookings.min.js": {
    176       "sha256": "f6046d4ee5b556126f2ea5d91511dc69d1fef81827bcf9177d14e99344dbe5e1",
    177       "bytes": 28731
    178     },
    179     "admin-staff.min.js": {
    180       "sha256": "3316771f28e0e574ad1ee09f68e993db71dc5943060c7b7a97ed39312a90a7a2",
    181       "bytes": 33428
    182     },
    183     "staff-portal.min.js": {
    184       "sha256": "2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68",
    185       "bytes": 220988
    186     },
    187     "frontend-checkout.min.js": {
    188       "sha256": "d45284aad5790bd63b1880dcc05fcb84c600fb771c04ce29dd2e68259b63c51f",
    189       "bytes": 27884
    190     },
    191     "frontend-recovery.min.js": {
    192       "sha256": "fcc85f9e563b837cab4f76d417b20efb3004378b16f34d81e6cc4af405588ff7",
    193       "bytes": 3081
    194     },
    195     "yatoon-app-shell.min.js": {
    196       "sha256": "801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877",
    197       "bytes": 9793
    198     },
    199     "yatoon-category-bar-4721.min.js": {
    200       "sha256": "cc9730273303f106ad4ca0a8d11a9723f00d2f4d0ab2d3fd7bccb6f80c2a70aa",
    201       "bytes": 7178
    202     },
    203     "frontend-flow.min.js": {
    204       "sha256": "1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392",
    205       "bytes": 233386
    206     },
    207     "frontend-cart.min.js": {
    208       "sha256": "24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4",
    209       "bytes": 120622
    210     },
    211     "frontend-boot.min.js": {
    212       "sha256": "b83003f71f6a02c18efa96508552fffd4087da007b02ac7a34eb86552b35b234",
    213       "bytes": 15656
    214     },
    215     "frontend-steps.min.js": {
    216       "sha256": "5244218e1cc8c6eda02c5ca557c104a66594ab53b95c1ec740972f2d9b103180",
    217       "bytes": 81847
    218     },
    219     "frontend.bundle.min.js": {
    220       "sha256": "c3a8002ca40541d6d7fc5534d2a8b24aed5a6816e134dcb28b094a7a9ce109ce",
    221       "bytes": 448361
    222     }
    223   },
    224   "css_inputs": {
    225     "frontend.css": {
    226       "sha256": "41a3b2b765cf4a4a6646664009bbc55e6b848c351061169449290751c94caf5c",
    227       "bytes": 274924
    228     },
    229     "yatoon-booking-bundle.css": {
    230       "sha256": "2b9f874ee912d2a42e2fedb93d045eb82c813e8713301f0a757b0f18cf144473",
    231       "bytes": 416
    232     },
    233     "yatoon-booking-final.css": {
    234       "sha256": "810d51757acea75ec0dbabac87dca581bf4c866dbee37c5d8937bfdbc5991d2b",
    235       "bytes": 71386
    236     },
    237     "apple-polish.css": {
    238       "sha256": "66aefa6e5e9daa980fac6f33ffe95b878f26a2cb50c791222fc368007c6afdb5",
    239       "bytes": 10131
    240     },
    241     "yatoon-ui-polish.css": {
    242       "sha256": "7bfb8a792cadb7412456e45d5e74f30b34749426ce3a8551573c4dedc5e976fb",
    243       "bytes": 111082
    244     },
    245     "yatoon-design-system.css": {
    246       "sha256": "981bbb291a331dff7529dfddec6a3ab176f79f599bb8b983dc76c987109da310",
    247       "bytes": 42448
    248     },
    249     "yatoon-motion-polish.css": {
    250       "sha256": "c7a758608be1b9272933ef4e9cbf0f5d23a4348e5e09731bbc5e51732e14ede7",
    251       "bytes": 2103
    252     },
    253     "yatoon-category-bar-423-path16.css": {
    254       "sha256": "e8b85960ff07b991b0f1b6714358a913a7097537c2a883020e153cb7d9a16c2c",
    255       "bytes": 4871
    256     },
    257     "yatoon-concierge-4757.css": {
    258       "sha256": "8e08fee86c08d37e4dc90787f28a375c0c32bc83ed8dee328336f4e368ab2373",
    259       "bytes": 26524
    260     },
    261     "admin-calendar.css": {
    262       "sha256": "39c78d8120ae664b4c4205c4306c377dc344f948624336dba63a74272823b8d0",
    263       "bytes": 21529
    264     },
    265     "admin-calendar-rtl.css": {
    266       "sha256": "9b3b47f492056ae3186f7ad278955cb154218dfb5f8557c644d29e0ad17e13d2",
    267       "bytes": 21529
    268     },
    269     "admin-clients.css": {
    270       "sha256": "9f38abe8dcb60ad4ee74af66839fffe730e84a1eb9474ca06f1db4374f1e0149",
    271       "bytes": 11234
    272     },
    273     "admin-clients-rtl.css": {
    274       "sha256": "00fe481c7c305a9cd07daf1af8609e276ab05ae32c3bb15f37f32888b28bfeca",
    275       "bytes": 11237
    276     },
    277     "admin-dashboard.css": {
    278       "sha256": "e9e2c37b664f26d1791977676453c6e125bc86330db03566a42348c8354a3fd9",
    279       "bytes": 12570
    280     },
    281     "yatoon-color-schemes.css": {
    282       "sha256": "784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872",
    283       "bytes": 71724
    284     },
    285     "yatoon-theme-preview.css": {
    286       "sha256": "707ece181bfb8eb30eb2c54384d02cae9b8638ece567e8150c548dc5ecc01e56",
    287       "bytes": 22155
    288     },
    289     "yatoon-app-shell.css": {
    290       "sha256": "79fe24a1d2852409a46796fa5b6a42a2ca9dfb8be4ed06fbbef4b639eaaff474",
    291       "bytes": 32896
    292     },
    293     "manage-booking.css": {
    294       "sha256": "ccde50e15dc10671de366575dcfa1bc93319aed409a1771d05dd4ec6ed7c76e9",
    295       "bytes": 19507
    296     },
    297     "staff-portal.css": {
    298       "sha256": "b60f1cd59069ad536b5088293f883aa41afa35d0bbcd2c3127783d4e04cb49a9",
    299       "bytes": 123789
    300     },
    301     "yatoon-seasonal-viewport-4781.css": {
    302       "sha256": "347ddefa10bba3327b1918be78913125e55164c643128caac5ceac81a5ca2723",
    303       "bytes": 8190
    304     },
    305     "frontend-glass.css": {
    306       "sha256": "060681cdf1a6b9805f40a6d788b12b39c00e2a6e0007ea57c69e17e9142374a3",
    307       "bytes": 664
    308     },
    309     "customer-portal.css": {
    310       "sha256": "f881d319ad2873eaa0de81cbb060c429744d334a8cfdcfd4e3444623a923d8ab",
    311       "bytes": 35043
    312     },
    313     "client-merge-ui.css": {
    314       "sha256": "d328401272323ca1026ef0928573c09aa3151f16e4d95388362a3dd3f9b0b5ac",
    315       "bytes": 2336
    316     },
    317     "staff-hours-editor.css": {
    318       "sha256": "70a2fac16897f3d9f523a1581f23de04d392f4110841229f064844432be39cd9",
    319       "bytes": 2226
    320     },
    321     "admin-tools.css": {
    322       "sha256": "f3f79f99b46c3e2613175254f54ce06454c952bebbdde278fa05c68ac923e464",
    323       "bytes": 14457
    324     },
    325     "admin-setup-wizard.css": {
    326       "sha256": "11b02bc28c20df7a5189165173103ebab2dba28318ead2520a261f7068953b66",
    327       "bytes": 7499
    328     },
    329     "yatoon-booking-discovery.css": {
    330       "sha256": "8c33bc55568bfd39b9acf0fe0ee1e380e69d475e992477a6909fccac0525b9a0",
    331       "bytes": 1231
    332     },
    333     "yatoon-booking-header.css": {
    334       "sha256": "597726d71d1e251243a7d56ddb6c81a2e51f0af33a345404b87f1b75443a3268",
    335       "bytes": 48073
    336     }
    337   },
    338   "css_outputs": {
    339     "frontend-rtl.css": {
    340       "sha256": "4fcc61fab31b13e36746b260f9a8658f784ebe5838527d00df96eabd372cf596",
    341       "bytes": 274828
    342     },
    343     "frontend.min.css": {
    344       "sha256": "d755cf890841367223dd97f290d8b933c80681f60cf85f6bcf8100a2373b7662",
    345       "bytes": 190078
    346     },
    347     "frontend.min-rtl.css": {
    348       "sha256": "576231fe623f4559e2cd918368e2d786276f1764680146544cfbf7bf14ae6c2e",
    349       "bytes": 189980
    350     },
    351     "yatoon-booking-final-rtl.css": {
    352       "sha256": "b1846d748cbe4fbc2d95be026aac1e111abe0481e4ec5b1bb11c5d8482bf0506",
    353       "bytes": 71396
    354     },
    355     "yatoon-booking-final.min.css": {
    356       "sha256": "fe13d8069725b96b098a0d233c871c89003553b72b2a677ca8203a8879f3179a",
    357       "bytes": 51544
    358     },
    359     "yatoon-booking-final.min-rtl.css": {
    360       "sha256": "78e4f83c38324104f7c09c6cc3305135741e03feba84077f008402bf04966aec",
    361       "bytes": 51554
    362     },
    363     "yatoon-booking-core.min.css": {
    364       "sha256": "2fe2d03c4426ca02ffefd11ffdbe1477b5f198913b4a8692c62a711884c6dd51",
    365       "bytes": 198836
    366     },
    367     "yatoon-booking-core.min-rtl.css": {
    368       "sha256": "2bbcea4c58e4b31386906c38ef89b5dc3e49bd7d219d24cc669c715e4d2808b6",
    369       "bytes": 198905
    370     },
    371     "yatoon-booking-flow.min.css": {
    372       "sha256": "6e6c980791ec4f5bcc4e09562700835fd86e23c532b5bc6a0654cbef12e1a5f5",
    373       "bytes": 90325
    374     },
    375     "yatoon-booking-flow.min-rtl.css": {
    376       "sha256": "3386631ef8de2623df26cd4f56c077859da5a5b753fef5bfedca98d625fde3b0",
    377       "bytes": 90334
    378     },
    379     "yatoon-design-system.min.css": {
    380       "sha256": "c2cbc79b447e9696a1c82dce32ca5c2acfe40c215f538ff1041856761c036c65",
    381       "bytes": 36073
    382     },
    383     "yatoon-design-system.min-rtl.css": {
    384       "sha256": "570028fe4afa537e34609ec8b6138f0287e2d9364bc057132f23287d249f3bd3",
    385       "bytes": 36079
    386     },
    387     "yatoon-design-system-rtl.css": {
    388       "sha256": "bb8a615fa8d19330e40b55622a4118764e83c5ea5b3ef5db8ae12f2dc2e3d5e8",
    389       "bytes": 42454
    390     },
    391     "yatoon-concierge-4757.min.css": {
    392       "sha256": "c03e6af34c28a629f11f891b00ae6323d81c12ea884fe91a4658855c485a6136",
    393       "bytes": 18402
    394     },
    395     "admin-calendar.min.css": {
    396       "sha256": "33404dd42bf72e0288d745e47835c77a8f943c65cbc975f542581ae1a2cc4116",
    397       "bytes": 20844
    398     },
    399     "admin-calendar.min-rtl.css": {
    400       "sha256": "893162b877b8db9281a895e3de1507b4ce80073e43e1ec969189d8ce6d20ea14",
    401       "bytes": 20846
    402     },
    403     "admin-clients.min.css": {
    404       "sha256": "ce423fecaf41cb6ce16a1caa9c90e7c167169a92ac14d0d8fa67ddf02a618eaa",
    405       "bytes": 10182
    406     },
    407     "admin-clients.min-rtl.css": {
    408       "sha256": "f68db34ec24385cdb44a8e53f21a0d576fa9ae12ac6d8f92ccabff3de3a8278d",
    409       "bytes": 10185
    410     },
    411     "admin-dashboard.min.css": {
    412       "sha256": "0037bfdb2dfaa0bda06d72c6bfb7861dc29a2c85d326857009f1efa7cd361c98",
    413       "bytes": 11515
    414     },
    415     "admin-dashboard.min-rtl.css": {
    416       "sha256": "c8cbf59d96754f7f5d18085462e3ddaf4d6795bfd4980d80ecaf39d13984c80f",
    417       "bytes": 11516
    418     },
    419     "yatoon-color-schemes.min.css": {
    420       "sha256": "2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e",
    421       "bytes": 64203
    422     },
    423     "yatoon-color-schemes.min-rtl.css": {
    424       "sha256": "2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e",
    425       "bytes": 64203
    426     },
    427     "yatoon-theme-preview.min.css": {
    428       "sha256": "d7eac486f0adced3d552784675de7c21cef46844c385f80b395c7f8f06441c11",
    429       "bytes": 19897
    430     },
    431     "yatoon-theme-preview.min-rtl.css": {
    432       "sha256": "b0580176744c9922574ce8629527c0a43fa533b095ddcc9769b4d25f64206a2a",
    433       "bytes": 19908
    434     },
    435     "yatoon-color-schemes-rtl.css": {
    436       "sha256": "784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872",
    437       "bytes": 71724
    438     },
    439     "yatoon-theme-preview-rtl.css": {
    440       "sha256": "5042c873ed908a826c434c6c83c4ebd053b02942a8e46c70bd9957cb51aabf57",
    441       "bytes": 22166
    442     },
    443     "yatoon-app-shell-rtl.css": {
    444       "sha256": "5bfff9f1a2487fb1f78279a7ac5cd80c67d692012808bc3f8f50cad3bf5c78d0",
    445       "bytes": 32908
    446     },
    447     "yatoon-app-shell.min.css": {
    448       "sha256": "cd9ccb19cbef6a0d42feb7c0c2ae70a23eb04b8693df459778cdc3624d4e439c",
    449       "bytes": 23417
    450     },
    451     "yatoon-app-shell.min-rtl.css": {
    452       "sha256": "c5c52c43dedfa22859427bed065649e2e57df6ea9d6e0ead55ece2b2be1e292f",
    453       "bytes": 23425
    454     },
    455     "manage-booking-rtl.css": {
    456       "sha256": "3ebdcf5de5c267e52ad3cbd47fd7a743501b298cb97532bbfa9dde6001f5777f",
    457       "bytes": 19510
    458     },
    459     "manage-booking.min.css": {
    460       "sha256": "f5d7287ca0ac4cf55a6b63e1fc8b04b63e6e70f043f1f23d32c5bdd52ea8b9ca",
    461       "bytes": 13560
    462     },
    463     "manage-booking.min-rtl.css": {
    464       "sha256": "92c6d808efd9c37c87a618ee97430688d4a8478d28648953b41dff28670a7570",
    465       "bytes": 13563
    466     },
    467     "staff-portal-rtl.css": {
    468       "sha256": "8af62f4563b1ab40a95eb2be04c547ae7e16d9ffa94e7b15d2c8aa8baf3088ac",
    469       "bytes": 123823
    470     },
    471     "staff-portal.min.css": {
    472       "sha256": "b33d5f555a6d1eda7aff23aa4c4ba926143fcad79a5fe68bd6384c44d8a5951e",
    473       "bytes": 92450
    474     },
    475     "staff-portal.min-rtl.css": {
    476       "sha256": "a9d3af72734dd81db4d5ddff67a0b044bb8d2ce041a4d69a66405cfb05f7a788",
    477       "bytes": 92481
    478     },
    479     "customer-portal-rtl.css": {
    480       "sha256": "3a3533ba65af2f364016e4e4ca30ecdc03f47b84a325835ce6b3fc84cc818581",
    481       "bytes": 34643
    482     },
    483     "customer-portal.min.css": {
    484       "sha256": "e886e0d65396c4dd202c93703547e3ec1f111f3be6910f135a951e392615733e",
    485       "bytes": 26556
    486     },
    487     "customer-portal.min-rtl.css": {
    488       "sha256": "dba031b25b63dbcde7ecd9004a71ef4174ba202aceb4e69e9f8590a879cedbf5",
    489       "bytes": 26343
    490     },
    491     "admin-setup-wizard-rtl.css": {
    492       "sha256": "8c2381f5b07e05067b316c10ce4a9333f1ab62714d197bcb85c7ce67e967d676",
    493       "bytes": 7502
    494     },
    495     "admin-setup-wizard.min.css": {
    496       "sha256": "4a9ef36fa5a16fd188b6db80e86a3d825bc786d844a3f1905bcc2facc504d0ba",
    497       "bytes": 7046
    498     },
    499     "admin-setup-wizard.min-rtl.css": {
    500       "sha256": "9cf36af333dae3b82ca7bc98f99ae297e5e59c34ac1766fafb4fa5364bd6e818",
    501       "bytes": 7048
    502     }
    503   },
    504   "frontend_builder": "terser-5.50.0",
    505   "staff_portal_builder": "Source-equivalent production JS/CSS; inherited assets retain their original build.",
    506   "internal_build": "490-free-20261025-theme-header-lang-picker-compact-languages-ordered-language-picker-mobile-single-brand-row",
    507   "css_note": "4.8.2: yatoon-booking-core/flow/final min bundles and frontend/ui-polish/final sources had declarations removed that a later same-selector rule in the same stylesheet always overrides; outputs were edited in place (not rebuilt with cssnano), so hashes are of the shipped files. 4.8.5: core/flow/final min files also had declarations removed that never take effect in the captured cascade and rules for class names the plugin never outputs; outputs edited in place.",
    508   "css_bundles": {
    509     "yatoon-booking-core.bundle.min.css": {
    510       "sha256": "bd16143171cd19aa9b2ef73ee546de2846fe78a8b4b3566d26ec9af34bd85de6",
    511       "bytes": 208911,
    512       "parts": [
    513         "yatoon-theme-isolation.css",
    514         "yatoon-booking-core.min.css"
    515       ]
    516     },
    517     "yatoon-booking-core.bundle.min-rtl.css": {
    518       "sha256": "95a8512c82e3839ce88cae5aaf50236a52e9c57b9abad0bc1a04e62bdbd6d399",
    519       "bytes": 208984,
    520       "parts": [
    521         "yatoon-theme-isolation.css",
    522         "yatoon-booking-core.min-rtl.css"
    523       ]
    524     },
    525     "yatoon-booking-layers.bundle.min.css": {
    526       "sha256": "319ec68f7b14bc6ccb1997f747d36bbab8f985cc73b0c21be51460ba60f96595",
    527       "bytes": 79694,
    528       "parts": [
    529         "yatoon-color-schemes.min.css",
    530         "yatoon-service-image-frame.css",
    531         "yatoon-theme-armor-4723.min.css",
    532         "yatoon-booking-responsive.min.css"
    533       ]
    534     },
    535     "yatoon-booking-layers.bundle.min-rtl.css": {
    536       "sha256": "0361d779e153376c92d30f991c2ffe1b20f9c39bd6ccd8130ac97f968765d74f",
    537       "bytes": 79698,
    538       "parts": [
    539         "yatoon-color-schemes.min.css",
    540         "yatoon-service-image-frame.css",
    541         "yatoon-theme-armor-4723.min.css",
    542         "yatoon-booking-responsive.min-rtl.css"
    543       ]
    544     },
    545     "yatoon-booking-extras.bundle.min.css": {
    546       "sha256": "8eb2ad13592a630dc19ff24101d4c69aead136eee39fef404ef6322a46d7d508",
    547       "bytes": 9565,
    548       "parts": [
    549         "yatoon-seasonal-viewport-4781.css",
    550         "yatoon-booking-discovery.css"
    551       ]
    552     }
    553   },
    554   "js_note": "4.8.5: the booking form is split into frontend.js, frontend-flow.js, frontend-cart.js and frontend-boot.js (sources). build_frontend.js generates the lazy chunks frontend-availability.js and frontend-steps.js, every .min.js, and frontend.bundle.min.js.",
    555   "css_sources": "4.9.0: assets/css/src/yatoon-booking-core.css and yatoon-booking-flow.css are readable sources with exactly the rules of the shipped .min files; rebuild with csso --no-restructure (keeps rule order), then rtlcss.",
    556   "header_note": "Compact local SVG country flags with native details navigation and a circular account control. Header remains visible on step one only.",
    557   "cart_note": "Single section dividers and aligned total/duration rows in the mobile drawer; booking calculations are unchanged."
    558 }
     1{"version":"4.10.0","builder":"terser-5.50.0 + postcss-8.5.6/cssnano-7.1.2/rtlcss-4.3.0","inputs":{"yatoon-category-bar-423-path16.js":{"sha256":"d9d21e398c540a08d8e07c291b54a5f670616e5c5da35fbff9f3b579b7424cc7","bytes":11121},"frontend.js":{"sha256":"338ebe2b74415737c7616e1e36b62a28028dae5aa2037f6a4b1a5b2bc68a7ded","bytes":182985},"manage-booking.js":{"sha256":"aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7","bytes":36499},"customer-portal.js":{"sha256":"c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707","bytes":83590},"admin-services.js":{"sha256":"7f8c293589fc1f2a4d9be24b19c78668e9b73106ec1eb8cd111f31b5ff88f376","bytes":79799},"admin.js":{"sha256":"4b0851842309bc4e794d67c903f8ae777f93f455a6b69e1729d2f813a88e4b9a","bytes":23364},"admin-settings.js":{"sha256":"0f6928ef9a7c2967f5759ff846bc2492af4faf8490352b9d01f7af4491dc2a64","bytes":58129},"admin-option-groups.js":{"sha256":"dab46594bdf73d1eea2a85e79f976570c34185d9434633483bb086facc2c3f9c","bytes":15069},"frontend-payments.js":{"sha256":"01bcee85136bc3826c39688d57df9082676556080e97185c884c3a63d5497d42","bytes":37720},"yatoon-concierge-4766.js":{"sha256":"079493bf51ced34a809eb3fbc06079f507070627e247d1955019297acbcb59a1","bytes":131985},"admin-calendar.js":{"sha256":"46ecdfa78ad656102c720dd6deb0e0ef97ca94e758c1b56f5ba9c33b38ce7c9a","bytes":36565},"admin-clients.js":{"sha256":"a42717f6b0c4f1f6515040a218cfa681be7e7d44d82261f6a7e1402c1cb3b2ce","bytes":29292},"admin-bookings.js":{"sha256":"c1f8b151b6d7e097443634572aad2714aeb36cda507f525f7fe5842a6f1d467f","bytes":41934},"admin-staff.js":{"sha256":"b6b6e56b538ed9f2b6768104c7b7cc2bda2d94ffa48ff00932e964b1fe454b89","bytes":54358},"staff-portal.js":{"sha256":"2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68","bytes":220988},"frontend-availability.js":{"sha256":"4a271e0ecb0db8a4385331c67ba27776c3c7212aafe4e29e9f9ae49eace017c8","bytes":70358},"frontend-checkout.js":{"sha256":"710c9ca74997dd979c4556910328d04a1db66a90a8974282d456701178c7c027","bytes":69189},"frontend-recovery.js":{"sha256":"f96d9a84be0724732eb6baf4292fa0f5a81a6523f53456de5e880c7cca1ccef8","bytes":4566},"frontend-glass.js":{"sha256":"595c0c6c539d1495470f9597465a863e913fe81f726c7c7824c4f92d5051d66d","bytes":1416},"client-merge-ui.js":{"sha256":"a4f80bea59a75459b0625f295ba486753fd266c8e410d847a640b7f2d9fb31fe","bytes":7635},"staff-hours-editor.js":{"sha256":"9c8f00b26da3dd8c26b9160f5e8e3dd55507743aebaaf84895faa584fce27a96","bytes":8209},"admin-tools.js":{"sha256":"d084fac962bd24d074f636ae04a3e084bf6e22ff4458ba79f57854647cd9c206","bytes":6818},"yatoon-app-shell.js":{"sha256":"801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877","bytes":9793},"yatoon-category-bar-4721.js":{"sha256":"3596279a0fb230ed54ae6fc8ba7f7f0dc207c81891ea345b758bc9397085ff68","bytes":14196},"frontend-flow.js":{"sha256":"1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392","bytes":233386},"frontend-cart.js":{"sha256":"24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4","bytes":120622},"frontend-boot.js":{"sha256":"c0d60031a8f1da307ea5e896a747f96616a76de18fa6021e5ddcb8d74fe8ae37","bytes":30947},"yatoon-booking-header.js":{"sha256":"2bb689d5bf4e08a80d27582ee5a4474c029c45b67a7c12da1401bb8e3bdc8aaf","bytes":11913},"yatoon-language-order.js":{"sha256":"599194bba4daf0a4258843f43df0082d3aae7f78c0e0432efc1f281d6527e9d4","bytes":2125}},"outputs":{"frontend.min.js":{"sha256":"2358e461fd346486cc1d89f8702c1bca3e762ffd68863288166a93c03dd38773","bytes":78688},"manage-booking.min.js":{"sha256":"aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7","bytes":36499},"customer-portal.min.js":{"sha256":"c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707","bytes":83590},"admin-services.min.js":{"sha256":"18c487c0411571490cf721d0b30b49f2eff8e60d3d6c662e36d3920b7120c32f","bytes":46201},"admin.min.js":{"sha256":"ff2b431a9d6dc0f5e8c391ca762c27172461acfcf4ff85773bc865e5e9f75c37","bytes":10612},"admin-settings.min.js":{"sha256":"38db0164f533a4edfe41d999727e1f67705d498a8f50f50af0532e10e656ccb0","bytes":31390},"admin-option-groups.min.js":{"sha256":"7bc60b7c3208f3dc3d6380207ed551ed1619d00924c5a62cafa071958fbcef3e","bytes":11420},"frontend-payments.min.js":{"sha256":"52b59f072af0b0df01b6d3505820515db57c968c2835c402e8af7fb05365b9d3","bytes":19251},"frontend-availability.min.js":{"sha256":"aaa225d5ee3a114a82e1af57be8180d866d140f0ddbab2f81c3059f7c4fdeefb","bytes":29644},"yatoon-category-bar-423-path16.min.js":{"sha256":"a5608fd4415c1dbf4e851a4f207e60c8dd48e96f8ff9e7da3b396f6c71673b7d","bytes":6115},"yatoon-concierge-4766.min.js":{"sha256":"091e689701686070f21841c298121cd67163b6a0214b39a002d4bf96aa5e7652","bytes":51279},"admin-calendar.min.js":{"sha256":"ff82d1459da6b1f422dfd4437df106919891ed8fcc05e756c708513c87cb54d7","bytes":18956},"admin-clients.min.js":{"sha256":"9dea7f478d7b3d9310a1f9c0375bf18ccde5857ce0402a712407d48227dd7dda","bytes":17378},"admin-bookings.min.js":{"sha256":"f6046d4ee5b556126f2ea5d91511dc69d1fef81827bcf9177d14e99344dbe5e1","bytes":28731},"admin-staff.min.js":{"sha256":"3316771f28e0e574ad1ee09f68e993db71dc5943060c7b7a97ed39312a90a7a2","bytes":33428},"staff-portal.min.js":{"sha256":"2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68","bytes":220988},"frontend-checkout.min.js":{"sha256":"bcfbd71409007b50fd60aa869c6bcd1cbad18b86c21b98a9c70696032e1217db","bytes":28012},"frontend-recovery.min.js":{"sha256":"fcc85f9e563b837cab4f76d417b20efb3004378b16f34d81e6cc4af405588ff7","bytes":3081},"yatoon-app-shell.min.js":{"sha256":"801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877","bytes":9793},"yatoon-category-bar-4721.min.js":{"sha256":"cc9730273303f106ad4ca0a8d11a9723f00d2f4d0ab2d3fd7bccb6f80c2a70aa","bytes":7178},"frontend-flow.min.js":{"sha256":"1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392","bytes":233386},"frontend-cart.min.js":{"sha256":"24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4","bytes":120622},"frontend-boot.min.js":{"sha256":"b83003f71f6a02c18efa96508552fffd4087da007b02ac7a34eb86552b35b234","bytes":15656},"frontend-steps.min.js":{"sha256":"5244218e1cc8c6eda02c5ca557c104a66594ab53b95c1ec740972f2d9b103180","bytes":81847},"frontend.bundle.min.js":{"sha256":"c3a8002ca40541d6d7fc5534d2a8b24aed5a6816e134dcb28b094a7a9ce109ce","bytes":448361}},"css_inputs":{"frontend.css":{"sha256":"41a3b2b765cf4a4a6646664009bbc55e6b848c351061169449290751c94caf5c","bytes":274924},"yatoon-booking-bundle.css":{"sha256":"2b9f874ee912d2a42e2fedb93d045eb82c813e8713301f0a757b0f18cf144473","bytes":416},"yatoon-booking-final.css":{"sha256":"810d51757acea75ec0dbabac87dca581bf4c866dbee37c5d8937bfdbc5991d2b","bytes":71386},"apple-polish.css":{"sha256":"66aefa6e5e9daa980fac6f33ffe95b878f26a2cb50c791222fc368007c6afdb5","bytes":10131},"yatoon-ui-polish.css":{"sha256":"7bfb8a792cadb7412456e45d5e74f30b34749426ce3a8551573c4dedc5e976fb","bytes":111082},"yatoon-design-system.css":{"sha256":"981bbb291a331dff7529dfddec6a3ab176f79f599bb8b983dc76c987109da310","bytes":42448},"yatoon-motion-polish.css":{"sha256":"c7a758608be1b9272933ef4e9cbf0f5d23a4348e5e09731bbc5e51732e14ede7","bytes":2103},"yatoon-category-bar-423-path16.css":{"sha256":"e8b85960ff07b991b0f1b6714358a913a7097537c2a883020e153cb7d9a16c2c","bytes":4871},"yatoon-concierge-4757.css":{"sha256":"8e08fee86c08d37e4dc90787f28a375c0c32bc83ed8dee328336f4e368ab2373","bytes":26524},"admin-calendar.css":{"sha256":"39c78d8120ae664b4c4205c4306c377dc344f948624336dba63a74272823b8d0","bytes":21529},"admin-calendar-rtl.css":{"sha256":"9b3b47f492056ae3186f7ad278955cb154218dfb5f8557c644d29e0ad17e13d2","bytes":21529},"admin-clients.css":{"sha256":"9f38abe8dcb60ad4ee74af66839fffe730e84a1eb9474ca06f1db4374f1e0149","bytes":11234},"admin-clients-rtl.css":{"sha256":"00fe481c7c305a9cd07daf1af8609e276ab05ae32c3bb15f37f32888b28bfeca","bytes":11237},"admin-dashboard.css":{"sha256":"e9e2c37b664f26d1791977676453c6e125bc86330db03566a42348c8354a3fd9","bytes":12570},"yatoon-color-schemes.css":{"sha256":"784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872","bytes":71724},"yatoon-theme-preview.css":{"sha256":"707ece181bfb8eb30eb2c54384d02cae9b8638ece567e8150c548dc5ecc01e56","bytes":22155},"yatoon-app-shell.css":{"sha256":"79fe24a1d2852409a46796fa5b6a42a2ca9dfb8be4ed06fbbef4b639eaaff474","bytes":32896},"manage-booking.css":{"sha256":"ccfdd6c0adbaad8fa3ed2dc8902f0911a65cea4dcecce99bc481ba4306cabc35","bytes":21975},"staff-portal.css":{"sha256":"b60f1cd59069ad536b5088293f883aa41afa35d0bbcd2c3127783d4e04cb49a9","bytes":123789},"yatoon-seasonal-viewport-4781.css":{"sha256":"347ddefa10bba3327b1918be78913125e55164c643128caac5ceac81a5ca2723","bytes":8190},"frontend-glass.css":{"sha256":"060681cdf1a6b9805f40a6d788b12b39c00e2a6e0007ea57c69e17e9142374a3","bytes":664},"customer-portal.css":{"sha256":"f881d319ad2873eaa0de81cbb060c429744d334a8cfdcfd4e3444623a923d8ab","bytes":35043},"client-merge-ui.css":{"sha256":"d328401272323ca1026ef0928573c09aa3151f16e4d95388362a3dd3f9b0b5ac","bytes":2336},"staff-hours-editor.css":{"sha256":"70a2fac16897f3d9f523a1581f23de04d392f4110841229f064844432be39cd9","bytes":2226},"admin-tools.css":{"sha256":"f3f79f99b46c3e2613175254f54ce06454c952bebbdde278fa05c68ac923e464","bytes":14457},"admin-setup-wizard.css":{"sha256":"11b02bc28c20df7a5189165173103ebab2dba28318ead2520a261f7068953b66","bytes":7499},"yatoon-booking-discovery.css":{"sha256":"8c33bc55568bfd39b9acf0fe0ee1e380e69d475e992477a6909fccac0525b9a0","bytes":1231},"yatoon-booking-header.css":{"sha256":"597726d71d1e251243a7d56ddb6c81a2e51f0af33a345404b87f1b75443a3268","bytes":48073}},"css_outputs":{"frontend-rtl.css":{"sha256":"4fcc61fab31b13e36746b260f9a8658f784ebe5838527d00df96eabd372cf596","bytes":274828},"frontend.min.css":{"sha256":"d755cf890841367223dd97f290d8b933c80681f60cf85f6bcf8100a2373b7662","bytes":190078},"frontend.min-rtl.css":{"sha256":"576231fe623f4559e2cd918368e2d786276f1764680146544cfbf7bf14ae6c2e","bytes":189980},"yatoon-booking-final-rtl.css":{"sha256":"b1846d748cbe4fbc2d95be026aac1e111abe0481e4ec5b1bb11c5d8482bf0506","bytes":71396},"yatoon-booking-final.min.css":{"sha256":"fe13d8069725b96b098a0d233c871c89003553b72b2a677ca8203a8879f3179a","bytes":51544},"yatoon-booking-final.min-rtl.css":{"sha256":"78e4f83c38324104f7c09c6cc3305135741e03feba84077f008402bf04966aec","bytes":51554},"yatoon-booking-core.min.css":{"sha256":"2fe2d03c4426ca02ffefd11ffdbe1477b5f198913b4a8692c62a711884c6dd51","bytes":198836},"yatoon-booking-core.min-rtl.css":{"sha256":"2bbcea4c58e4b31386906c38ef89b5dc3e49bd7d219d24cc669c715e4d2808b6","bytes":198905},"yatoon-booking-flow.min.css":{"sha256":"6e6c980791ec4f5bcc4e09562700835fd86e23c532b5bc6a0654cbef12e1a5f5","bytes":90325},"yatoon-booking-flow.min-rtl.css":{"sha256":"3386631ef8de2623df26cd4f56c077859da5a5b753fef5bfedca98d625fde3b0","bytes":90334},"yatoon-design-system.min.css":{"sha256":"c2cbc79b447e9696a1c82dce32ca5c2acfe40c215f538ff1041856761c036c65","bytes":36073},"yatoon-design-system.min-rtl.css":{"sha256":"570028fe4afa537e34609ec8b6138f0287e2d9364bc057132f23287d249f3bd3","bytes":36079},"yatoon-design-system-rtl.css":{"sha256":"bb8a615fa8d19330e40b55622a4118764e83c5ea5b3ef5db8ae12f2dc2e3d5e8","bytes":42454},"yatoon-concierge-4757.min.css":{"sha256":"c03e6af34c28a629f11f891b00ae6323d81c12ea884fe91a4658855c485a6136","bytes":18402},"admin-calendar.min.css":{"sha256":"33404dd42bf72e0288d745e47835c77a8f943c65cbc975f542581ae1a2cc4116","bytes":20844},"admin-calendar.min-rtl.css":{"sha256":"893162b877b8db9281a895e3de1507b4ce80073e43e1ec969189d8ce6d20ea14","bytes":20846},"admin-clients.min.css":{"sha256":"ce423fecaf41cb6ce16a1caa9c90e7c167169a92ac14d0d8fa67ddf02a618eaa","bytes":10182},"admin-clients.min-rtl.css":{"sha256":"f68db34ec24385cdb44a8e53f21a0d576fa9ae12ac6d8f92ccabff3de3a8278d","bytes":10185},"admin-dashboard.min.css":{"sha256":"0037bfdb2dfaa0bda06d72c6bfb7861dc29a2c85d326857009f1efa7cd361c98","bytes":11515},"admin-dashboard.min-rtl.css":{"sha256":"c8cbf59d96754f7f5d18085462e3ddaf4d6795bfd4980d80ecaf39d13984c80f","bytes":11516},"yatoon-color-schemes.min.css":{"sha256":"2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e","bytes":64203},"yatoon-color-schemes.min-rtl.css":{"sha256":"2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e","bytes":64203},"yatoon-theme-preview.min.css":{"sha256":"d7eac486f0adced3d552784675de7c21cef46844c385f80b395c7f8f06441c11","bytes":19897},"yatoon-theme-preview.min-rtl.css":{"sha256":"b0580176744c9922574ce8629527c0a43fa533b095ddcc9769b4d25f64206a2a","bytes":19908},"yatoon-color-schemes-rtl.css":{"sha256":"784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872","bytes":71724},"yatoon-theme-preview-rtl.css":{"sha256":"5042c873ed908a826c434c6c83c4ebd053b02942a8e46c70bd9957cb51aabf57","bytes":22166},"yatoon-app-shell-rtl.css":{"sha256":"5bfff9f1a2487fb1f78279a7ac5cd80c67d692012808bc3f8f50cad3bf5c78d0","bytes":32908},"yatoon-app-shell.min.css":{"sha256":"cd9ccb19cbef6a0d42feb7c0c2ae70a23eb04b8693df459778cdc3624d4e439c","bytes":23417},"yatoon-app-shell.min-rtl.css":{"sha256":"c5c52c43dedfa22859427bed065649e2e57df6ea9d6e0ead55ece2b2be1e292f","bytes":23425},"manage-booking-rtl.css":{"sha256":"3ebdcf5de5c267e52ad3cbd47fd7a743501b298cb97532bbfa9dde6001f5777f","bytes":19510},"manage-booking.min.css":{"sha256":"431d40da00dd526cb762ebddf10377431e1982b465a3a0943124a28b17c8f444","bytes":16028},"manage-booking.min-rtl.css":{"sha256":"92c6d808efd9c37c87a618ee97430688d4a8478d28648953b41dff28670a7570","bytes":13563},"staff-portal-rtl.css":{"sha256":"8af62f4563b1ab40a95eb2be04c547ae7e16d9ffa94e7b15d2c8aa8baf3088ac","bytes":123823},"staff-portal.min.css":{"sha256":"b33d5f555a6d1eda7aff23aa4c4ba926143fcad79a5fe68bd6384c44d8a5951e","bytes":92450},"staff-portal.min-rtl.css":{"sha256":"a9d3af72734dd81db4d5ddff67a0b044bb8d2ce041a4d69a66405cfb05f7a788","bytes":92481},"customer-portal-rtl.css":{"sha256":"3a3533ba65af2f364016e4e4ca30ecdc03f47b84a325835ce6b3fc84cc818581","bytes":34643},"customer-portal.min.css":{"sha256":"e886e0d65396c4dd202c93703547e3ec1f111f3be6910f135a951e392615733e","bytes":26556},"customer-portal.min-rtl.css":{"sha256":"dba031b25b63dbcde7ecd9004a71ef4174ba202aceb4e69e9f8590a879cedbf5","bytes":26343},"admin-setup-wizard-rtl.css":{"sha256":"8c2381f5b07e05067b316c10ce4a9333f1ab62714d197bcb85c7ce67e967d676","bytes":7502},"admin-setup-wizard.min.css":{"sha256":"4a9ef36fa5a16fd188b6db80e86a3d825bc786d844a3f1905bcc2facc504d0ba","bytes":7046},"admin-setup-wizard.min-rtl.css":{"sha256":"9cf36af333dae3b82ca7bc98f99ae297e5e59c34ac1766fafb4fa5364bd6e818","bytes":7048}},"frontend_builder":"terser-5.50.0","staff_portal_builder":"Source-equivalent production JS/CSS; inherited assets retain their original build.","internal_build":"490-free-20261025-theme-header-lang-picker-compact-languages-ordered-language-picker-mobile-single-brand-row","css_note":"4.8.2: yatoon-booking-core/flow/final min bundles and frontend/ui-polish/final sources had declarations removed that a later same-selector rule in the same stylesheet always overrides; outputs were edited in place (not rebuilt with cssnano), so hashes are of the shipped files. 4.8.5: core/flow/final min files also had declarations removed that never take effect in the captured cascade and rules for class names the plugin never outputs; outputs edited in place.","css_bundles":{"yatoon-booking-core.bundle.min.css":{"sha256":"bd16143171cd19aa9b2ef73ee546de2846fe78a8b4b3566d26ec9af34bd85de6","bytes":208911,"parts":["yatoon-theme-isolation.css","yatoon-booking-core.min.css"]},"yatoon-booking-core.bundle.min-rtl.css":{"sha256":"95a8512c82e3839ce88cae5aaf50236a52e9c57b9abad0bc1a04e62bdbd6d399","bytes":208984,"parts":["yatoon-theme-isolation.css","yatoon-booking-core.min-rtl.css"]},"yatoon-booking-layers.bundle.min.css":{"sha256":"319ec68f7b14bc6ccb1997f747d36bbab8f985cc73b0c21be51460ba60f96595","bytes":79694,"parts":["yatoon-color-schemes.min.css","yatoon-service-image-frame.css","yatoon-theme-armor-4723.min.css","yatoon-booking-responsive.min.css"]},"yatoon-booking-layers.bundle.min-rtl.css":{"sha256":"0361d779e153376c92d30f991c2ffe1b20f9c39bd6ccd8130ac97f968765d74f","bytes":79698,"parts":["yatoon-color-schemes.min.css","yatoon-service-image-frame.css","yatoon-theme-armor-4723.min.css","yatoon-booking-responsive.min-rtl.css"]},"yatoon-booking-extras.bundle.min.css":{"sha256":"8eb2ad13592a630dc19ff24101d4c69aead136eee39fef404ef6322a46d7d508","bytes":9565,"parts":["yatoon-seasonal-viewport-4781.css","yatoon-booking-discovery.css"]}},"js_note":"4.8.5: the booking form is split into frontend.js, frontend-flow.js, frontend-cart.js and frontend-boot.js (sources). build_frontend.js generates the lazy chunks frontend-availability.js and frontend-steps.js, every .min.js, and frontend.bundle.min.js.","css_sources":"4.9.0: assets/css/src/yatoon-booking-core.css and yatoon-booking-flow.css are readable sources with exactly the rules of the shipped .min files; rebuild with csso --no-restructure (keeps rule order), then rtlcss.","header_note":"Compact local SVG country flags with native details navigation and a circular account control. Header remains visible on step one only.","cart_note":"Single section dividers and aligned total/duration rows in the mobile drawer; booking calculations are unchanged."}
  • yatoon-booking-system/tags/4.10.2/changelog.txt

    r3780403 r3780965  
    66* Security check: with Strict mode off, a Turnstile check that cannot load (for example behind an ad blocker) no longer blocks a real customer; Strict mode on still blocks.
    77* Checkout: a refusal before any payment (security check, booking limit) now shows a message and keeps the form editable instead of switching to the booking-status panel.
     8* Stripe checkout: a payment Stripe refuses (no payment method chosen, card declined, failed verification) now shows the reason and keeps the payment form open so the customer can pay again; it no longer ends the checkout in the booking-status panel.
     9* Orders: a cancelled appointment no longer counts toward the balance due. When every appointment on an order is cancelled the order shows as Cancelled with nothing owed; any deposit kept or refunded stays in the payment history.
     10* Stripe checkout: in a cart with two paid appointments, the second payment form now starts with a working Pay button. Before, it kept the first payment's "Processing..." state and the customer could not pay for the second appointment.
     11* Bookings list: a cart with appointments on different days shows the date range, and each expanded row shows its own day, instead of one long block on the first day.
    812* Manage booking: date and time follow the appointments that are still booked after a partial cancel; carts on different days show each day.
    913
  • yatoon-booking-system/tags/4.10.2/includes/class-yatoon-commerce.php

    r3780403 r3780965  
    8181        add_action( 'yatoon_booking_created', array( __CLASS__, 'create_for_booking' ), 70, 1 );
    8282        add_action( 'yatoon_payment_status_changed', array( __CLASS__, 'sync_payment_status' ), 20, 4 );
     83        // 4.10.2: after the cancellation refund (priority 20) so the order shows what is really owed.
     84        add_action( 'yatoon_booking_status_changed', array( __CLASS__, 'booking_status_changed' ), 30, 3 );
    8385        add_action( 'admin_menu', array( __CLASS__, 'add_menu' ), 23 );
    8486        add_action( 'admin_post_yatoon_record_order_payment', array( __CLASS__, 'record_payment' ) );
    … …  
    602604        ) ) );
    603605        $net_paid = max( 0, $paid - $refunds );
    604         $balance = max( 0, (float) $order->total - $net_paid );
    605         $status = $refunds >= $paid && $paid > 0 ? 'refunded' : ( $balance <= 0 ? 'paid' : ( $net_paid > 0 ? 'partial' : 'open' ) );
     606        // 4.10.2: a cancelled appointment is no longer owed. Its line stays on the
     607        // order for the record, but it does not count toward the balance due. When
     608        // every appointment is cancelled nothing is owed (a tip line included) and
     609        // the order is marked cancelled; money kept or refunded stays in the ledger.
     610        $lines = yatoon_db_get_row( $wpdb->prepare(
     611            "SELECT COUNT(i.booking_id) AS booked,
     612                    COALESCE(SUM(CASE WHEN LOWER(b.status) IN ('cancelled','canceled','cancelled_by_customer','cancelled_by_seller','declined') THEN 1 ELSE 0 END),0) AS voided,
     613                    COALESCE(SUM(CASE WHEN LOWER(b.status) IN ('cancelled','canceled','cancelled_by_customer','cancelled_by_seller','declined') THEN i.line_total ELSE 0 END),0) AS voided_total
     614             FROM {$wpdb->prefix}yatoon_order_items i
     615             LEFT JOIN {$wpdb->prefix}yatoon_bookings b ON b.id=i.booking_id
     616             WHERE i.order_id=%d",
     617            $order_id
     618        ) );
     619        $all_cancelled = $lines && (int) $lines->booked > 0 && (int) $lines->voided >= (int) $lines->booked;
     620        $owed = $all_cancelled ? 0.0 : max( 0, (float) $order->total - ( $lines ? (float) $lines->voided_total : 0 ) );
     621        $balance = max( 0, round( $owed - $net_paid, 2 ) );
     622        $status = $refunds >= $paid && $paid > 0 ? 'refunded' : ( $all_cancelled ? 'cancelled' : ( $balance <= 0 ? 'paid' : ( $net_paid > 0 ? 'partial' : 'open' ) ) );
    606623        $saved_order = yatoon_db_update(
    607624            $wpdb->prefix . 'yatoon_orders',
    … …  
    615632        );
    616633        return false !== $saved_order && false !== $saved_invoice;
     634    }
     635
     636    /** 4.10.2: refresh the order balance when one of its appointments is cancelled or restored. */
     637    public static function booking_status_changed( $booking_id, $new_status = '', $old_status = '' ) {
     638        global $wpdb;
     639        $booking_id = absint( $booking_id );
     640        if ( ! $booking_id || (string) $new_status === (string) $old_status ) {
     641            return;
     642        }
     643        $order_ids = array_map( 'absint', (array) yatoon_db_get_col( $wpdb->prepare(
     644            "SELECT DISTINCT order_id FROM {$wpdb->prefix}yatoon_order_items WHERE booking_id=%d",
     645            $booking_id
     646        ) ) );
     647        foreach ( array_filter( $order_ids ) as $order_id ) {
     648            self::recalculate( $order_id );
     649        }
    617650    }
    618651
  • yatoon-booking-system/tags/4.10.2/readme.txt

    r3780403 r3780965  
    212212* Security check: with Strict mode off, a Turnstile check that cannot load (for example behind an ad blocker) no longer blocks a real customer; Strict mode on still blocks.
    213213* Checkout: a refusal before any payment (security check, booking limit) now shows a message and keeps the form editable instead of switching to the booking-status panel.
     214* Stripe checkout: a payment Stripe refuses (no payment method chosen, card declined, failed verification) now shows the reason and keeps the payment form open so the customer can pay again; it no longer ends the checkout in the booking-status panel.
     215* Orders: a cancelled appointment no longer counts toward the balance due. When every appointment on an order is cancelled the order shows as Cancelled with nothing owed; any deposit kept or refunded stays in the payment history.
     216* Stripe checkout: in a cart with two paid appointments, the second payment form now starts with a working Pay button. Before, it kept the first payment's "Processing..." state and the customer could not pay for the second appointment.
     217* Bookings list: a cart with appointments on different days shows the date range, and each expanded row shows its own day, instead of one long block on the first day.
    214218* Manage booking: date and time follow the appointments that are still booked after a partial cancel; carts on different days show each day.
    215219
  • yatoon-booking-system/trunk/admin/views/bookings.php

    r3780403 r3780965  
    367367                        if ( $summary_end && ( ! $get || $summary_end > $get ) ) { $get = $summary_end; }
    368368                    }
     369                    // 4.10.2: label each expanded row with its day when the group covers more than one day (cancelled rows included).
     370                    $yatoon_group_days = array();
     371                    foreach ( $gbks as $yatoon_day_row ) {
     372                        $yatoon_day_start = yatoon_booking_display_dt( $yatoon_day_row, 'start_time', $_sbs_local_tz, $_sbs_utc_tz );
     373                        if ( $yatoon_day_start ) { $yatoon_group_days[ $yatoon_day_start->format( 'Y-m-d' ) ] = true; }
     374                    }
     375                    $yatoon_group_multi_day = count( $yatoon_group_days ) > 1;
    369376                    $total_price = array_sum(array_column($summary_bookings, 'total_price'));
    370377                    $group_statuses = array_unique( array_map( static fn( $b ) => (string) $b->status, $summary_bookings ) );
    … …  
    432439                    <td><span style="font-size:12px;"><?php echo esc_html( $staff_names ); ?></span></td>
    433440                    <td>
     441                        <?php // 4.10.2: a cart can hold appointments on different days; show the date range, not one long block. ?>
     442                        <?php if ( $gst->format( 'Y-m-d' ) === $get->format( 'Y-m-d' ) ) : ?>
    434443                        <strong><?php echo esc_html( $gst->format('M j, Y') ); ?></strong><br>
    435444                        <small><?php echo esc_html( $gst->format('g:i A') ); ?> - <?php echo esc_html( $get->format('g:i A') ); ?></small>
     445                        <?php else : ?>
     446                        <strong><?php echo esc_html( $gst->format( 'Y' ) === $get->format( 'Y' ) ? $gst->format( 'M j' ) . ' – ' . $get->format( 'M j, Y' ) : $gst->format( 'M j, Y' ) . ' – ' . $get->format( 'M j, Y' ) ); ?></strong><br>
     447                        <small><?php esc_html_e( 'Several days. Expand for each time.', 'yatoon-booking-system' ); ?></small>
     448                        <?php endif; ?>
    436449                    </td>
    437450                    <td><?php echo esc_html( $yatoon_admin_money( $total_price ) ); if ( $cancelled_count ) { echo '<br><small>' . esc_html( $active_bookings ? __( 'Excludes cancelled items', 'yatoon-booking-system' ) : __( 'Cancelled service total', 'yatoon-booking-system' ) ) . '</small>'; } echo wp_kses_post(YATOON_Gift_Cards::summary_html($summary_bookings)); ?></td>
    … …  
    493506                    <td><?php echo esc_html($yatoon_booking_row->staff_name ?: 'N/A'); ?></td>
    494507                    <td>
    495                         <small style="color:#0369a1;font-weight:600;"><?php echo esc_html( $yatoon_booking_start_time->format('g:i A') ); ?></small>
     508                        <small style="color:#0369a1;font-weight:600;"><?php echo esc_html( ( $yatoon_group_multi_day ? $yatoon_booking_start_time->format( 'M j' ) . ' · ' : '' ) . $yatoon_booking_start_time->format('g:i A') ); ?></small>
    496509                        <small> - <?php echo esc_html( $yatoon_booking_end_time->format('g:i A') ); ?></small>
    497510                    </td>
  • yatoon-booking-system/trunk/assets/css/manage-booking.css

    r3729808 r3780965  
    737737 .yatoon-manage-wrap .yatoon-rebook-btn { width:100%; }
    738738}
     739/* Customer appointment management language menu. */
     740.yatoon-manage-wrap .sbs-header-language{margin:0;position:relative;font-family:inherit}
     741.yatoon-manage-wrap .sbs-language-picker{position:relative;margin:0;padding:0;border:0}
     742.yatoon-manage-wrap .sbs-language-trigger{display:flex;align-items:center;gap:6px;min-height:40px;padding:7px 9px;border:0;border-radius:999px;background:transparent;color:#111827;font-family:inherit;font-size:13px;font-weight:600;line-height:1.4;cursor:pointer;list-style:none}
     743.yatoon-manage-wrap .sbs-language-trigger::-webkit-details-marker{display:none}.yatoon-manage-wrap .sbs-language-trigger::marker{content:''}.yatoon-manage-wrap .sbs-language-trigger::before,.yatoon-manage-wrap .sbs-language-trigger::after{content:none!important}
     744.yatoon-manage-wrap .sbs-language-flag{display:inline-flex!important;flex:0 0 21px;width:21px!important;height:21px!important;overflow:hidden;border-radius:50%;border:1px solid rgba(128,128,128,.22);box-sizing:border-box}
     745.yatoon-manage-wrap .sbs-language-flag svg{display:block;width:100%!important;height:100%!important;max-width:none!important}
     746.yatoon-manage-wrap .sbs-language-chevron{width:12px;height:12px;flex:0 0 12px}.yatoon-manage-wrap .sbs-language-picker[open] .sbs-language-chevron{transform:rotate(180deg)}
     747.yatoon-manage-wrap .sbs-language-panel{position:absolute;z-index:10004;right:0;top:calc(100% + 5px);box-sizing:border-box;width:min(220px,calc(100vw - 40px));max-height:65vh;overflow:auto;padding:5px;border:1px solid #e5e7eb;border-radius:12px;background:#fff;box-shadow:0 8px 28px rgba(0,0,0,.16)}
     748.yatoon-manage-wrap .sbs-language-picker:not([open]) .sbs-language-panel{display:none!important}
     749.yatoon-manage-wrap .sbs-language-choice{display:flex!important;align-items:center;gap:9px;width:100%;min-height:42px;margin:0;padding:9px;border:0!important;border-radius:8px;background:transparent!important;color:#111827!important;box-shadow:none!important;font-family:inherit;font-size:13px;font-weight:500;line-height:1.4;text-align:left;text-transform:none;cursor:pointer}
     750.yatoon-manage-wrap .sbs-language-choice:hover,.yatoon-manage-wrap .sbs-language-choice[aria-current=true],.yatoon-manage-wrap .sbs-language-trigger:hover{background:#f3f4f6!important}.yatoon-manage-wrap .sbs-language-check{margin-left:auto}.yatoon-manage-wrap .sbs-language-trigger:focus-visible,.yatoon-manage-wrap .sbs-language-choice:focus-visible{outline:2px solid #2563eb;outline-offset:2px}
  • yatoon-booking-system/trunk/assets/css/manage-booking.min.css

    r3767282 r3780965  
    11/*! Yatoon Booking System 4.9.0 */.yatoon-manage-wrap{--yatoon-primary-light:#fdf2f8;--yatoon-text:#1f2937;--yatoon-radius:12px;color:var(--yatoon-text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;margin:0 auto;max-width:520px;padding:20px 16px 40px}.yatoon-manage-card{background:#fff;border:1px solid var(--yatoon-border);box-shadow:0 4px 24px rgba(0,0,0,.07)}.yatoon-manage-header{background:var(--yatoon-primary);color:#fff;padding:24px 28px 20px}.yatoon-manage-header h2{font-size:20px;font-weight:700}.yatoon-manage-subtitle{opacity:.85}.yatoon-manage-details{border-bottom:1px solid var(--yatoon-border);padding:20px 28px}.yatoon-manage-row{align-items:baseline;border-bottom:1px solid #f3f4f6;padding:7px 0}.ymr-label{flex:0 0 90px;letter-spacing:.04em;text-transform:uppercase}.yatoon-manage-actions{gap:12px;padding:24px 28px 4px}.yatoon-btn{border:2px solid transparent;border-radius:8px;font-size:14px;font-weight:600;padding:10px 20px;transition:all .15s}.yatoon-btn:disabled{opacity:.6}.yatoon-btn-primary:hover:not(:disabled){filter:brightness(1.1)}.yatoon-btn-outline{background:transparent;border-color:var(--yatoon-primary)}.yatoon-btn-ghost{background:transparent;border-color:var(--yatoon-border)}.yatoon-btn-ghost:hover:not(:disabled){background:#f9fafb}.yatoon-btn-sm{padding:7px 14px}.yatoon-reschedule-panel{background:#fafafa;border-top:1px solid var(--yatoon-border);padding:20px 28px}.yatoon-reschedule-panel h3{font-size:15px}.yatoon-reschedule-date-row{align-items:center;display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.yatoon-reschedule-date-row label{color:var(--yatoon-muted);font-size:13px;font-weight:600}.yatoon-reschedule-date-row input[type=date]{border:1.5px solid var(--yatoon-border);border-radius:7px;outline:none;padding:8px 10px}.yatoon-slots-list{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}.yatoon-slot-chip{background:#fff;border:1.5px solid var(--yatoon-border);border-radius:20px;cursor:pointer;font-weight:600;padding:8px 14px;transition:all .15s}.yatoon-reschedule-confirm-row{display:flex;gap:10px;margin-top:4px}.yatoon-change-services-help{color:var(--yatoon-muted);font-size:13px;line-height:1.45;margin:-4px 0 14px}.yatoon-change-services-status{border-radius:10px;font-size:14px;line-height:1.5;margin:12px 0;padding:12px 14px}.yatoon-change-services-panel .yatoon-reschedule-confirm-row{flex-wrap:wrap}.yatoon-change-services-panel .yatoon-reschedule-confirm-row .yatoon-btn{flex:1 1 150px}.yatoon-service-change-row{align-items:center;display:grid;gap:10px;grid-template-columns:92px minmax(0,1fr);margin-bottom:12px}.yatoon-service-change-row label{color:var(--yatoon-muted);font-size:13px;font-weight:700}.yatoon-service-change-select,.yatoon-service-change-staff{background:#fff;border:1px solid var(--yatoon-border);border-radius:10px;color:var(--yatoon-text);font-size:14px;min-height:42px;padding:8px 10px;width:100%}.yatoon-service-change-staff-note{color:var(--yatoon-muted);font-size:12px;grid-column:2;margin-top:-4px}.yatoon-service-remove-row{background:#fff;border:1px solid var(--yatoon-border);border-radius:999px;color:#dc2626;cursor:pointer;font-weight:700;grid-column:2;min-height:38px;padding:7px 12px}.yatoon-manage-notice{border-radius:8px;margin:16px 28px;padding:12px 16px}.yatoon-notice-success{background:#d1fae5;color:#065f46}.yatoon-notice-warning{background:#fef3c7;color:#92400e}.yatoon-notice-info{color:#1e40af}.yatoon-manage-error{background:#fee2e2;border-radius:8px;color:#991b1b;padding:14px 18px}.yatoon-manage-footer{border-top:1px solid var(--yatoon-border);padding:16px 28px;text-align:center}.yatoon-after-cancel-actions{display:flex;flex-wrap:wrap;gap:10px;padding:0 28px 18px}.yatoon-after-cancel-actions .yatoon-btn{flex:1 1 180px;justify-content:center}@media (max-width:480px){.yatoon-manage-wrap{padding:12px 8px 32px}.yatoon-manage-actions,.yatoon-manage-details,.yatoon-manage-header,.yatoon-manage-notice,.yatoon-reschedule-panel{padding-left:18px;padding-right:18px}.yatoon-manage-actions{flex-direction:column}.yatoon-after-cancel-actions{flex-direction:column;padding-left:18px;padding-right:18px}.yatoon-btn{justify-content:center}.yatoon-service-change-row{grid-template-columns:1fr}.yatoon-service-change-staff-note,.yatoon-service-remove-row{grid-column:1}}.yatoon-manage-footer .yatoon-manage-return{color:var(--yatoon-text);display:inline-flex;text-decoration:none}.yatoon-manage-footer .yatoon-manage-return:hover{text-decoration:none}.yatoon-manage-wrap{--yatoon-border:#e5e7eb;--yatoon-radius:16px}.yatoon-manage-card{box-shadow:0 14px 36px rgba(15,23,42,.08)}.yatoon-manage-header h2{letter-spacing:0}.ymr-value{color:#111827}.yatoon-btn{border-width:1px;min-height:42px}.yatoon-btn-primary{box-shadow:0 10px 22px rgba(236,72,153,.16)}.yatoon-btn-ghost,.yatoon-btn-outline{background:#fff}.yatoon-reschedule-panel{background:#f8fafc}.yatoon-reschedule-date-row input[type=date]{border-width:1px;min-height:40px}.yatoon-reschedule-date-row input[type=date]:focus{box-shadow:0 0 0 3px rgba(236,72,153,.12)}.yatoon-slot-chip{border-width:1px}.yatoon-manage-wrap{--yatoon-primary:#ec4899;--yatoon-primary-light:rgba(236,72,153,.1);--yatoon-text:#111827;--yatoon-muted:#6b7280;--yatoon-border:rgba(0,0,0,.08);--yatoon-surface:#fff;--yatoon-surface2:#f8fafc;--yatoon-radius:20px;--yatoon-ease:cubic-bezier(.4,0,.2,1);-webkit-font-smoothing:antialiased;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,Arial,sans-serif}.yatoon-manage-card{background:var(--yatoon-surface);border:1px solid rgba(0,0,0,.08);border-radius:var(--yatoon-radius);box-shadow:0 16px 48px rgba(15,23,42,.11),inset 0 1px 0 hsla(0,0%,100%,.9);overflow:hidden}.yatoon-manage-header{background:#111827;padding:28px 30px 22px}.yatoon-manage-header h2{border:none;color:#fff;font-size:22px;font-weight:900;letter-spacing:-.03em;margin:0 0 4px}.yatoon-manage-subtitle{color:hsla(0,0%,100%,.65);font-size:13px;margin:0}.yatoon-manage-details{border-bottom:1px solid rgba(0,0,0,.06);padding:22px 30px}.yatoon-manage-row{align-items:flex-start;border-bottom:1px solid rgba(0,0,0,.05);display:flex;font-size:14px;gap:16px;justify-content:space-between;padding:8px 0;transition:background .15s}.yatoon-manage-row:last-child{border-bottom:none}.ymr-label{color:var(--yatoon-muted);flex:0 0 100px;font-size:12px;font-weight:700;letter-spacing:0;text-transform:none}.ymr-value{color:var(--yatoon-text);font-weight:600;text-align:right}.yatoon-manage-actions{display:flex;flex-wrap:wrap;gap:10px;padding:22px 30px 8px}.yatoon-btn{align-items:center;border:1px solid transparent;border-radius:999px;cursor:pointer;display:inline-flex;font-size:15px;font-weight:700;gap:6px;letter-spacing:-.01em;min-height:44px;padding:10px 22px;text-decoration:none;transition:all .18s var(--yatoon-ease)}.yatoon-btn:hover{transform:translateY(-1px)}.yatoon-btn:active{transform:scale(.97)}.yatoon-btn:disabled{cursor:not-allowed;opacity:.5;transform:none}.yatoon-btn-primary{background:var(--yatoon-primary);border-color:var(--yatoon-primary);box-shadow:0 6px 18px rgba(236,72,153,.25);color:#fff}.yatoon-btn-primary:hover:not(:disabled){box-shadow:0 10px 26px rgba(236,72,153,.32);filter:brightness(1.06)}.yatoon-btn-outline{background:var(--yatoon-surface);border-color:rgba(236,72,153,.3);color:var(--yatoon-primary)}.yatoon-btn-outline:hover:not(:disabled){background:var(--yatoon-primary-light);border-color:var(--yatoon-primary)}.yatoon-btn-ghost{background:var(--yatoon-surface);border-color:rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.06);color:var(--yatoon-muted)}.yatoon-btn-ghost:hover:not(:disabled){background:var(--yatoon-surface2);color:var(--yatoon-text)}.yatoon-btn-sm{font-size:13px;min-height:36px;padding:7px 16px}.yatoon-reschedule-panel{background:var(--yatoon-surface2);border-top:1px solid rgba(0,0,0,.07);padding:22px 30px}.yatoon-reschedule-panel h3{color:var(--yatoon-text);font-size:16px;font-weight:800;letter-spacing:-.015em;margin:0 0 16px}.yatoon-reschedule-date-row input[type=date]{background:var(--yatoon-surface);border:1px solid rgba(0,0,0,.14);border-radius:10px;font-size:14px;min-height:42px;transition:border-color .18s,box-shadow .18s}.yatoon-reschedule-date-row input[type=date]:focus{border-color:var(--yatoon-primary);box-shadow:0 0 0 4px rgba(236,72,153,.12);outline:none}.yatoon-slot-chip{background:var(--yatoon-surface);border:1px solid rgba(0,0,0,.1);border-radius:999px;box-shadow:0 1px 3px rgba(0,0,0,.06);color:var(--yatoon-text);font-size:13px;font-weight:700;padding:9px 16px;transition:all .15s var(--yatoon-ease)}.yatoon-slot-chip:hover{border-color:var(--yatoon-primary);box-shadow:0 3px 10px rgba(236,72,153,.15);color:var(--yatoon-primary);transform:translateY(-1px)}.yatoon-slot-chip.selected{background:var(--yatoon-primary);border-color:var(--yatoon-primary);box-shadow:0 4px 14px rgba(236,72,153,.28);color:#fff}.yatoon-manage-notice{border-radius:12px;font-size:14px;font-weight:500;line-height:1.5;margin:14px 30px;padding:13px 16px}.yatoon-notice-success{background:#dcfce7;border-left:3px solid #22c55e;color:#15803d}.yatoon-notice-error{background:#fee2e2;border-left:3px solid #ef4444;color:#991b1b}.yatoon-notice-warning{background:#fef9c3;border-left:3px solid #f59e0b;color:#a16207}.yatoon-notice-info{background:#eff6ff;border-left:3px solid #3b82f6;color:#1d4ed8}.yatoon-manage-footer{background:var(--yatoon-surface2);border-top:1px solid rgba(0,0,0,.06);color:var(--yatoon-muted);font-size:13px;padding:16px 30px}.yatoon-manage-footer a{color:var(--yatoon-primary);font-weight:600;text-decoration:none}.yatoon-manage-footer a:hover{text-decoration:underline}.yatoon-status-pending{color:#d97706;font-weight:700}.yatoon-status-confirmed{color:#059669;font-weight:700}.yatoon-status-completed{color:#6b7280;font-weight:700}.yatoon-status-cancelled,.yatoon-status-cancelled_by_customer{color:#dc2626;font-weight:700}.yatoon-cancel-choice{align-items:flex-start;cursor:pointer;display:inline-flex;gap:10px;justify-content:flex-end}.yatoon-cancel-choice input{flex:0 0 auto;margin-top:3px}.yatoon-cancel-choice span{display:block}@media (max-width:480px){.yatoon-manage-wrap{padding:10px 6px 28px}.yatoon-manage-actions,.yatoon-manage-details,.yatoon-manage-footer,.yatoon-manage-header,.yatoon-manage-notice,.yatoon-reschedule-panel{padding-left:18px;padding-right:18px}.yatoon-manage-actions{flex-direction:column}.yatoon-btn{justify-content:center}}#yatoon-after-cancel-actions.yatoon-after-cancel-actions{align-items:center;display:flex;flex-wrap:wrap;gap:10px;padding:0 30px 18px}#yatoon-after-cancel-actions.yatoon-after-cancel-actions .yatoon-btn{border-radius:14px;box-shadow:none;flex:1 1 180px;font-size:14px;height:auto;line-height:1.2;max-height:48px;min-height:44px;padding:10px 16px}#yatoon-manage-overlay :where(button,a,input,select,textarea,[tabindex]):focus-visible,.yatoon-manage-wrap :where(button,a,input,select,textarea):focus-visible{outline:3px solid #2563eb;outline-offset:3px}@media (prefers-reduced-motion:reduce){#yatoon-manage-overlay *,.yatoon-manage-wrap *{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}@media (max-width:480px){#yatoon-after-cancel-actions.yatoon-after-cancel-actions{flex-direction:column;padding-left:18px;padding-right:18px}#yatoon-after-cancel-actions.yatoon-after-cancel-actions .yatoon-btn{flex:0 0 auto;width:100%}}.yatoon-manage-wrap{box-sizing:border-box;min-width:0}.yatoon-manage-wrap .ymr-value{min-width:0;overflow-wrap:anywhere}.yatoon-manage-wrap .yatoon-manage-row{gap:14px}#yatoon-manage-overlay button,.yatoon-manage-wrap :is(.yatoon-btn,input:not([type=checkbox]),select,textarea){border-radius:12px;box-sizing:border-box;line-height:1.35;min-height:44px}.yatoon-manage-wrap .yatoon-btn{text-align:center;white-space:normal}@media(max-width:640px){.yatoon-manage-wrap :is(input:not([type=checkbox]),select,textarea){font-size:16px}.yatoon-manage-wrap .yatoon-manage-row{align-items:flex-start}.yatoon-manage-wrap .ymr-label{flex-basis:85px}}.yatoon-manage-wrap #yatoon-reschedule-btn{background:#15803d!important;border-color:#15803d!important;color:#fff!important}.yatoon-manage-wrap #yatoon-change-services-btn{background:#1d4ed8!important;border-color:#1d4ed8!important;color:#fff!important}.yatoon-manage-wrap #yatoon-cancel-btn{background:#b91c1c!important;border-color:#b91c1c!important;color:#fff!important}.yatoon-manage-wrap #yatoon-reschedule-btn:hover{background:#166534!important}.yatoon-manage-wrap #yatoon-change-services-btn:hover{background:#1e40af!important}.yatoon-manage-wrap #yatoon-cancel-btn:hover{background:#991b1b!important}.yatoon-manage-wrap .yatoon-slot-chip{background:#fff!important;border:1px solid #cbd5e1!important;color:#111827!important;min-height:44px}.yatoon-manage-wrap .yatoon-slot-chip:hover{background:#f1f5f9!important}.yatoon-manage-wrap .yatoon-slot-chip.selected{background:#fce7f3!important;border:2px solid #be185d!important;color:#111827!important}.yatoon-manage-wrap .yatoon-manage-rebook{margin:0;padding:0 30px 18px}.yatoon-manage-wrap .yatoon-rebook-btn{background:#15803d!important;border-color:#15803d!important;color:#fff!important;justify-content:center;min-height:44px!important;text-decoration:none!important}.yatoon-manage-wrap .yatoon-rebook-btn:hover{background:#166534!important}@media(max-width:480px){.yatoon-manage-wrap .yatoon-manage-rebook{padding-inline:18px}.yatoon-manage-wrap .yatoon-rebook-btn{width:100%}}
     2
     3/* Customer appointment management language menu. */
     4.yatoon-manage-wrap .sbs-header-language{margin:0;position:relative;font-family:inherit}
     5.yatoon-manage-wrap .sbs-language-picker{position:relative;margin:0;padding:0;border:0}
     6.yatoon-manage-wrap .sbs-language-trigger{display:flex;align-items:center;gap:6px;min-height:40px;padding:7px 9px;border:0;border-radius:999px;background:transparent;color:#111827;font-family:inherit;font-size:13px;font-weight:600;line-height:1.4;cursor:pointer;list-style:none}
     7.yatoon-manage-wrap .sbs-language-trigger::-webkit-details-marker{display:none}.yatoon-manage-wrap .sbs-language-trigger::marker{content:''}.yatoon-manage-wrap .sbs-language-trigger::before,.yatoon-manage-wrap .sbs-language-trigger::after{content:none!important}
     8.yatoon-manage-wrap .sbs-language-flag{display:inline-flex!important;flex:0 0 21px;width:21px!important;height:21px!important;overflow:hidden;border-radius:50%;border:1px solid rgba(128,128,128,.22);box-sizing:border-box}
     9.yatoon-manage-wrap .sbs-language-flag svg{display:block;width:100%!important;height:100%!important;max-width:none!important}
     10.yatoon-manage-wrap .sbs-language-chevron{width:12px;height:12px;flex:0 0 12px}.yatoon-manage-wrap .sbs-language-picker[open] .sbs-language-chevron{transform:rotate(180deg)}
     11.yatoon-manage-wrap .sbs-language-panel{position:absolute;z-index:10004;right:0;top:calc(100% + 5px);box-sizing:border-box;width:min(220px,calc(100vw - 40px));max-height:65vh;overflow:auto;padding:5px;border:1px solid #e5e7eb;border-radius:12px;background:#fff;box-shadow:0 8px 28px rgba(0,0,0,.16)}
     12.yatoon-manage-wrap .sbs-language-picker:not([open]) .sbs-language-panel{display:none!important}
     13.yatoon-manage-wrap .sbs-language-choice{display:flex!important;align-items:center;gap:9px;width:100%;min-height:42px;margin:0;padding:9px;border:0!important;border-radius:8px;background:transparent!important;color:#111827!important;box-shadow:none!important;font-family:inherit;font-size:13px;font-weight:500;line-height:1.4;text-align:left;text-transform:none;cursor:pointer}
     14.yatoon-manage-wrap .sbs-language-choice:hover,.yatoon-manage-wrap .sbs-language-choice[aria-current=true],.yatoon-manage-wrap .sbs-language-trigger:hover{background:#f3f4f6!important}.yatoon-manage-wrap .sbs-language-check{margin-left:auto}.yatoon-manage-wrap .sbs-language-trigger:focus-visible,.yatoon-manage-wrap .sbs-language-choice:focus-visible{outline:2px solid #2563eb;outline-offset:2px}
  • yatoon-booking-system/trunk/assets/js/frontend-checkout.js

    r3780403 r3780965  
    988988                        })
    989989                        .catch(function(error) {
     990                            // 4.10.2: the Stripe form could not open, so nothing was charged.
     991                            // On the first appointment, let the customer simply try again.
     992                            if (idx === 0 && error && error.data && error.data.preflight) {
     993                                self._showToast(error.message || __('Stripe payment could not be started.'), 'error');
     994                                restoreSubmitButtons();
     995                                return;
     996                            }
    990997                            self._showBookingRecovery({reference:payload.client_reference,failedIndex:idx,message:error.message,uncertain:true});
    991998                            self._showToast(error && error.message ? error.message : __('Stripe payment could not be started.'), 'error');
  • yatoon-booking-system/trunk/assets/js/frontend-checkout.min.js

    r3780403 r3780965  
    1 !function(e,t){"use strict";const o=e.wp?.i18n?.__?t=>e.wp.i18n.__(t,"yatoon-booking-system"):e=>e,{ysSiteNow:n,ysSiteOffsetMin:s,ysFormatMoney:a,ysPriceVaries:i,ysMoneyFrom:r,ysCalcDeposit:c,validateBookingPhone:d,validateUSPhone:l,showFieldError:u,showPhoneError:m,validateCustomerName:g,validateCustomerEmail:f,ysFocusTrap:p}=e.YatoonBookingHelpers;class _{_paidCheckoutKey(e){return"yatoon-checkout:"+location.pathname+":"+e}_forgetPaidCheckout(e){if(e)try{sessionStorage.removeItem(this._paidCheckoutKey(e))}catch(e){}}_unresolvedPaidCheckout(e){const t=this._paidCheckoutKey("");try{for(let o=0;o<sessionStorage.length;o++){const n=sessionStorage.key(o);if(!n||0!==n.indexOf(t))continue;const s=n.slice(t.length);if(!s||s===e)continue;const a=JSON.parse(sessionStorage.getItem(n)||"null");if(a&&a.payment&&a.payment.id&&!a.resolved)return s}}catch(e){}return""}_showPaidRecoveryNotice(e,n){const s=this,a=t("#sbs-booking-recovery");a.length&&(a.addClass("sbs-booking-recovery-paid"),a.find("h3").first().text(o("Payment received, appointment not confirmed yet")),a.find(".sbs-booking-recovery-guidance").text(o('Your payment went through, but this appointment could not be confirmed. Please do not pay again. Use "Check payment and appointment status" to finish it, or contact the salon with the reference below so they can confirm another time or refund you.')),n&&!a.find(".sbs-paid-recovery-resolved").length&&t("<button/>",{type:"button",class:"sbs-paid-recovery-resolved",text:o("The salon has resolved this payment")}).appendTo(a.find(".sbs-booking-recovery-actions")).on("click",function(){try{const t=s._paidCheckoutKey(e),o=JSON.parse(sessionStorage.getItem(t)||"null");o&&(o.resolved=!0,sessionStorage.setItem(t,JSON.stringify(o)))}catch(e){}s._clearBookingRecovery(),s._saveSession()}))}submitBooking(){const n=this;if(this._handleBookingRecoveryAttempt())return;if(this._paymentsConfigured()&&"function"!=typeof this._ensurePaymentSdk){if(this._submissionChunkPromise)return;const e=t(".sbs-btn-submit").prop("disabled",!0).text(o("Loading secure payment..."));return void(this._submissionChunkPromise=this._loadFeatureChunk("payments").then(function(){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n.submitBooking()}).catch(function(t){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n._showToast(t.message||o("The secure payment component could not be loaded."),"error")}))}if(this._isSubmitting)return;if(this.designUploadInProgress)return void this._showToast(o("Please wait for the reference photo to finish uploading."),"info");if(!this._paymentsConfigured()&&(!this._turnstileTokenFetched||Date.now()>(this._turnstileTokenExpires||0))&&"function"==typeof e.yatoonGetTurnstileToken)return this._turnstileTokenFetched=!0,void e.yatoonGetTurnstileToken().then(function(t){e.yatoonTurnstileToken=t,n._turnstileTokenFetched=!0,n._turnstileTokenExpires=Date.now()+9e4,n.submitBooking()}).catch(function(){n._turnstileTokenFetched=!1,n._turnstileTokenExpires=0,n._showToast(o("Verification could not finish. Please try again."),"error")});const s=document.getElementById("sbs-otp-section"),a="undefined"!=typeof yatoonData&&"1"===yatoonData.verifyEmailOtp,i="undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp;if(s&&(a||i)){const e=(document.getElementById("sbs-customer-email")||{}).value||"",t=(document.getElementById("sbs-customer-phone")||{}).value||"",a=e.trim()+"|"+t.trim();s.dataset.sentContact&&s.dataset.sentContact!==a&&(s.dataset.sent="",s.dataset.sentContact="");const r="1"===s.dataset.verified&&s.dataset.verifiedContact===a;"1"!==s.dataset.verified||r||(s.dataset.verified="",s.dataset.verifiedKey="");const c=function(e){const t=String(e||"").replace(/\D/g,"");return 11===t.length&&"1"===t.charAt(0)?t.slice(1):t},l=i?""!==c(s.dataset.verifiedPhone)&&c(t)===c(s.dataset.verifiedPhone):!!s.dataset.verifiedEmail&&e.trim().toLowerCase()===s.dataset.verifiedEmail.toLowerCase(),g=s.dataset.deviceVerified===a;if(!r&&!l&&!g){const r=f(e);if(r){u("sbs-customer-email",r);const e=document.getElementById("sbs-customer-email");return void(e&&e.scrollIntoView({behavior:"smooth",block:"center"}))}if(i){const e=d(t);if(e){m(e);const t=document.getElementById("sbs-phone-display")||document.getElementById("sbs-customer-phone");return void(t&&t.scrollIntoView({behavior:"smooth",block:"center"}))}}if(s.dataset.deviceChecked!==a)return void jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_otp_device_check",nonce:yatoonData.nonce,email:e,phone:t},function(e){s.dataset.deviceChecked=a,e&&e.success&&e.data&&e.data.verified&&(s.dataset.deviceVerified=a),n.submitBooking()}).fail(function(){s.dataset.deviceChecked=a,n.submitBooking()});const c=(document.getElementById("sbs-customer-name")||{}).value||"",l=i?"sms":"email";s.style.display="block";const g=document.getElementById("sbs-otp-remember");if(g&&"1"===g.dataset.regionAuto&&!g.dataset.regionChecked){g.dataset.regionChecked="1";let e="";try{e=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(t){e=""}const t=/^Europe\/(Moscow|Minsk|Kaliningrad|Samara|Volgograd|Kirov|Astrakhan|Saratov|Ulyanovsk|Simferopol|Istanbul|Kiev|Kyiv|Uzhgorod|Zaporozhye|Chisinau|Tirane|Skopje|Sarajevo|Belgrade|Podgorica)$/;(/^Europe\//.test(e)&&!t.test(e)||/^Atlantic\/(Reykjavik|Madeira|Azores|Canary)$/.test(e))&&(g.checked=!1)}if(document.getElementById("sbs-otp-prompt").textContent="sms"===l?o("We are sending a verification code to your phone. Please enter it below."):o("We are sending a verification code to")+" "+e+". "+o("Please enter it below."),!s.dataset.sent){s.dataset.sent="1",s.dataset.sentContact=a;const n=document.getElementById("sbs-otp-msg"),i=document.getElementById("sbs-otp-resend-btn");n&&(n.textContent=o("Sending verification code..."),n.style.color=""),i&&(i.disabled=!0),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_send_otp",nonce:yatoonData.nonce,channel:l,email:e,phone:t,name:c},function(e){const t=!(!e||!e.success);n&&(n.textContent=t?e.data&&e.data.message?e.data.message:o("Verification code sent."):e&&e.data&&e.data.message?e.data.message:o("Failed to send code.")),i&&(i.style.display=""),i&&(i.disabled=!1),t||(s.dataset.sent="",s.dataset.sentContact="",n&&(n.style.color="#dc2626"))}).fail(function(){s.dataset.sent="",s.dataset.sentContact="",n&&(n.textContent=o("Could not send the verification code. Please try again."),n.style.color="#dc2626"),i&&(i.style.display="",i.disabled=!1)})}const p=document.getElementById("sbs-otp-verify-btn");p&&!p.dataset.bound&&(p.dataset.bound="1",p.addEventListener("click",function(){const e=(document.getElementById("sbs-otp-input")||{}).value||"",t=document.getElementById("sbs-otp-msg"),a=(document.getElementById("sbs-customer-email")||{}).value||"",i=(document.getElementById("sbs-customer-phone")||{}).value||"",r=a.trim()+"|"+i.trim();e.trim()?(p.disabled=!0,p.dataset.pending="1",t&&(t.textContent=o("Verifying..."),t.style.color=""),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_verify_otp",nonce:yatoonData.nonce,channel:l,email:a,phone:i,code:e,remember:document.getElementById("sbs-otp-remember")&&document.getElementById("sbs-otp-remember").checked?1:0},function(e){e.success?(s.dataset.verified="1",s.dataset.verifiedKey=e.data&&e.data.verified_key?e.data.verified_key:"",n._giftSubmissionQuote=null,n._dynamicQuoteKey="",s.dataset.verifiedContact=r,s.style.display="none",n.submitBooking()):(t&&(t.textContent=e.data&&e.data.message?e.data.message:o("Incorrect code.")),t&&(t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending="")}).fail(function(){t&&(t.textContent=o("Could not verify the code. Please try again."),t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending=""})):t&&(t.textContent=o("Enter the verification code first."),t.style.color="#dc2626")}));const _=document.getElementById("sbs-otp-resend-btn");return void(_&&!_.dataset.bound&&(_.dataset.bound="1",_.addEventListener("click",function(){if(_.disabled)return;s.dataset.sent="",s.dataset.sentContact="",s.dataset.verified="",s.dataset.verifiedKey="",s.dataset.verifiedContact="";const e=document.getElementById("sbs-otp-msg");e&&(e.textContent=""),n.submitBooking()})))}}if(this._isSubmitting=!0,this._clearBookingRecovery(),0===this.bookingCart.length&&this.selectedDateTime&&(this.pendingServices.length>0||this.selectedService)&&this.addToCart(),0===this.bookingCart.length)return this._isSubmitting=!1,void this._showToast(o("Please add at least one service to your booking."),"error");const r=t(".sbs-btn-submit"),c=t("#sbs-cart-next-btn"),l=t("#sbs-sidebar-next-btn"),g=r.text(),p=c.text(),_=l.text(),h=()=>{this._isSubmitting=!1,this._turnstileTokenFetched=!1,r.prop("disabled",!1).text(g),c.prop("disabled",!1).removeClass("sbs-submitting").text(p||o("Confirm Booking")),l.prop("disabled",!1).removeClass("sbs-submitting").text(_||o("Confirm Booking")),this._applyConfirmBtn(),this._updateMobileCartBar()};r.prop("disabled",!0).text(o("Confirming your booking...")),c.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),l.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),this._updateLegalBoxes();const y={customer_name:t("#sbs-customer-name").val(),customer_email:t("#sbs-customer-email").val(),customer_phone:t("#sbs-customer-phone").val(),client_timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"",notes:t("#sbs-customer-notes").val(),coupon_code:t("#sbs-coupon-code").length?t("#sbs-coupon-code").val():"",marketing_email_optin:t("#sbs-marketing-email-optin").is(":checked")?1:0,marketing_sms_optin:t("#sbs-marketing-sms-optin").is(":checked")?1:0,consent_accepted:t("#sbs-consent-accepted").length&&t("#sbs-consent-accepted").is(":checked")?1:0,consent_signature:t("#sbs-consent-signature").length?t("#sbs-consent-signature").val():"",policy_acknowledged:t("#sbs-policy-acknowledged").length&&t("#sbs-policy-acknowledged").is(":checked")?1:0,tip_amount:parseFloat(t("#sbs-tip-amount").val())||0,gift_card_code:t("#sbs-gift-card-code-confirmed").val()||"",custom_fields:function(){const e={};return t('[name^="cf["]').each(function(){const o=t(this).attr("name").replace(/^cf\[|\]$/g,"");t(this).is(":checkbox")||t(this).is(":radio")?t(this).is(":checked")&&(e[o]=t(this).val()):e[o]=t(this).val()}),JSON.stringify(e)}()},b=this._collectGuestContacts(),k=f(y.customer_email);if(k)return h(),u("sbs-customer-email",k),void(document.getElementById("sbs-customer-email")&&document.getElementById("sbs-customer-email").scrollIntoView({behavior:"smooth",block:"center"}));"undefined"!=typeof yatoonData&&yatoonData.phoneMode&&yatoonData.phoneMode;if(y.customer_phone){const e=d(y.customer_phone);if(e)return h(),m(e),void(document.getElementById("sbs-customer-phone")&&document.getElementById("sbs-customer-phone").scrollIntoView({behavior:"smooth",block:"center"}))}if(this._needsPolicyAck()&&!y.policy_acknowledged)return h(),this._showToast(o("Please review and acknowledge the cancellation / no-show policy before booking."),"error"),void this._focusBookingValidation(t("#sbs-policy-acknowledged"),t(".sbs-policy-box:visible").first());if(this._needsConsent()&&(!y.consent_accepted||!y.consent_signature.trim())){h(),this._showToast(o("Please read and sign the consent form before booking."),"error");const e=y.consent_accepted?t("#sbs-consent-signature"):t("#sbs-consent-accepted");return void this._focusBookingValidation(e,t(".sbs-consent-box:visible").first())}const v=this.bookingCart.slice(),C=[],w=JSON.stringify(v.map(e=>({start:e.dateTime&&e.dateTime.start_at,guest:e.guestLabel||"Me",services:(e.services||[{service:e.service,options:e.options,staff:e.staff}]).map(t=>({service:t.service&&t.service.id,options:(t.options||[]).map(e=>e.id),staff:t.staff&&t.staff.id||0,guest:t.guestLabel||e.guestLabel||"Me"}))}))),T=this._newBookingReference(w),I="undefined"!=typeof yatoonData&&(yatoonData.ajax_url||yatoonData.ajaxUrl)?yatoonData.ajax_url||yatoonData.ajaxUrl:"";if(!I)return h(),void this._showToast(o("Booking could not start because the WordPress AJAX URL is missing. Please refresh and try again."),"error");if(!v.length)return h(),void this._showToast(o("Please choose a service and time before confirming."),"error");const S=v.some(e=>e&&e.services&&e.services.length>1),P=v.length>1||S?"yatoon-"+T:"";let x=this.designUploadAttachmentId||0,B=this.designUploadToken||"";const D=o=>{const s=v[o],a=s.services||[{service:s.service,options:s.options,staff:s.staff}],i=s.dateTime&&s.dateTime.staff_map?s.dateTime.staff_map:null,r="string"==typeof i?function(){try{return JSON.parse(i)}catch(e){return null}}():i,c={},d={};a.forEach(function(e,t){const o=void 0!==e._originalIndex?e._originalIndex:t;s.dateTime.local_staff_map&&Number(s.dateTime.local_staff_map[o])>0&&(d[t]=Number(s.dateTime.local_staff_map[o])),r&&void 0!==r[o]&&(c[t]=r[o])});const l=Object.keys(c).length?JSON.stringify(c):i?"string"==typeof i?i:JSON.stringify(i):"",u=s.guestLabel||a[0]&&a[0].guestLabel||"Me",m=b[u]||{};let g=y.notes||"";if("Me"!==u){const e=[`Guest: ${u}`];m.name&&e.push(`Name: ${m.name}`),m.phone&&e.push(`Phone: ${m.phone}`),g=`${g}${g?"\n\n":""}${e.join(" - ")}`}const f={},p=new Date(s.dateTime.start_at).getTime(),_=a.map(e=>{const t=e.guestLabel||s.guestLabel||"Me",o=f[t]||0,a=Number.isFinite(p)?new Date(p+6e4*o).toISOString():s.dateTime.start_at;return f[t]=o+n._serviceDuration(e),{service_id:e.service.id,staff_id:e.staff?e.staff.id:0,gender_preference:n._genderPreferenceFor(e),option_ids:e.options.map(e=>e.id),guest_label:t,start_at:a,design_attachment_id:parseInt(e.designAttachmentId||0,10)||0,design_upload_token:String(e.designUploadToken||"")}});return{action:"yatoon_create_booking",nonce:yatoonData.nonce,client_reference:`${T}-${o+1}`,start_at:s.dateTime.start_at,local_staff_map_json:JSON.stringify(d),staff_map_json:l,services_json:JSON.stringify(_),service_id:a[0].service.id,staff_id:a[0].staff?a[0].staff.id:0,gender_preference:n._genderPreferenceFor(a[0]),selected_options:a[0].options.map(e=>e.id),notes:g,guest_label:u,guest_name:m.name||"",guest_phone:m.phone||"",booking_group_id:P,coupon_code:y.coupon_code,tip_amount:0===o?y.tip_amount:0,gift_card_code:n._giftSubmissionQuote?.cart[o]?.gift_card_amount>0?y.gift_card_code||n._giftCode:"",gift_card_amount:n._giftSubmissionQuote?.cart[o]?.gift_card_amount||0,custom_fields:y.custom_fields,customer_name:y.customer_name,customer_email:y.customer_email,customer_phone:y.customer_phone,otp_channel:"undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp?"sms":"email",otp_verified_key:document.getElementById("sbs-otp-section")&&document.getElementById("sbs-otp-section").dataset.verifiedKey?document.getElementById("sbs-otp-section").dataset.verifiedKey:"",marketing_email_optin:y.marketing_email_optin,marketing_sms_optin:y.marketing_sms_optin,consent_accepted:y.consent_accepted,consent_signature:y.consent_signature,policy_acknowledged:y.policy_acknowledged,client_timezone:y.client_timezone,design_attachment_id:x||"",design_upload_token:B||"",website:t("#sbs-website").val()||"",form_rendered_at:t("#sbs-form-rendered-at").val()||"",cf_turnstile_response:void 0!==e.yatoonTurnstileToken?e.yatoonTurnstileToken:"",yatoon_test_booking:t(".sbs-booking-container").hasClass("yatoon-test-mode")?"1":""}},E=()=>{v.length<2||!n._paymentsConfigured()?F(0):n._loadFeatureChunk("recovery").then(async()=>{for(let e=0;e<v.length;e++)try{await n._prepareCheckout(D(e))}catch(t){throw t.cartIndex=e,t}}).then(()=>F(0)).catch(e=>{const t=e&&"number"==typeof e.cartIndex?e.cartIndex:0;if(e&&e.data&&e.data.preflight)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:`${T}-${t+1}`,failedIndex:t,confirmedCount:0,message:e&&e.message||o("This appointment is no longer available. Please choose another time."),uncertain:!1}),h()})},F=s=>{if(s>=v.length){if(C.every(e=>e&&e.success)){const s=C.find(e=>e&&e.success&&e.data&&e.data.woocommerce_checkout_url);if(s&&s.data.woocommerce_checkout_url)return this.bookingConfirmed=!0,this._clearSession(),void e.location.assign(s.data.woocommerce_checkout_url);this.bookingConfirmed=!0,t(".sbs-form-step").removeClass("active"),t(".sbs-step-success").show(),this._updateStepAccordion(),this._markBookingFlowComplete(),t("#sbs-success-count").text(v.length);const a=1!==v.length;t("#sbs-success-booking-word").text(o(a?"Bookings":"Booking"));const i=this._confirmedBookingItems(v,C);this._renderSuccessBookingSummary(i);const r=C.filter(e=>e&&e.success&&e.data&&e.data.manage_url&&String(e.data.manage_url).length),d=r.length&&r[0].data?r[0].data.manage_url:"",u=C.find(e=>e&&e.success&&e.data&&e.data.calendar_url&&String(e.data.calendar_url).length),m=u&&u.data?u.data.calendar_url:"",g=yatoonData.portalUrl||"",f=yatoonData.portalLabel||o("My Appointments"),p=t("#sbs-success-actions").empty();if(t("<div/>",{class:"sbs-success-action-note",text:yatoonData.successNote||o("Your appointment is booked. Save this page or use the manage link below if you need to change services, reschedule, or cancel.")}).appendTo(p),d&&1===r.length&&t("<a/>",{class:"sbs-success-action-link primary",href:d,target:"_blank",rel:"noopener",text:"undefined"!=typeof yatoonData&&yatoonData.isPro?o("Change services, reschedule, or cancel"):o("Reschedule or cancel")}).appendTo(p),r.length>1){const e=t("<div/>",{class:"sbs-success-manage-list"}).appendTo(p),n=v.some(e=>e.guestLabel&&"Me"!==e.guestLabel);r.forEach((s,a)=>{const i=v[a],r=i&&i.guestLabel&&"Me"!==i.guestLabel?i.guestLabel:null;let c;if(r)c=o("Change or cancel for")+" "+r;else if(n)c=o("Change or cancel my appointment");else{const e=s.data&&s.data.service_name?s.data.service_name:o("Appointment")+" "+(a+1);c=o("Change or cancel:")+" "+e}t("<a/>",{class:"sbs-success-action-link compact",href:s.data.manage_url,target:"_blank",rel:"noopener",text:c}).appendTo(e)})}if(m&&!t("#sbs-success-cal-row").length&&t("<a/>",{class:"sbs-success-action-link",href:m,text:o("Add to calendar")}).appendTo(p),g&&t("<a/>",{class:"sbs-success-action-link",href:g,text:f}).appendTo(p),t("<button/>",{type:"button",class:"sbs-success-action-link sbs-success-book-again"+(d?"":" primary"),text:o("Book again")}).appendTo(p),p.show(),m){t("#sbs-cal-ics").attr("href",m);const e=C.find(e=>e&&e.success&&e.data&&(e.data.start_at||e.data.start_time)),n=i[0]||null,s=n&&n.dateTime?n.dateTime.start_at:"",a=n?this._getItemVisitDuration(n):0,r=s?new Date(s):null,c=r&&!isNaN(r.getTime())&&a?new Date(r.getTime()+6e4*a).toISOString():"",d=e&&e.data&&(e.data.start_at||e.data.start_time)||s,l=e&&e.data&&(e.data.end_at||e.data.end_time)||c;if(d&&l){const e=function(e){const t=new Date(e);return isNaN(t.getTime())?"":t.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}Z$/,"Z")},s=(n?n.services||[{service:n.service}]:[]).map(e=>e.service&&e.service.name).filter(Boolean).join(", "),a=encodeURIComponent((yatoonData.businessName||o("Appointment"))+(s?" - "+s:"")),i=encodeURIComponent(e(d)),r=encodeURIComponent(e(l)),c=encodeURIComponent(yatoonData&&yatoonData.siteTimezone?yatoonData.siteTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone);i&&r?t("#sbs-cal-google").attr("href","https://calendar.google.com/calendar/render?action=TEMPLATE&text="+a+"&dates="+i+"/"+r+"&ctz="+c):t("#sbs-cal-google").attr("href",m)}else t("#sbs-cal-google").attr("href",m);t("#sbs-success-cal-row").show()}yatoonData.rebookingNudgeText&&(t("#sbs-rebooking-nudge .sbs-rebooking-text").text(yatoonData.rebookingNudgeText),t("#sbs-rebooking-nudge .sbs-rebooking-btn").text(yatoonData.rebookingNudgeBtn||o("Book next visit")),setTimeout(function(){t("#sbs-rebooking-nudge").fadeIn(400)},1200));const _=C.find(e=>e&&e.success&&e.data&&e.data.booking_id);if(_&&_.data.manage_token&&t("#sbs-recurring-setup").length){const e=_.data.booking_id,n=_.data.manage_token;let s=2;setTimeout(function(){t("#sbs-recurring-setup").fadeIn(400)},2e3),t(document).on("click",".sbs-freq-btn",function(){t(".sbs-freq-btn").removeClass("active"),t(this).addClass("active"),s=parseInt(t(this).data("weeks"))}),t(document).on("click","#sbs-recurring-confirm",function(){const a=t(this).prop("disabled",!0).text(o("Setting up...")),i=parseInt(t("#sbs-recurring-count").val())||6;t.post(ajaxurl,{action:"yatoon_setup_recurring",nonce:yatoonData.nonce,yatoon_location_id:parseInt(yatoonData.locationId||0,10),booking_id:e,manage_token:n,frequency_weeks:s,total_count:i},function(e){const n=t("#sbs-recurring-result");e.success?(a.text(o("Done")),n.removeClass("error").text(e.data.message).show()):(a.prop("disabled",!1).text(o("Try again")),n.addClass("error").text(e.data&&e.data.message||o("Could not set up recurring.")).show())}).fail(function(){a.prop("disabled",!1).text(o("Try again")),t("#sbs-recurring-result").addClass("error").text(o("Network error. Please try again.")).show()})})}n.bookingCart=[],n.pendingServices=[],n._clearSession(),n._clearBookingRecovery(),n._bookingAttemptReference="",n._bookingAttemptFingerprint="",n._giftCode="",n._giftSubmissionQuote=null,t("#sbs-gift-card-code-confirmed").val(""),n._hideMobileCartBar(),n._closeDrawer(),n._isSubmitting=!1,h(),c.hide(),l.hide()}else{const e=C.filter(e=>!e||!e.success),t=e.map(e=>e&&e.data&&e.data.message?e.data.message:"").filter(Boolean).join(" "),s=t?`\n\nDetails: ${t}`:"",a=C.length-e.length;n._showToast(`${a} booking(s) confirmed, but ${e.length} failed. Please contact us.${s}`,"error");const i=e[0]&&e[0].data?e[0].data:{};n._showBookingRecovery({reference:i.reference||T,failedIndex:a,confirmedCount:a,message:t||o("One or more appointments were not confirmed."),safeToRetry:!0===i.retry_safe}),h()}return}const a=D(s),i=document.getElementById("sbs-design-reference"),r=i&&i.files&&i.files[0]?i.files[0]:null;let d=a,u=!0,m="application/x-www-form-urlencoded; charset=UTF-8";r&&!x&&(d=new FormData,Object.keys(a).forEach(function(e){const t=a[e];Array.isArray(t)?t.forEach(function(t){d.append(e+"[]",t)}):d.append(e,t)}),d.append("design_reference",r),u=!1,m=!1);const g=function(){t.ajax({url:I,type:"POST",timeout:9e4,data:d,processData:u,contentType:m,success:function(e,i,r){try{if("string"==typeof e){const t=e.indexOf("{"),o=e.lastIndexOf("}"),n=t>=0&&o>t?e.slice(t,o+1):e;e=JSON.parse(n)}if(C.push(e),!e||!e.success){const t=e&&e.data&&e.data.message?e.data.message:"",i=t?`\n\nDetails: ${t}`:"",r=C.length-1;n._showToast(`${r} booking(s) confirmed, but appointment ${s+1} failed. Please contact us.${i}`,"error");const c=e&&e.data?e.data:{},l=!!function(){const e=["deposit_intent_id","paypal_order_id","square_payment_id"];for(let t=0;t<e.length;t++){const o=d instanceof FormData?d.get(e[t]):d[e[t]];if(o)return String(o)}return""}();return n._showBookingRecovery({reference:a.client_reference||c.reference||`${T}-${s+1}`,failedIndex:s,confirmedCount:r,message:t||o("The appointment was not confirmed."),safeToRetry:!0===c.retry_safe&&!l,uncertain:!0===c.outcome_uncertain||l}),l&&n._showPaidRecoveryNotice(a.client_reference),void h()}n._forgetPaidCheckout(a.client_reference),e.data&&e.data.design_attachment_id&&(x=parseInt(e.data.design_attachment_id,10)||x,n.designUploadAttachmentId=x),e.data&&e.data.design_upload_token&&(B=String(e.data.design_upload_token||""),n.designUploadToken=B),F(s+1)}catch(a){if(console.error(o("Yatoon booking submit failed after server response:"),a),n.bookingConfirmed||t(".sbs-step-success:visible").length>0)return;const i=r&&r.responseText?String(r.responseText):"string"==typeof e?e:"";if(i&&-1!==i.indexOf('"success":true'))return C.push({success:!0,data:{}}),void F(s+1);n._showToast(o("We could not read the booking confirmation response. Please check the booking list before trying again."),"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:C.filter(e=>e&&e.success).length,message:o("We could not verify the server response."),uncertain:!0}),h()}},error:function(e,t){C.push({success:!1});const a=C.length-1,i="timeout"===t?"the request timed out":"a network error";n._showToast(`${a} booking(s) confirmed, but appointment ${s+1} failed due to ${i}. Please contact us.`,"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:a,message:o("timeout"===t?"The request timed out.":"The network connection was interrupted."),uncertain:!0}),h()}})},f=function(){const e=(e,t)=>{n._rememberCheckoutPayment&&n._rememberCheckoutPayment(a.client_reference,e,t),n._giftSubmissionQuote&&(n._giftSubmissionQuote.payments=n._giftSubmissionQuote.payments||{},n._giftSubmissionQuote.payments[s]={field:e,id:t},n._saveSession())},t=n._checkoutSavedPayment||n._giftSubmissionQuote?.payments?.[s];n._checkoutSavedPayment=null;const i=t?"":n._unresolvedPaidCheckout(a.client_reference);if(i)return n._showBookingRecovery({reference:i,failedIndex:s,confirmedCount:s,message:o("A payment for an earlier attempt was received, but that appointment is not confirmed yet."),uncertain:!0}),n._showPaidRecoveryNotice(i,!0),void h();t?(d instanceof FormData?d.set(t.field,t.id):d[t.field]=t.id,g()):a.gift_card_code&&0===n._giftSubmissionQuote?.cart[s]?.amount_due?g():n._squarePreparedPaymentId&&n._squarePreparedPaymentReference===a.client_reference?(e("square_payment_id",n._squarePreparedPaymentId),d instanceof FormData?d.set("square_payment_id",n._squarePreparedPaymentId):d.square_payment_id=n._squarePreparedPaymentId,n._squarePreparedPaymentId="",n._squarePreparedPaymentReference="",g()):n._paymentProviderEnabled("square")?n._ensurePaymentSdk("square").then(function(){return n._createSquarePaymentForPayload(a)}).then(function(t){e("square_payment_id",t),d instanceof FormData?d.set("square_payment_id",t):d.square_payment_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Square payment could not be started."),"error"),h()}):n._paymentProviderEnabled("paypal")?n._ensurePaymentSdk("paypal").then(function(){return n._createPayPalPaymentForPayload(a)}).then(function(t){e("paypal_order_id",t),d instanceof FormData?d.set("paypal_order_id",t):d.paypal_order_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("PayPal Checkout could not be started."),"error"),h()}):n._paymentProviderEnabled("stripe")?n._ensurePaymentSdk("stripe").then(function(){return n._createStripePaymentForPayload(a)}).then(function(t){e("deposit_intent_id",t),d instanceof FormData?d.set("deposit_intent_id",t):d.deposit_intent_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Stripe payment could not be started."),"error"),h()}):g()};n._paymentsConfigured()?n._loadFeatureChunk("recovery").then(()=>n._prepareCheckout(a)).then(e=>{d instanceof FormData?d.set("checkout_token",a.checkout_token):d.checkout_token=a.checkout_token,n._checkoutSavedPayment=e.payment||null,f()}).catch(e=>{const t=!!(e&&e.data&&e.data.preflight);if(t&&0===s)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:a.client_reference,failedIndex:s,confirmedCount:s,message:e.message,uncertain:!t}),h()}):f()};try{if(y.gift_card_code||n._giftCode){(n._giftSubmissionQuote?Promise.resolve(n._giftSubmissionQuote):n._fetchGiftQuote(parseFloat(y.tip_amount)||0)).then(e=>{n._giftSubmissionQuote=e,n._saveSession(),n._renderGiftQuote(e),E()}).catch(e=>{n._showToast(e.message,"error"),n._isSubmitting=!1,h()})}else E()}catch(e){console.error(o("Yatoon booking submit error:"),e),this._showToast(o("Something went wrong before the booking could be sent. Please refresh and try again."),"error"),h()}}}Object.getOwnPropertyNames(_.prototype).filter(e=>"constructor"!==e).forEach(t=>e.YatoonSquareBookingForm.prototype[t]=_.prototype[t])}(window,window.jQuery);
     1!function(e,t){"use strict";const o=e.wp?.i18n?.__?t=>e.wp.i18n.__(t,"yatoon-booking-system"):e=>e,{ysSiteNow:n,ysSiteOffsetMin:s,ysFormatMoney:a,ysPriceVaries:i,ysMoneyFrom:r,ysCalcDeposit:c,validateBookingPhone:d,validateUSPhone:l,showFieldError:u,showPhoneError:m,validateCustomerName:g,validateCustomerEmail:f,ysFocusTrap:p}=e.YatoonBookingHelpers;class _{_paidCheckoutKey(e){return"yatoon-checkout:"+location.pathname+":"+e}_forgetPaidCheckout(e){if(e)try{sessionStorage.removeItem(this._paidCheckoutKey(e))}catch(e){}}_unresolvedPaidCheckout(e){const t=this._paidCheckoutKey("");try{for(let o=0;o<sessionStorage.length;o++){const n=sessionStorage.key(o);if(!n||0!==n.indexOf(t))continue;const s=n.slice(t.length);if(!s||s===e)continue;const a=JSON.parse(sessionStorage.getItem(n)||"null");if(a&&a.payment&&a.payment.id&&!a.resolved)return s}}catch(e){}return""}_showPaidRecoveryNotice(e,n){const s=this,a=t("#sbs-booking-recovery");a.length&&(a.addClass("sbs-booking-recovery-paid"),a.find("h3").first().text(o("Payment received, appointment not confirmed yet")),a.find(".sbs-booking-recovery-guidance").text(o('Your payment went through, but this appointment could not be confirmed. Please do not pay again. Use "Check payment and appointment status" to finish it, or contact the salon with the reference below so they can confirm another time or refund you.')),n&&!a.find(".sbs-paid-recovery-resolved").length&&t("<button/>",{type:"button",class:"sbs-paid-recovery-resolved",text:o("The salon has resolved this payment")}).appendTo(a.find(".sbs-booking-recovery-actions")).on("click",function(){try{const t=s._paidCheckoutKey(e),o=JSON.parse(sessionStorage.getItem(t)||"null");o&&(o.resolved=!0,sessionStorage.setItem(t,JSON.stringify(o)))}catch(e){}s._clearBookingRecovery(),s._saveSession()}))}submitBooking(){const n=this;if(this._handleBookingRecoveryAttempt())return;if(this._paymentsConfigured()&&"function"!=typeof this._ensurePaymentSdk){if(this._submissionChunkPromise)return;const e=t(".sbs-btn-submit").prop("disabled",!0).text(o("Loading secure payment..."));return void(this._submissionChunkPromise=this._loadFeatureChunk("payments").then(function(){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n.submitBooking()}).catch(function(t){n._submissionChunkPromise=null,e.prop("disabled",!1),n._applyConfirmBtn(),n._showToast(t.message||o("The secure payment component could not be loaded."),"error")}))}if(this._isSubmitting)return;if(this.designUploadInProgress)return void this._showToast(o("Please wait for the reference photo to finish uploading."),"info");if(!this._paymentsConfigured()&&(!this._turnstileTokenFetched||Date.now()>(this._turnstileTokenExpires||0))&&"function"==typeof e.yatoonGetTurnstileToken)return this._turnstileTokenFetched=!0,void e.yatoonGetTurnstileToken().then(function(t){e.yatoonTurnstileToken=t,n._turnstileTokenFetched=!0,n._turnstileTokenExpires=Date.now()+9e4,n.submitBooking()}).catch(function(){n._turnstileTokenFetched=!1,n._turnstileTokenExpires=0,n._showToast(o("Verification could not finish. Please try again."),"error")});const s=document.getElementById("sbs-otp-section"),a="undefined"!=typeof yatoonData&&"1"===yatoonData.verifyEmailOtp,i="undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp;if(s&&(a||i)){const e=(document.getElementById("sbs-customer-email")||{}).value||"",t=(document.getElementById("sbs-customer-phone")||{}).value||"",a=e.trim()+"|"+t.trim();s.dataset.sentContact&&s.dataset.sentContact!==a&&(s.dataset.sent="",s.dataset.sentContact="");const r="1"===s.dataset.verified&&s.dataset.verifiedContact===a;"1"!==s.dataset.verified||r||(s.dataset.verified="",s.dataset.verifiedKey="");const c=function(e){const t=String(e||"").replace(/\D/g,"");return 11===t.length&&"1"===t.charAt(0)?t.slice(1):t},l=i?""!==c(s.dataset.verifiedPhone)&&c(t)===c(s.dataset.verifiedPhone):!!s.dataset.verifiedEmail&&e.trim().toLowerCase()===s.dataset.verifiedEmail.toLowerCase(),g=s.dataset.deviceVerified===a;if(!r&&!l&&!g){const r=f(e);if(r){u("sbs-customer-email",r);const e=document.getElementById("sbs-customer-email");return void(e&&e.scrollIntoView({behavior:"smooth",block:"center"}))}if(i){const e=d(t);if(e){m(e);const t=document.getElementById("sbs-phone-display")||document.getElementById("sbs-customer-phone");return void(t&&t.scrollIntoView({behavior:"smooth",block:"center"}))}}if(s.dataset.deviceChecked!==a)return void jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_otp_device_check",nonce:yatoonData.nonce,email:e,phone:t},function(e){s.dataset.deviceChecked=a,e&&e.success&&e.data&&e.data.verified&&(s.dataset.deviceVerified=a),n.submitBooking()}).fail(function(){s.dataset.deviceChecked=a,n.submitBooking()});const c=(document.getElementById("sbs-customer-name")||{}).value||"",l=i?"sms":"email";s.style.display="block";const g=document.getElementById("sbs-otp-remember");if(g&&"1"===g.dataset.regionAuto&&!g.dataset.regionChecked){g.dataset.regionChecked="1";let e="";try{e=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(t){e=""}const t=/^Europe\/(Moscow|Minsk|Kaliningrad|Samara|Volgograd|Kirov|Astrakhan|Saratov|Ulyanovsk|Simferopol|Istanbul|Kiev|Kyiv|Uzhgorod|Zaporozhye|Chisinau|Tirane|Skopje|Sarajevo|Belgrade|Podgorica)$/;(/^Europe\//.test(e)&&!t.test(e)||/^Atlantic\/(Reykjavik|Madeira|Azores|Canary)$/.test(e))&&(g.checked=!1)}if(document.getElementById("sbs-otp-prompt").textContent="sms"===l?o("We are sending a verification code to your phone. Please enter it below."):o("We are sending a verification code to")+" "+e+". "+o("Please enter it below."),!s.dataset.sent){s.dataset.sent="1",s.dataset.sentContact=a;const n=document.getElementById("sbs-otp-msg"),i=document.getElementById("sbs-otp-resend-btn");n&&(n.textContent=o("Sending verification code..."),n.style.color=""),i&&(i.disabled=!0),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_send_otp",nonce:yatoonData.nonce,channel:l,email:e,phone:t,name:c},function(e){const t=!(!e||!e.success);n&&(n.textContent=t?e.data&&e.data.message?e.data.message:o("Verification code sent."):e&&e.data&&e.data.message?e.data.message:o("Failed to send code.")),i&&(i.style.display=""),i&&(i.disabled=!1),t||(s.dataset.sent="",s.dataset.sentContact="",n&&(n.style.color="#dc2626"))}).fail(function(){s.dataset.sent="",s.dataset.sentContact="",n&&(n.textContent=o("Could not send the verification code. Please try again."),n.style.color="#dc2626"),i&&(i.style.display="",i.disabled=!1)})}const p=document.getElementById("sbs-otp-verify-btn");p&&!p.dataset.bound&&(p.dataset.bound="1",p.addEventListener("click",function(){const e=(document.getElementById("sbs-otp-input")||{}).value||"",t=document.getElementById("sbs-otp-msg"),a=(document.getElementById("sbs-customer-email")||{}).value||"",i=(document.getElementById("sbs-customer-phone")||{}).value||"",r=a.trim()+"|"+i.trim();e.trim()?(p.disabled=!0,p.dataset.pending="1",t&&(t.textContent=o("Verifying..."),t.style.color=""),jQuery.post(yatoonData.ajaxUrl,{action:"yatoon_verify_otp",nonce:yatoonData.nonce,channel:l,email:a,phone:i,code:e,remember:document.getElementById("sbs-otp-remember")&&document.getElementById("sbs-otp-remember").checked?1:0},function(e){e.success?(s.dataset.verified="1",s.dataset.verifiedKey=e.data&&e.data.verified_key?e.data.verified_key:"",n._giftSubmissionQuote=null,n._dynamicQuoteKey="",s.dataset.verifiedContact=r,s.style.display="none",n.submitBooking()):(t&&(t.textContent=e.data&&e.data.message?e.data.message:o("Incorrect code.")),t&&(t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending="")}).fail(function(){t&&(t.textContent=o("Could not verify the code. Please try again."),t.style.color="#dc2626"),p.disabled=!1,p.dataset.pending=""})):t&&(t.textContent=o("Enter the verification code first."),t.style.color="#dc2626")}));const _=document.getElementById("sbs-otp-resend-btn");return void(_&&!_.dataset.bound&&(_.dataset.bound="1",_.addEventListener("click",function(){if(_.disabled)return;s.dataset.sent="",s.dataset.sentContact="",s.dataset.verified="",s.dataset.verifiedKey="",s.dataset.verifiedContact="";const e=document.getElementById("sbs-otp-msg");e&&(e.textContent=""),n.submitBooking()})))}}if(this._isSubmitting=!0,this._clearBookingRecovery(),0===this.bookingCart.length&&this.selectedDateTime&&(this.pendingServices.length>0||this.selectedService)&&this.addToCart(),0===this.bookingCart.length)return this._isSubmitting=!1,void this._showToast(o("Please add at least one service to your booking."),"error");const r=t(".sbs-btn-submit"),c=t("#sbs-cart-next-btn"),l=t("#sbs-sidebar-next-btn"),g=r.text(),p=c.text(),_=l.text(),h=()=>{this._isSubmitting=!1,this._turnstileTokenFetched=!1,r.prop("disabled",!1).text(g),c.prop("disabled",!1).removeClass("sbs-submitting").text(p||o("Confirm Booking")),l.prop("disabled",!1).removeClass("sbs-submitting").text(_||o("Confirm Booking")),this._applyConfirmBtn(),this._updateMobileCartBar()};r.prop("disabled",!0).text(o("Confirming your booking...")),c.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),l.prop("disabled",!0).addClass("sbs-submitting").text("CONFIRMING..."),this._updateLegalBoxes();const y={customer_name:t("#sbs-customer-name").val(),customer_email:t("#sbs-customer-email").val(),customer_phone:t("#sbs-customer-phone").val(),client_timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"",notes:t("#sbs-customer-notes").val(),coupon_code:t("#sbs-coupon-code").length?t("#sbs-coupon-code").val():"",marketing_email_optin:t("#sbs-marketing-email-optin").is(":checked")?1:0,marketing_sms_optin:t("#sbs-marketing-sms-optin").is(":checked")?1:0,consent_accepted:t("#sbs-consent-accepted").length&&t("#sbs-consent-accepted").is(":checked")?1:0,consent_signature:t("#sbs-consent-signature").length?t("#sbs-consent-signature").val():"",policy_acknowledged:t("#sbs-policy-acknowledged").length&&t("#sbs-policy-acknowledged").is(":checked")?1:0,tip_amount:parseFloat(t("#sbs-tip-amount").val())||0,gift_card_code:t("#sbs-gift-card-code-confirmed").val()||"",custom_fields:function(){const e={};return t('[name^="cf["]').each(function(){const o=t(this).attr("name").replace(/^cf\[|\]$/g,"");t(this).is(":checkbox")||t(this).is(":radio")?t(this).is(":checked")&&(e[o]=t(this).val()):e[o]=t(this).val()}),JSON.stringify(e)}()},b=this._collectGuestContacts(),k=f(y.customer_email);if(k)return h(),u("sbs-customer-email",k),void(document.getElementById("sbs-customer-email")&&document.getElementById("sbs-customer-email").scrollIntoView({behavior:"smooth",block:"center"}));"undefined"!=typeof yatoonData&&yatoonData.phoneMode&&yatoonData.phoneMode;if(y.customer_phone){const e=d(y.customer_phone);if(e)return h(),m(e),void(document.getElementById("sbs-customer-phone")&&document.getElementById("sbs-customer-phone").scrollIntoView({behavior:"smooth",block:"center"}))}if(this._needsPolicyAck()&&!y.policy_acknowledged)return h(),this._showToast(o("Please review and acknowledge the cancellation / no-show policy before booking."),"error"),void this._focusBookingValidation(t("#sbs-policy-acknowledged"),t(".sbs-policy-box:visible").first());if(this._needsConsent()&&(!y.consent_accepted||!y.consent_signature.trim())){h(),this._showToast(o("Please read and sign the consent form before booking."),"error");const e=y.consent_accepted?t("#sbs-consent-signature"):t("#sbs-consent-accepted");return void this._focusBookingValidation(e,t(".sbs-consent-box:visible").first())}const v=this.bookingCart.slice(),C=[],w=JSON.stringify(v.map(e=>({start:e.dateTime&&e.dateTime.start_at,guest:e.guestLabel||"Me",services:(e.services||[{service:e.service,options:e.options,staff:e.staff}]).map(t=>({service:t.service&&t.service.id,options:(t.options||[]).map(e=>e.id),staff:t.staff&&t.staff.id||0,guest:t.guestLabel||e.guestLabel||"Me"}))}))),T=this._newBookingReference(w),S="undefined"!=typeof yatoonData&&(yatoonData.ajax_url||yatoonData.ajaxUrl)?yatoonData.ajax_url||yatoonData.ajaxUrl:"";if(!S)return h(),void this._showToast(o("Booking could not start because the WordPress AJAX URL is missing. Please refresh and try again."),"error");if(!v.length)return h(),void this._showToast(o("Please choose a service and time before confirming."),"error");const I=v.some(e=>e&&e.services&&e.services.length>1),P=v.length>1||I?"yatoon-"+T:"";let x=this.designUploadAttachmentId||0,B=this.designUploadToken||"";const D=o=>{const s=v[o],a=s.services||[{service:s.service,options:s.options,staff:s.staff}],i=s.dateTime&&s.dateTime.staff_map?s.dateTime.staff_map:null,r="string"==typeof i?function(){try{return JSON.parse(i)}catch(e){return null}}():i,c={},d={};a.forEach(function(e,t){const o=void 0!==e._originalIndex?e._originalIndex:t;s.dateTime.local_staff_map&&Number(s.dateTime.local_staff_map[o])>0&&(d[t]=Number(s.dateTime.local_staff_map[o])),r&&void 0!==r[o]&&(c[t]=r[o])});const l=Object.keys(c).length?JSON.stringify(c):i?"string"==typeof i?i:JSON.stringify(i):"",u=s.guestLabel||a[0]&&a[0].guestLabel||"Me",m=b[u]||{};let g=y.notes||"";if("Me"!==u){const e=[`Guest: ${u}`];m.name&&e.push(`Name: ${m.name}`),m.phone&&e.push(`Phone: ${m.phone}`),g=`${g}${g?"\n\n":""}${e.join(" - ")}`}const f={},p=new Date(s.dateTime.start_at).getTime(),_=a.map(e=>{const t=e.guestLabel||s.guestLabel||"Me",o=f[t]||0,a=Number.isFinite(p)?new Date(p+6e4*o).toISOString():s.dateTime.start_at;return f[t]=o+n._serviceDuration(e),{service_id:e.service.id,staff_id:e.staff?e.staff.id:0,gender_preference:n._genderPreferenceFor(e),option_ids:e.options.map(e=>e.id),guest_label:t,start_at:a,design_attachment_id:parseInt(e.designAttachmentId||0,10)||0,design_upload_token:String(e.designUploadToken||"")}});return{action:"yatoon_create_booking",nonce:yatoonData.nonce,client_reference:`${T}-${o+1}`,start_at:s.dateTime.start_at,local_staff_map_json:JSON.stringify(d),staff_map_json:l,services_json:JSON.stringify(_),service_id:a[0].service.id,staff_id:a[0].staff?a[0].staff.id:0,gender_preference:n._genderPreferenceFor(a[0]),selected_options:a[0].options.map(e=>e.id),notes:g,guest_label:u,guest_name:m.name||"",guest_phone:m.phone||"",booking_group_id:P,coupon_code:y.coupon_code,tip_amount:0===o?y.tip_amount:0,gift_card_code:n._giftSubmissionQuote?.cart[o]?.gift_card_amount>0?y.gift_card_code||n._giftCode:"",gift_card_amount:n._giftSubmissionQuote?.cart[o]?.gift_card_amount||0,custom_fields:y.custom_fields,customer_name:y.customer_name,customer_email:y.customer_email,customer_phone:y.customer_phone,otp_channel:"undefined"!=typeof yatoonData&&"1"===yatoonData.verifySmsOtp?"sms":"email",otp_verified_key:document.getElementById("sbs-otp-section")&&document.getElementById("sbs-otp-section").dataset.verifiedKey?document.getElementById("sbs-otp-section").dataset.verifiedKey:"",marketing_email_optin:y.marketing_email_optin,marketing_sms_optin:y.marketing_sms_optin,consent_accepted:y.consent_accepted,consent_signature:y.consent_signature,policy_acknowledged:y.policy_acknowledged,client_timezone:y.client_timezone,design_attachment_id:x||"",design_upload_token:B||"",website:t("#sbs-website").val()||"",form_rendered_at:t("#sbs-form-rendered-at").val()||"",cf_turnstile_response:void 0!==e.yatoonTurnstileToken?e.yatoonTurnstileToken:"",yatoon_test_booking:t(".sbs-booking-container").hasClass("yatoon-test-mode")?"1":""}},E=()=>{v.length<2||!n._paymentsConfigured()?F(0):n._loadFeatureChunk("recovery").then(async()=>{for(let e=0;e<v.length;e++)try{await n._prepareCheckout(D(e))}catch(t){throw t.cartIndex=e,t}}).then(()=>F(0)).catch(e=>{const t=e&&"number"==typeof e.cartIndex?e.cartIndex:0;if(e&&e.data&&e.data.preflight)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:`${T}-${t+1}`,failedIndex:t,confirmedCount:0,message:e&&e.message||o("This appointment is no longer available. Please choose another time."),uncertain:!1}),h()})},F=s=>{if(s>=v.length){if(C.every(e=>e&&e.success)){const s=C.find(e=>e&&e.success&&e.data&&e.data.woocommerce_checkout_url);if(s&&s.data.woocommerce_checkout_url)return this.bookingConfirmed=!0,this._clearSession(),void e.location.assign(s.data.woocommerce_checkout_url);this.bookingConfirmed=!0,t(".sbs-form-step").removeClass("active"),t(".sbs-step-success").show(),this._updateStepAccordion(),this._markBookingFlowComplete(),t("#sbs-success-count").text(v.length);const a=1!==v.length;t("#sbs-success-booking-word").text(o(a?"Bookings":"Booking"));const i=this._confirmedBookingItems(v,C);this._renderSuccessBookingSummary(i);const r=C.filter(e=>e&&e.success&&e.data&&e.data.manage_url&&String(e.data.manage_url).length),d=r.length&&r[0].data?r[0].data.manage_url:"",u=C.find(e=>e&&e.success&&e.data&&e.data.calendar_url&&String(e.data.calendar_url).length),m=u&&u.data?u.data.calendar_url:"",g=yatoonData.portalUrl||"",f=yatoonData.portalLabel||o("My Appointments"),p=t("#sbs-success-actions").empty();if(t("<div/>",{class:"sbs-success-action-note",text:yatoonData.successNote||o("Your appointment is booked. Save this page or use the manage link below if you need to change services, reschedule, or cancel.")}).appendTo(p),d&&1===r.length&&t("<a/>",{class:"sbs-success-action-link primary",href:d,target:"_blank",rel:"noopener",text:"undefined"!=typeof yatoonData&&yatoonData.isPro?o("Change services, reschedule, or cancel"):o("Reschedule or cancel")}).appendTo(p),r.length>1){const e=t("<div/>",{class:"sbs-success-manage-list"}).appendTo(p),n=v.some(e=>e.guestLabel&&"Me"!==e.guestLabel);r.forEach((s,a)=>{const i=v[a],r=i&&i.guestLabel&&"Me"!==i.guestLabel?i.guestLabel:null;let c;if(r)c=o("Change or cancel for")+" "+r;else if(n)c=o("Change or cancel my appointment");else{const e=s.data&&s.data.service_name?s.data.service_name:o("Appointment")+" "+(a+1);c=o("Change or cancel:")+" "+e}t("<a/>",{class:"sbs-success-action-link compact",href:s.data.manage_url,target:"_blank",rel:"noopener",text:c}).appendTo(e)})}if(m&&!t("#sbs-success-cal-row").length&&t("<a/>",{class:"sbs-success-action-link",href:m,text:o("Add to calendar")}).appendTo(p),g&&t("<a/>",{class:"sbs-success-action-link",href:g,text:f}).appendTo(p),t("<button/>",{type:"button",class:"sbs-success-action-link sbs-success-book-again"+(d?"":" primary"),text:o("Book again")}).appendTo(p),p.show(),m){t("#sbs-cal-ics").attr("href",m);const e=C.find(e=>e&&e.success&&e.data&&(e.data.start_at||e.data.start_time)),n=i[0]||null,s=n&&n.dateTime?n.dateTime.start_at:"",a=n?this._getItemVisitDuration(n):0,r=s?new Date(s):null,c=r&&!isNaN(r.getTime())&&a?new Date(r.getTime()+6e4*a).toISOString():"",d=e&&e.data&&(e.data.start_at||e.data.start_time)||s,l=e&&e.data&&(e.data.end_at||e.data.end_time)||c;if(d&&l){const e=function(e){const t=new Date(e);return isNaN(t.getTime())?"":t.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}Z$/,"Z")},s=(n?n.services||[{service:n.service}]:[]).map(e=>e.service&&e.service.name).filter(Boolean).join(", "),a=encodeURIComponent((yatoonData.businessName||o("Appointment"))+(s?" - "+s:"")),i=encodeURIComponent(e(d)),r=encodeURIComponent(e(l)),c=encodeURIComponent(yatoonData&&yatoonData.siteTimezone?yatoonData.siteTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone);i&&r?t("#sbs-cal-google").attr("href","https://calendar.google.com/calendar/render?action=TEMPLATE&text="+a+"&dates="+i+"/"+r+"&ctz="+c):t("#sbs-cal-google").attr("href",m)}else t("#sbs-cal-google").attr("href",m);t("#sbs-success-cal-row").show()}yatoonData.rebookingNudgeText&&(t("#sbs-rebooking-nudge .sbs-rebooking-text").text(yatoonData.rebookingNudgeText),t("#sbs-rebooking-nudge .sbs-rebooking-btn").text(yatoonData.rebookingNudgeBtn||o("Book next visit")),setTimeout(function(){t("#sbs-rebooking-nudge").fadeIn(400)},1200));const _=C.find(e=>e&&e.success&&e.data&&e.data.booking_id);if(_&&_.data.manage_token&&t("#sbs-recurring-setup").length){const e=_.data.booking_id,n=_.data.manage_token;let s=2;setTimeout(function(){t("#sbs-recurring-setup").fadeIn(400)},2e3),t(document).on("click",".sbs-freq-btn",function(){t(".sbs-freq-btn").removeClass("active"),t(this).addClass("active"),s=parseInt(t(this).data("weeks"))}),t(document).on("click","#sbs-recurring-confirm",function(){const a=t(this).prop("disabled",!0).text(o("Setting up...")),i=parseInt(t("#sbs-recurring-count").val())||6;t.post(ajaxurl,{action:"yatoon_setup_recurring",nonce:yatoonData.nonce,yatoon_location_id:parseInt(yatoonData.locationId||0,10),booking_id:e,manage_token:n,frequency_weeks:s,total_count:i},function(e){const n=t("#sbs-recurring-result");e.success?(a.text(o("Done")),n.removeClass("error").text(e.data.message).show()):(a.prop("disabled",!1).text(o("Try again")),n.addClass("error").text(e.data&&e.data.message||o("Could not set up recurring.")).show())}).fail(function(){a.prop("disabled",!1).text(o("Try again")),t("#sbs-recurring-result").addClass("error").text(o("Network error. Please try again.")).show()})})}n.bookingCart=[],n.pendingServices=[],n._clearSession(),n._clearBookingRecovery(),n._bookingAttemptReference="",n._bookingAttemptFingerprint="",n._giftCode="",n._giftSubmissionQuote=null,t("#sbs-gift-card-code-confirmed").val(""),n._hideMobileCartBar(),n._closeDrawer(),n._isSubmitting=!1,h(),c.hide(),l.hide()}else{const e=C.filter(e=>!e||!e.success),t=e.map(e=>e&&e.data&&e.data.message?e.data.message:"").filter(Boolean).join(" "),s=t?`\n\nDetails: ${t}`:"",a=C.length-e.length;n._showToast(`${a} booking(s) confirmed, but ${e.length} failed. Please contact us.${s}`,"error");const i=e[0]&&e[0].data?e[0].data:{};n._showBookingRecovery({reference:i.reference||T,failedIndex:a,confirmedCount:a,message:t||o("One or more appointments were not confirmed."),safeToRetry:!0===i.retry_safe}),h()}return}const a=D(s),i=document.getElementById("sbs-design-reference"),r=i&&i.files&&i.files[0]?i.files[0]:null;let d=a,u=!0,m="application/x-www-form-urlencoded; charset=UTF-8";r&&!x&&(d=new FormData,Object.keys(a).forEach(function(e){const t=a[e];Array.isArray(t)?t.forEach(function(t){d.append(e+"[]",t)}):d.append(e,t)}),d.append("design_reference",r),u=!1,m=!1);const g=function(){t.ajax({url:S,type:"POST",timeout:9e4,data:d,processData:u,contentType:m,success:function(e,i,r){try{if("string"==typeof e){const t=e.indexOf("{"),o=e.lastIndexOf("}"),n=t>=0&&o>t?e.slice(t,o+1):e;e=JSON.parse(n)}if(C.push(e),!e||!e.success){const t=e&&e.data&&e.data.message?e.data.message:"",i=t?`\n\nDetails: ${t}`:"",r=C.length-1;n._showToast(`${r} booking(s) confirmed, but appointment ${s+1} failed. Please contact us.${i}`,"error");const c=e&&e.data?e.data:{},l=!!function(){const e=["deposit_intent_id","paypal_order_id","square_payment_id"];for(let t=0;t<e.length;t++){const o=d instanceof FormData?d.get(e[t]):d[e[t]];if(o)return String(o)}return""}();return n._showBookingRecovery({reference:a.client_reference||c.reference||`${T}-${s+1}`,failedIndex:s,confirmedCount:r,message:t||o("The appointment was not confirmed."),safeToRetry:!0===c.retry_safe&&!l,uncertain:!0===c.outcome_uncertain||l}),l&&n._showPaidRecoveryNotice(a.client_reference),void h()}n._forgetPaidCheckout(a.client_reference),e.data&&e.data.design_attachment_id&&(x=parseInt(e.data.design_attachment_id,10)||x,n.designUploadAttachmentId=x),e.data&&e.data.design_upload_token&&(B=String(e.data.design_upload_token||""),n.designUploadToken=B),F(s+1)}catch(a){if(console.error(o("Yatoon booking submit failed after server response:"),a),n.bookingConfirmed||t(".sbs-step-success:visible").length>0)return;const i=r&&r.responseText?String(r.responseText):"string"==typeof e?e:"";if(i&&-1!==i.indexOf('"success":true'))return C.push({success:!0,data:{}}),void F(s+1);n._showToast(o("We could not read the booking confirmation response. Please check the booking list before trying again."),"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:C.filter(e=>e&&e.success).length,message:o("We could not verify the server response."),uncertain:!0}),h()}},error:function(e,t){C.push({success:!1});const a=C.length-1,i="timeout"===t?"the request timed out":"a network error";n._showToast(`${a} booking(s) confirmed, but appointment ${s+1} failed due to ${i}. Please contact us.`,"error"),n._showBookingRecovery({reference:`${T}-${s+1}`,failedIndex:s,confirmedCount:a,message:o("timeout"===t?"The request timed out.":"The network connection was interrupted."),uncertain:!0}),h()}})},f=function(){const e=(e,t)=>{n._rememberCheckoutPayment&&n._rememberCheckoutPayment(a.client_reference,e,t),n._giftSubmissionQuote&&(n._giftSubmissionQuote.payments=n._giftSubmissionQuote.payments||{},n._giftSubmissionQuote.payments[s]={field:e,id:t},n._saveSession())},t=n._checkoutSavedPayment||n._giftSubmissionQuote?.payments?.[s];n._checkoutSavedPayment=null;const i=t?"":n._unresolvedPaidCheckout(a.client_reference);if(i)return n._showBookingRecovery({reference:i,failedIndex:s,confirmedCount:s,message:o("A payment for an earlier attempt was received, but that appointment is not confirmed yet."),uncertain:!0}),n._showPaidRecoveryNotice(i,!0),void h();t?(d instanceof FormData?d.set(t.field,t.id):d[t.field]=t.id,g()):a.gift_card_code&&0===n._giftSubmissionQuote?.cart[s]?.amount_due?g():n._squarePreparedPaymentId&&n._squarePreparedPaymentReference===a.client_reference?(e("square_payment_id",n._squarePreparedPaymentId),d instanceof FormData?d.set("square_payment_id",n._squarePreparedPaymentId):d.square_payment_id=n._squarePreparedPaymentId,n._squarePreparedPaymentId="",n._squarePreparedPaymentReference="",g()):n._paymentProviderEnabled("square")?n._ensurePaymentSdk("square").then(function(){return n._createSquarePaymentForPayload(a)}).then(function(t){e("square_payment_id",t),d instanceof FormData?d.set("square_payment_id",t):d.square_payment_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Square payment could not be started."),"error"),h()}):n._paymentProviderEnabled("paypal")?n._ensurePaymentSdk("paypal").then(function(){return n._createPayPalPaymentForPayload(a)}).then(function(t){e("paypal_order_id",t),d instanceof FormData?d.set("paypal_order_id",t):d.paypal_order_id=t,g()}).catch(function(e){n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("PayPal Checkout could not be started."),"error"),h()}):n._paymentProviderEnabled("stripe")?n._ensurePaymentSdk("stripe").then(function(){return n._createStripePaymentForPayload(a)}).then(function(t){e("deposit_intent_id",t),d instanceof FormData?d.set("deposit_intent_id",t):d.deposit_intent_id=t,g()}).catch(function(e){if(0===s&&e&&e.data&&e.data.preflight)return n._showToast(e.message||o("Stripe payment could not be started."),"error"),void h();n._showBookingRecovery({reference:a.client_reference,failedIndex:s,message:e.message,uncertain:!0}),n._showToast(e&&e.message?e.message:o("Stripe payment could not be started."),"error"),h()}):g()};n._paymentsConfigured()?n._loadFeatureChunk("recovery").then(()=>n._prepareCheckout(a)).then(e=>{d instanceof FormData?d.set("checkout_token",a.checkout_token):d.checkout_token=a.checkout_token,n._checkoutSavedPayment=e.payment||null,f()}).catch(e=>{const t=!!(e&&e.data&&e.data.preflight);if(t&&0===s)return n._showToast(e.message,"error"),void h();n._showBookingRecovery({reference:a.client_reference,failedIndex:s,confirmedCount:s,message:e.message,uncertain:!t}),h()}):f()};try{if(y.gift_card_code||n._giftCode){(n._giftSubmissionQuote?Promise.resolve(n._giftSubmissionQuote):n._fetchGiftQuote(parseFloat(y.tip_amount)||0)).then(e=>{n._giftSubmissionQuote=e,n._saveSession(),n._renderGiftQuote(e),E()}).catch(e=>{n._showToast(e.message,"error"),n._isSubmitting=!1,h()})}else E()}catch(e){console.error(o("Yatoon booking submit error:"),e),this._showToast(o("Something went wrong before the booking could be sent. Please refresh and try again."),"error"),h()}}}Object.getOwnPropertyNames(_.prototype).filter(e=>"constructor"!==e).forEach(t=>e.YatoonSquareBookingForm.prototype[t]=_.prototype[t])}(window,window.jQuery);
  • yatoon-booking-system/trunk/assets/js/frontend-payments.js

    r3780403 r3780965  
    8787            return new Promise(function(resolve, reject) {
    8888                if (!self._stripePaymentsReady()) {
    89                     reject(new Error(__('Stripe payment form is not ready. Please refresh and try again.')));
     89                    const notReady = new Error(__('Stripe payment form is not ready. Please refresh and try again.'));
     90                    notReady.data = { preflight: true };
     91                    reject(notReady);
    9092                    return;
    9193                }
    … …  
    126128                        paymentElement.mount('#sbs-stripe-payment-element');
    127129                        const $status = $('#sbs-stripe-payment-status');
     130                        const payLabel = yatoonData.nativePaymentMode === 'full' ? __('Pay total') : __('Pay deposit');
     131                        // 4.10.2: the box is reused for the next appointment in a cart.
     132                        // Start each payment with a fresh, clickable button and no old status.
     133                        $status.text('');
     134                        $('#sbs-stripe-pay-button').prop('disabled', false).text(payLabel);
    128135                        $('#sbs-stripe-pay-button').off('.yatoonStripe').on('click.yatoonStripe', function() {
    129136                            const $button = $(this).prop('disabled', true).text(__('Processing...'));
    130137                            $status.text(__('Confirming your payment securely...'));
     138                            // 4.10.2: a Stripe error (no payment method chosen, card declined,
     139                            // failed verification) means nothing was charged. Keep the customer
     140                            // in this payment box so they can fix it and pay again, instead of
     141                            // ending the checkout and sending them to the booking-status panel.
     142                            const retryHere = function(message) {
     143                                $button.prop('disabled', false).text(payLabel);
     144                                $status.text(message);
     145                                self._showToast(message, 'error');
     146                            };
    131147                            stripe.confirmPayment({
    132148                                elements: elements,
    133149                                redirect: 'if_required'
    134150                            }).then(function(result) {
    135                                 if (result.error) throw new Error(result.error.message || __('Stripe could not complete the payment.'));
     151                                let intent = result.paymentIntent || null;
     152                                // A repeat click after a lost response: Stripe reports the intent
     153                                // is already paid. Treat that as success, not as an error.
     154                                if (result.error && result.error.payment_intent) {
     155                                    const already = result.error.payment_intent.status;
     156                                    if (already === 'succeeded' || already === 'processing') intent = result.error.payment_intent;
     157                                }
     158                                if (result.error && !intent) {
     159                                    retryHere(result.error.message || __('Stripe could not complete the payment.'));
     160                                    return;
     161                                }
    136162                                // 4.10.2: bank debits stay "processing" for a few days. The
    137163                                // appointment is booked now and marked paid when the bank clears.
    138                                 const intentStatus = result.paymentIntent ? result.paymentIntent.status : '';
     164                                const intentStatus = intent ? intent.status : '';
    139165                                if (intentStatus !== 'succeeded' && intentStatus !== 'processing') {
    140                                     throw new Error(__('Stripe payment is not complete.'));
     166                                    retryHere(__('Stripe payment is not complete. Please check your payment details and try again.'));
     167                                    return;
    141168                                }
    142169                                $status.text(intentStatus === 'processing'
    … …  
    144171                                    : __('Payment approved. Confirming your appointment...'));
    145172                                $box.hide();
    146                                 resolve(result.paymentIntent.id);
     173                                resolve(intent.id);
    147174                            }).catch(function(error) {
    148                                 $button.prop('disabled', false).text(yatoonData.nativePaymentMode === 'full' ? __('Pay total') : __('Pay deposit'));
    149                                 $status.text(error.message || __('Stripe payment failed.'));
    150                                 self._showToast(error.message || __('Stripe payment failed.'), 'error');
    151                                 reject(error);
     175                                // Stripe.js itself failed (network). The charge state is unknown,
     176                                // so let the customer press Pay again: Stripe answers a repeat
     177                                // confirm of a paid intent with its paid status (handled above).
     178                                retryHere((error && error.message) || __('We could not reach Stripe. Please check your connection and press Pay again.'));
    152179                            });
    153180                        });
    154181                        $box.show();
    155182                        if ($box[0] && $box[0].scrollIntoView) $box[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
    156                     }).catch(reject);
     183                    }).catch(function(error) {
     184                        // The payment form could not be opened, so nothing was charged.
     185                        if (error && typeof error === 'object') error.data = Object.assign({}, error.data, { preflight: true });
     186                        reject(error);
     187                    });
    157188            });
    158189        }
  • yatoon-booking-system/trunk/assets/js/frontend-payments.min.js

    r3780403 r3780965  
    1 !function(e,t){"use strict";const a=e.wp&&e.wp.i18n&&"function"==typeof e.wp.i18n.__?function(t){return e.wp.i18n.__(t,"yatoon-booking-system")}:function(e){return e},n=e.YatoonSquareBookingForm;if(n&&t){class o{_squarePaymentsReady(){return!!("undefined"!=typeof yatoonData&&yatoonData.squarePaymentsEnabled&&yatoonData.squareApplicationId&&yatoonData.squareLocationId&&e.Square&&"function"==typeof e.Square.payments)}_paymentProviderEnabled(e){return"undefined"!=typeof yatoonData&&("square"===e?!!yatoonData.squarePaymentsEnabled:"paypal"===e?!!yatoonData.paypalPaymentsEnabled:"stripe"===e&&!!yatoonData.depositEnabled)}_ensurePaymentSdk(e){const t={square:()=>this._squarePaymentsReady(),paypal:()=>this._paypalPaymentsReady(),stripe:()=>this._stripePaymentsReady()}[e],n={square:yatoonData.squareSdkUrl,paypal:yatoonData.paypalSdkUrl,stripe:yatoonData.stripeSdkUrl}[e];return t&&t()?Promise.resolve():n?(this._paymentSdkPromises=this._paymentSdkPromises||{},this._paymentSdkPromises[e]||(this._paymentSdkPromises[e]=new Promise(function(o,r){const s=document.createElement("script");s.src=n,s.async=!0,s.dataset.yatoonPaymentSdk=e,s.onload=function(){t&&t()?o():r(new Error(a("The secure payment service did not initialize.")))},s.onerror=function(){r(new Error(a("The secure payment service could not be loaded. Check your connection and try again.")))},document.head.appendChild(s)}).catch(t=>{throw delete this._paymentSdkPromises[e],t})),this._paymentSdkPromises[e]):Promise.reject(new Error(a("The secure payment service is not configured.")))}_paypalPaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.paypalPaymentsEnabled||!e.paypal||"function"!=typeof e.paypal.Buttons)}_stripePaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.depositEnabled||!yatoonData.stripePublishableKey||"function"!=typeof e.Stripe)}_createStripePaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._stripePaymentsReady())return void s(new Error(a("Stripe payment form is not ready. Please refresh and try again.")));const i=new FormData;Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){i.append(e+"[]",t)}):i.append(e,null==t?"":t)}),i.set("action","yatoon_stripe_create_intent"),i.set("nonce",yatoonData.nonce),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:i,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(n){if(n?.success&&n.data?.already_paid&&n.data.intent_id)return void r(n.data.intent_id);if(!(n&&n.success&&n.data&&n.data.client_secret))throw new Error(n&&n.data&&n.data.message?n.data.message:a("Could not start the Stripe payment."));let i=t("#sbs-stripe-payment-box");if(!i.length){i=t(`\n                                <div id="sbs-stripe-payment-box" style="margin:16px 0;padding:16px;border:1px solid #ddd6fe;border-radius:14px;background:#f5f3ff;">\n                                    <div style="font-size:14px;font-weight:800;color:#2e1065;margin-bottom:6px;">Secure Stripe Payment</div>\n                                    <div id="sbs-stripe-payment-amount" style="font-size:13px;color:#475569;margin-bottom:12px;"></div>\n                                    <div id="sbs-stripe-payment-element"></div>\n                                    <button type="button" id="sbs-stripe-pay-button" style="width:100%;min-height:46px;margin-top:14px;border:0;border-radius:9px;background:#635bff;color:#fff;font-weight:800;cursor:pointer;">${"full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit")}</button>\n                                    <div id="sbs-stripe-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                                </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(i):t(".sbs-step-5").prepend(i)}t("#sbs-stripe-payment-element").empty(),t("#sbs-stripe-payment-amount").text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+(n.data.deposit_display||""));const p=e.Stripe(yatoonData.stripePublishableKey),d=p.elements({clientSecret:n.data.client_secret});d.create("payment").mount("#sbs-stripe-payment-element");const u=t("#sbs-stripe-payment-status");t("#sbs-stripe-pay-button").off(".yatoonStripe").on("click.yatoonStripe",function(){const e=t(this).prop("disabled",!0).text(a("Processing..."));u.text(a("Confirming your payment securely...")),p.confirmPayment({elements:d,redirect:"if_required"}).then(function(e){if(e.error)throw new Error(e.error.message||a("Stripe could not complete the payment."));const t=e.paymentIntent?e.paymentIntent.status:"";if("succeeded"!==t&&"processing"!==t)throw new Error(a("Stripe payment is not complete."));u.text(a("processing"===t?"Bank payment submitted. Confirming your appointment...":"Payment approved. Confirming your appointment...")),i.hide(),r(e.paymentIntent.id)}).catch(function(t){e.prop("disabled",!1).text("full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit")),u.text(t.message||a("Stripe payment failed.")),o._showToast(t.message||a("Stripe payment failed."),"error"),s(t)})}),i.show(),i[0]&&i[0].scrollIntoView&&i[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(s)})}_createPayPalPaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._paypalPaymentsReady())return void s(new Error(a("PayPal Checkout is not ready. Please refresh and try again.")));let i=t("#sbs-paypal-payment-box");if(!i.length){i=t(`\n                        <div id="sbs-paypal-payment-box" style="margin:16px 0;padding:16px;border:1px solid #bfdbfe;border-radius:14px;background:#eff6ff;">\n                            <div style="font-size:14px;font-weight:800;color:#172554;margin-bottom:6px;">Secure PayPal Payment</div>\n                            <div style="font-size:13px;color:#475569;margin-bottom:12px;">${"full"===yatoonData.nativePaymentMode?a("Pay the full appointment total with PayPal to confirm your booking."):a("Choose PayPal below to pay the deposit and confirm your appointment.")}</div>\n                            <div id="sbs-paypal-button-container"></div>\n                            <div id="sbs-paypal-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                        </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(i):t(".sbs-step-5").prepend(i)}const p=t("#sbs-paypal-payment-status"),d=yatoonData.ajax_url||yatoonData.ajaxUrl;function u(e,t){const o=new FormData;return Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){o.append(e+"[]",t)}):o.append(e,null==t?"":t)}),o.set("action",e),o.set("nonce",yatoonData.nonce),Object.keys(t||{}).forEach(function(e){o.set(e,t[e])}),fetch(d,{method:"POST",body:o,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data)throw new Error(e&&e.data&&e.data.message?e.data.message:a("PayPal payment failed."));return e.data})}t("#sbs-paypal-button-container").empty(),e.paypal.Buttons({style:{layout:"vertical",shape:"rect",label:"paypal"},createOrder:function(){return p.text(a("Preparing secure PayPal Checkout...")),u("yatoon_paypal_create_order").then(function(e){return e.amount_display&&p.text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+e.amount_display),e.order_id})},onApprove:function(e){return p.text(a("Confirming your PayPal payment...")),u("yatoon_paypal_capture_order",{order_id:e.orderID}).then(function(t){p.text(a("Payment approved. Confirming your appointment...")),i.hide(),r(t.order_id||e.orderID)}).catch(function(e){p.text(e.message||a("PayPal could not capture the payment.")),o._showToast(e.message||a("PayPal could not capture the payment."),"error"),s(e)})},onCancel:function(){const e=new Error(a("PayPal Checkout was cancelled."));p.text(e.message),s(e)},onError:function(e){p.text(a("PayPal Checkout could not be opened. Please try again.")),o._showToast(e&&e.message?e.message:a("PayPal Checkout failed."),"error"),s(e instanceof Error?e:new Error(a("PayPal Checkout failed.")))}}).render("#sbs-paypal-button-container").then(function(){i.show(),i[0]&&i[0].scrollIntoView&&i[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(s)})}_squareMethods(){const e=yatoonData&&Array.isArray(yatoonData.squarePaymentMethods)?yatoonData.squarePaymentMethods:["card"];return e.length?e:["card"]}_previewSquarePayment(e){const t=new FormData;return Object.keys(e).forEach(function(a){const n=e[a];Array.isArray(n)?n.forEach(function(e){t.append(a+"[]",e)}):t.append(a,null==n?"":n)}),t.set("action","yatoon_square_preview_payment"),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:t,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data){const t=e&&e.data&&e.data.message?e.data.message:a("Could not calculate the Square payment amount.");throw new Error(t)}return e.data})}_buildSquarePaymentRequest(e,t){return e.paymentRequest({countryCode:(yatoonData.squareCountryCode||"US").toUpperCase(),currencyCode:(t.currency||yatoonData.currency||"USD").toUpperCase(),total:{amount:t.amount||"0.00",label:"full"===yatoonData.squarePaymentMode?a("Appointment total"):a("Booking deposit")}})}_ensureSquarePaymentCard(n){const o=this,r=JSON.stringify({services_json:n.services_json||"",service_id:n.service_id||"",selected_options:n.selected_options||[],coupon_code:n.coupon_code||"",customer_email:n.customer_email||"",customer_phone:n.customer_phone||"",client_reference:n.client_reference||"",checkout_token:n.checkout_token||"",start_at:n.start_at||"",location:n.yatoon_location_id||0,tip_amount:n.tip_amount||0,gift_card_code:n.gift_card_code||""});return this._squareCardPromise&&this._squarePaymentPayloadKey===r||(this._squarePaymentPayloadKey=r,this._squareCardPromise=new Promise(function(r,s){if(!o._squarePaymentsReady())return void s(new Error(a("Square payment form is not ready. Please refresh and try again.")));let i=t("#sbs-square-payment-box");if(!i.length){const e="full"===yatoonData.squarePaymentMode?a("Pay securely now to confirm your appointment."):a("Pay your deposit securely now to confirm your appointment.");i=t(`\n                            <div id="sbs-square-payment-box" class="sbs-square-payment-box" style="margin:16px 0;padding:16px;border:1px solid #dbeafe;border-radius:14px;background:#eff6ff;">\n                                <div style="font-size:14px;font-weight:800;color:#1e3a8a;margin-bottom:6px;">Secure Square Payment</div>\n                                <div style="font-size:13px;color:#475569;margin-bottom:12px;">${e}</div>\n                                <div id="sbs-square-payment-amount" style="font-size:13px;font-weight:800;color:#0f172a;margin-bottom:12px;"></div>\n                                <div id="sbs-square-method-tabs" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;"></div>\n                                <button id="sbs-square-apple-pay-button" type="button" class="sbs-square-method-panel" data-method-panel="apple_pay" style="display:none;width:100%;min-height:44px;border-radius:8px;border:0;background:#000;color:#fff;font-weight:800;">Apple Pay</button>\n                                <div id="sbs-square-google-pay" class="sbs-square-method-panel" data-method-panel="google_pay" style="display:none;"></div>\n                                <div id="sbs-square-cash-app-pay" class="sbs-square-method-panel" data-method-panel="cash_app_pay" style="display:none;"></div>\n                                <div id="sbs-square-gift-card-container" class="sbs-square-method-panel" data-method-panel="gift_card" style="display:none;"></div>\n                                <div id="sbs-square-card-container"></div>\n                                <div id="sbs-square-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                            </div>\n                        `);const n=t("#sbs-booking-form");n.length?n.prepend(i):t(".sbs-step-5").prepend(i)}t("#sbs-square-payment-status").text(a("Loading secure Square card form...")),o._previewSquarePayment(n).then(function(s){return t("#sbs-square-payment-amount").text(("full"===s.payment_mode?a("Pay now:"):a("Deposit due now:"))+" "+s.amount_display),Promise.resolve(e.Square.payments(yatoonData.squareApplicationId,yatoonData.squareLocationId)).then(function(i){const p=o._squareMethods(),d={payments:i,preview:s,methods:{},selected:p[0]||"card"},u=o._buildSquarePaymentRequest(i,s),c={card:"Card",apple_pay:a("Apple Pay"),google_pay:a("Google Pay"),cash_app_pay:a("Cash App"),gift_card:a("Gift Card")},y=t("#sbs-square-method-tabs").empty();function l(e){d.selected=e,t(".sbs-square-method-tab").each(function(){const a=t(this).data("method")===e;t(this).css({background:a?"#1d4ed8":"#fff",color:a?"#fff":"#1e3a8a"})}),t(".sbs-square-method-panel").hide(),t("#sbs-square-card-container").toggle("card"===e),t('[data-method-panel="'+e+'"]').show(),t("#sbs-square-payment-status").text(a("apple_pay"===e||"cash_app_pay"===e?"Tap the wallet button above to authorize payment, then booking will continue automatically.":"Payment details are encrypted by Square."))}p.forEach(function(e){t("<button/>",{type:"button",class:"sbs-square-method-tab","data-method":e,text:c[e]||e}).css({border:"1px solid #bfdbfe",background:e===d.selected?"#1d4ed8":"#fff",color:e===d.selected?"#fff":"#1e3a8a",borderRadius:"999px",padding:"8px 12px",fontWeight:"800",cursor:"pointer"}).appendTo(y)}),t(document).off("click.squareMethod").on("click.squareMethod",".sbs-square-method-tab",function(){l(t(this).data("method"))});const m=[];return p.includes("card")&&m.push(i.card().then(function(e){return d.methods.card=e,e.attach("#sbs-square-card-container")})),p.includes("gift_card")&&"function"==typeof i.giftCard&&m.push(i.giftCard().then(function(e){return d.methods.gift_card=e,e.attach("#sbs-square-gift-card-container")}).catch(function(){})),p.includes("apple_pay")&&"function"==typeof i.applePay&&m.push(i.applePay(u).then(function(e){d.methods.apple_pay=e,t("#sbs-square-apple-pay-button").off("click.squareApple").on("click.squareApple",function(t){t.preventDefault(),o._handleSquareWalletButton(n,e,"apple_pay")})}).catch(function(){t('[data-method="apple_pay"], [data-method-panel="apple_pay"]').hide()})),p.includes("google_pay")&&"function"==typeof i.googlePay&&m.push(i.googlePay(u).then(function(e){return d.methods.google_pay=e,e.attach("#sbs-square-google-pay")}).catch(function(){t('[data-method="google_pay"], [data-method-panel="google_pay"]').hide()})),p.includes("cash_app_pay")&&"function"==typeof i.cashAppPay&&m.push(i.cashAppPay(u,{redirectURL:e.location.href,referenceId:"yatoon-"+Date.now()}).then(function(e){return d.methods.cash_app_pay=e,e.addEventListener("ontokenization",function(e){const t=e.detail||{};t.error?o._showToast(a("Cash App Pay could not be authorized."),"error"):t.tokenResult&&"OK"===t.tokenResult.status&&o._finishSquareTokenPayment(n,t.tokenResult.token,"cash_app_pay")}),e.attach("#sbs-square-cash-app-pay")}).catch(function(){t('[data-method="cash_app_pay"], [data-method-panel="cash_app_pay"]').hide()})),Promise.all(m).then(function(){o._squarePaymentState=d,l(d.methods[d.selected]?d.selected:d.methods.card?"card":Object.keys(d.methods)[0]),r(d)})})}).catch(function(e){o._squareCardPromise=null,s(e)})})),this._squareCardPromise}_finishSquareTokenPayment(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Completing")+" "+(o||a("Square"))+" "+a("payment...")),this._sendSquareTokenPayment(e,n).then(function(n){return r._squarePreparedPaymentId=n,r._squarePreparedPaymentReference=e.client_reference,r._rememberCheckoutPayment&&r._rememberCheckoutPayment(e.client_reference,"square_payment_id",n),t("#sbs-square-payment-status").text(a("Payment approved by Square. Confirming your booking...")),r._isSubmitting=!1,r.submitBooking(),n}).catch(function(e){throw r._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e})}_handleSquareWalletButton(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Opening")+" "+a("apple_pay"===o?"Apple Pay":"Square wallet")+"..."),n.tokenize().then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("The wallet payment was not authorized.");throw new Error(e)}return r._finishSquareTokenPayment(e,t.token,o)}).catch(function(e){t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Wallet payment failed.")),r._showToast(e&&e.message?e.message:a("Wallet payment failed. Please try again."),"error")})}_sendSquareTokenPayment(e,t){const n=new FormData;return Object.keys(e).forEach(function(t){const a=e[t];Array.isArray(a)?a.forEach(function(e){n.append(t+"[]",e)}):n.append(t,null==a?"":a)}),n.set("action","yatoon_square_create_payment"),n.set("source_id",t),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:n,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!(e&&e.success&&e.data&&e.data.payment_id)){const t=e&&e.data&&e.data.message?e.data.message:a("Square payment failed. Please try again.");throw new Error(t)}return e.data.payment_id})}_createSquarePaymentForPayload(e){const n=this;return this._squarePaymentsReady()?this._ensureSquarePaymentCard(e).then(function(e){t("#sbs-square-payment-box").show();const n=document.getElementById("sbs-square-payment-box");n&&n.scrollIntoView({behavior:"smooth",block:"center"});const o=e.selected||"card",r=e.methods[o];if(!r)throw new Error(a("This Square payment method is not available on this device. Please choose another method."));return"cash_app_pay"===o?(t("#sbs-square-payment-status").text(a("Tap the Cash App Pay button above to authorize payment.")),new Promise(function(){})):(t("#sbs-square-payment-status").text(a("Securing your payment with Square...")),r.tokenize())}).then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("Please check your card details and try again.");throw new Error(e)}return n._sendSquareTokenPayment(e,t.token)}).then(function(e){return t("#sbs-square-payment-status").text(a("Payment approved by Square.")),e}).catch(function(e){throw t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Square payment failed.")),n._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e}):Promise.reject(new Error(a("Square payment is enabled but the secure payment form is unavailable.")))}}Object.getOwnPropertyNames(o.prototype).forEach(function(e){"constructor"!==e&&Object.defineProperty(n.prototype,e,Object.getOwnPropertyDescriptor(o.prototype,e))})}}(window,window.jQuery);
     1!function(e,t){"use strict";const a=e.wp&&e.wp.i18n&&"function"==typeof e.wp.i18n.__?function(t){return e.wp.i18n.__(t,"yatoon-booking-system")}:function(e){return e},n=e.YatoonSquareBookingForm;if(n&&t){class o{_squarePaymentsReady(){return!!("undefined"!=typeof yatoonData&&yatoonData.squarePaymentsEnabled&&yatoonData.squareApplicationId&&yatoonData.squareLocationId&&e.Square&&"function"==typeof e.Square.payments)}_paymentProviderEnabled(e){return"undefined"!=typeof yatoonData&&("square"===e?!!yatoonData.squarePaymentsEnabled:"paypal"===e?!!yatoonData.paypalPaymentsEnabled:"stripe"===e&&!!yatoonData.depositEnabled)}_ensurePaymentSdk(e){const t={square:()=>this._squarePaymentsReady(),paypal:()=>this._paypalPaymentsReady(),stripe:()=>this._stripePaymentsReady()}[e],n={square:yatoonData.squareSdkUrl,paypal:yatoonData.paypalSdkUrl,stripe:yatoonData.stripeSdkUrl}[e];return t&&t()?Promise.resolve():n?(this._paymentSdkPromises=this._paymentSdkPromises||{},this._paymentSdkPromises[e]||(this._paymentSdkPromises[e]=new Promise(function(o,r){const s=document.createElement("script");s.src=n,s.async=!0,s.dataset.yatoonPaymentSdk=e,s.onload=function(){t&&t()?o():r(new Error(a("The secure payment service did not initialize.")))},s.onerror=function(){r(new Error(a("The secure payment service could not be loaded. Check your connection and try again.")))},document.head.appendChild(s)}).catch(t=>{throw delete this._paymentSdkPromises[e],t})),this._paymentSdkPromises[e]):Promise.reject(new Error(a("The secure payment service is not configured.")))}_paypalPaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.paypalPaymentsEnabled||!e.paypal||"function"!=typeof e.paypal.Buttons)}_stripePaymentsReady(){return!("undefined"==typeof yatoonData||!yatoonData.depositEnabled||!yatoonData.stripePublishableKey||"function"!=typeof e.Stripe)}_createStripePaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._stripePaymentsReady()){const e=new Error(a("Stripe payment form is not ready. Please refresh and try again."));return e.data={preflight:!0},void s(e)}const i=new FormData;Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){i.append(e+"[]",t)}):i.append(e,null==t?"":t)}),i.set("action","yatoon_stripe_create_intent"),i.set("nonce",yatoonData.nonce),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:i,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(n){if(n?.success&&n.data?.already_paid&&n.data.intent_id)return void r(n.data.intent_id);if(!(n&&n.success&&n.data&&n.data.client_secret))throw new Error(n&&n.data&&n.data.message?n.data.message:a("Could not start the Stripe payment."));let s=t("#sbs-stripe-payment-box");if(!s.length){s=t(`\n                                <div id="sbs-stripe-payment-box" style="margin:16px 0;padding:16px;border:1px solid #ddd6fe;border-radius:14px;background:#f5f3ff;">\n                                    <div style="font-size:14px;font-weight:800;color:#2e1065;margin-bottom:6px;">Secure Stripe Payment</div>\n                                    <div id="sbs-stripe-payment-amount" style="font-size:13px;color:#475569;margin-bottom:12px;"></div>\n                                    <div id="sbs-stripe-payment-element"></div>\n                                    <button type="button" id="sbs-stripe-pay-button" style="width:100%;min-height:46px;margin-top:14px;border:0;border-radius:9px;background:#635bff;color:#fff;font-weight:800;cursor:pointer;">${"full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit")}</button>\n                                    <div id="sbs-stripe-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                                </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(s):t(".sbs-step-5").prepend(s)}t("#sbs-stripe-payment-element").empty(),t("#sbs-stripe-payment-amount").text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+(n.data.deposit_display||""));const i=e.Stripe(yatoonData.stripePublishableKey),p=i.elements({clientSecret:n.data.client_secret});p.create("payment").mount("#sbs-stripe-payment-element");const d=t("#sbs-stripe-payment-status"),c="full"===yatoonData.nativePaymentMode?a("Pay total"):a("Pay deposit");d.text(""),t("#sbs-stripe-pay-button").prop("disabled",!1).text(c),t("#sbs-stripe-pay-button").off(".yatoonStripe").on("click.yatoonStripe",function(){const e=t(this).prop("disabled",!0).text(a("Processing..."));d.text(a("Confirming your payment securely..."));const n=function(t){e.prop("disabled",!1).text(c),d.text(t),o._showToast(t,"error")};i.confirmPayment({elements:p,redirect:"if_required"}).then(function(e){let t=e.paymentIntent||null;if(e.error&&e.error.payment_intent){const a=e.error.payment_intent.status;"succeeded"!==a&&"processing"!==a||(t=e.error.payment_intent)}if(e.error&&!t)return void n(e.error.message||a("Stripe could not complete the payment."));const o=t?t.status:"";"succeeded"===o||"processing"===o?(d.text(a("processing"===o?"Bank payment submitted. Confirming your appointment...":"Payment approved. Confirming your appointment...")),s.hide(),r(t.id)):n(a("Stripe payment is not complete. Please check your payment details and try again."))}).catch(function(e){n(e&&e.message||a("We could not reach Stripe. Please check your connection and press Pay again."))})}),s.show(),s[0]&&s[0].scrollIntoView&&s[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(function(e){e&&"object"==typeof e&&(e.data=Object.assign({},e.data,{preflight:!0})),s(e)})})}_createPayPalPaymentForPayload(n){const o=this;return new Promise(function(r,s){if(!o._paypalPaymentsReady())return void s(new Error(a("PayPal Checkout is not ready. Please refresh and try again.")));let i=t("#sbs-paypal-payment-box");if(!i.length){i=t(`\n                        <div id="sbs-paypal-payment-box" style="margin:16px 0;padding:16px;border:1px solid #bfdbfe;border-radius:14px;background:#eff6ff;">\n                            <div style="font-size:14px;font-weight:800;color:#172554;margin-bottom:6px;">Secure PayPal Payment</div>\n                            <div style="font-size:13px;color:#475569;margin-bottom:12px;">${"full"===yatoonData.nativePaymentMode?a("Pay the full appointment total with PayPal to confirm your booking."):a("Choose PayPal below to pay the deposit and confirm your appointment.")}</div>\n                            <div id="sbs-paypal-button-container"></div>\n                            <div id="sbs-paypal-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                        </div>`);const e=t("#sbs-booking-form");e.length?e.prepend(i):t(".sbs-step-5").prepend(i)}const p=t("#sbs-paypal-payment-status"),d=yatoonData.ajax_url||yatoonData.ajaxUrl;function c(e,t){const o=new FormData;return Object.keys(n).forEach(function(e){const t=n[e];Array.isArray(t)?t.forEach(function(t){o.append(e+"[]",t)}):o.append(e,null==t?"":t)}),o.set("action",e),o.set("nonce",yatoonData.nonce),Object.keys(t||{}).forEach(function(e){o.set(e,t[e])}),fetch(d,{method:"POST",body:o,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data)throw new Error(e&&e.data&&e.data.message?e.data.message:a("PayPal payment failed."));return e.data})}t("#sbs-paypal-button-container").empty(),e.paypal.Buttons({style:{layout:"vertical",shape:"rect",label:"paypal"},createOrder:function(){return p.text(a("Preparing secure PayPal Checkout...")),c("yatoon_paypal_create_order").then(function(e){return e.amount_display&&p.text(("full"===yatoonData.nativePaymentMode?a("Full payment due now:"):a("Deposit due now:"))+" "+e.amount_display),e.order_id})},onApprove:function(e){return p.text(a("Confirming your PayPal payment...")),c("yatoon_paypal_capture_order",{order_id:e.orderID}).then(function(t){p.text(a("Payment approved. Confirming your appointment...")),i.hide(),r(t.order_id||e.orderID)}).catch(function(e){p.text(e.message||a("PayPal could not capture the payment.")),o._showToast(e.message||a("PayPal could not capture the payment."),"error"),s(e)})},onCancel:function(){const e=new Error(a("PayPal Checkout was cancelled."));p.text(e.message),s(e)},onError:function(e){p.text(a("PayPal Checkout could not be opened. Please try again.")),o._showToast(e&&e.message?e.message:a("PayPal Checkout failed."),"error"),s(e instanceof Error?e:new Error(a("PayPal Checkout failed.")))}}).render("#sbs-paypal-button-container").then(function(){i.show(),i[0]&&i[0].scrollIntoView&&i[0].scrollIntoView({behavior:"smooth",block:"center"})}).catch(s)})}_squareMethods(){const e=yatoonData&&Array.isArray(yatoonData.squarePaymentMethods)?yatoonData.squarePaymentMethods:["card"];return e.length?e:["card"]}_previewSquarePayment(e){const t=new FormData;return Object.keys(e).forEach(function(a){const n=e[a];Array.isArray(n)?n.forEach(function(e){t.append(a+"[]",e)}):t.append(a,null==n?"":n)}),t.set("action","yatoon_square_preview_payment"),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:t,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!e||!e.success||!e.data){const t=e&&e.data&&e.data.message?e.data.message:a("Could not calculate the Square payment amount.");throw new Error(t)}return e.data})}_buildSquarePaymentRequest(e,t){return e.paymentRequest({countryCode:(yatoonData.squareCountryCode||"US").toUpperCase(),currencyCode:(t.currency||yatoonData.currency||"USD").toUpperCase(),total:{amount:t.amount||"0.00",label:"full"===yatoonData.squarePaymentMode?a("Appointment total"):a("Booking deposit")}})}_ensureSquarePaymentCard(n){const o=this,r=JSON.stringify({services_json:n.services_json||"",service_id:n.service_id||"",selected_options:n.selected_options||[],coupon_code:n.coupon_code||"",customer_email:n.customer_email||"",customer_phone:n.customer_phone||"",client_reference:n.client_reference||"",checkout_token:n.checkout_token||"",start_at:n.start_at||"",location:n.yatoon_location_id||0,tip_amount:n.tip_amount||0,gift_card_code:n.gift_card_code||""});return this._squareCardPromise&&this._squarePaymentPayloadKey===r||(this._squarePaymentPayloadKey=r,this._squareCardPromise=new Promise(function(r,s){if(!o._squarePaymentsReady())return void s(new Error(a("Square payment form is not ready. Please refresh and try again.")));let i=t("#sbs-square-payment-box");if(!i.length){const e="full"===yatoonData.squarePaymentMode?a("Pay securely now to confirm your appointment."):a("Pay your deposit securely now to confirm your appointment.");i=t(`\n                            <div id="sbs-square-payment-box" class="sbs-square-payment-box" style="margin:16px 0;padding:16px;border:1px solid #dbeafe;border-radius:14px;background:#eff6ff;">\n                                <div style="font-size:14px;font-weight:800;color:#1e3a8a;margin-bottom:6px;">Secure Square Payment</div>\n                                <div style="font-size:13px;color:#475569;margin-bottom:12px;">${e}</div>\n                                <div id="sbs-square-payment-amount" style="font-size:13px;font-weight:800;color:#0f172a;margin-bottom:12px;"></div>\n                                <div id="sbs-square-method-tabs" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;"></div>\n                                <button id="sbs-square-apple-pay-button" type="button" class="sbs-square-method-panel" data-method-panel="apple_pay" style="display:none;width:100%;min-height:44px;border-radius:8px;border:0;background:#000;color:#fff;font-weight:800;">Apple Pay</button>\n                                <div id="sbs-square-google-pay" class="sbs-square-method-panel" data-method-panel="google_pay" style="display:none;"></div>\n                                <div id="sbs-square-cash-app-pay" class="sbs-square-method-panel" data-method-panel="cash_app_pay" style="display:none;"></div>\n                                <div id="sbs-square-gift-card-container" class="sbs-square-method-panel" data-method-panel="gift_card" style="display:none;"></div>\n                                <div id="sbs-square-card-container"></div>\n                                <div id="sbs-square-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>\n                            </div>\n                        `);const n=t("#sbs-booking-form");n.length?n.prepend(i):t(".sbs-step-5").prepend(i)}t("#sbs-square-payment-status").text(a("Loading secure Square card form...")),o._previewSquarePayment(n).then(function(s){return t("#sbs-square-payment-amount").text(("full"===s.payment_mode?a("Pay now:"):a("Deposit due now:"))+" "+s.amount_display),Promise.resolve(e.Square.payments(yatoonData.squareApplicationId,yatoonData.squareLocationId)).then(function(i){const p=o._squareMethods(),d={payments:i,preview:s,methods:{},selected:p[0]||"card"},c=o._buildSquarePaymentRequest(i,s),u={card:"Card",apple_pay:a("Apple Pay"),google_pay:a("Google Pay"),cash_app_pay:a("Cash App"),gift_card:a("Gift Card")},y=t("#sbs-square-method-tabs").empty();function l(e){d.selected=e,t(".sbs-square-method-tab").each(function(){const a=t(this).data("method")===e;t(this).css({background:a?"#1d4ed8":"#fff",color:a?"#fff":"#1e3a8a"})}),t(".sbs-square-method-panel").hide(),t("#sbs-square-card-container").toggle("card"===e),t('[data-method-panel="'+e+'"]').show(),t("#sbs-square-payment-status").text(a("apple_pay"===e||"cash_app_pay"===e?"Tap the wallet button above to authorize payment, then booking will continue automatically.":"Payment details are encrypted by Square."))}p.forEach(function(e){t("<button/>",{type:"button",class:"sbs-square-method-tab","data-method":e,text:u[e]||e}).css({border:"1px solid #bfdbfe",background:e===d.selected?"#1d4ed8":"#fff",color:e===d.selected?"#fff":"#1e3a8a",borderRadius:"999px",padding:"8px 12px",fontWeight:"800",cursor:"pointer"}).appendTo(y)}),t(document).off("click.squareMethod").on("click.squareMethod",".sbs-square-method-tab",function(){l(t(this).data("method"))});const m=[];return p.includes("card")&&m.push(i.card().then(function(e){return d.methods.card=e,e.attach("#sbs-square-card-container")})),p.includes("gift_card")&&"function"==typeof i.giftCard&&m.push(i.giftCard().then(function(e){return d.methods.gift_card=e,e.attach("#sbs-square-gift-card-container")}).catch(function(){})),p.includes("apple_pay")&&"function"==typeof i.applePay&&m.push(i.applePay(c).then(function(e){d.methods.apple_pay=e,t("#sbs-square-apple-pay-button").off("click.squareApple").on("click.squareApple",function(t){t.preventDefault(),o._handleSquareWalletButton(n,e,"apple_pay")})}).catch(function(){t('[data-method="apple_pay"], [data-method-panel="apple_pay"]').hide()})),p.includes("google_pay")&&"function"==typeof i.googlePay&&m.push(i.googlePay(c).then(function(e){return d.methods.google_pay=e,e.attach("#sbs-square-google-pay")}).catch(function(){t('[data-method="google_pay"], [data-method-panel="google_pay"]').hide()})),p.includes("cash_app_pay")&&"function"==typeof i.cashAppPay&&m.push(i.cashAppPay(c,{redirectURL:e.location.href,referenceId:"yatoon-"+Date.now()}).then(function(e){return d.methods.cash_app_pay=e,e.addEventListener("ontokenization",function(e){const t=e.detail||{};t.error?o._showToast(a("Cash App Pay could not be authorized."),"error"):t.tokenResult&&"OK"===t.tokenResult.status&&o._finishSquareTokenPayment(n,t.tokenResult.token,"cash_app_pay")}),e.attach("#sbs-square-cash-app-pay")}).catch(function(){t('[data-method="cash_app_pay"], [data-method-panel="cash_app_pay"]').hide()})),Promise.all(m).then(function(){o._squarePaymentState=d,l(d.methods[d.selected]?d.selected:d.methods.card?"card":Object.keys(d.methods)[0]),r(d)})})}).catch(function(e){o._squareCardPromise=null,s(e)})})),this._squareCardPromise}_finishSquareTokenPayment(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Completing")+" "+(o||a("Square"))+" "+a("payment...")),this._sendSquareTokenPayment(e,n).then(function(n){return r._squarePreparedPaymentId=n,r._squarePreparedPaymentReference=e.client_reference,r._rememberCheckoutPayment&&r._rememberCheckoutPayment(e.client_reference,"square_payment_id",n),t("#sbs-square-payment-status").text(a("Payment approved by Square. Confirming your booking...")),r._isSubmitting=!1,r.submitBooking(),n}).catch(function(e){throw r._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e})}_handleSquareWalletButton(e,n,o){const r=this;return t("#sbs-square-payment-status").text(a("Opening")+" "+a("apple_pay"===o?"Apple Pay":"Square wallet")+"..."),n.tokenize().then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("The wallet payment was not authorized.");throw new Error(e)}return r._finishSquareTokenPayment(e,t.token,o)}).catch(function(e){t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Wallet payment failed.")),r._showToast(e&&e.message?e.message:a("Wallet payment failed. Please try again."),"error")})}_sendSquareTokenPayment(e,t){const n=new FormData;return Object.keys(e).forEach(function(t){const a=e[t];Array.isArray(a)?a.forEach(function(e){n.append(t+"[]",e)}):n.append(t,null==a?"":a)}),n.set("action","yatoon_square_create_payment"),n.set("source_id",t),fetch(yatoonData.ajax_url||yatoonData.ajaxUrl,{method:"POST",body:n,credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){if(!(e&&e.success&&e.data&&e.data.payment_id)){const t=e&&e.data&&e.data.message?e.data.message:a("Square payment failed. Please try again.");throw new Error(t)}return e.data.payment_id})}_createSquarePaymentForPayload(e){const n=this;return this._squarePaymentsReady()?this._ensureSquarePaymentCard(e).then(function(e){t("#sbs-square-payment-box").show();const n=document.getElementById("sbs-square-payment-box");n&&n.scrollIntoView({behavior:"smooth",block:"center"});const o=e.selected||"card",r=e.methods[o];if(!r)throw new Error(a("This Square payment method is not available on this device. Please choose another method."));return"cash_app_pay"===o?(t("#sbs-square-payment-status").text(a("Tap the Cash App Pay button above to authorize payment.")),new Promise(function(){})):(t("#sbs-square-payment-status").text(a("Securing your payment with Square...")),r.tokenize())}).then(function(t){if(!t||"OK"!==t.status||!t.token){const e=t&&t.errors&&t.errors.length?t.errors.map(function(e){return e.message}).join(" "):a("Please check your card details and try again.");throw new Error(e)}return n._sendSquareTokenPayment(e,t.token)}).then(function(e){return t("#sbs-square-payment-status").text(a("Payment approved by Square.")),e}).catch(function(e){throw t("#sbs-square-payment-status").text(e&&e.message?e.message:a("Square payment failed.")),n._showToast(e&&e.message?e.message:a("Square payment failed. Please try again."),"error"),e}):Promise.reject(new Error(a("Square payment is enabled but the secure payment form is unavailable.")))}}Object.getOwnPropertyNames(o.prototype).forEach(function(e){"constructor"!==e&&Object.defineProperty(n.prototype,e,Object.getOwnPropertyDescriptor(o.prototype,e))})}}(window,window.jQuery);
  • yatoon-booking-system/trunk/assets/js/yatoon-asset-manifest.json

    r3780403 r3780965  
    1 {
    2   "version": "4.10.0",
    3   "builder": "terser-5.50.0 + postcss-8.5.6/cssnano-7.1.2/rtlcss-4.3.0",
    4   "inputs": {
    5     "yatoon-category-bar-423-path16.js": {
    6       "sha256": "d9d21e398c540a08d8e07c291b54a5f670616e5c5da35fbff9f3b579b7424cc7",
    7       "bytes": 11121
    8     },
    9     "frontend.js": {
    10       "sha256": "338ebe2b74415737c7616e1e36b62a28028dae5aa2037f6a4b1a5b2bc68a7ded",
    11       "bytes": 182985
    12     },
    13     "manage-booking.js": {
    14       "sha256": "aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7",
    15       "bytes": 36499
    16     },
    17     "customer-portal.js": {
    18       "sha256": "c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707",
    19       "bytes": 83590
    20     },
    21     "admin-services.js": {
    22       "sha256": "7f8c293589fc1f2a4d9be24b19c78668e9b73106ec1eb8cd111f31b5ff88f376",
    23       "bytes": 79799
    24     },
    25     "admin.js": {
    26       "sha256": "4b0851842309bc4e794d67c903f8ae777f93f455a6b69e1729d2f813a88e4b9a",
    27       "bytes": 23364
    28     },
    29     "admin-settings.js": {
    30       "sha256": "0f6928ef9a7c2967f5759ff846bc2492af4faf8490352b9d01f7af4491dc2a64",
    31       "bytes": 58129
    32     },
    33     "admin-option-groups.js": {
    34       "sha256": "dab46594bdf73d1eea2a85e79f976570c34185d9434633483bb086facc2c3f9c",
    35       "bytes": 15069
    36     },
    37     "frontend-payments.js": {
    38       "sha256": "17b903150a7629e803d3c8664642a7a4c3b693f7473bff24216e35375a70cf5c",
    39       "bytes": 35380
    40     },
    41     "yatoon-concierge-4766.js": {
    42       "sha256": "079493bf51ced34a809eb3fbc06079f507070627e247d1955019297acbcb59a1",
    43       "bytes": 131985
    44     },
    45     "admin-calendar.js": {
    46       "sha256": "46ecdfa78ad656102c720dd6deb0e0ef97ca94e758c1b56f5ba9c33b38ce7c9a",
    47       "bytes": 36565
    48     },
    49     "admin-clients.js": {
    50       "sha256": "a42717f6b0c4f1f6515040a218cfa681be7e7d44d82261f6a7e1402c1cb3b2ce",
    51       "bytes": 29292
    52     },
    53     "admin-bookings.js": {
    54       "sha256": "c1f8b151b6d7e097443634572aad2714aeb36cda507f525f7fe5842a6f1d467f",
    55       "bytes": 41934
    56     },
    57     "admin-staff.js": {
    58       "sha256": "b6b6e56b538ed9f2b6768104c7b7cc2bda2d94ffa48ff00932e964b1fe454b89",
    59       "bytes": 54358
    60     },
    61     "staff-portal.js": {
    62       "sha256": "2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68",
    63       "bytes": 220988
    64     },
    65     "frontend-availability.js": {
    66       "sha256": "4a271e0ecb0db8a4385331c67ba27776c3c7212aafe4e29e9f9ae49eace017c8",
    67       "bytes": 70358
    68     },
    69     "frontend-checkout.js": {
    70       "sha256": "44d9771ecd3505f72066e6eae910998ff38a2cb5dd4ff8363276f71549e062d2",
    71       "bytes": 68665
    72     },
    73     "frontend-recovery.js": {
    74       "sha256": "f96d9a84be0724732eb6baf4292fa0f5a81a6523f53456de5e880c7cca1ccef8",
    75       "bytes": 4566
    76     },
    77     "frontend-glass.js": {
    78       "sha256": "595c0c6c539d1495470f9597465a863e913fe81f726c7c7824c4f92d5051d66d",
    79       "bytes": 1416
    80     },
    81     "client-merge-ui.js": {
    82       "sha256": "a4f80bea59a75459b0625f295ba486753fd266c8e410d847a640b7f2d9fb31fe",
    83       "bytes": 7635
    84     },
    85     "staff-hours-editor.js": {
    86       "sha256": "9c8f00b26da3dd8c26b9160f5e8e3dd55507743aebaaf84895faa584fce27a96",
    87       "bytes": 8209
    88     },
    89     "admin-tools.js": {
    90       "sha256": "d084fac962bd24d074f636ae04a3e084bf6e22ff4458ba79f57854647cd9c206",
    91       "bytes": 6818
    92     },
    93     "yatoon-app-shell.js": {
    94       "sha256": "801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877",
    95       "bytes": 9793
    96     },
    97     "yatoon-category-bar-4721.js": {
    98       "sha256": "3596279a0fb230ed54ae6fc8ba7f7f0dc207c81891ea345b758bc9397085ff68",
    99       "bytes": 14196
    100     },
    101     "frontend-flow.js": {
    102       "sha256": "1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392",
    103       "bytes": 233386
    104     },
    105     "frontend-cart.js": {
    106       "sha256": "24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4",
    107       "bytes": 120622
    108     },
    109     "frontend-boot.js": {
    110       "sha256": "c0d60031a8f1da307ea5e896a747f96616a76de18fa6021e5ddcb8d74fe8ae37",
    111       "bytes": 30947
    112     },
    113     "yatoon-booking-header.js": {
    114       "sha256": "2bb689d5bf4e08a80d27582ee5a4474c029c45b67a7c12da1401bb8e3bdc8aaf",
    115       "bytes": 11913
    116     },
    117     "yatoon-language-order.js": {
    118       "sha256": "599194bba4daf0a4258843f43df0082d3aae7f78c0e0432efc1f281d6527e9d4",
    119       "bytes": 2125
    120     }
    121   },
    122   "outputs": {
    123     "frontend.min.js": {
    124       "sha256": "2358e461fd346486cc1d89f8702c1bca3e762ffd68863288166a93c03dd38773",
    125       "bytes": 78688
    126     },
    127     "manage-booking.min.js": {
    128       "sha256": "aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7",
    129       "bytes": 36499
    130     },
    131     "customer-portal.min.js": {
    132       "sha256": "c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707",
    133       "bytes": 83590
    134     },
    135     "admin-services.min.js": {
    136       "sha256": "18c487c0411571490cf721d0b30b49f2eff8e60d3d6c662e36d3920b7120c32f",
    137       "bytes": 46201
    138     },
    139     "admin.min.js": {
    140       "sha256": "ff2b431a9d6dc0f5e8c391ca762c27172461acfcf4ff85773bc865e5e9f75c37",
    141       "bytes": 10612
    142     },
    143     "admin-settings.min.js": {
    144       "sha256": "38db0164f533a4edfe41d999727e1f67705d498a8f50f50af0532e10e656ccb0",
    145       "bytes": 31390
    146     },
    147     "admin-option-groups.min.js": {
    148       "sha256": "7bc60b7c3208f3dc3d6380207ed551ed1619d00924c5a62cafa071958fbcef3e",
    149       "bytes": 11420
    150     },
    151     "frontend-payments.min.js": {
    152       "sha256": "e2c70a3d8bde13b32d4016a02835d6a2b2dc63e0171f74ef06ce829609f40925",
    153       "bytes": 18861
    154     },
    155     "frontend-availability.min.js": {
    156       "sha256": "aaa225d5ee3a114a82e1af57be8180d866d140f0ddbab2f81c3059f7c4fdeefb",
    157       "bytes": 29644
    158     },
    159     "yatoon-category-bar-423-path16.min.js": {
    160       "sha256": "a5608fd4415c1dbf4e851a4f207e60c8dd48e96f8ff9e7da3b396f6c71673b7d",
    161       "bytes": 6115
    162     },
    163     "yatoon-concierge-4766.min.js": {
    164       "sha256": "091e689701686070f21841c298121cd67163b6a0214b39a002d4bf96aa5e7652",
    165       "bytes": 51279
    166     },
    167     "admin-calendar.min.js": {
    168       "sha256": "ff82d1459da6b1f422dfd4437df106919891ed8fcc05e756c708513c87cb54d7",
    169       "bytes": 18956
    170     },
    171     "admin-clients.min.js": {
    172       "sha256": "9dea7f478d7b3d9310a1f9c0375bf18ccde5857ce0402a712407d48227dd7dda",
    173       "bytes": 17378
    174     },
    175     "admin-bookings.min.js": {
    176       "sha256": "f6046d4ee5b556126f2ea5d91511dc69d1fef81827bcf9177d14e99344dbe5e1",
    177       "bytes": 28731
    178     },
    179     "admin-staff.min.js": {
    180       "sha256": "3316771f28e0e574ad1ee09f68e993db71dc5943060c7b7a97ed39312a90a7a2",
    181       "bytes": 33428
    182     },
    183     "staff-portal.min.js": {
    184       "sha256": "2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68",
    185       "bytes": 220988
    186     },
    187     "frontend-checkout.min.js": {
    188       "sha256": "d45284aad5790bd63b1880dcc05fcb84c600fb771c04ce29dd2e68259b63c51f",
    189       "bytes": 27884
    190     },
    191     "frontend-recovery.min.js": {
    192       "sha256": "fcc85f9e563b837cab4f76d417b20efb3004378b16f34d81e6cc4af405588ff7",
    193       "bytes": 3081
    194     },
    195     "yatoon-app-shell.min.js": {
    196       "sha256": "801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877",
    197       "bytes": 9793
    198     },
    199     "yatoon-category-bar-4721.min.js": {
    200       "sha256": "cc9730273303f106ad4ca0a8d11a9723f00d2f4d0ab2d3fd7bccb6f80c2a70aa",
    201       "bytes": 7178
    202     },
    203     "frontend-flow.min.js": {
    204       "sha256": "1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392",
    205       "bytes": 233386
    206     },
    207     "frontend-cart.min.js": {
    208       "sha256": "24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4",
    209       "bytes": 120622
    210     },
    211     "frontend-boot.min.js": {
    212       "sha256": "b83003f71f6a02c18efa96508552fffd4087da007b02ac7a34eb86552b35b234",
    213       "bytes": 15656
    214     },
    215     "frontend-steps.min.js": {
    216       "sha256": "5244218e1cc8c6eda02c5ca557c104a66594ab53b95c1ec740972f2d9b103180",
    217       "bytes": 81847
    218     },
    219     "frontend.bundle.min.js": {
    220       "sha256": "c3a8002ca40541d6d7fc5534d2a8b24aed5a6816e134dcb28b094a7a9ce109ce",
    221       "bytes": 448361
    222     }
    223   },
    224   "css_inputs": {
    225     "frontend.css": {
    226       "sha256": "41a3b2b765cf4a4a6646664009bbc55e6b848c351061169449290751c94caf5c",
    227       "bytes": 274924
    228     },
    229     "yatoon-booking-bundle.css": {
    230       "sha256": "2b9f874ee912d2a42e2fedb93d045eb82c813e8713301f0a757b0f18cf144473",
    231       "bytes": 416
    232     },
    233     "yatoon-booking-final.css": {
    234       "sha256": "810d51757acea75ec0dbabac87dca581bf4c866dbee37c5d8937bfdbc5991d2b",
    235       "bytes": 71386
    236     },
    237     "apple-polish.css": {
    238       "sha256": "66aefa6e5e9daa980fac6f33ffe95b878f26a2cb50c791222fc368007c6afdb5",
    239       "bytes": 10131
    240     },
    241     "yatoon-ui-polish.css": {
    242       "sha256": "7bfb8a792cadb7412456e45d5e74f30b34749426ce3a8551573c4dedc5e976fb",
    243       "bytes": 111082
    244     },
    245     "yatoon-design-system.css": {
    246       "sha256": "981bbb291a331dff7529dfddec6a3ab176f79f599bb8b983dc76c987109da310",
    247       "bytes": 42448
    248     },
    249     "yatoon-motion-polish.css": {
    250       "sha256": "c7a758608be1b9272933ef4e9cbf0f5d23a4348e5e09731bbc5e51732e14ede7",
    251       "bytes": 2103
    252     },
    253     "yatoon-category-bar-423-path16.css": {
    254       "sha256": "e8b85960ff07b991b0f1b6714358a913a7097537c2a883020e153cb7d9a16c2c",
    255       "bytes": 4871
    256     },
    257     "yatoon-concierge-4757.css": {
    258       "sha256": "8e08fee86c08d37e4dc90787f28a375c0c32bc83ed8dee328336f4e368ab2373",
    259       "bytes": 26524
    260     },
    261     "admin-calendar.css": {
    262       "sha256": "39c78d8120ae664b4c4205c4306c377dc344f948624336dba63a74272823b8d0",
    263       "bytes": 21529
    264     },
    265     "admin-calendar-rtl.css": {
    266       "sha256": "9b3b47f492056ae3186f7ad278955cb154218dfb5f8557c644d29e0ad17e13d2",
    267       "bytes": 21529
    268     },
    269     "admin-clients.css": {
    270       "sha256": "9f38abe8dcb60ad4ee74af66839fffe730e84a1eb9474ca06f1db4374f1e0149",
    271       "bytes": 11234
    272     },
    273     "admin-clients-rtl.css": {
    274       "sha256": "00fe481c7c305a9cd07daf1af8609e276ab05ae32c3bb15f37f32888b28bfeca",
    275       "bytes": 11237
    276     },
    277     "admin-dashboard.css": {
    278       "sha256": "e9e2c37b664f26d1791977676453c6e125bc86330db03566a42348c8354a3fd9",
    279       "bytes": 12570
    280     },
    281     "yatoon-color-schemes.css": {
    282       "sha256": "784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872",
    283       "bytes": 71724
    284     },
    285     "yatoon-theme-preview.css": {
    286       "sha256": "707ece181bfb8eb30eb2c54384d02cae9b8638ece567e8150c548dc5ecc01e56",
    287       "bytes": 22155
    288     },
    289     "yatoon-app-shell.css": {
    290       "sha256": "79fe24a1d2852409a46796fa5b6a42a2ca9dfb8be4ed06fbbef4b639eaaff474",
    291       "bytes": 32896
    292     },
    293     "manage-booking.css": {
    294       "sha256": "ccde50e15dc10671de366575dcfa1bc93319aed409a1771d05dd4ec6ed7c76e9",
    295       "bytes": 19507
    296     },
    297     "staff-portal.css": {
    298       "sha256": "b60f1cd59069ad536b5088293f883aa41afa35d0bbcd2c3127783d4e04cb49a9",
    299       "bytes": 123789
    300     },
    301     "yatoon-seasonal-viewport-4781.css": {
    302       "sha256": "347ddefa10bba3327b1918be78913125e55164c643128caac5ceac81a5ca2723",
    303       "bytes": 8190
    304     },
    305     "frontend-glass.css": {
    306       "sha256": "060681cdf1a6b9805f40a6d788b12b39c00e2a6e0007ea57c69e17e9142374a3",
    307       "bytes": 664
    308     },
    309     "customer-portal.css": {
    310       "sha256": "f881d319ad2873eaa0de81cbb060c429744d334a8cfdcfd4e3444623a923d8ab",
    311       "bytes": 35043
    312     },
    313     "client-merge-ui.css": {
    314       "sha256": "d328401272323ca1026ef0928573c09aa3151f16e4d95388362a3dd3f9b0b5ac",
    315       "bytes": 2336
    316     },
    317     "staff-hours-editor.css": {
    318       "sha256": "70a2fac16897f3d9f523a1581f23de04d392f4110841229f064844432be39cd9",
    319       "bytes": 2226
    320     },
    321     "admin-tools.css": {
    322       "sha256": "f3f79f99b46c3e2613175254f54ce06454c952bebbdde278fa05c68ac923e464",
    323       "bytes": 14457
    324     },
    325     "admin-setup-wizard.css": {
    326       "sha256": "11b02bc28c20df7a5189165173103ebab2dba28318ead2520a261f7068953b66",
    327       "bytes": 7499
    328     },
    329     "yatoon-booking-discovery.css": {
    330       "sha256": "8c33bc55568bfd39b9acf0fe0ee1e380e69d475e992477a6909fccac0525b9a0",
    331       "bytes": 1231
    332     },
    333     "yatoon-booking-header.css": {
    334       "sha256": "597726d71d1e251243a7d56ddb6c81a2e51f0af33a345404b87f1b75443a3268",
    335       "bytes": 48073
    336     }
    337   },
    338   "css_outputs": {
    339     "frontend-rtl.css": {
    340       "sha256": "4fcc61fab31b13e36746b260f9a8658f784ebe5838527d00df96eabd372cf596",
    341       "bytes": 274828
    342     },
    343     "frontend.min.css": {
    344       "sha256": "d755cf890841367223dd97f290d8b933c80681f60cf85f6bcf8100a2373b7662",
    345       "bytes": 190078
    346     },
    347     "frontend.min-rtl.css": {
    348       "sha256": "576231fe623f4559e2cd918368e2d786276f1764680146544cfbf7bf14ae6c2e",
    349       "bytes": 189980
    350     },
    351     "yatoon-booking-final-rtl.css": {
    352       "sha256": "b1846d748cbe4fbc2d95be026aac1e111abe0481e4ec5b1bb11c5d8482bf0506",
    353       "bytes": 71396
    354     },
    355     "yatoon-booking-final.min.css": {
    356       "sha256": "fe13d8069725b96b098a0d233c871c89003553b72b2a677ca8203a8879f3179a",
    357       "bytes": 51544
    358     },
    359     "yatoon-booking-final.min-rtl.css": {
    360       "sha256": "78e4f83c38324104f7c09c6cc3305135741e03feba84077f008402bf04966aec",
    361       "bytes": 51554
    362     },
    363     "yatoon-booking-core.min.css": {
    364       "sha256": "2fe2d03c4426ca02ffefd11ffdbe1477b5f198913b4a8692c62a711884c6dd51",
    365       "bytes": 198836
    366     },
    367     "yatoon-booking-core.min-rtl.css": {
    368       "sha256": "2bbcea4c58e4b31386906c38ef89b5dc3e49bd7d219d24cc669c715e4d2808b6",
    369       "bytes": 198905
    370     },
    371     "yatoon-booking-flow.min.css": {
    372       "sha256": "6e6c980791ec4f5bcc4e09562700835fd86e23c532b5bc6a0654cbef12e1a5f5",
    373       "bytes": 90325
    374     },
    375     "yatoon-booking-flow.min-rtl.css": {
    376       "sha256": "3386631ef8de2623df26cd4f56c077859da5a5b753fef5bfedca98d625fde3b0",
    377       "bytes": 90334
    378     },
    379     "yatoon-design-system.min.css": {
    380       "sha256": "c2cbc79b447e9696a1c82dce32ca5c2acfe40c215f538ff1041856761c036c65",
    381       "bytes": 36073
    382     },
    383     "yatoon-design-system.min-rtl.css": {
    384       "sha256": "570028fe4afa537e34609ec8b6138f0287e2d9364bc057132f23287d249f3bd3",
    385       "bytes": 36079
    386     },
    387     "yatoon-design-system-rtl.css": {
    388       "sha256": "bb8a615fa8d19330e40b55622a4118764e83c5ea5b3ef5db8ae12f2dc2e3d5e8",
    389       "bytes": 42454
    390     },
    391     "yatoon-concierge-4757.min.css": {
    392       "sha256": "c03e6af34c28a629f11f891b00ae6323d81c12ea884fe91a4658855c485a6136",
    393       "bytes": 18402
    394     },
    395     "admin-calendar.min.css": {
    396       "sha256": "33404dd42bf72e0288d745e47835c77a8f943c65cbc975f542581ae1a2cc4116",
    397       "bytes": 20844
    398     },
    399     "admin-calendar.min-rtl.css": {
    400       "sha256": "893162b877b8db9281a895e3de1507b4ce80073e43e1ec969189d8ce6d20ea14",
    401       "bytes": 20846
    402     },
    403     "admin-clients.min.css": {
    404       "sha256": "ce423fecaf41cb6ce16a1caa9c90e7c167169a92ac14d0d8fa67ddf02a618eaa",
    405       "bytes": 10182
    406     },
    407     "admin-clients.min-rtl.css": {
    408       "sha256": "f68db34ec24385cdb44a8e53f21a0d576fa9ae12ac6d8f92ccabff3de3a8278d",
    409       "bytes": 10185
    410     },
    411     "admin-dashboard.min.css": {
    412       "sha256": "0037bfdb2dfaa0bda06d72c6bfb7861dc29a2c85d326857009f1efa7cd361c98",
    413       "bytes": 11515
    414     },
    415     "admin-dashboard.min-rtl.css": {
    416       "sha256": "c8cbf59d96754f7f5d18085462e3ddaf4d6795bfd4980d80ecaf39d13984c80f",
    417       "bytes": 11516
    418     },
    419     "yatoon-color-schemes.min.css": {
    420       "sha256": "2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e",
    421       "bytes": 64203
    422     },
    423     "yatoon-color-schemes.min-rtl.css": {
    424       "sha256": "2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e",
    425       "bytes": 64203
    426     },
    427     "yatoon-theme-preview.min.css": {
    428       "sha256": "d7eac486f0adced3d552784675de7c21cef46844c385f80b395c7f8f06441c11",
    429       "bytes": 19897
    430     },
    431     "yatoon-theme-preview.min-rtl.css": {
    432       "sha256": "b0580176744c9922574ce8629527c0a43fa533b095ddcc9769b4d25f64206a2a",
    433       "bytes": 19908
    434     },
    435     "yatoon-color-schemes-rtl.css": {
    436       "sha256": "784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872",
    437       "bytes": 71724
    438     },
    439     "yatoon-theme-preview-rtl.css": {
    440       "sha256": "5042c873ed908a826c434c6c83c4ebd053b02942a8e46c70bd9957cb51aabf57",
    441       "bytes": 22166
    442     },
    443     "yatoon-app-shell-rtl.css": {
    444       "sha256": "5bfff9f1a2487fb1f78279a7ac5cd80c67d692012808bc3f8f50cad3bf5c78d0",
    445       "bytes": 32908
    446     },
    447     "yatoon-app-shell.min.css": {
    448       "sha256": "cd9ccb19cbef6a0d42feb7c0c2ae70a23eb04b8693df459778cdc3624d4e439c",
    449       "bytes": 23417
    450     },
    451     "yatoon-app-shell.min-rtl.css": {
    452       "sha256": "c5c52c43dedfa22859427bed065649e2e57df6ea9d6e0ead55ece2b2be1e292f",
    453       "bytes": 23425
    454     },
    455     "manage-booking-rtl.css": {
    456       "sha256": "3ebdcf5de5c267e52ad3cbd47fd7a743501b298cb97532bbfa9dde6001f5777f",
    457       "bytes": 19510
    458     },
    459     "manage-booking.min.css": {
    460       "sha256": "f5d7287ca0ac4cf55a6b63e1fc8b04b63e6e70f043f1f23d32c5bdd52ea8b9ca",
    461       "bytes": 13560
    462     },
    463     "manage-booking.min-rtl.css": {
    464       "sha256": "92c6d808efd9c37c87a618ee97430688d4a8478d28648953b41dff28670a7570",
    465       "bytes": 13563
    466     },
    467     "staff-portal-rtl.css": {
    468       "sha256": "8af62f4563b1ab40a95eb2be04c547ae7e16d9ffa94e7b15d2c8aa8baf3088ac",
    469       "bytes": 123823
    470     },
    471     "staff-portal.min.css": {
    472       "sha256": "b33d5f555a6d1eda7aff23aa4c4ba926143fcad79a5fe68bd6384c44d8a5951e",
    473       "bytes": 92450
    474     },
    475     "staff-portal.min-rtl.css": {
    476       "sha256": "a9d3af72734dd81db4d5ddff67a0b044bb8d2ce041a4d69a66405cfb05f7a788",
    477       "bytes": 92481
    478     },
    479     "customer-portal-rtl.css": {
    480       "sha256": "3a3533ba65af2f364016e4e4ca30ecdc03f47b84a325835ce6b3fc84cc818581",
    481       "bytes": 34643
    482     },
    483     "customer-portal.min.css": {
    484       "sha256": "e886e0d65396c4dd202c93703547e3ec1f111f3be6910f135a951e392615733e",
    485       "bytes": 26556
    486     },
    487     "customer-portal.min-rtl.css": {
    488       "sha256": "dba031b25b63dbcde7ecd9004a71ef4174ba202aceb4e69e9f8590a879cedbf5",
    489       "bytes": 26343
    490     },
    491     "admin-setup-wizard-rtl.css": {
    492       "sha256": "8c2381f5b07e05067b316c10ce4a9333f1ab62714d197bcb85c7ce67e967d676",
    493       "bytes": 7502
    494     },
    495     "admin-setup-wizard.min.css": {
    496       "sha256": "4a9ef36fa5a16fd188b6db80e86a3d825bc786d844a3f1905bcc2facc504d0ba",
    497       "bytes": 7046
    498     },
    499     "admin-setup-wizard.min-rtl.css": {
    500       "sha256": "9cf36af333dae3b82ca7bc98f99ae297e5e59c34ac1766fafb4fa5364bd6e818",
    501       "bytes": 7048
    502     }
    503   },
    504   "frontend_builder": "terser-5.50.0",
    505   "staff_portal_builder": "Source-equivalent production JS/CSS; inherited assets retain their original build.",
    506   "internal_build": "490-free-20261025-theme-header-lang-picker-compact-languages-ordered-language-picker-mobile-single-brand-row",
    507   "css_note": "4.8.2: yatoon-booking-core/flow/final min bundles and frontend/ui-polish/final sources had declarations removed that a later same-selector rule in the same stylesheet always overrides; outputs were edited in place (not rebuilt with cssnano), so hashes are of the shipped files. 4.8.5: core/flow/final min files also had declarations removed that never take effect in the captured cascade and rules for class names the plugin never outputs; outputs edited in place.",
    508   "css_bundles": {
    509     "yatoon-booking-core.bundle.min.css": {
    510       "sha256": "bd16143171cd19aa9b2ef73ee546de2846fe78a8b4b3566d26ec9af34bd85de6",
    511       "bytes": 208911,
    512       "parts": [
    513         "yatoon-theme-isolation.css",
    514         "yatoon-booking-core.min.css"
    515       ]
    516     },
    517     "yatoon-booking-core.bundle.min-rtl.css": {
    518       "sha256": "95a8512c82e3839ce88cae5aaf50236a52e9c57b9abad0bc1a04e62bdbd6d399",
    519       "bytes": 208984,
    520       "parts": [
    521         "yatoon-theme-isolation.css",
    522         "yatoon-booking-core.min-rtl.css"
    523       ]
    524     },
    525     "yatoon-booking-layers.bundle.min.css": {
    526       "sha256": "319ec68f7b14bc6ccb1997f747d36bbab8f985cc73b0c21be51460ba60f96595",
    527       "bytes": 79694,
    528       "parts": [
    529         "yatoon-color-schemes.min.css",
    530         "yatoon-service-image-frame.css",
    531         "yatoon-theme-armor-4723.min.css",
    532         "yatoon-booking-responsive.min.css"
    533       ]
    534     },
    535     "yatoon-booking-layers.bundle.min-rtl.css": {
    536       "sha256": "0361d779e153376c92d30f991c2ffe1b20f9c39bd6ccd8130ac97f968765d74f",
    537       "bytes": 79698,
    538       "parts": [
    539         "yatoon-color-schemes.min.css",
    540         "yatoon-service-image-frame.css",
    541         "yatoon-theme-armor-4723.min.css",
    542         "yatoon-booking-responsive.min-rtl.css"
    543       ]
    544     },
    545     "yatoon-booking-extras.bundle.min.css": {
    546       "sha256": "8eb2ad13592a630dc19ff24101d4c69aead136eee39fef404ef6322a46d7d508",
    547       "bytes": 9565,
    548       "parts": [
    549         "yatoon-seasonal-viewport-4781.css",
    550         "yatoon-booking-discovery.css"
    551       ]
    552     }
    553   },
    554   "js_note": "4.8.5: the booking form is split into frontend.js, frontend-flow.js, frontend-cart.js and frontend-boot.js (sources). build_frontend.js generates the lazy chunks frontend-availability.js and frontend-steps.js, every .min.js, and frontend.bundle.min.js.",
    555   "css_sources": "4.9.0: assets/css/src/yatoon-booking-core.css and yatoon-booking-flow.css are readable sources with exactly the rules of the shipped .min files; rebuild with csso --no-restructure (keeps rule order), then rtlcss.",
    556   "header_note": "Compact local SVG country flags with native details navigation and a circular account control. Header remains visible on step one only.",
    557   "cart_note": "Single section dividers and aligned total/duration rows in the mobile drawer; booking calculations are unchanged."
    558 }
     1{"version":"4.10.0","builder":"terser-5.50.0 + postcss-8.5.6/cssnano-7.1.2/rtlcss-4.3.0","inputs":{"yatoon-category-bar-423-path16.js":{"sha256":"d9d21e398c540a08d8e07c291b54a5f670616e5c5da35fbff9f3b579b7424cc7","bytes":11121},"frontend.js":{"sha256":"338ebe2b74415737c7616e1e36b62a28028dae5aa2037f6a4b1a5b2bc68a7ded","bytes":182985},"manage-booking.js":{"sha256":"aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7","bytes":36499},"customer-portal.js":{"sha256":"c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707","bytes":83590},"admin-services.js":{"sha256":"7f8c293589fc1f2a4d9be24b19c78668e9b73106ec1eb8cd111f31b5ff88f376","bytes":79799},"admin.js":{"sha256":"4b0851842309bc4e794d67c903f8ae777f93f455a6b69e1729d2f813a88e4b9a","bytes":23364},"admin-settings.js":{"sha256":"0f6928ef9a7c2967f5759ff846bc2492af4faf8490352b9d01f7af4491dc2a64","bytes":58129},"admin-option-groups.js":{"sha256":"dab46594bdf73d1eea2a85e79f976570c34185d9434633483bb086facc2c3f9c","bytes":15069},"frontend-payments.js":{"sha256":"01bcee85136bc3826c39688d57df9082676556080e97185c884c3a63d5497d42","bytes":37720},"yatoon-concierge-4766.js":{"sha256":"079493bf51ced34a809eb3fbc06079f507070627e247d1955019297acbcb59a1","bytes":131985},"admin-calendar.js":{"sha256":"46ecdfa78ad656102c720dd6deb0e0ef97ca94e758c1b56f5ba9c33b38ce7c9a","bytes":36565},"admin-clients.js":{"sha256":"a42717f6b0c4f1f6515040a218cfa681be7e7d44d82261f6a7e1402c1cb3b2ce","bytes":29292},"admin-bookings.js":{"sha256":"c1f8b151b6d7e097443634572aad2714aeb36cda507f525f7fe5842a6f1d467f","bytes":41934},"admin-staff.js":{"sha256":"b6b6e56b538ed9f2b6768104c7b7cc2bda2d94ffa48ff00932e964b1fe454b89","bytes":54358},"staff-portal.js":{"sha256":"2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68","bytes":220988},"frontend-availability.js":{"sha256":"4a271e0ecb0db8a4385331c67ba27776c3c7212aafe4e29e9f9ae49eace017c8","bytes":70358},"frontend-checkout.js":{"sha256":"710c9ca74997dd979c4556910328d04a1db66a90a8974282d456701178c7c027","bytes":69189},"frontend-recovery.js":{"sha256":"f96d9a84be0724732eb6baf4292fa0f5a81a6523f53456de5e880c7cca1ccef8","bytes":4566},"frontend-glass.js":{"sha256":"595c0c6c539d1495470f9597465a863e913fe81f726c7c7824c4f92d5051d66d","bytes":1416},"client-merge-ui.js":{"sha256":"a4f80bea59a75459b0625f295ba486753fd266c8e410d847a640b7f2d9fb31fe","bytes":7635},"staff-hours-editor.js":{"sha256":"9c8f00b26da3dd8c26b9160f5e8e3dd55507743aebaaf84895faa584fce27a96","bytes":8209},"admin-tools.js":{"sha256":"d084fac962bd24d074f636ae04a3e084bf6e22ff4458ba79f57854647cd9c206","bytes":6818},"yatoon-app-shell.js":{"sha256":"801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877","bytes":9793},"yatoon-category-bar-4721.js":{"sha256":"3596279a0fb230ed54ae6fc8ba7f7f0dc207c81891ea345b758bc9397085ff68","bytes":14196},"frontend-flow.js":{"sha256":"1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392","bytes":233386},"frontend-cart.js":{"sha256":"24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4","bytes":120622},"frontend-boot.js":{"sha256":"c0d60031a8f1da307ea5e896a747f96616a76de18fa6021e5ddcb8d74fe8ae37","bytes":30947},"yatoon-booking-header.js":{"sha256":"2bb689d5bf4e08a80d27582ee5a4474c029c45b67a7c12da1401bb8e3bdc8aaf","bytes":11913},"yatoon-language-order.js":{"sha256":"599194bba4daf0a4258843f43df0082d3aae7f78c0e0432efc1f281d6527e9d4","bytes":2125}},"outputs":{"frontend.min.js":{"sha256":"2358e461fd346486cc1d89f8702c1bca3e762ffd68863288166a93c03dd38773","bytes":78688},"manage-booking.min.js":{"sha256":"aadbdf0f0c0b4e26eaa5dd4625d79387d7e0f9906cb9a6403cf3626d7d4907c7","bytes":36499},"customer-portal.min.js":{"sha256":"c139f4be47fa849e90565371e16c10919c697736b0d22dd33ef6d85d3f005707","bytes":83590},"admin-services.min.js":{"sha256":"18c487c0411571490cf721d0b30b49f2eff8e60d3d6c662e36d3920b7120c32f","bytes":46201},"admin.min.js":{"sha256":"ff2b431a9d6dc0f5e8c391ca762c27172461acfcf4ff85773bc865e5e9f75c37","bytes":10612},"admin-settings.min.js":{"sha256":"38db0164f533a4edfe41d999727e1f67705d498a8f50f50af0532e10e656ccb0","bytes":31390},"admin-option-groups.min.js":{"sha256":"7bc60b7c3208f3dc3d6380207ed551ed1619d00924c5a62cafa071958fbcef3e","bytes":11420},"frontend-payments.min.js":{"sha256":"52b59f072af0b0df01b6d3505820515db57c968c2835c402e8af7fb05365b9d3","bytes":19251},"frontend-availability.min.js":{"sha256":"aaa225d5ee3a114a82e1af57be8180d866d140f0ddbab2f81c3059f7c4fdeefb","bytes":29644},"yatoon-category-bar-423-path16.min.js":{"sha256":"a5608fd4415c1dbf4e851a4f207e60c8dd48e96f8ff9e7da3b396f6c71673b7d","bytes":6115},"yatoon-concierge-4766.min.js":{"sha256":"091e689701686070f21841c298121cd67163b6a0214b39a002d4bf96aa5e7652","bytes":51279},"admin-calendar.min.js":{"sha256":"ff82d1459da6b1f422dfd4437df106919891ed8fcc05e756c708513c87cb54d7","bytes":18956},"admin-clients.min.js":{"sha256":"9dea7f478d7b3d9310a1f9c0375bf18ccde5857ce0402a712407d48227dd7dda","bytes":17378},"admin-bookings.min.js":{"sha256":"f6046d4ee5b556126f2ea5d91511dc69d1fef81827bcf9177d14e99344dbe5e1","bytes":28731},"admin-staff.min.js":{"sha256":"3316771f28e0e574ad1ee09f68e993db71dc5943060c7b7a97ed39312a90a7a2","bytes":33428},"staff-portal.min.js":{"sha256":"2e36384d5c6d37a7f50d02b21505bdff0cc4c4bb39e247fe9f1edf4f62ceef68","bytes":220988},"frontend-checkout.min.js":{"sha256":"bcfbd71409007b50fd60aa869c6bcd1cbad18b86c21b98a9c70696032e1217db","bytes":28012},"frontend-recovery.min.js":{"sha256":"fcc85f9e563b837cab4f76d417b20efb3004378b16f34d81e6cc4af405588ff7","bytes":3081},"yatoon-app-shell.min.js":{"sha256":"801018f7cf5f30cfade4ed8ae3905624df97d3a714a6cf3ec335db094edfc877","bytes":9793},"yatoon-category-bar-4721.min.js":{"sha256":"cc9730273303f106ad4ca0a8d11a9723f00d2f4d0ab2d3fd7bccb6f80c2a70aa","bytes":7178},"frontend-flow.min.js":{"sha256":"1529602f4b91c5be53f8c84f3fcd63b10e8f7e295d46697ad1edada2afc62392","bytes":233386},"frontend-cart.min.js":{"sha256":"24fd1e3756f95e3e9715f5945ee201e1095df68c12fd24d57dbc14a3184296d4","bytes":120622},"frontend-boot.min.js":{"sha256":"b83003f71f6a02c18efa96508552fffd4087da007b02ac7a34eb86552b35b234","bytes":15656},"frontend-steps.min.js":{"sha256":"5244218e1cc8c6eda02c5ca557c104a66594ab53b95c1ec740972f2d9b103180","bytes":81847},"frontend.bundle.min.js":{"sha256":"c3a8002ca40541d6d7fc5534d2a8b24aed5a6816e134dcb28b094a7a9ce109ce","bytes":448361}},"css_inputs":{"frontend.css":{"sha256":"41a3b2b765cf4a4a6646664009bbc55e6b848c351061169449290751c94caf5c","bytes":274924},"yatoon-booking-bundle.css":{"sha256":"2b9f874ee912d2a42e2fedb93d045eb82c813e8713301f0a757b0f18cf144473","bytes":416},"yatoon-booking-final.css":{"sha256":"810d51757acea75ec0dbabac87dca581bf4c866dbee37c5d8937bfdbc5991d2b","bytes":71386},"apple-polish.css":{"sha256":"66aefa6e5e9daa980fac6f33ffe95b878f26a2cb50c791222fc368007c6afdb5","bytes":10131},"yatoon-ui-polish.css":{"sha256":"7bfb8a792cadb7412456e45d5e74f30b34749426ce3a8551573c4dedc5e976fb","bytes":111082},"yatoon-design-system.css":{"sha256":"981bbb291a331dff7529dfddec6a3ab176f79f599bb8b983dc76c987109da310","bytes":42448},"yatoon-motion-polish.css":{"sha256":"c7a758608be1b9272933ef4e9cbf0f5d23a4348e5e09731bbc5e51732e14ede7","bytes":2103},"yatoon-category-bar-423-path16.css":{"sha256":"e8b85960ff07b991b0f1b6714358a913a7097537c2a883020e153cb7d9a16c2c","bytes":4871},"yatoon-concierge-4757.css":{"sha256":"8e08fee86c08d37e4dc90787f28a375c0c32bc83ed8dee328336f4e368ab2373","bytes":26524},"admin-calendar.css":{"sha256":"39c78d8120ae664b4c4205c4306c377dc344f948624336dba63a74272823b8d0","bytes":21529},"admin-calendar-rtl.css":{"sha256":"9b3b47f492056ae3186f7ad278955cb154218dfb5f8557c644d29e0ad17e13d2","bytes":21529},"admin-clients.css":{"sha256":"9f38abe8dcb60ad4ee74af66839fffe730e84a1eb9474ca06f1db4374f1e0149","bytes":11234},"admin-clients-rtl.css":{"sha256":"00fe481c7c305a9cd07daf1af8609e276ab05ae32c3bb15f37f32888b28bfeca","bytes":11237},"admin-dashboard.css":{"sha256":"e9e2c37b664f26d1791977676453c6e125bc86330db03566a42348c8354a3fd9","bytes":12570},"yatoon-color-schemes.css":{"sha256":"784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872","bytes":71724},"yatoon-theme-preview.css":{"sha256":"707ece181bfb8eb30eb2c54384d02cae9b8638ece567e8150c548dc5ecc01e56","bytes":22155},"yatoon-app-shell.css":{"sha256":"79fe24a1d2852409a46796fa5b6a42a2ca9dfb8be4ed06fbbef4b639eaaff474","bytes":32896},"manage-booking.css":{"sha256":"ccfdd6c0adbaad8fa3ed2dc8902f0911a65cea4dcecce99bc481ba4306cabc35","bytes":21975},"staff-portal.css":{"sha256":"b60f1cd59069ad536b5088293f883aa41afa35d0bbcd2c3127783d4e04cb49a9","bytes":123789},"yatoon-seasonal-viewport-4781.css":{"sha256":"347ddefa10bba3327b1918be78913125e55164c643128caac5ceac81a5ca2723","bytes":8190},"frontend-glass.css":{"sha256":"060681cdf1a6b9805f40a6d788b12b39c00e2a6e0007ea57c69e17e9142374a3","bytes":664},"customer-portal.css":{"sha256":"f881d319ad2873eaa0de81cbb060c429744d334a8cfdcfd4e3444623a923d8ab","bytes":35043},"client-merge-ui.css":{"sha256":"d328401272323ca1026ef0928573c09aa3151f16e4d95388362a3dd3f9b0b5ac","bytes":2336},"staff-hours-editor.css":{"sha256":"70a2fac16897f3d9f523a1581f23de04d392f4110841229f064844432be39cd9","bytes":2226},"admin-tools.css":{"sha256":"f3f79f99b46c3e2613175254f54ce06454c952bebbdde278fa05c68ac923e464","bytes":14457},"admin-setup-wizard.css":{"sha256":"11b02bc28c20df7a5189165173103ebab2dba28318ead2520a261f7068953b66","bytes":7499},"yatoon-booking-discovery.css":{"sha256":"8c33bc55568bfd39b9acf0fe0ee1e380e69d475e992477a6909fccac0525b9a0","bytes":1231},"yatoon-booking-header.css":{"sha256":"597726d71d1e251243a7d56ddb6c81a2e51f0af33a345404b87f1b75443a3268","bytes":48073}},"css_outputs":{"frontend-rtl.css":{"sha256":"4fcc61fab31b13e36746b260f9a8658f784ebe5838527d00df96eabd372cf596","bytes":274828},"frontend.min.css":{"sha256":"d755cf890841367223dd97f290d8b933c80681f60cf85f6bcf8100a2373b7662","bytes":190078},"frontend.min-rtl.css":{"sha256":"576231fe623f4559e2cd918368e2d786276f1764680146544cfbf7bf14ae6c2e","bytes":189980},"yatoon-booking-final-rtl.css":{"sha256":"b1846d748cbe4fbc2d95be026aac1e111abe0481e4ec5b1bb11c5d8482bf0506","bytes":71396},"yatoon-booking-final.min.css":{"sha256":"fe13d8069725b96b098a0d233c871c89003553b72b2a677ca8203a8879f3179a","bytes":51544},"yatoon-booking-final.min-rtl.css":{"sha256":"78e4f83c38324104f7c09c6cc3305135741e03feba84077f008402bf04966aec","bytes":51554},"yatoon-booking-core.min.css":{"sha256":"2fe2d03c4426ca02ffefd11ffdbe1477b5f198913b4a8692c62a711884c6dd51","bytes":198836},"yatoon-booking-core.min-rtl.css":{"sha256":"2bbcea4c58e4b31386906c38ef89b5dc3e49bd7d219d24cc669c715e4d2808b6","bytes":198905},"yatoon-booking-flow.min.css":{"sha256":"6e6c980791ec4f5bcc4e09562700835fd86e23c532b5bc6a0654cbef12e1a5f5","bytes":90325},"yatoon-booking-flow.min-rtl.css":{"sha256":"3386631ef8de2623df26cd4f56c077859da5a5b753fef5bfedca98d625fde3b0","bytes":90334},"yatoon-design-system.min.css":{"sha256":"c2cbc79b447e9696a1c82dce32ca5c2acfe40c215f538ff1041856761c036c65","bytes":36073},"yatoon-design-system.min-rtl.css":{"sha256":"570028fe4afa537e34609ec8b6138f0287e2d9364bc057132f23287d249f3bd3","bytes":36079},"yatoon-design-system-rtl.css":{"sha256":"bb8a615fa8d19330e40b55622a4118764e83c5ea5b3ef5db8ae12f2dc2e3d5e8","bytes":42454},"yatoon-concierge-4757.min.css":{"sha256":"c03e6af34c28a629f11f891b00ae6323d81c12ea884fe91a4658855c485a6136","bytes":18402},"admin-calendar.min.css":{"sha256":"33404dd42bf72e0288d745e47835c77a8f943c65cbc975f542581ae1a2cc4116","bytes":20844},"admin-calendar.min-rtl.css":{"sha256":"893162b877b8db9281a895e3de1507b4ce80073e43e1ec969189d8ce6d20ea14","bytes":20846},"admin-clients.min.css":{"sha256":"ce423fecaf41cb6ce16a1caa9c90e7c167169a92ac14d0d8fa67ddf02a618eaa","bytes":10182},"admin-clients.min-rtl.css":{"sha256":"f68db34ec24385cdb44a8e53f21a0d576fa9ae12ac6d8f92ccabff3de3a8278d","bytes":10185},"admin-dashboard.min.css":{"sha256":"0037bfdb2dfaa0bda06d72c6bfb7861dc29a2c85d326857009f1efa7cd361c98","bytes":11515},"admin-dashboard.min-rtl.css":{"sha256":"c8cbf59d96754f7f5d18085462e3ddaf4d6795bfd4980d80ecaf39d13984c80f","bytes":11516},"yatoon-color-schemes.min.css":{"sha256":"2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e","bytes":64203},"yatoon-color-schemes.min-rtl.css":{"sha256":"2947b3269dad3bfecffd53e411987326c0cf72248955edfe1c6ecb26a88d339e","bytes":64203},"yatoon-theme-preview.min.css":{"sha256":"d7eac486f0adced3d552784675de7c21cef46844c385f80b395c7f8f06441c11","bytes":19897},"yatoon-theme-preview.min-rtl.css":{"sha256":"b0580176744c9922574ce8629527c0a43fa533b095ddcc9769b4d25f64206a2a","bytes":19908},"yatoon-color-schemes-rtl.css":{"sha256":"784d7e6dd912894c3fef4a278e90eb75350f2a3dd0bb12319d5d4e98cbfca872","bytes":71724},"yatoon-theme-preview-rtl.css":{"sha256":"5042c873ed908a826c434c6c83c4ebd053b02942a8e46c70bd9957cb51aabf57","bytes":22166},"yatoon-app-shell-rtl.css":{"sha256":"5bfff9f1a2487fb1f78279a7ac5cd80c67d692012808bc3f8f50cad3bf5c78d0","bytes":32908},"yatoon-app-shell.min.css":{"sha256":"cd9ccb19cbef6a0d42feb7c0c2ae70a23eb04b8693df459778cdc3624d4e439c","bytes":23417},"yatoon-app-shell.min-rtl.css":{"sha256":"c5c52c43dedfa22859427bed065649e2e57df6ea9d6e0ead55ece2b2be1e292f","bytes":23425},"manage-booking-rtl.css":{"sha256":"3ebdcf5de5c267e52ad3cbd47fd7a743501b298cb97532bbfa9dde6001f5777f","bytes":19510},"manage-booking.min.css":{"sha256":"431d40da00dd526cb762ebddf10377431e1982b465a3a0943124a28b17c8f444","bytes":16028},"manage-booking.min-rtl.css":{"sha256":"92c6d808efd9c37c87a618ee97430688d4a8478d28648953b41dff28670a7570","bytes":13563},"staff-portal-rtl.css":{"sha256":"8af62f4563b1ab40a95eb2be04c547ae7e16d9ffa94e7b15d2c8aa8baf3088ac","bytes":123823},"staff-portal.min.css":{"sha256":"b33d5f555a6d1eda7aff23aa4c4ba926143fcad79a5fe68bd6384c44d8a5951e","bytes":92450},"staff-portal.min-rtl.css":{"sha256":"a9d3af72734dd81db4d5ddff67a0b044bb8d2ce041a4d69a66405cfb05f7a788","bytes":92481},"customer-portal-rtl.css":{"sha256":"3a3533ba65af2f364016e4e4ca30ecdc03f47b84a325835ce6b3fc84cc818581","bytes":34643},"customer-portal.min.css":{"sha256":"e886e0d65396c4dd202c93703547e3ec1f111f3be6910f135a951e392615733e","bytes":26556},"customer-portal.min-rtl.css":{"sha256":"dba031b25b63dbcde7ecd9004a71ef4174ba202aceb4e69e9f8590a879cedbf5","bytes":26343},"admin-setup-wizard-rtl.css":{"sha256":"8c2381f5b07e05067b316c10ce4a9333f1ab62714d197bcb85c7ce67e967d676","bytes":7502},"admin-setup-wizard.min.css":{"sha256":"4a9ef36fa5a16fd188b6db80e86a3d825bc786d844a3f1905bcc2facc504d0ba","bytes":7046},"admin-setup-wizard.min-rtl.css":{"sha256":"9cf36af333dae3b82ca7bc98f99ae297e5e59c34ac1766fafb4fa5364bd6e818","bytes":7048}},"frontend_builder":"terser-5.50.0","staff_portal_builder":"Source-equivalent production JS/CSS; inherited assets retain their original build.","internal_build":"490-free-20261025-theme-header-lang-picker-compact-languages-ordered-language-picker-mobile-single-brand-row","css_note":"4.8.2: yatoon-booking-core/flow/final min bundles and frontend/ui-polish/final sources had declarations removed that a later same-selector rule in the same stylesheet always overrides; outputs were edited in place (not rebuilt with cssnano), so hashes are of the shipped files. 4.8.5: core/flow/final min files also had declarations removed that never take effect in the captured cascade and rules for class names the plugin never outputs; outputs edited in place.","css_bundles":{"yatoon-booking-core.bundle.min.css":{"sha256":"bd16143171cd19aa9b2ef73ee546de2846fe78a8b4b3566d26ec9af34bd85de6","bytes":208911,"parts":["yatoon-theme-isolation.css","yatoon-booking-core.min.css"]},"yatoon-booking-core.bundle.min-rtl.css":{"sha256":"95a8512c82e3839ce88cae5aaf50236a52e9c57b9abad0bc1a04e62bdbd6d399","bytes":208984,"parts":["yatoon-theme-isolation.css","yatoon-booking-core.min-rtl.css"]},"yatoon-booking-layers.bundle.min.css":{"sha256":"319ec68f7b14bc6ccb1997f747d36bbab8f985cc73b0c21be51460ba60f96595","bytes":79694,"parts":["yatoon-color-schemes.min.css","yatoon-service-image-frame.css","yatoon-theme-armor-4723.min.css","yatoon-booking-responsive.min.css"]},"yatoon-booking-layers.bundle.min-rtl.css":{"sha256":"0361d779e153376c92d30f991c2ffe1b20f9c39bd6ccd8130ac97f968765d74f","bytes":79698,"parts":["yatoon-color-schemes.min.css","yatoon-service-image-frame.css","yatoon-theme-armor-4723.min.css","yatoon-booking-responsive.min-rtl.css"]},"yatoon-booking-extras.bundle.min.css":{"sha256":"8eb2ad13592a630dc19ff24101d4c69aead136eee39fef404ef6322a46d7d508","bytes":9565,"parts":["yatoon-seasonal-viewport-4781.css","yatoon-booking-discovery.css"]}},"js_note":"4.8.5: the booking form is split into frontend.js, frontend-flow.js, frontend-cart.js and frontend-boot.js (sources). build_frontend.js generates the lazy chunks frontend-availability.js and frontend-steps.js, every .min.js, and frontend.bundle.min.js.","css_sources":"4.9.0: assets/css/src/yatoon-booking-core.css and yatoon-booking-flow.css are readable sources with exactly the rules of the shipped .min files; rebuild with csso --no-restructure (keeps rule order), then rtlcss.","header_note":"Compact local SVG country flags with native details navigation and a circular account control. Header remains visible on step one only.","cart_note":"Single section dividers and aligned total/duration rows in the mobile drawer; booking calculations are unchanged."}
  • yatoon-booking-system/trunk/changelog.txt

    r3780403 r3780965  
    66* Security check: with Strict mode off, a Turnstile check that cannot load (for example behind an ad blocker) no longer blocks a real customer; Strict mode on still blocks.
    77* Checkout: a refusal before any payment (security check, booking limit) now shows a message and keeps the form editable instead of switching to the booking-status panel.
     8* Stripe checkout: a payment Stripe refuses (no payment method chosen, card declined, failed verification) now shows the reason and keeps the payment form open so the customer can pay again; it no longer ends the checkout in the booking-status panel.
     9* Orders: a cancelled appointment no longer counts toward the balance due. When every appointment on an order is cancelled the order shows as Cancelled with nothing owed; any deposit kept or refunded stays in the payment history.
     10* Stripe checkout: in a cart with two paid appointments, the second payment form now starts with a working Pay button. Before, it kept the first payment's "Processing..." state and the customer could not pay for the second appointment.
     11* Bookings list: a cart with appointments on different days shows the date range, and each expanded row shows its own day, instead of one long block on the first day.
    812* Manage booking: date and time follow the appointments that are still booked after a partial cancel; carts on different days show each day.
    913
  • yatoon-booking-system/trunk/includes/class-yatoon-commerce.php

    r3780403 r3780965  
    8181        add_action( 'yatoon_booking_created', array( __CLASS__, 'create_for_booking' ), 70, 1 );
    8282        add_action( 'yatoon_payment_status_changed', array( __CLASS__, 'sync_payment_status' ), 20, 4 );
     83        // 4.10.2: after the cancellation refund (priority 20) so the order shows what is really owed.
     84        add_action( 'yatoon_booking_status_changed', array( __CLASS__, 'booking_status_changed' ), 30, 3 );
    8385        add_action( 'admin_menu', array( __CLASS__, 'add_menu' ), 23 );
    8486        add_action( 'admin_post_yatoon_record_order_payment', array( __CLASS__, 'record_payment' ) );
    … …  
    602604        ) ) );
    603605        $net_paid = max( 0, $paid - $refunds );
    604         $balance = max( 0, (float) $order->total - $net_paid );
    605         $status = $refunds >= $paid && $paid > 0 ? 'refunded' : ( $balance <= 0 ? 'paid' : ( $net_paid > 0 ? 'partial' : 'open' ) );
     606        // 4.10.2: a cancelled appointment is no longer owed. Its line stays on the
     607        // order for the record, but it does not count toward the balance due. When
     608        // every appointment is cancelled nothing is owed (a tip line included) and
     609        // the order is marked cancelled; money kept or refunded stays in the ledger.
     610        $lines = yatoon_db_get_row( $wpdb->prepare(
     611            "SELECT COUNT(i.booking_id) AS booked,
     612                    COALESCE(SUM(CASE WHEN LOWER(b.status) IN ('cancelled','canceled','cancelled_by_customer','cancelled_by_seller','declined') THEN 1 ELSE 0 END),0) AS voided,
     613                    COALESCE(SUM(CASE WHEN LOWER(b.status) IN ('cancelled','canceled','cancelled_by_customer','cancelled_by_seller','declined') THEN i.line_total ELSE 0 END),0) AS voided_total
     614             FROM {$wpdb->prefix}yatoon_order_items i
     615             LEFT JOIN {$wpdb->prefix}yatoon_bookings b ON b.id=i.booking_id
     616             WHERE i.order_id=%d",
     617            $order_id
     618        ) );
     619        $all_cancelled = $lines && (int) $lines->booked > 0 && (int) $lines->voided >= (int) $lines->booked;
     620        $owed = $all_cancelled ? 0.0 : max( 0, (float) $order->total - ( $lines ? (float) $lines->voided_total : 0 ) );
     621        $balance = max( 0, round( $owed - $net_paid, 2 ) );
     622        $status = $refunds >= $paid && $paid > 0 ? 'refunded' : ( $all_cancelled ? 'cancelled' : ( $balance <= 0 ? 'paid' : ( $net_paid > 0 ? 'partial' : 'open' ) ) );
    606623        $saved_order = yatoon_db_update(
    607624            $wpdb->prefix . 'yatoon_orders',
    … …  
    615632        );
    616633        return false !== $saved_order && false !== $saved_invoice;
     634    }
     635
     636    /** 4.10.2: refresh the order balance when one of its appointments is cancelled or restored. */
     637    public static function booking_status_changed( $booking_id, $new_status = '', $old_status = '' ) {
     638        global $wpdb;
     639        $booking_id = absint( $booking_id );
     640        if ( ! $booking_id || (string) $new_status === (string) $old_status ) {
     641            return;
     642        }
     643        $order_ids = array_map( 'absint', (array) yatoon_db_get_col( $wpdb->prepare(
     644            "SELECT DISTINCT order_id FROM {$wpdb->prefix}yatoon_order_items WHERE booking_id=%d",
     645            $booking_id
     646        ) ) );
     647        foreach ( array_filter( $order_ids ) as $order_id ) {
     648            self::recalculate( $order_id );
     649        }
    617650    }
    618651
  • yatoon-booking-system/trunk/readme.txt

    r3780403 r3780965  
    212212* Security check: with Strict mode off, a Turnstile check that cannot load (for example behind an ad blocker) no longer blocks a real customer; Strict mode on still blocks.
    213213* Checkout: a refusal before any payment (security check, booking limit) now shows a message and keeps the form editable instead of switching to the booking-status panel.
     214* Stripe checkout: a payment Stripe refuses (no payment method chosen, card declined, failed verification) now shows the reason and keeps the payment form open so the customer can pay again; it no longer ends the checkout in the booking-status panel.
     215* Orders: a cancelled appointment no longer counts toward the balance due. When every appointment on an order is cancelled the order shows as Cancelled with nothing owed; any deposit kept or refunded stays in the payment history.
     216* Stripe checkout: in a cart with two paid appointments, the second payment form now starts with a working Pay button. Before, it kept the first payment's "Processing..." state and the customer could not pay for the second appointment.
     217* Bookings list: a cart with appointments on different days shows the date range, and each expanded row shows its own day, instead of one long block on the first day.
    214218* Manage booking: date and time follow the appointments that are still booked after a partial cancel; carts on different days show each day.
    215219
Note: See TracChangeset for help on using the changeset viewer.