Server : Apache/2.4.18 (Ubuntu) System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64 User : oppastar ( 1041) PHP Version : 7.0.33-0ubuntu0.16.04.15 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, Directory : /var/www/laciasmara.com/public_html/shop/application/views/ |
Upload File : |
<style> /* Base Styles */ :root { --primary-color: #7A4397; --primary-hover: #66357a; --secondary-color: #f3f4f6; --success-color: #10b981; --warning-color: #f59e0b; --danger-color: #ef4444; --gold-color: #fbbf24; --text-primary: #333333; --text-secondary: #6b7280; --border-color: #e5e7eb; --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } main { line-height: 1.6; color: var(--text-color); background-color: var(--background-color); } /* Container */ .checkout-container { margin: 0 auto; padding: 20px 60px; display: flex; flex-wrap: wrap; gap: 24px; } .checkout-left { flex: 1.7; display: flex; flex-direction: column; gap: 4px; } /* Kolom kanan (payment dan summary) */ .checkout-right { flex: 1; display: flex; flex-direction: column; background-color: white; border-radius: 8px; height: 100%; position: sticky; top: 125px; } .checkout-title { width: 100%; font-size: 24px; margin: 0; } /* Insurance */ .insurance-header { display: flex; align-items: center; margin-top: 8px; padding: 0; gap: 12px; } .insurance-section-title { display: flex; align-items: center; gap: 8px; color: #333; font-weight: 500; font-size: 16px; margin: 0; } .insurance-section-title i { font-size: 18px; color: #7A4397; } /* Checkbox styling */ .insurance-checkbox-wrapper { position: relative; cursor: pointer; user-select: none; display: inline-flex; align-items: center; } .insurance-checkbox-wrapper input { position: absolute; opacity: 0; cursor: pointer; height: 0; width: 0; } .checkmark { height: 16px; width: 16px; background-color: #fff; border: 2px solid #ddd; border-radius: 4px; position: relative; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; } .insurance-checkbox-wrapper:hover input~.checkmark { border-color: #7A4397; box-shadow: 0 0 0 3px rgba(122, 67, 151, 0.1); } .insurance-checkbox-wrapper input:checked~.checkmark { background-color: #7A4397; border-color: #7A4397; } .insurance-checkbox-wrapper input:focus~.checkmark { outline: 2px solid #7A4397; outline-offset: 2px; } .checkmark:after { content: ""; position: absolute; display: none; left: 6px; top: 2px; width: 6px; height: 10px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } .insurance-checkbox-wrapper input:checked~.checkmark:after { display: block; } /* Responsive sizing */ @media (max-width: 768px) { .checkmark { height: 14px; width: 14px; } .checkmark:after { left: 3px; top: 1px; width: 3px; height: 7px; } .insurance-section-title i { font-size: 14px; } } /* Gift Section */ .checkout-gift { background: #fff; padding: 0; margin: 0; } .gift-header { display: flex; align-items: center; margin-bottom: 8px; gap: 12px; } .gift-section-title { color: #333; font-size: 16px; margin: 0; font-weight: 500; } /* Checkbox styling */ .gift-checkbox-wrapper { position: relative; cursor: pointer; user-select: none; display: inline-flex; align-items: center; } .gift-checkbox-wrapper input { position: absolute; opacity: 0; cursor: pointer; height: 0; width: 0; } .gift-checkbox-wrapper .checkmark { height: 16px; width: 16px; background-color: #fff; border: 2px solid #ddd; border-radius: 4px; position: relative; } .gift-checkbox-wrapper:hover input~.checkmark { border-color: #7A4397; } .gift-checkbox-wrapper input:checked~.checkmark { background-color: #7A4397; border-color: #7A4397; } .checkmark:after { content: ""; position: absolute; display: none; left: 4px; top: 0px; width: 5px; height: 10px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } .gift-checkbox-wrapper input:checked~.checkmark:after { display: block; } /* Form styling */ .gift-form { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; } .gift-form.active { max-height: 500px; } .gift-form-group { margin-bottom: 1.25rem; } .gift-form-group label { display: block; margin-bottom: 0.5rem; color: #555; font-size: 0.9rem; } .gift-form-group input, .gift-form-group textarea { width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; transition: border-color 0.2s ease; box-sizing: border-box; } .gift-form-group input:focus, .gift-form-group textarea:focus { outline: none; border-color: #7A4397; box-shadow: 0 0 0 2px rgba(122, 67, 151, 0.1); } /* Notes styling - integrated into gift form */ .checkout-notes { position: relative; } .checkout-notes-input { width: 100%; padding: 12px; border: 1px solid var(--border-color); border-radius: 4px; resize: vertical; min-height: 80px; box-sizing: border-box; font-size: 14px; transition: border-color 0.2s ease; } .checkout-notes-input:focus { outline: none; border-color: #7A4397; box-shadow: 0 0 0 2px rgba(122, 67, 151, 0.1); } .checkout-notes-count { position: absolute; right: 12px; bottom: 8px; color: #666; font-size: 12px; background: rgba(255, 255, 255, 0.9); padding: 2px 4px; border-radius: 2px; } /* Responsive */ @media (max-width: 768px) { .checkout-gift { padding: 0; } .gift-form-group input, .gift-form-group textarea, .checkout-notes-input { font-size: 13px; } } /* Address Section */ .checkout-shipping { background: #fff; border-radius: 8px; padding: 0; margin-bottom: 8px; } .checkout-address-section { margin-bottom: 20px; } /* Address Card */ .checkout-address-card { border-radius: 12px; padding: 20px; margin-bottom: 12px; box-shadow: var(--shadow); border: 1px solid var(--border-color); background: white; transition: all 0.2s ease; } .checkout-address-card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } /* Address Label Badge */ .address-label-badge { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; } .label-text { color: #333; font-size: 12px; font-weight: 500; } .default-badge { background: #d1d5db; color: #333; padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: 500; } /* Recipient Info */ .address-recipient { margin-bottom: 12px; display: flex; flex-direction: row; gap: 8px; align-items: center; } .recipient-name { margin: 0; font-size: 16px; font-weight: 600; color: var(--text-color); } .recipient-phone { color: #666; font-size: 13px; margin: 0; } /* Address Details */ .address-details { margin-bottom: 16px; } .full-address { margin: 0 0 8px 0; font-size: 14px; line-height: 1.5; color: var(--text-color); } .address-notes { color: orange; font-style: italic; font-size: 14px; } /* Address Actions */ .address-actions { display: flex; gap: 10px; } .btn-change-address { display: flex; align-items: center; gap: 6px; background: none; border: 1px solid var(--primary-color); color: var(--primary-color); padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s ease; } .btn-change-address:hover { background: var(--primary-color); color: white; } .btn-icon { width: 16px; height: 16px; } /* Empty Address State */ .empty-address { text-align: center; padding: 40px 20px; border: 2px dashed var(--border-color); background: #f9f9f9; } .empty-address-content { display: flex; flex-direction: column; align-items: center; gap: 12px; } .empty-icon { width: 48px; height: 48px; color: #ccc; } .empty-address h4 { margin: 0; color: #666; } .empty-address p { margin: 0; color: #888; font-size: 14px; } .btn-add-address { display: flex; align-items: center; gap: 6px; background: var(--primary-color); color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; margin-top: 8px; } /* Modal Address Styles */ .address-selection-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 9999; display: flex; align-items: center; justify-content: center; } .address-selection-modal-content { background: white; border-radius: 12px; max-width: 600px; width: 90%; max-height: 80vh; display: flex; flex-direction: column; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); } .address-selection-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #eee; } .address-selection-modal-header h3 { margin: 0; font-size: 18px; color: var(--text-color); } .address-selection-modal-close { background: none; border: none; cursor: pointer; padding: 8px; border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; transition: background 0.2s ease; } .address-selection-modal-close:hover { background: #f0f0f0; } .address-selection-modal-body { flex: 1; overflow-y: auto; padding: 0 20px; } .address-list { display: flex; flex-direction: column; gap: 12px; padding: 20px 0; } .address-option { cursor: pointer; transition: all 0.3s ease; border: 1px solid var(--border-color); border-radius: 12px; padding: 20px; position: relative; background: white; user-select: none; } .address-option:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); border-color: var(--primary-color); } .address-option.selected { border-color: var(--primary-color); background: var(--background-light); box-shadow: 0 4px 12px rgba(122, 67, 151, 0.15); } .address-option-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; } .address-label-badge { display: flex; gap: 8px; align-items: center; } /* Custom Checkbox/Checkmark Design */ .custom-checkbox { width: 18px; height: 18px; border: 1px solid var(--border-color); border-radius: 4px; background: white; position: relative; transition: all 0.3s ease; flex-shrink: 0; } .address-option:hover .custom-checkbox { border-color: var(--primary-color); } .address-option.selected .custom-checkbox { background: var(--primary-color); border-color: var(--primary-color); } /* Checkmark icon */ .custom-checkbox::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0); width: 14px; height: 14px; background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='20,6 9,17 4,12'%3e%3c/polyline%3e%3c/svg%3e") no-repeat center; background-size: contain; transition: transform 0.2s ease; } .address-option.selected .custom-checkbox::after { transform: translate(-50%, -50%) scale(1); } /* Hide the original radio button */ .address-option input[type="radio"] { display: none; } .address-option-content { margin-top: 8px; } .address-text { margin: 8px 0; font-size: 14px; color: var(--text-color); line-height: 1.4; } .notes { color: orange; font-style: italic; font-size: 13px; margin: 8px 0 0 0; } .address-selection-modal-footer { display: flex; justify-content: flex-end; gap: 12px; padding: 20px; border-top: 1px solid #eee; } .address-selection-btn-secondary { background: #f8f9fa; color: #6c757d; border: 1px solid #dee2e6; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; transition: all 0.2s ease; } .address-selection-btn-secondary:hover { background: #e9ecef; border-color: #adb5bd; } .address-selection-btn-primary { background: var(--primary-color); color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; transition: all 0.2s ease; } .address-selection-btn-primary:hover { background: var(--primary-hover); } .address-selection-btn-primary:disabled { background: #ccc; cursor: not-allowed; } /* Responsive */ @media (max-width: 768px) { .address-selection-modal-content { width: 90%; margin: 10px; } .btn-change-address { width: 100%; justify-content: center; } .address-recipient, .address-option-header { margin-bottom: 8px; } .address-text { font-size: 12px; line-height: 1.2; } .address-option { padding: 16px; } .address-selection-modal-footer { flex-direction: column-reverse; } .address-selection-btn-secondary, .address-selection-btn-primary { width: 100%; justify-content: center; } } /* Animation for modal appearance */ .address-selection-modal-overlay { animation: fadeIn 0.3s ease; } .address-selection-modal-content { animation: slideUp 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } /* Product Section */ .checkout-products { background: #fff; border-radius: 8px; padding: 16px; /* box-shadow: var(--shadow); */ border: 1px solid var(--border-color); } .checkout-section-title { font-size: 20px; font-weight: 600; color: var(--text-primary); margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid var(--secondary-color); display: flex; align-items: center; gap: 8px; } /* Product Item */ .checkout-product-item { border-bottom: 0.75px solid var(--border-color); padding-bottom: 20px; margin-bottom: 20px; } .checkout-product-item:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } .checkout-product-details { display: flex; gap: 16px; align-items: flex-start; } /* Product Image */ .checkout-product-image { width: 100px; height: 100px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border-color); flex-shrink: 0; } /* Product Info */ .checkout-product-info { flex: 1; min-width: 0; } .checkout-product-name { font-size: 16px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; margin-top: 0; line-height: 1.4; } .product-meta { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 12px; } .meta-item { display: flex; align-items: center; gap: 4px; font-size: 14px; color: var(--text-secondary); } .meta-item .icon { width: 16px; height: 16px; opacity: 0.7; } .variant-badge { background: var(--secondary-color); color: var(--text-primary); padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; } /* Pricing Section */ .pricing-section { margin-top: 12px; } .price-display { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 600; color: var(--text-primary); } .price-current { color: var(--primary-color); } .price-original { font-size: 14px; color: var(--text-secondary); text-decoration: line-through; font-weight: 400; } .quantity-multiplier { color: var(--text-secondary); font-weight: 400; } /* Mobile Responsive */ @media (max-width: 768px) { .checkout-products { padding: 16px; } .checkout-product-details { gap: 12px; } .checkout-product-image { width: 80px; height: 80px; } .product-meta { flex-direction: column-reverse; gap: 8px; } .pricing-section { padding: 0px; } .checkout-section-title { font-size: 18px; } .meta-item { font-size: 13px; } .price-current, .quantity-multiplier { font-size: 13px; } .price-original { font-size: 13px; } .checkout-change-btn { font-size: 13px; } } @media (max-width: 480px) { .checkout-product-name { font-size: 15px; } .checkout-container { gap: 8px; } .price-display { flex-wrap: wrap; gap: 6px; } } /* Shipping Options */ .checkout-shipping-options { background: #fff; border-radius: 8px; padding: 12px; margin-bottom: 20px; box-sizing: border-box; } .checkout-shipping-selection { margin-bottom: 12px; } .checkout-shipping-selection h4 { font-weight: 500; color: #333; margin: 0; } .checkout-insurance { display: flex; align-items: center; gap: 8px; } /* Payment Section */ .checkout-payment { background: #fff; border-radius: 8px; padding: 0; box-sizing: border-box; } .checkout-view-all { color: var(--primary-color); text-decoration: none; } .checkout-payment-option { display: flex; align-items: center; gap: 12px; padding: 12px; border: 1px solid var(--border-color); border-radius: 8px; cursor: pointer; box-sizing: border-box; flex-grow: 1; width: 100%; } .checkout-payment-icon { width: 40px; height: auto; } /* Shipping */ .checkout-shipping-method { position: relative; margin-bottom: 20px; background: #fff; border-radius: 12px; padding: 0; margin-bottom: 20px; box-sizing: border-box; } /* Payment Category Styles */ .payment-category { margin-bottom: 16px; } .payment-category:last-child { margin-bottom: 0; } .payment-category-title { font-size: 14px; margin-bottom: 8px; margin-top: 0; font-weight: 500; color: #333; } .checkout-payment-options { display: flex; flex-wrap: wrap; gap: 12px; } /* Summary Section */ .checkout-summary { background: #fff; border-radius: 12px; padding: 0; margin-top: 0; } .checkout-summary-details { margin-bottom: 20px; } .checkout-summary-row { display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px; } .checkout-summary-row:last-child { border-bottom: none; } .checkout-summary-row span:first-child { color: var(--text-secondary); display: flex; align-items: center; gap: 8px; } .checkout-summary-row span:last-child { font-weight: 500; color: var(--text-primary); } /* Discount Section */ .discount-section { background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; margin: 8px 0; overflow: hidden; } .discount-toggle { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; cursor: pointer; background: #fef2f2; border: none; width: 100%; font-size: 14px; color: var(--danger-color); font-weight: 500; } .discount-toggle:hover { background: #fee2e2; } .discount-content { display: none; padding: 0 16px 12px; background: #fff; } .discount-content.active { display: block; } .discount-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; font-size: 13px; color: var(--text-secondary); } .discount-item span:last-child { color: var(--danger-color); font-weight: 500; } /* Special styling for different row types */ .discount-row span:last-child { color: var(--danger-color) !important; font-weight: 600; } .point-redeem-row span:last-child { color: var(--danger-color) !important; font-weight: 600; } .bonus-point-row span:last-child { color: var(--gold-color) !important; font-weight: 600; } .savings-row { background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 12px 16px !important; margin: 8px 0; } .savings-row span:first-child { color: var(--primary-color) !important; font-weight: 600; } .savings-row span:last-child { color: var(--primary-color) !important; font-weight: 600; } .checkout-summary-total { display: flex; justify-content: space-between; align-items: center; font-weight: 700; font-size: 16px; margin: 20px 0; padding: 16px; background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); border-radius: 8px; border: 2px solid var(--primary-color); } .checkout-summary-total span:first-child { color: var(--text-primary); } .checkout-summary-total span:last-child { color: var(--primary-color); } .checkout-pay-btn { width: 100%; background: linear-gradient(135deg, var(--primary-color) 0%, #6a3085 100%); color: #fff; border: none; padding: 16px; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 16px; transition: all 0.3s ease; } .checkout-pay-btn:hover { background: linear-gradient(135deg, #6a3085 0%, #7A4397 100%); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3); } .checkout-terms { text-align: center; font-size: 14px; color: #666; line-height: 1.5; } .checkout-terms-link { color: var(--primary-color); text-decoration: none; } .checkout-terms-link:hover { text-decoration: underline; } /* Icons */ .summary-icon { width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center; } .summary-icon-success { color: var(--success-color); } .summary-icon-warning { color: var(--warning-color); } .summary-icon-danger { color: var(--danger-color); } .summary-icon-gold { color: var(--gold-color); } .summary-icon-primary { color: var(--primary-color); } /* Responsive Design */ @media (max-width: 768px) { .checkout-section-title { font-size: 18px; } .checkout-summary-total { font-size: 16px; padding: 12px; } .checkout-summary-row { font-size: 13px; padding: 10px 0; } .checkout-pay-btn { padding: 14px; font-size: 15px; } .discount-toggle { font-size: 13px; padding: 10px 12px; } .discount-item { font-size: 12px; } } @media (max-width: 480px) { .checkout-summary-row { font-size: 12px; } .checkout-summary-total { font-size: 15px; } } /* Checkout Point */ .checkout-point { background: white; } .point-balance { background: linear-gradient(135deg, #764ba2 0%, #7a4397 100%); color: white; padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: center; position: relative; overflow: hidden; } @keyframes shimmer { 0% { transform: translateX(-100%) translateY(-100%); } 100% { transform: translateX(100%) translateY(100%); } } .point-balance-label { font-size: 14px; opacity: 0.9; margin-bottom: 4px; } .point-balance-value { font-size: 20px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 8px; } .conversion-info { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; padding: 12px; margin-bottom: 20px; display: flex; align-items: center; gap: 8px; font-size: 14px; color: #6c757d; } .conversion-info::before { content: "💡"; font-size: 16px; } .checkout-point-container { position: relative; } .input-group { display: flex; gap: 12px; align-items: stretch; } .point-input-wrapper { flex: 1; position: relative; } .point-input { width: 100%; height: 48px; padding: 12px 40px 12px 16px; border: 2px solid #e9ecef; border-radius: 8px; font-size: 14px; transition: all 0.3s ease; background: white; outline: none; box-sizing: border-box; } .point-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .point-input.error { border-color: #dc3545; background-color: #fff5f5; } .point-input.success { border-color: #28a745; background-color: #f0fff4; } .input-icon { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: #6c757d; font-size: 12px; } .point-redeem-btn { width: 100px; height: 48px; padding: 8px 10px; font-size: 14px; font-weight: 600; background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; flex-shrink: 0; box-sizing: border-box; } .point-redeem-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3); } .point-redeem-btn:active { transform: translateY(0); } .point-redeem-btn:disabled { background: #6c757d; cursor: not-allowed; transform: none; box-shadow: none; } .point-redeem-btn.loading { background: #6c757d; cursor: not-allowed; } .point-redeem-btn.loading::after { content: ''; position: absolute; top: 50%; left: 50%; width: 20px; height: 20px; margin: -10px 0 0 -10px; border: 2px solid transparent; border-top: 2px solid white; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .feedback-message { margin-top: 12px; padding: 12px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; display: none; align-items: center; gap: 8px; animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } .feedback-message.success { background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%); border: 1px solid #b8dabd; color: #155724; } .feedback-message.error { background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%); border: 1px solid #f1b0b7; color: #721c24; } .feedback-message i { font-size: 16px; } .quick-select { margin-top: 16px; display: none; } .quick-select-label { font-size: 14px; font-weight: 500; color: #495057; margin-bottom: 8px; display: block; } .quick-select-buttons { display: flex; gap: 8px; flex-wrap: wrap; } .quick-select-btn { padding: 8px 16px; font-size: 12px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 20px; cursor: pointer; transition: all 0.2s ease; color: #495057; } .quick-select-btn:hover { background: #e9ecef; border-color: #adb5bd; } .quick-select-btn.active { background: linear-gradient(135deg, #764ba2 0%, #7A4397 100%); color: white; border-color: #7A4397; } .discount-preview { background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 6px; padding: 12px; margin-top: 16px; display: none; align-items: center; gap: 8px; font-size: 14px; color: #856404; } .discount-preview span i { margin-right: 8px; } /* Responsive Design */ @media (max-width: 768px) { .point-balance-label, .conversion-info { font-size: 13px; } .input-group { flex-direction: column; gap: 12px; } .point-input-wrapper { max-width: 100%; } .point-redeem-btn { width: 100%; font-size: 14px; } .point-balance-value { font-size: 16px; } .point-input { height: 44px; font-size: 13px; } .point-redeem-btn { height: 44px; font-size: 13px; } } @media (max-width: 480px) { .point-input { height: 44px; font-size: 12px; } .point-redeem-btn { height: 44px; font-size: 12px; } } /* Voucher */ .checkout-voucher { padding: 0; } .voucher-button { display: flex; align-items: center; width: 100%; padding: 12px 16px; background-color: #f8f8f8; border: 1px solid #7A4397; border-radius: 8px; cursor: pointer; text-decoration: none; box-sizing: border-box; transition: all 0.2s ease; position: relative; } .voucher-button:hover:not(.disabled) { background-color: #f0f0f0; border-color: #6a3085; transform: translateY(-1px); } .voucher-button:focus { outline: 2px solid #7A4397; outline-offset: 2px; } .voucher-button.disabled { opacity: 0.6; cursor: not-allowed; pointer-events: none; } .voucher-content { display: flex; align-items: center; gap: 12px; flex-grow: 1; } .voucher-icon { width: 20px; height: 20px; stroke: #7A4397; flex-shrink: 0; } .voucher-text { color: #333; font-weight: 600; font-size: 16px; flex-grow: 1; } .checkout-arrow-icon { width: 16px; height: 16px; stroke: #999; flex-shrink: 0; margin-left: auto; } .discount-info-text { color: #e74c3c; font-size: 12px; margin-top: 4px; } /* Shipment methods */ .shipping-method-option { background: white; border: 1px solid #e9ecef; border-radius: 8px; padding: 16px; cursor: pointer; transition: all 0.2s ease; } .shipping-method-option:hover { border-color: #7A4397; box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1); } .shipping-method-option input[type="radio"] { display: none; } .shipping-method-option:has(input[type="radio"]:checked) { border: 2px solid #7A4397 !important; background-color: #f8f9ff !important; } /* Fallback for browsers that don't support :has() */ .shipping-method-option.selected { border: 2px solid #7A4397 !important; background-color: #f8f9ff !important; } .shipping-method-label { display: block; cursor: pointer; margin: 0; } @media (max-width: 768px) { .voucher-button { padding: 14px 16px; } .voucher-text { font-size: 13px; } } .loading-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; } .loading-content { text-align: center; padding: 20px; box-sizing: border-box; border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; } .loading-content p { color: white; } .loading-content .spinner { width: 50px; height: 50px; border: 6px solid white; border-top-color: #7A4397; border-radius: 50%; animation: spin 1s linear infinite; } .info-message { display: block; margin-top: 10px; padding: 12px 16px; background-color: #fef3c7; border-left: 5px solid #facc15; color: #7a4397; font-size: 14px; border-radius: 8px; line-height: 1.6; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); transition: all 0.3s ease-in-out; } .info-message small { font-weight: 500; } .info-message strong { color: #5b21b6; /* Memberikan kontras pada teks penting */ } .info-message:hover { background-color: #fef08a; /* Warna lebih terang saat hover */ } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Responsive Design */ @media (max-width: 768px) { .info-message small { font-size: 13px; } .voucher-card { padding: 8px; } .checkout-shipping-selection h4 { font-size: 14px; } .shipping-method-option input[type="radio"] { margin-bottom: 5px; } .shipping-method-option label { font-size: 13px; } } /* Responsive Adjustments */ @media (max-width: 768px) { .change-address-modal-content { padding: 15px; width: 80%; } .change-address-modal h3 { font-size: 16px; } .change-address-btn-submit, .change-address-btn-close { font-size: 13px; padding: 8px 10px; } } .shipping-method-selection>p { margin: 0 0 8px 0; color: #333; font-size: 14px; } .shipping-method-options { display: flex; flex-direction: column; gap: 8px; } @media (max-width: 768px) { .checkout-container { flex-direction: column; padding: 20px; } .checkout-address-label { font-size: 10px; } .checkout-terms, .checkout-payment-option span, .checkout-product-price, .gift-form-group label, .checkout-address-detail, .checkout-address-name { font-size: 13px; } .shipping-method-option { flex-direction: row; padding: 12px; } .checkout-product-name, .insurance-section-title { font-size: 14px; } .gift-section-title { font-size: 13px; } .payment-category-title, .insurance-section-title .insurance-fee { font-size: 13px; } .checkout-left, .checkout-right { flex: 1; } .checkout-right { top: 0; } .checkout-pay-btn { padding: 12px; font-size: 14px; } .gift-form-group input { padding: 8px; font-size: 13px; } } @media (max-width: 480px) { .insurance-section-title { font-size: 12px; } .insurance-section-title i { font-size: 12px; } .insurance-section-title .insurance-fee { font-size: 12px; } .checkmark { height: 12px; width: 12px; } .checkmark:after { left: 3px; top: 1px; width: 3px; height: 6px; } } .doku-payment-wrapper { flex-direction: column; align-items: flex-start; } .doku-main-option { display: flex; align-items: center; gap: 8px; width: 100%; } .doku-description { color: #333; font-size: 12px; margin-left: 30px; margin-bottom: 0; margin-top: 0; /* 40px (lebar ikon) + 12px (gap) */ } .doku-payment-methods { width: 100%; padding-left: 30px; } .doku-payment-logos { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } .payment-partner-icon { height: auto; width: 40px; } .method-info { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; } .method-details { flex: 1; } .method-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 4px; } .method-icon { width: 48px; height: 48px; object-fit: contain; border-radius: 4px; flex-shrink: 0; } .method-name { font-weight: 600; color: #333; font-size: 14px; flex: 1; margin-bottom: 0; } .etd-info { font-size: 12px; color: #636e72; font-weight: 500; opacity: 0.8; } /* Price section styles */ .method-price { text-align: right; display: flex; flex-direction: column; gap: 4px; align-items: flex-end; } .price-group { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; } .price-row { display: flex; align-items: center; gap: 8px; } .shipping-price { font-weight: 600; color: #333; font-size: 14px; } .original-price { font-size: 14px; color: #636e72; text-decoration: line-through; font-weight: 500; opacity: 0.8; } .free-shipping { background: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 600; text-transform: uppercase; } .discount-info { font-size: 12px; color: #28a745; font-weight: 500; } .no-shipping-available { text-align: center; padding: 20px; color: #666; font-style: italic; } /* Responsive design */ @media (max-width: 768px) { .method-icon { width: 36px; height: 36px; } .method-header { gap: 10px; } .method-info { gap: 8px; } .method-price { text-align: left; align-items: flex-start; } .method-name { font-size: 13px; } .etd-info { font-size: 11px; } .original-price { font-size: 12px; } .free-shipping { font-size: 13px; } .shipping-price { font-size: 13px; } .doku-description { font-size: 11px; } } .insurance-fee { color: #6c757d; font-weight: 400; font-size: 14px; } .hidden { display: none !important; } </style> <main class="checkout-container"> <input type="hidden" name="<?= $this->security->get_csrf_token_name() ?>" value="<?= $this->security->get_csrf_hash() ?>" class="csrf_token"> <h1 class="checkout-title">Checkout</h1> <div class="checkout-left"> <!-- Shipping Address Section --> <h2 class="checkout-section-title">Alamat Pengiriman Kamu</h2> <section class="checkout-shipping"> <?php if (!empty($id_customer)) : ?> <div class="checkout-address-section"> <?php if (!empty($addresses) && is_array($addresses) && count($addresses) > 0): ?> <?php // Cari alamat default atau alamat pertama jika tidak ada default $selected_address = null; foreach ($addresses as $addr) { if ($addr->is_default == 1) { $selected_address = $addr; break; } } if (!$selected_address && count($addresses) > 0) { $selected_address = $addresses[0]; } ?> <div class="checkout-address-card" id="selected-address-card"> <div class="address-label-badge"> <span class="label-text"><?= $selected_address->label ?></span> <?php if ($selected_address->is_default == 1): ?> <span class="default-badge">Utama</span> <?php endif; ?> </div> <div class="address-recipient"> <h4 class="recipient-name"><?= $selected_address->recipient_name ?></h4> <span class="recipient-phone">(<?= $selected_address->phone ?>)</span> </div> <div class="address-details"> <p class="full-address"> <?= $selected_address->address ?> <?php if (!empty($selected_address->notes)): ?> <br><small class="address-notes">Catatan: <?= $selected_address->notes ?></small> <?php endif; ?> </p> </div> <div class="address-actions"> <button class="btn-change-address" id="btn-change-address" data-current-id="<?= $selected_address->id ?>"> <i data-feather="map-pin" class="btn-icon"></i> Pilih Alamat Lain </button> </div> </div> <?php else: ?> <div class="checkout-address-card empty-address"> <div class="empty-address-content"> <i data-feather="map-pin" class="empty-icon"></i> <h4>Belum ada alamat pengiriman</h4> <p>Tambahkan alamat pengiriman untuk melanjutkan pemesanan</p> <button class="btn-add-address" id="btn-add-address"> <i data-feather="plus" class="btn-icon"></i> Tambah Alamat </button> </div> </div> <?php endif; ?> </div> <!-- Modal untuk memilih alamat --> <div id="address-selection-modal" class="address-selection-modal-overlay" style="display: none;"> <div class="address-selection-modal-content"> <div class="address-selection-modal-header"> <h3>Pilih Alamat Pengiriman</h3> <button class="address-selection-modal-close" id="close-address-modal"> <i data-feather="x"></i> </button> </div> <div class="address-selection-modal-body"> <div class="address-list" id="address-list"> <?php if (!empty($addresses) && is_array($addresses)): ?> <?php foreach ($addresses as $address): ?> <div class="address-option" data-address-id="<?= $address->id ?>"> <div class="address-option-header"> <div class="address-label-badge"> <span class="label-text"><?= $address->label ?></span> <?php if ($address->is_default == 1): ?> <span class="default-badge">Utama</span> <?php endif; ?> </div> <div class="custom-checkbox"></div> <input type="radio" name="selected_address" value="<?= $address->id ?>" <?= ($selected_address && $selected_address->id == $address->id) ? 'checked' : '' ?>> </div> <div class="address-option-content"> <div class="address-recipient"> <h4 class="recipient-name"><?= $address->recipient_name ?></h4> <span class="recipient-phone">(<?= $address->phone ?>)</span> </div> <p class="address-text"><?= $address->address ?></p> <?php if (!empty($address->notes)): ?> <p class="notes">Catatan: <?= $address->notes ?></p> <?php endif; ?> </div> </div> <?php endforeach; ?> <?php else: ?> <div class="no-addresses"> <p>Belum ada alamat tersimpan. Silakan tambah alamat terlebih dahulu.</p> </div> <?php endif; ?> </div> </div> <div class="address-selection-modal-footer"> <button class="address-selection-btn-secondary" id="cancel-address-selection">Batal</button> <button class="address-selection-btn-secondary" id="add-address-selection">Tambah Alamat</button> <button class="address-selection-btn-primary" id="confirm-address-selection">Pilih Alamat</button> </div> </div> </div> <?php endif; ?> <!-- Shipping Method --> <h2 class="checkout-section-title">Metode Pengiriman</h2> <div class="shipping-method-selection"> <p>Dikirim dari Kota Jakarta</p> <div class="shipping-method-options"> <?php if (!empty($all_shipping_methods)): ?> <?php foreach ($all_shipping_methods as $index => $shipping_method): ?> <div class="shipping-method-option"> <input type="radio" name="shipping_method" id="shipping_<?= $shipping_method['id'] ?>" value="<?= $shipping_method['id'] ?>" <?= $index === 0 ? 'checked' : '' ?>> <label for="shipping_<?= $shipping_method['id'] ?>" class="shipping-method-label"> <div class="method-info"> <div class="method-details"> <div class="method-header"> <?php if (!empty($shipping_method['icon_url'])): ?> <img src="<?= $shipping_method['icon_url'] ?>" alt="<?= $shipping_method['carrier'] ?>" class="method-icon"> <?php endif; ?> <div class="method-name"><?= ucwords($shipping_method['display_name']) ?></div> <div class="method-price"> <div class="price-group"> <?php if ($shipping_method['is_free']): ?> <div class="price-row"> <?php if ($shipping_method['original_price'] > 0): ?> <span class="original-price">IDR <?= number_format($shipping_method['original_price'], 0, ',', '.') ?></span> <?php endif; ?> <span class="free-shipping">GRATIS ONGKIR</span> </div> <?php elseif ($shipping_method['discount_amount'] > 0): ?> <div class="price-row"> <span class="shipping-price">IDR <?= number_format($shipping_method['final_price'], 0, ',', '.') ?></span> <span class="original-price">IDR <?= number_format($shipping_method['original_price'], 0, ',', '.') ?></span> </div> <div class="discount-info">Hemat IDR <?= number_format($shipping_method['discount_amount'], 0, ',', '.') ?></div> <?php else: ?> <div class="price-row"> <span class="shipping-price">IDR <?= number_format($shipping_method['final_price'], 0, ',', '.') ?></span> </div> <?php endif; ?> </div> </div> </div> <div class="etd-info">Estimasi tiba <?= $shipping_method['etd_info']['formatted_text'] ?></div> </div> </div> </label> </div> <?php endforeach; ?> <?php else: ?> <div class="no-shipping-available"> <span>Tidak ada metode pengiriman yang tersedia untuk semua produk.</span> </div> <?php endif; ?> </div> </div> <!-- Insurance --> <div class="insurance-header"> <?php $insuranceFee = ($grand_total * 0.2 / 100) + 5000 ?> <h2 class="insurance-section-title"> <i class="fa-solid fa-shield-heart fa-bounce"></i> Pakai Asuransi Pengiriman <span class="insurance-fee" id="use-insurance">(IDR 0)</span> </h2> <label class="insurance-checkbox-wrapper" for="shipping_insurance"> <input type="checkbox" id="shipping_insurance" name="shipping_insurance" aria-label="Pakai Asuransi Pengiriman"> <span class="checkmark"></span> </label> </div> </section> <!-- Gift Section --> <h2 class="checkout-section-title">Catatan</h2> <section class="checkout-gift"> <div class="checkout-notes"> <textarea id="gift_notes" name="gift_notes" placeholder="Beri tahu kami jika ini untuk hadiah atau tambahkan pesan khusus..." maxlength="200" class="checkout-notes-input" aria-label="Pesan untuk hadiah"></textarea> <span class="checkout-notes-count">0/200</span> </div> <div class="gift-header"> <h2 class="gift-section-title">Kirim ini ke Orang Lain?</h2> <label class="gift-checkbox-wrapper" for="is_gift"> <input type="checkbox" id="is_gift" name="is_gift" aria-label="Kirim sebagai hadiah"> <span class="checkmark"></span> </label> </div> <div class="gift-form" id="gift_form"> <div class="gift-form-group"> <label for="gift_recipient">Nama Penerima Hadiah</label> <input type="text" id="gift_recipient" name="gift_recipient" placeholder="Masukkan nama penerima" aria-label="Nama penerima hadiah"> </div> <div class="gift-form-group"> <label for="gift_phone">Nomor Telepon Penerima</label> <input type="tel" id="gift_phone" name="gift_phone" placeholder="Contoh: 08123456789" pattern="[0-9]{10,13}" aria-label="Nomor telepon penerima hadiah"> </div> </div> </section> <!-- Product Section --> <h2 class="checkout-section-title">Rincian Produk</h2> <section class="checkout-products"> <?php foreach ($cart_items as $item): ?> <div class="checkout-product-item"> <div class="checkout-product-details" data-rowid="<?= $item['rowid']; ?>" data-qty="<?= $item['qty']; ?>" data-price="<?= $item['price']; ?>" data-product-id="<?= $item['product_id']; ?>" data-detail-id="<?= $item['id']; ?>" data-product-name="<?= $item['name'] ?>" data-attribute-detail-id="<?= $item['options']['attribute_detail_ids'] ?>" data-attribute="<?= $item['variant'] ?>" data-sku="<?= $item['sku'] ?>"> <img src="<?= base_url('uploads/product/' . $item['options']['image']); ?>" alt="<?= $item['name']; ?>" class="checkout-product-image"> <div class="checkout-product-info"> <h3 class="checkout-product-name"><?= $item['name']; ?></h3> <div class="product-meta"> <div class="meta-item"> <span>SKU: <?= $item['sku']; ?></span> </div> <?php if (!empty($item['variant'])): ?> <div class="variant-badge"><?= $item['variant']; ?></div> <?php endif; ?> </div> <div class="pricing-section"> <?php if (isset($item['is_discounted']) && $item['is_discounted'] && isset($item['real_price']) && $item['real_price'] > $item['price']): ?> <!-- Tampilkan dengan format: harga diskon (harga asli) x qty --> <div class="price-display"> <span class="price-original">IDR <?= number_format($item['real_price'], 0, ',', '.'); ?></span> <span class="price-current">IDR <?= number_format($item['price'], 0, ',', '.'); ?></span> <span class="quantity-multiplier">x <?= $item['qty']; ?></span> </div> <?php else: ?> <!-- Tampilkan format normal: harga x qty --> <div class="price-display"> <span class="price-current">IDR <?= number_format($item['price'], 0, ',', '.'); ?></span> <span class="quantity-multiplier">x <?= $item['qty']; ?></span> </div> <?php endif; ?> </div> </div> </div> </div> <?php endforeach; ?> </section> </div> <div class="checkout-right"> <!-- Payment Method Section --> <h2 class="checkout-section-title">Metode Pembayaran</h2> <section class="checkout-payment"> <!-- Transfer Bank Section --> <div class="payment-category"> <h3 class="payment-category-title">Transfer Bank Manual</h3> <div class="checkout-payment-options"> <label class="checkout-payment-option"> <input type="radio" name="payment_method" value="bank transfer BCA" checked> <img src="https://www.bca.co.id/-/media/Feature/Card/List-Card/Tentang-BCA/Brand-Assets/Logo-BCA/Logo-BCA_Biru.png" alt="BCA" class="checkout-payment-icon"> <span>Bank BCA</span> </label> <label class="checkout-payment-option"> <input type="radio" name="payment_method" value="bank transfer MANDIRI"> <img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/01-Mandiri%20Master%20Brand%20Logo.png" alt="Mandiri" class="checkout-payment-icon"> <span>Bank Mandiri</span> </label> </div> </div> <!-- Pembayaran Lainnya Section --> <div class="payment-category"> <h3 class="payment-category-title">Pembayaran Lainnya</h3> <div class="checkout-payment-options"> <label class="checkout-payment-option doku-payment-wrapper"> <div class="doku-main-option"> <input type="radio" name="payment_method" value="DOKU"> <img src="https://www.laciasmara.com/shop/assets/frontend/img/doku.png" alt="Doku" class="checkout-payment-icon"> <span>Doku</span> </div> <p class="doku-description">Bayar dengan Kartu Kredit, Bank Virtual Account, atau Alfa Group melalui DOKU.</p> <div class="doku-payment-methods"> <div class="doku-payment-logos"> <img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/01-Mandiri%20Master%20Brand%20Logo%20Blue.png" alt="VA Mandiri" class="payment-partner-icon"> <img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/mc_symbol_opt_73_3x.png" alt="Mastercard" class="payment-partner-icon"> <img src="https://corporate.visa.com/content/dam/VCOM/corporate/about-visa/images/visa-brandmark-blue-1960x622.png" alt="Visa" class="payment-partner-icon"> <img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/358102_card_jcb_payment_icon.png" alt="JCB" class="payment-partner-icon"> <img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/ALFAMART_LOGO_BARU.png" alt="Alfamart" class="payment-partner-icon"> </div> </div> </label> <?php if ($this->cart->total() > 1000000): ?> <label class="checkout-payment-option"> <input type="radio" name="payment_method" value="Paypal"> <img src="https://www.paypalobjects.com/webstatic/mktg/logo/pp_cc_mark_111x69.jpg" alt="PayPal" class="checkout-payment-icon"> <span>PayPal</span> </label> <?php endif; ?> </div> <span id="paypalInformation" style="display: none;" class="info-message"> <small> Jangan lupa untuk kembali ke <strong>Laciasmara</strong> dengan mengklik tombol di bagian bawah halaman pembayaran sukses. </small> </span> </div> </section> <!-- Point --> <h2 class="checkout-section-title">Point Redeem</h2> <section class="checkout-point"> <!-- Point Balance Display --> <div class="point-balance"> <div class="point-balance-label">Poin Kamu Saat Ini</div> <div class="point-balance-value" id="current-points-display"><?= isset($point_reward_data->current_pointreward) ? $point_reward_data->current_pointreward : '0'; ?></div> </div> <!-- Conversion Info --> <div class="conversion-info"> <span>1 poin = IDR 100 | Minimum penukaran: 1 poin</span> </div> <!-- Input Container --> <div class="checkout-point-container"> <div class="input-group"> <div class="point-input-wrapper"> <input type="number" id="redeem-points" class="point-input" placeholder="Masukkan jumlah poin" min="0" max="<?= isset($point_reward_data->current_pointreward) ? $point_reward_data->current_pointreward : '0'; ?>"> <span class="input-icon"><i class="fa-regular fa-star"></i></span> </div> <button type="button" id="redeem-button" class="point-redeem-btn"> Tukar Poin </button> </div> <!-- Quick Select Buttons --> <div class="quick-select"> <label class="quick-select-label">Pilihan Cepat:</label> <div class="quick-select-buttons"> <button type="button" class="quick-select-btn" data-points="100">100 poin</button> <button type="button" class="quick-select-btn" data-points="250">250 poin</button> <button type="button" class="quick-select-btn" data-points="500">500 poin</button> <button type="button" class="quick-select-btn" data-points="1000">1000 poin</button> </div> </div> <!-- Discount Preview --> <div class="discount-preview" id="discount-preview"> <span><i class="fa-solid fa-gift"></i>Hemat sampai <strong id="discount-amount">IDR 0</strong></span> </div> </div> <!-- Feedback Message --> <div class="feedback-message" id="point-redeem-feedback"></div> </section> <!-- Voucher --> <?php $cartHasDiscount = $this->session->userdata('cart_has_discounted_items'); $shouldDisable = $cartHasDiscount == 'yes'; ?> <?php if ($id_customer && empty($id_reseller)): ?> <h2 class="checkout-section-title">Voucher</h2> <section class="checkout-voucher"> <a href="#" class="voucher-button <?= $shouldDisable ? 'disabled' : '' ?>" id="showVoucherModal" data-vouchers='<?= json_encode($customer_vouchers) ?>' <?= $shouldDisable ? 'disabled' : '' ?>> <div class="voucher-content"> <i data-feather="tag" class="voucher-icon"></i> <span class="voucher-text"><?= $shouldDisable ? 'Voucher tidak berlaku untuk produk diskon' : 'Pilih Voucher' ?></span> </div> <i data-feather="chevron-right" class="checkout-arrow-icon"></i> </a> <?php if ($shouldDisable): ?> <span class="discount-info-text" style="color: #ff0000; font-size: 12px;">Voucher tidak berlaku untuk produk yang sudah diskon.</span> <?php endif; ?> </section> <?php endif; ?> <!-- Order Summary --> <h2 class="checkout-section-title">Ringkasan Transaksi Kamu</h2> <section class="checkout-summary"> <div class="checkout-summary-details"> <!-- 1. Total Harga (sebelum diskon) --> <div class="checkout-summary-row"> <span> <!-- <i class="fas fa-shopping-cart summary-icon summary-icon-primary"></i> --> Total Harga (<?= $this->cart->total_items() ?> Item) </span> <span>IDR <?= number_format($grand_total_before_discount, 0, ',', '.') ?></span> <span style="display:none;" id="subtotal_product"><?= number_format($grand_total, 0, ',', '.') ?></span> </div> <!-- 2. Biaya Ongkos Kirim --> <div class="checkout-summary-row"> <span> <!-- <i class="fas fa-truck summary-icon summary-icon-success"></i> --> Biaya Ongkos Kirim </span> <span id="original-shipping-fee">IDR 0</span> </div> <!-- 3. Asuransi Pengiriman --> <div class="checkout-summary-row" id="insurance-section"> <span> <!-- <i class="fa-solid fa-shield-heart summary-icon summary-icon-success"></i> --> Asuransi Pengiriman </span> <span id="insurance-fee">IDR 2.500</span> </div> <!-- 4. Bagian Diskon (Dropdown) --> <div class="discount-section"> <button class="discount-toggle" onclick="toggleDiscount()"> <span> <i class="fas fa-percentage summary-icon summary-icon-danger"></i> Total Diskon </span> <span> <span id="total-discount">IDR 0</span> <i class="fas fa-chevron-down" id="discount-arrow" style="margin-left: 8px; font-size: 12px;"></i> </span> </button> <div class="discount-content" id="discount-content"> <div class="discount-item"> <span>Diskon Produk</span> <span>-IDR <span id="total-product-discount"><?= number_format($total_product_discount, 0, ',', '.') ?></span></span> </div> <div class="discount-item" id="voucher-redeemed-section" style="display: none;"> <span>Diskon Voucher <span id="voucher-redeem-name"></span></span> <span><span id="voucher-redeem-value">0</span></span> </div> <div class="discount-item"> <span>Diskon Ongkir</span> <span><span id="shipping-fee" class="shipping-fee">0</span></span> </div> </div> </div> <!-- 5. Pakai Poin --> <div class="checkout-summary-row point-redeem-row" id="point-section" style="display: none;"> <span> <!-- <i class="fas fa-star summary-icon summary-icon-danger"></i> --> Pakai Poin </span> <span id="point-redeem">IDR 0</span> </div> <!-- 6. Bonus Poin --> <div class="checkout-summary-row bonus-point-row"> <span> <i class="fas fa-gift summary-icon summary-icon-gold"></i> Bonus Poin </span> <span>+<?= round($grand_total / 10000) ?></span> </div> <!-- 7. Total Tagihan --> <div class="checkout-summary-total"> <span> <!-- <i class="fas fa-receipt summary-icon summary-icon-primary" style="margin-right: 8px;"></i> --> Total Tagihan </span> <span>IDR <span id="final_grand_total">0</span></span> </div> <!-- 8. Kamu Hemat --> <div class="checkout-summary-row savings-row"> <span> <i class="fas fa-piggy-bank summary-icon summary-icon-primary"></i> Kamu Hemat </span> <span id="checkout-savings"></span> </div> </div> <button class="checkout-pay-btn"> <i class="fas fa-credit-card"></i> Bayar Sekarang </button> <p class="checkout-terms"> Dengan melanjutkan pembayaran, kamu menyetujui <a href="https://www.laciasmara.com/shop/page/Syarat-dan-Ketentuan" class="checkout-terms-link">Syarat dan Ketentuan Pembelian</a> </p> </section> </div> <!-- Modal Apply Voucher --> <div class="apply-voucher-modal" id="voucherModal"> <div class="apply-voucher-modal-content"> <div class="apply-voucher-modal-header"> <h3 class="apply-voucher-modal-title">Pilih Voucher</h3> <button class="apply-voucher-close-button">×</button> </div> <div class="voucher-list" id="voucherList"> <!-- Vouchers will be inserted here --> </div> <div class="manual-input"> <h3 class="apply-voucher-modal-title">Atau Masukkan Kode Voucher:</h3> <!-- <label for="manualVoucher" class="manual-input-label">Masukkan Kode Voucher:</label> --> <div class="manual-input-container"> <input type="text" id="inputReedemVoucher" class="manual-input-field" name="voucherRedeem" placeholder="Masukkan kode voucher Anda" /> <button class="manual-input-button" id="redeemVoucher">Tukar</button> </div> </div> <button class="apply-button" id="applyVoucher" style="display: none;" disabled> Pakai Voucher </button> </div> </div> <style> /* Modal Container */ .apply-voucher-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; } .apply-voucher-modal-content { position: fixed; bottom: 0; left: 0; width: 100%; background-color: white; border-radius: 16px 16px 0 0; padding: 20px; box-sizing: border-box; animation: slideUp 0.3s ease-out; } @keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } } /* Modal Header */ .apply-voucher-modal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; } .apply-voucher-modal-title { font-size: 18px; font-weight: bold; color: #333; } .apply-voucher-close-button { background: none; border: none; font-size: 24px; color: #999; cursor: pointer; padding: 0; } .voucher-list { max-height: 60vh; overflow-y: auto; padding: 0 4px; } /* Voucher Card */ .voucher-card { display: flex; align-items: center; padding: 16px; margin-bottom: 12px; border: 1px solid #7A4397; border-radius: 8px; background-color: #fff; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; } .voucher-card:hover { /* transform: translateY(-2px); */ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .voucher-card.selected { background-color: #f8f1ff; border-color: #7A4397; } .voucher-info { flex-grow: 1; } .voucher-name { font-size: 14px; color: #333; margin-bottom: 4px; } .voucher-value { font-size: 18px; font-weight: bold; color: #7A4397; } .radio-button { width: 20px; height: 20px; border: 2px solid #7A4397; border-radius: 50%; margin-left: 12px; position: relative; } .selected .radio-button::after { content: ''; position: absolute; width: 12px; height: 12px; background-color: #7A4397; border-radius: 50%; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Apply Button */ .apply-button { width: 100%; padding: 16px; background-color: #7A4397; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; margin-top: 16px; cursor: pointer; transition: background-color 0.2s; } .apply-button:disabled { background-color: #ccc; cursor: not-allowed; } /* Empty State */ .empty-state { text-align: center; padding: 32px 16px; color: #666; } .empty-state-icon { font-size: 48px; margin-bottom: 16px; } /* Manual Input Section */ .manual-input { padding: 0; margin-bottom: 48px; box-sizing: border-box; border-top: 1px solid #eee; } .manual-input-label { display: block; font-size: 14px; color: #333; margin-bottom: 8px; } .manual-input-container { display: flex; gap: 8px; } .manual-input-field { flex: 1; padding: 12px; font-size: 14px; border: 1px solid #7A4397; border-radius: 8px; box-sizing: border-box; } .manual-input-button { padding: 12px 16px; background-color: #7A4397; color: white; border: none; border-radius: 8px; font-size: 14px; cursor: pointer; box-sizing: border-box; transition: background-color 0.2s; } .manual-input-button:hover { background-color: #68287A; } .manual-input-button:disabled { background-color: #ccc; cursor: not-allowed; } /* Hide Empty State when there are vouchers */ .voucher-list:not(:empty) #emptyState { display: none; } /* Responsive adjustments */ @media (min-width: 768px) { .modal-content { width: 480px; left: 50%; transform: translateX(-50%); border-radius: 16px; margin: 32px 0; top: 50%; transform: translate(-50%, -50%); } @keyframes slideUp { from { transform: translate(-50%, 100%); } to { transform: translate(-50%, -50%); } } } </style> <div id="loading-modal" class="loading-modal" style="display: none;"> <div class="loading-content"> <div class="spinner"></div> <p>Loading, please wait...</p> </div> </div> </main> <script> // CSRF const CSRF_TOKEN = "<?= $this->security->get_csrf_token_name(); ?>"; const CSRF_HASH = "<?= $this->security->get_csrf_hash(); ?>"; const notyf = new Notyf({ duration: 3500, position: { x: 'right', y: 'top' }, types: [{ type: 'success', background: '#7A4397', icon: { className: 'fas fa-check', tagName: 'i', color: 'white' } }, { type: 'error', background: '#dc3545', icon: { className: 'fas fa-times', tagName: 'i', color: 'white' } } ] }); const BASE_URLS = { SHIPPING: "<?= base_url('shipping/'); ?>", CALCULATE_SHIPPING: "<?= base_url('shipping/calculate_shipping_fee'); ?>", SET_VOUCHER: "<?= base_url('shipping/set_voucher') ?>", INSERT_ORDER: "<?= base_url('shipping/insert_order_data') ?>", INSERT_ORDER_DETAIL: "<?= base_url('shipping/create_order_detail') ?>", PAYMENT_PROCESSING: "<?= base_url('payment/process') ?>", DELETE_ORDER: "<?= base_url('shipping/delete_order_data') ?>", GET_AVAILABLE_SHIPPING_METHODS: "<?= base_url('shipping/get_available_shipping_methods') ?>", ADD_ADDRESS: "<?= base_url('account/shipping') ?>", }; const BUTTONS = { CHANGE_ADDRESS: document.getElementById('btn-change-address'), ADDRESS_SELECTION_CLOSES: document.getElementById('close-address-modal'), ADDRESS_SELECTION_CANCEL: document.getElementById('cancel-address-selection'), ADDRESS_SELECTION_CONFIRM: document.getElementById('confirm-address-selection'), ADDRESS_SELECTION_ADD: document.getElementById('add-address-selection'), ADD_ADDRESS: document.getElementById('btn-add-address'), }; const OPTIONS = { ADDRESS_SELECTION: document.querySelectorAll('.address-option'), } const MODALS = { CHANGE_ADDRESS: document.getElementById('address-modal'), loadingModal: document.getElementById('loading-modal'), ADDRESS_SELECTION: document.getElementById('address-selection-modal'), ADDRESS_CARD: document.getElementById('selected-address-card') }; // Change Address Form const CHANGE_ADDRESS_FORM = { form: document.getElementById('address-form'), province: document.getElementById('ca_province'), district: document.getElementById('ca_district'), subdistrict: document.getElementById('ca_subdistrict'), } const customerAddresses = <?= json_encode($addresses) ?>; // Initial Shipping Data let currentAddressData = <?= !empty($addresses) && !empty($selected_address) ? json_encode($selected_address) : 'null' ?>; const SHIPPING_DATA = { shippingMethodInputs: document.querySelectorAll('input[name^="shipping_method"]'), shippingFeeElement: document.getElementById('shipping-fee'), originalShippingFeeElement: document.getElementById('original-shipping-fee'), recipient_name: currentAddressData ? currentAddressData.recipient_name || '' : '', recipient_phone: currentAddressData ? currentAddressData.phone || '' : '', notes: currentAddressData ? currentAddressData.notes || '' : '', province: currentAddressData ? currentAddressData.province || '' : '', city: currentAddressData ? currentAddressData.city || '' : '', district: currentAddressData ? currentAddressData.district || '' : '', subdistrict: currentAddressData ? currentAddressData.subdistrict || '' : '', district_id: currentAddressData ? currentAddressData.rajaongkir_district_id || 0 : 0, subdistrict_id: currentAddressData ? currentAddressData.rajaongkir_subdistrict_id || 0 : 0, postcode: currentAddressData ? currentAddressData.postal_code || '' : '', full_address: currentAddressData ? currentAddressData.address || '' : '', address_id: currentAddressData ? currentAddressData.id : null, totalShippingFee: 0, originalShippingFee: 0, shippingDetails: '', isFreeShipping: false, discountApplied: 0, } // Shipping Method const SHIPPING_METHODS = { input: document.querySelectorAll('input[name^="shipping_method"]') } // Insurance const SHIPPING_INSURANCE = { insuranceCheckbox: document.querySelector('input[name="shipping_insurance"]'), insuranceSection: document.getElementById('insurance-section'), insuranceFeeElement: document.getElementById('insurance-fee'), insuranceFee: 0 } // Shipping notes & gift const SHIPPING_NOTES = { giftCheckbox: document.getElementById('is_gift'), giftForm: document.getElementById('gift_form'), giftPhone: document.getElementById('gift_phone'), characterCount: document.querySelector('.character-count'), receiverName: document.getElementById('gift_recipient'), receiverPhone: document.getElementById('gift_phone'), notesInput: document.querySelector('.checkout-notes-input'), notesCount: document.querySelector('.checkout-notes-count'), } // Redeem Point const POINT_ELEMENTS = { pointSection: document.getElementById('point-section'), pointRedeemedElement: document.getElementById('point-redeem'), redeemButton: document.getElementById('redeem-button'), pointInput: document.getElementById('redeem-points'), currentPoints: 0, pointEarned: 0, pointRedeemed: 0, feedbackMessage: document.getElementById('point-redeem-feedback'), discountPreview: document.getElementById('discount-preview'), discountAmount: document.getElementById('discount-amount'), currentPointsDisplay: document.getElementById('current-points-display'), quickSelectButtons: document.querySelectorAll('.quick-select-btn'), pointValue: 100, minimumRedeem: 1, } // Voucher const VOUCHER_ELEMENTS = { selectedVoucher: null, isVoucherRedeemed: false, voucherRedeemedValue: 0, voucherRedeemedName: '', voucherRedeemedCode: '', voucherRedeemedType: '', voucherRedeemedAmount: 0, voucherDiscount: 0, voucherRedeemedSection: document.getElementById('voucher-redeemed-section'), voucherNameElement: document.getElementById('voucher-redeem-name'), voucherValueElement: document.getElementById('voucher-redeem-value'), modal: document.getElementById('voucherModal'), showModalButton: document.getElementById('showVoucherModal'), closeButton: document.querySelector('.apply-voucher-close-button'), voucherList: document.getElementById('voucherList'), applyButton: document.getElementById('applyVoucher'), redeemVoucherButton: document.getElementById('redeemVoucher'), redeemVoucherInput: document.getElementById('inputReedemVoucher') } // Payment method const PAYMENT_METHOD_ELEMENTS = { payButton: document.querySelector('.checkout-pay-btn'), paymentOptions: document.querySelectorAll('.checkout-payment-option input[type="radio"]'), paypalInformation: document.getElementById('paypalInformation'), selectedPaymentMethod: 'bank transfer BCA', } // Discount Summary const DISCOUNT_SUMMARY_ELEMENTS = { productDiscount: 0, voucherDiscount: 0, shippingDiscount: 0, totalDiscount: 0, totalDiscountDisplay: document.getElementById('total-discount'), } // Total Price const TOTAL_PRICE_SUMMARY = { subtotal: 0, totalProductDiscount: 0, shippingFee: 0, shippingFeeDiscount: 0, totalPrice: 0, totalSavings: 0 } let currentShippingMethods = []; let selectedShippingMethod = null; document.addEventListener('DOMContentLoaded', async () => { try { const initialShippingMethods = <?= json_encode($all_shipping_methods) ?>; const initialSelectedMethod = <?= json_encode($initial_shipping_method) ?>; // Set global variables currentShippingMethods = initialShippingMethods || []; selectedShippingMethod = initialSelectedMethod; // Setup handlers setupAddressSelectionModal(); setupShippingMethodHandler(); // Set initial shipping fee display if (selectedShippingMethod) { updateShippingFeeDisplay(selectedShippingMethod); } // Setup other handlers setupShippingNotesHandler(); setupInsuranceHandler(); setupPaymentMethodsHandler(); setupPaymentButtonHandler(); setupPointsHandler(); setupVouchersHandler(); updateTotalPrice(); toggleDiscount(); } catch (error) { console.error('Error initializing shipping data:', error); } }); // ======== Shipping Address ========= const normalizeShippingData = (shippingFeeData) => { if (Array.isArray(shippingFeeData)) { return shippingFeeData[0] || {}; } // Jika sudah object, return as is return shippingFeeData; }; const setupAddressSelectionModal = () => { if (BUTTONS.ADD_ADDRESS) { BUTTONS.ADD_ADDRESS.addEventListener('click', () => { window.location.href = BASE_URLS.ADD_ADDRESS; }); } if (BUTTONS.ADDRESS_SELECTION_ADD) { BUTTONS.ADDRESS_SELECTION_ADD.addEventListener('click', () => { window.location.href = BASE_URLS.ADD_ADDRESS; }); } if (BUTTONS.CHANGE_ADDRESS) { BUTTONS.CHANGE_ADDRESS.addEventListener('click', () => { MODALS.ADDRESS_SELECTION.style.display = 'flex'; if (typeof feather !== 'undefined') { feather.replace(); } }); } [BUTTONS.ADDRESS_SELECTION_CLOSES, BUTTONS.ADDRESS_SELECTION_CANCEL].forEach(btn => { if (btn) { btn.addEventListener('click', function() { MODALS.ADDRESS_SELECTION.style.display = 'none'; }); } }); // Improved: Make entire address card clickable, not just radio button OPTIONS.ADDRESS_SELECTION.forEach(option => { option.addEventListener('click', function(e) { // Prevent event if clicking directly on radio button (to avoid double trigger) if (e.target.type === 'radio') { return; } const radio = this.querySelector('input[type="radio"]'); if (radio) { radio.checked = true; // Trigger change event for any other listeners radio.dispatchEvent(new Event('change', { bubbles: true })); } console.log('clicked'); // Visual feedback - update all options updateAddressOptionVisuals(this); }); // Also handle direct radio button clicks for visual feedback const radio = option.querySelector('input[type="radio"]'); if (radio) { radio.addEventListener('change', function() { if (this.checked) { updateAddressOptionVisuals(option); } }); } // // Add hover effect for better UX // option.addEventListener('mouseenter', function() { // if (!this.querySelector('input[type="radio"]').checked) { // this.style.borderColor = 'var(--primary-color)'; // this.style.opacity = '0.8'; // } // }); // option.addEventListener('mouseleave', function() { // if (!this.querySelector('input[type="radio"]').checked) { // this.style.borderColor = '#ddd'; // this.style.opacity = '1'; // } // }); const checkedOption = document.querySelector('.address-option input[type="radio"]:checked'); if (checkedOption) { updateAddressOptionVisuals(checkedOption.closest('.address-option')); } }); // Helper function to update visual feedback function updateAddressOptionVisuals(selectedOption) { OPTIONS.ADDRESS_SELECTION.forEach(opt => { opt.classList.remove('selected'); }); selectedOption.classList.add('selected'); BUTTONS.ADDRESS_SELECTION_CONFIRM.disabled = false; } if (BUTTONS.ADDRESS_SELECTION_CONFIRM) { BUTTONS.ADDRESS_SELECTION_CONFIRM.addEventListener('click', async function() { const selectedRadio = document.querySelector('input[name="selected_address"]:checked'); if (!selectedRadio) { alert('Silakan pilih alamat terlebih dahulu'); return; } const selectedAddressId = selectedRadio.value; selectedAddressData = customerAddresses.find(addr => addr.id == selectedAddressId); if (!selectedAddressData) { console.error('Address data not found for ID:', selectedAddressId); return; } MODALS.loadingModal.style.display = 'flex'; try { // 1. Update address card updateAddressCard(selectedAddressData); // 2. Fetch shipping methods untuk alamat baru const newShippingMethods = await fetchShippingMethods(selectedAddressData); // 3. Update global variables currentShippingMethods = newShippingMethods || []; selectedShippingMethod = currentShippingMethods.length > 0 ? currentShippingMethods[0] : null; // 4. Update shipping methods UI updateShippingMethodsUI(currentShippingMethods); // 5. Update SHIPPING_DATA dengan address data updateShippingData(selectedAddressData, currentShippingMethods); // 6. Update shipping fee display dengan method pertama sebagai default if (selectedShippingMethod) { updateShippingFeeDisplay(selectedShippingMethod); } else { updateShippingFeeDisplay(null); } // 7. Close modal MODALS.ADDRESS_SELECTION.style.display = 'none'; } catch (error) { console.error('Error updating address:', error); alert('Terjadi kesalahan saat memperbarui alamat. Silakan coba lagi.'); } finally { MODALS.loadingModal.style.display = 'none'; updateTotalPrice(); } }); } } function updateAddressCard(addressData) { const addressCard = document.getElementById('selected-address-card'); if (!addressCard) return; const labelText = addressCard.querySelector('.label-text'); const defaultBadge = addressCard.querySelector('.default-badge'); if (labelText) { labelText.textContent = addressData.label; } // Handle default badge if (addressData.is_default == 1) { if (!defaultBadge) { const labelBadge = addressCard.querySelector('.address-label-badge'); const badge = document.createElement('span'); badge.className = 'default-badge'; badge.textContent = 'Utama'; labelBadge.appendChild(badge); } } else { if (defaultBadge) { defaultBadge.remove(); } } // Update recipient info const recipientName = addressCard.querySelector('.recipient-name'); const recipientPhone = addressCard.querySelector('.recipient-phone'); if (recipientName) { recipientName.textContent = addressData.recipient_name; } if (recipientPhone) { recipientPhone.textContent = `(${addressData.phone})`; } // Update address details const fullAddress = addressCard.querySelector('.full-address'); if (fullAddress) { let addressHTML = addressData.address; if (addressData.notes) { addressHTML += `<br><small class="address-notes">Catatan: ${addressData.notes}</small>`; } fullAddress.innerHTML = addressHTML; } // Update button data attribute const changeAddressBtn = addressCard.querySelector('#btn-change-address'); if (changeAddressBtn) { changeAddressBtn.setAttribute('data-current-id', addressData.id); } } // Fungsi untuk fetch shipping methods async function fetchShippingMethods(addressData) { try { const formData = new FormData(); formData.append('address_id', addressData.id || 0); formData.append('destination_latitude', addressData.latitude); formData.append('destination_longitude', addressData.longitude); formData.append('destination_postal_code', addressData.postal_code); formData.append(CSRF_TOKEN, CSRF_HASH); const response = await fetch(BASE_URLS.GET_AVAILABLE_SHIPPING_METHODS, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: formData }); if (!response.ok) { throw new Error('Network response was not ok'); } if (!response.ok) { if (response.status === 403) { throw new Error('Access forbidden. Please refresh the page and try again.'); } else if (response.status === 401) { throw new Error('Session expired. Please login again.'); } else { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } } const result = await response.json(); if (result.success === false) { throw new Error(result.error || 'Failed to fetch shipping methods'); } return result.data || result; } catch (error) { console.error('Error fetching shipping methods:', error); throw error; } } // Display Shipping Methods function updateShippingFeeDisplay(shippingMethodData) { if (!shippingMethodData) { // Handle case when no shipping available PAYMENT_METHOD_ELEMENTS.payButton.disabled = true; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = 'Layanan tidak tersedia'; PAYMENT_METHOD_ELEMENTS.payButton.style.backgroundColor = '#ccc'; PAYMENT_METHOD_ELEMENTS.payButton.style.cursor = 'not-allowed'; PAYMENT_METHOD_ELEMENTS.payButton.style.opacity = '0.9'; // Reset shipping data SHIPPING_DATA.totalShippingFee = 0; SHIPPING_DATA.originalShippingFee = 0; SHIPPING_DATA.isFreeShipping = false; SHIPPING_DATA.discountApplied = 0; return; } // Update SHIPPING_DATA dengan data method yang dipilih SHIPPING_DATA.totalShippingFee = shippingMethodData.final_price; SHIPPING_DATA.originalShippingFee = shippingMethodData.original_price; SHIPPING_DATA.isFreeShipping = shippingMethodData.is_free; SHIPPING_DATA.discountApplied = shippingMethodData.discount_amount; // Update display elements if (shippingMethodData.is_free) { SHIPPING_DATA.originalShippingFeeElement.textContent = `IDR ${shippingMethodData.original_price.toLocaleString('id-ID')}`; SHIPPING_DATA.shippingFeeElement.textContent = `-IDR ${shippingMethodData.original_price.toLocaleString('id-ID')}`; } else { SHIPPING_DATA.originalShippingFeeElement.textContent = `IDR ${shippingMethodData.original_price.toLocaleString('id-ID')}`; SHIPPING_DATA.shippingFeeElement.textContent = `-IDR ${shippingMethodData.discount_amount.toLocaleString('id-ID')}`; } // Update hidden actual shipping fee input let actualShippingFeeElement = document.getElementById('actual-shipping-fee'); if (!actualShippingFeeElement) { actualShippingFeeElement = document.createElement('input'); actualShippingFeeElement.type = 'hidden'; actualShippingFeeElement.id = 'actual-shipping-fee'; actualShippingFeeElement.name = 'actual_shipping_fee'; document.body.appendChild(actualShippingFeeElement); } actualShippingFeeElement.value = shippingMethodData.final_price; // Add hidden input for selected shipping method ID let selectedMethodElement = document.getElementById('selected-shipping-method'); if (!selectedMethodElement) { selectedMethodElement = document.createElement('input'); selectedMethodElement.type = 'hidden'; selectedMethodElement.id = 'selected-shipping-method'; selectedMethodElement.name = 'selected_shipping_method'; document.body.appendChild(selectedMethodElement); } selectedMethodElement.value = shippingMethodData.id; // Enable pay button PAYMENT_METHOD_ELEMENTS.payButton.disabled = false; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = 'Bayar Sekarang'; PAYMENT_METHOD_ELEMENTS.payButton.style.backgroundColor = ''; PAYMENT_METHOD_ELEMENTS.payButton.style.cursor = 'pointer'; PAYMENT_METHOD_ELEMENTS.payButton.style.opacity = '1'; } // Helper function untuk format number function formatNumber(number) { return parseInt(number).toLocaleString('id-ID'); } // Helper function untuk capitalize words function capitalizeWords(str) { return str.replace(/\w\S*/g, (txt) => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); } // Fungsi untuk update SHIPPING_DATA function updateShippingData(addressData, shippingMethods = null) { // Update currentAddressData currentAddressData = addressData; // Update address-related SHIPPING_DATA SHIPPING_DATA.recipient_name = addressData.recipient_name || ''; SHIPPING_DATA.recipient_phone = addressData.phone || ''; SHIPPING_DATA.notes = addressData.notes || ''; SHIPPING_DATA.province = addressData.province || ''; SHIPPING_DATA.city = addressData.city || ''; SHIPPING_DATA.district = addressData.district || ''; SHIPPING_DATA.subdistrict = addressData.subdistrict || ''; SHIPPING_DATA.district_id = addressData.rajaongkir_district_id || 0; SHIPPING_DATA.subdistrict_id = addressData.rajaongkir_subdistrict_id || 0; SHIPPING_DATA.postcode = addressData.postal_code || ''; SHIPPING_DATA.full_address = addressData.address || ''; SHIPPING_DATA.address_id = addressData.id || null; // Update shipping methods data dan variabel global if (shippingMethods && shippingMethods.length > 0) { // Update variabel global currentShippingMethods = shippingMethods; selectedShippingMethod = shippingMethods[0]; // Set yang pertama sebagai default // Update SHIPPING_DATA dengan method pertama const firstShipping = shippingMethods[0]; SHIPPING_DATA.totalShippingFee = firstShipping.final_price || 0; SHIPPING_DATA.originalShippingFee = firstShipping.original_price || 0; SHIPPING_DATA.isFreeShipping = firstShipping.is_free || false; SHIPPING_DATA.discountApplied = firstShipping.discount_amount || 0; // Update display elements untuk shipping fee if (SHIPPING_DATA.shippingFeeElement) { if (firstShipping.is_free) { SHIPPING_DATA.shippingFeeElement.textContent = `-IDR ${firstShipping.original_price.toLocaleString('id-ID')}`; } else { SHIPPING_DATA.shippingFeeElement.textContent = `-IDR ${firstShipping.discount_amount.toLocaleString('id-ID')}`; } } if (SHIPPING_DATA.originalShippingFeeElement) { SHIPPING_DATA.originalShippingFeeElement.textContent = `IDR ${firstShipping.original_price.toLocaleString('id-ID')}`; } // Update hidden inputs updateHiddenShippingInputs(firstShipping); } else { // Reset shipping data jika tidak ada methods tersedia currentShippingMethods = []; selectedShippingMethod = null; SHIPPING_DATA.totalShippingFee = 0; SHIPPING_DATA.originalShippingFee = 0; SHIPPING_DATA.isFreeShipping = false; SHIPPING_DATA.discountApplied = 0; // Clear display elements if (SHIPPING_DATA.shippingFeeElement) { SHIPPING_DATA.shippingFeeElement.textContent = 'IDR 0'; } if (SHIPPING_DATA.originalShippingFeeElement) { SHIPPING_DATA.originalShippingFeeElement.textContent = 'IDR 0'; } // Clear hidden inputs clearHiddenShippingInputs(); } } // ======== End of Shipping Address ========= // ======== Shipping Method ========= const checkInitialShippingFee = () => { MODALS.loadingModal.style.display = 'flex'; try { const shippingFeeData = <?= json_encode($initial_shipping_method) ?>; updateShippingFeeDisplay(shippingFeeData); } catch (error) { console.error('Error calculating shipping fee:', error); PAYMENT_METHOD_ELEMENTS.payButton.disabled = true; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = 'Layanan tidak tersedia'; PAYMENT_METHOD_ELEMENTS.payButton.style.backgroundColor = '#ccc'; PAYMENT_METHOD_ELEMENTS.payButton.style.cursor = 'not-allowed'; PAYMENT_METHOD_ELEMENTS.payButton.style.opacity = '0.9'; } finally { MODALS.loadingModal.style.display = 'none'; updateTotalPrice(); } } function setupShippingMethodHandler() { const shippingContainer = document.querySelector('.shipping-method-options'); if (!shippingContainer) return; // Remove existing listeners const existingHandler = shippingContainer._shippingHandler; if (existingHandler) { shippingContainer.removeEventListener('change', existingHandler); } const shippingHandler = function(event) { if (!event.target.matches('input[name^="shipping_method"]')) return; const selectedMethodId = event.target.value; // Cari method yang dipilih dari data yang sudah ada const selectedMethod = currentShippingMethods.find(method => method.id === selectedMethodId); if (selectedMethod) { selectedShippingMethod = selectedMethod; updateShippingFeeDisplay(selectedMethod); updateTotalPrice(); } else { console.error('Shipping method not found:', selectedMethodId); } }; shippingContainer._shippingHandler = shippingHandler; shippingContainer.addEventListener('change', shippingHandler); } // Fungsi untuk update shipping methods UI function updateShippingMethodsUI(shippingMethods) { const shippingOptions = document.querySelector('.shipping-method-options'); if (!shippingOptions) return; // Clear existing options shippingOptions.innerHTML = ''; if (!shippingMethods || shippingMethods.length === 0) { shippingOptions.innerHTML = ` <div class="no-shipping-available"> <span>Tidak ada metode pengiriman yang tersedia untuk alamat ini.</span> </div> `; return; } // Generate new shipping options shippingMethods.forEach((shippingMethod, index) => { const isChecked = index === 0; let priceHTML = ''; if (shippingMethod.is_free) { priceHTML = ` <div class="price-row"> ${shippingMethod.original_price > 0 ? `<span class="original-price">IDR ${formatNumber(shippingMethod.original_price)}</span>` : '' } <span class="free-shipping">GRATIS ONGKIR</span> </div> `; } else if (shippingMethod.discount_amount > 0) { priceHTML = ` <div class="price-row"> <span class="shipping-price">IDR ${formatNumber(shippingMethod.final_price)}</span> <span class="original-price">IDR ${formatNumber(shippingMethod.original_price)}</span> </div> <div class="discount-info">Hemat IDR ${formatNumber(shippingMethod.discount_amount)}</div> `; } else { priceHTML = `<span class="shipping-price">IDR ${formatNumber(shippingMethod.final_price)}</span>`; } const optionHTML = ` <div class="shipping-method-option"> <input type="radio" name="shipping_method" id="shipping_${shippingMethod.id}" value="${shippingMethod.id}" ${isChecked ? 'checked' : ''}> <label for="shipping_${shippingMethod.id}" class="shipping-method-label"> <div class="method-info"> <div class="method-details"> <div class="method-header"> <img src="${shippingMethod.icon_url}" alt="${shippingMethod.carrier}" class="method-icon"> <div class="method-name">${capitalizeWords(shippingMethod.name)}</div> </div> <div class="etd-info">Estimasi tiba ${shippingMethod.etd_info.formatted_text}</div> </div> <div class="method-price"> <div class="price-group"> ${priceHTML} </div> </div> </div> </label> </div> `; shippingOptions.insertAdjacentHTML('beforeend', optionHTML); }); } // ======== End of Shipping Method ========= // ======== Insurance ========= const setupInsuranceHandler = () => { if (SHIPPING_INSURANCE.insuranceCheckbox) { SHIPPING_INSURANCE.insuranceCheckbox.addEventListener('change', function() { if (SHIPPING_INSURANCE.insuranceCheckbox.checked) { SHIPPING_INSURANCE.insuranceSection.style.display = 'flex'; } else { SHIPPING_INSURANCE.insuranceSection.style.display = 'none'; } updateTotalPrice(); }); } } // ======== End of Insurance ========= // ======== Notes ========= const setupShippingNotesHandler = () => { if (SHIPPING_NOTES.notesInput && SHIPPING_NOTES.notesCount) { SHIPPING_NOTES.notesInput.addEventListener('input', function() { const remaining = this.value.length; SHIPPING_NOTES.notesCount.textContent = `${remaining}/200`; }); } SHIPPING_NOTES.giftCheckbox.addEventListener('change', function() { if (this.checked) { SHIPPING_NOTES.giftForm.style.display = 'block'; setTimeout(() => { SHIPPING_NOTES.giftForm.classList.add('active'); }, 10); } else { SHIPPING_NOTES.giftForm.classList.remove('active'); setTimeout(() => { SHIPPING_NOTES.giftForm.style.display = 'none'; }, 300); // Reset form const inputs = SHIPPING_NOTES.giftForm.querySelectorAll('input, textarea'); inputs.forEach(input => input.value = ''); } }); SHIPPING_NOTES.giftPhone?.addEventListener('input', function(e) { // Remove non-numeric characters this.value = this.value.replace(/\D/g, ''); // Limit length if (this.value.length > 13) { this.value = this.value.slice(0, 13); } }); } // ======== End of Notes ========= // ======== Payment Methods ========= const setupPaymentMethodsHandler = () => { PAYMENT_METHOD_ELEMENTS.paymentOptions.forEach(option => { option.addEventListener('change', function() { // Remove active class from all options PAYMENT_METHOD_ELEMENTS.paymentOptions.forEach(opt => { opt.closest('.checkout-payment-option').classList.remove('active'); }); // Add active class to selected option if (this.checked) { this.closest('.checkout-payment-option').classList.add('active'); if (this.value === 'Paypal') { PAYMENT_METHOD_ELEMENTS.paypalInformation.style.display = 'block'; } else { PAYMENT_METHOD_ELEMENTS.paypalInformation.style.display = 'none'; } } }); }); PAYMENT_METHOD_ELEMENTS.paymentOptions.forEach((option) => { option.addEventListener('change', function() { PAYMENT_METHOD_ELEMENTS.selectedPaymentMethod = document.querySelector('input[name="payment_method"]:checked')?.value; }); }); } const setupPaymentButtonHandler = () => { PAYMENT_METHOD_ELEMENTS.payButton.addEventListener('click', async function(e) { e.preventDefault(); await handlePaymentSubmission(); PAYMENT_METHOD_ELEMENTS.payButton.disabled = false; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = ` Bayar Sekarang `; }) } // ======== End of Payment Methods ========= // ======== Point Redeem ========= const setupPointsHandler = () => { POINT_ELEMENTS.pointEarned = <?= $this->cart->total() ?> / 10000; POINT_ELEMENTS.currentPoints = parseInt(POINT_ELEMENTS.pointInput.getAttribute('max')); POINT_ELEMENTS.currentPointsDisplay.textContent = POINT_ELEMENTS.currentPoints.toLocaleString('id-ID'); POINT_ELEMENTS.pointInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') { redeemButton.click(); } }); POINT_ELEMENTS.pointInput.addEventListener('input', function() { const value = parseInt(this.value) || 0; // Reset classes this.classList.remove('error', 'success'); // Hide feedback hideFeedback(); if (value > 0) { // Show discount preview const discount = value * POINT_ELEMENTS.pointValue; POINT_ELEMENTS.discountAmount.textContent = `IDR ${discount.toLocaleString('id-ID')}`; POINT_ELEMENTS.discountPreview.style.display = 'flex'; // Validation if (value < POINT_ELEMENTS.minimumRedeem) { this.classList.add('error'); showFeedback(`Minimum penukaran adalah ${POINT_ELEMENTS.minimumRedeem} poin`, 'error'); } else if (value > POINT_ELEMENTS.currentPoints) { this.classList.add('error'); showFeedback('Poin yang dimasukkan melebihi poin yang kamu miliki', 'error'); } else { this.classList.add('success'); } POINT_ELEMENTS.pointRedeemed = value; } else { POINT_ELEMENTS.discountPreview.style.display = 'none'; } }); // Quick select buttons POINT_ELEMENTS.quickSelectButtons.forEach(button => { const buttonPoints = parseInt(button.dataset.points); if (buttonPoints > POINT_ELEMENTS.currentPoints) { button.style.display = 'none'; } else { button.addEventListener('click', function() { const points = parseInt(this.dataset.points); POINT_ELEMENTS.quickSelectButtons.forEach(btn => btn.classList.remove('active')); this.classList.add('active'); POINT_ELEMENTS.pointInput.value = points; POINT_ELEMENTS.pointInput.dispatchEvent(new Event('input')); }); } }); // Main redeem function POINT_ELEMENTS.redeemButton.addEventListener('click', function() { const pointsToRedeem = parseInt(POINT_ELEMENTS.pointInput.value) || 0; // Validation if (pointsToRedeem <= 0) { showFeedback('Silakan masukkan jumlah poin yang ingin ditukar', 'error'); return; } if (pointsToRedeem < POINT_ELEMENTS.minimumRedeem) { showFeedback(`Minimum penukaran adalah ${POINT_ELEMENTS.minimumRedeem} poin`, 'error'); return; } if (pointsToRedeem > POINT_ELEMENTS.currentPoints) { showFeedback('Poin yang dimasukkan melebihi poin yang Anda miliki', 'error'); return; } // Show loading state setLoadingState(true); setTimeout(() => { const pointDiscount = pointsToRedeem * POINT_ELEMENTS.pointValue; // Show success message showFeedback(`Berhasil menukar ${pointsToRedeem} poin, kamu hemat IDR ${pointDiscount.toLocaleString('id-ID')}`, 'success'); // Clear input POINT_ELEMENTS.pointInput.value = ''; POINT_ELEMENTS.discountPreview.style.display = 'none'; // Remove quick select active state POINT_ELEMENTS.quickSelectButtons.forEach(btn => btn.classList.remove('active')); window.pointDiscount = pointDiscount; POINT_ELEMENTS.pointSection.style.display = 'flex'; POINT_ELEMENTS.pointRedeemedElement.textContent = `- IDR ${pointDiscount.toLocaleString('id-ID')}`; updateTotalPrice(); setLoadingState(false); }, 1500); }); } // ======== End of Point Redeem ========= // ======== Voucher Redeem ========= const setupVouchersHandler = () => { // Voucher Reedem click listener VOUCHER_ELEMENTS.redeemVoucherButton.addEventListener('click', function(event) { event.preventDefault(); const voucherCode = VOUCHER_ELEMENTS.redeemVoucherInput.value.trim(); if (!voucherCode) { notyf.error('Masukkan kode voucher terlebih dahulu!'); return; } const csrfToken = document.querySelector('.csrf_token').value; MODALS.loadingModal.style.display = 'flex'; document.body.style.overflow = 'auto'; $.ajax({ url: BASE_URLS.SET_VOUCHER, method: 'POST', data: { [document.querySelector('.csrf_token').name]: csrfToken, voucher: voucherCode }, success: function(result) { if (typeof result === 'string') { result = JSON.parse(result); } try { if (result.success) { VOUCHER_ELEMENTS.isVoucherRedeemed = true; VOUCHER_ELEMENTS.voucherRedeemedValue = result.voucher.discount_value; VOUCHER_ELEMENTS.voucherRedeemedName = result.voucher.name; VOUCHER_ELEMENTS.voucherRedeemedCode = result.voucher.code; VOUCHER_ELEMENTS.voucherRedeemedType = result.voucher.discount_type; VOUCHER_ELEMENTS.voucherRedeemedSection.style.display = 'flex'; updateTotalPrice(); updateTotalDiscount(); VOUCHER_ELEMENTS.modal.style.display = 'none'; MODALS.loadingModal.style.display = 'none'; document.body.style.overflow = 'auto'; notyf.success(result.message || 'Voucher berhasil ditukarkan!'); } else { VOUCHER_ELEMENTS.voucherRedeemedValue = 0; VOUCHER_ELEMENTS.voucherRedeemedSection.style.display = 'none'; VOUCHER_ELEMENTS.modal.style.display = 'none'; MODALS.loadingModal.style.display = 'none'; document.body.style.overflow = 'auto'; notyf.error(result.message || 'Terjadi kesalahan saat menukarkan voucher'); } } catch (error) { console.error('Terjadi kesalahan:', error); VOUCHER_ELEMENTS.modal.style.display = 'none'; MODALS.loadingModal.style.display = 'none'; document.body.style.overflow = 'auto'; // Pastikan scroll kembali normal notyf.error('Terjadi kesalahan saat menukarkan voucher'); } }, error: function(xhr, status, error) { console.error('Error redeeming voucher:', error); notyf.error('Terjadi kesalahan saat menukarkan voucher. Silakan coba lagi.'); MODALS.loadingModal.style.display = 'none'; VOUCHER_ELEMENTS.modal.style.display = 'none'; } }); }); if (VOUCHER_ELEMENTS.showModalButton) { VOUCHER_ELEMENTS.showModalButton.addEventListener('click', function(e) { if (this.classList.contains('disabled') || this.getAttribute('aria-disabled') === 'true') { // Jika tidak bisa diklik, tampilkan pesan atau lakukan sesuatu e.preventDefault(); // Mencegah aksi default notyf.error('Voucher tidak dapat digunakan pada produk diskon.'); return; } // Jika tombol aktif, lanjutkan membuka modal e.preventDefault(); VOUCHER_ELEMENTS.modal.style.display = 'block'; document.body.style.overflow = 'hidden'; // Get vouchers from data attribute const vouchers = JSON.parse(this.getAttribute('data-vouchers')); renderVouchers(vouchers); }); } VOUCHER_ELEMENTS.closeButton.addEventListener('click', closeModal); VOUCHER_ELEMENTS.modal.addEventListener('click', function(e) { if (e.target === VOUCHER_ELEMENTS.modal) { closeModal(); } }); VOUCHER_ELEMENTS.redeemVoucherInput.addEventListener('input', function() { const redeemVoucherInput = document.getElementById('inputReedemVoucher'); VOUCHER_ELEMENTS.redeemVoucherButton.disabled = false; VOUCHER_ELEMENTS.applyButton.style.display = 'none'; const cards = document.querySelectorAll('.voucher-card'); cards.forEach(c => c.classList.remove('selected')); VOUCHER_ELEMENTS.selectedVoucher = null; }) // Apply voucher VOUCHER_ELEMENTS.applyButton.addEventListener('click', function() { MODALS.loadingModal.style.display = 'flex'; try { if (VOUCHER_ELEMENTS.selectedVoucher) { closeModal(); VOUCHER_ELEMENTS.voucherRedeemedSection.style.display = 'flex'; VOUCHER_ELEMENTS.showModalButton.querySelector('.voucher-text').textContent = `Applied ${VOUCHER_ELEMENTS.selectedVoucher.name} (-${VOUCHER_ELEMENTS.selectedVoucher.value}%)`; } } catch (error) { console.error('Error calculating shipping fee:', error); } finally { MODALS.loadingModal.style.display = 'none'; updateTotalPrice(); const event = new CustomEvent('voucherSelected', { detail: VOUCHER_ELEMENTS.selectedVoucher }); document.dispatchEvent(event); } }); } // ======== End of Voucher Redeem ========= // ======== Helper ========= async function handlePaymentSubmission() { const loadingText = MODALS.loadingModal.querySelector('p'); let paymentData = null; try { // Tampilkan loading modal MODALS.loadingModal.style.display = 'flex'; PAYMENT_METHOD_ELEMENTS.payButton.disabled = true; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = ` <span class="checkout-pay-icon">⏳</span> Memproses... `; // Ambil data pengiriman const recipientName = SHIPPING_DATA.recipient_name; const address = SHIPPING_DATA.full_address; const post_code = SHIPPING_DATA.postcode; const subdistrict = SHIPPING_DATA.subdistrict; const district = SHIPPING_DATA.district; const province = SHIPPING_DATA.province; const phone = SHIPPING_DATA.recipient_phone; // Validasi untuk memastikan data tidak kosong let errorMessages = []; let missingFields = []; if (!recipientName.trim()) missingFields.push("Nama penerima"); if (!address.trim()) missingFields.push("alamat"); if (!subdistrict.trim()) missingFields.push("kecamatan"); if (!district.trim()) missingFields.push("kabupaten/kota"); if (!province.trim()) missingFields.push("provinsi"); if (!phone.trim()) missingFields.push("nomor telepon"); if (missingFields.length > 0) { errorMessages.push(`${missingFields.join(", ")} masih kosong. Yuk, lengkapi dulu!`); } // Jika ada error, tampilkan pesan dan hentikan proses pembayaran if (errorMessages.length > 0) { errorMessages.forEach(msg => notyf.error(msg)); PAYMENT_METHOD_ELEMENTS.payButton.disabled = false; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`; MODALS.loadingModal.style.display = 'none'; return; } // Update loading status loadingText.textContent = "Menyiapkan pesanan Anda..."; const ordersFormData = { customerId: <?= (int) $id_customer ?>, totalAmount: TOTAL_PRICE_SUMMARY.subtotal, orderDate: "<?= date('Y-m-d H:i:s') ?>", recipientName: SHIPPING_DATA.recipient_name, address: SHIPPING_DATA.full_address, subdistrict: SHIPPING_DATA.subdistrict, district: SHIPPING_DATA.district, province: SHIPPING_DATA.province, postcode: SHIPPING_DATA.postcode, phone: SHIPPING_DATA.recipient_phone, email: "<?= $customer_data->email ?>", first: <?= $customer_data->is_first ?> + 1, country: "Indonesia", shippingFee: SHIPPING_DATA.totalShippingFee, isFreeShipping: SHIPPING_DATA.isFreeShipping, createdBy: "System", customerNote: SHIPPING_NOTES.notesInput.value, giftRecipientName: SHIPPING_NOTES.receiverName.value, giftRecipientPhone: SHIPPING_NOTES.receiverPhone.value, insuranceStatus: SHIPPING_INSURANCE.insuranceCheckbox.checked ? "Yes" : "No", insuranceFee: SHIPPING_INSURANCE.insuranceFee, referral: "<?= !empty($customer_data->refferal) ? $customer_data->refferal : $this->session->userdata('referral') ?>", $source: "<?= $this->session->userdata('initial_visitor_tracking')['source'] ?>", $medium: "<?= $this->session->userdata('initial_visitor_tracking')['medium'] ?>", $campaign: "<?= $this->session->userdata('initial_visitor_tracking')['campaign'] ?>", orderLanguage: "<?= $this->session->userdata('site_lang') == 'english' ? 'english' : 'indonesian' ?>", redeemedVoucherCode: VOUCHER_ELEMENTS.voucherRedeemedCode, voucherRedeemedValue: VOUCHER_ELEMENTS.voucherRedeemedValue, voucherRedeemedType: VOUCHER_ELEMENTS.voucherRedeemedType, voucherRedeemedAmount: VOUCHER_ELEMENTS.voucherDiscount, plusPointReward: "<?= !$id_reseller ? $this->cart->total() / 10000 : 0 ?>", minusPointReward: parseInt(POINT_ELEMENTS.pointRedeemed), pointRedeemedAmount: window.pointDiscount ? window.pointDiscount : 0, grandTotalAmount: TOTAL_PRICE_SUMMARY.totalPrice, paymentType: PAYMENT_METHOD_ELEMENTS.selectedPaymentMethod }; // Insert ke table orders loadingText.textContent = "Membuat pesanan..."; const orderData = await makeRequest(BASE_URLS.INSERT_ORDER, ordersFormData); if (!orderData || orderData.error) { throw new Error(orderData.error || 'Gagal membuat pesanan'); } // Persiapkan detail pesanan loadingText.textContent = "Menyiapkan detail produk..."; const orderDetailData = []; const productItems = document.querySelectorAll('.checkout-product-details'); productItems.forEach(item => { const rowId = item.dataset.rowid; const qty = parseInt(item.dataset.qty); const price = parseInt(item.dataset.price); const productId = item.dataset.productId; const detailId = item.dataset.detailId; const productName = item.dataset.productName; const attributeDetailId = item.dataset.attributeDetailId; const variant = item.dataset.attribute; const sku = item.dataset.sku; // Cari metode pengiriman yang dipilih const selectedShipping = document.querySelector('input[name="shipping_method"]:checked'); const shippingMethodId = selectedShipping ? selectedShipping.value : null; if (shippingMethodId) { orderDetailData.push({ orderId: parseInt(orderData.order_id), // ID dari pesanan utama productName: productName, productId: parseInt(productId), detailId: parseInt(detailId), quantity: parseInt(qty), itemPrice: parseInt(price), shippingMethodId: parseInt(shippingMethodId), warehouseId: 1, shippingFee: parseInt(TOTAL_PRICE_SUMMARY.totalShippingFee), attributeDetailId: attributeDetailId, attribute: variant, subtotal: price * qty, sku: sku, }); } else { console.warn(`Tidak ada metode pengiriman dipilih untuk item ${rowId}`); } }); const payload = { orderDetailData: orderDetailData }; // Setup order timer untuk timeout let orderTimeout = setTimeout(() => { loadingText.textContent = "Proses membutuhkan waktu lebih lama dari biasanya..."; }, 10000); try { loadingText.textContent = "Menyimpan detail pesanan..."; const orderDetail = await makeRequest(BASE_URLS.INSERT_ORDER_DETAIL, payload); clearTimeout(orderTimeout); // Periksa apakah ada masalah stok if (!orderDetail.status && orderDetail.insufficient_stock_items && orderDetail.insufficient_stock_items.length > 0) { let stockErrorMessage = "Stok tidak mencukupi untuk produk: "; const itemNames = orderDetail.insufficient_stock_items.map(item => { if (item.available !== undefined) { return `${item.product_name} (diminta: ${item.requested}, tersedia: ${item.available})`; } return item.product_name; }); stockErrorMessage += itemNames.join(", "); notyf.error(stockErrorMessage); // console.error("Insufficient stock error:", orderDetail.insufficient_stock_items); throw new Error(stockErrorMessage); } if (!orderDetail.status) { throw new Error(orderDetail.message || 'Gagal membuat detail pesanan'); } // Setup payment request loadingText.textContent = "Menghubungi gateway pembayaran..."; const paymentFormData = { orderId: parseInt(orderData.order_id), paymentType: PAYMENT_METHOD_ELEMENTS.selectedPaymentMethod }; // Setup payment timer let paymentTimeout = setTimeout(() => { loadingText.textContent = "Menunggu gateway pembayaran merespons..."; }, 8000); const paymentData = await makeRequest(BASE_URLS.PAYMENT_PROCESSING, paymentFormData); clearTimeout(paymentTimeout); if (paymentData.status === 'success' && paymentData.payment_type.toLowerCase() === 'doku') { loadingText.textContent = "Mengarahkan ke halaman pembayaran DOKU..."; const form = document.createElement('form'); form.method = 'POST'; form.action = paymentData.redirect_url; form.id = 'MerchatPaymentPage'; form.name = 'MerchatPaymentPage'; Object.entries(paymentData.response).forEach(([key, value]) => { if (value !== null) { const hiddenField = document.createElement('input'); hiddenField.type = 'hidden'; hiddenField.name = key; hiddenField.id = key; hiddenField.value = value; form.appendChild(hiddenField); } }); document.body.appendChild(form); setTimeout(() => { form.submit(); }, 500); return; } else if (paymentData.status === 'success') { loadingText.textContent = "Mengarahkan ke halaman pembayaran..."; setTimeout(() => { window.location.href = paymentData.redirect_url; }, 500); return; } else { notyf.error('Error: ' + paymentData.message); throw new Error(paymentData.message || 'Gagal memproses pembayaran'); } } catch (orderDetailError) { loadingText.textContent = "Terjadi kesalahan. Membersihkan pesanan..."; try { await makeRequest(BASE_URLS.DELETE_ORDER, { orderId: orderData.order_id }); } catch (deleteError) { console.error("Failed to delete main order:", deleteError); } throw orderDetailError; // Re-throw error asli untuk ditangani di catch utama } } catch (error) { let errorMessage = error.message || 'Terjadi kesalahan saat memproses pembayaran'; notyf.error(errorMessage); console.error('Payment error:', error); MODALS.loadingModal.style.display = 'none'; PAYMENT_METHOD_ELEMENTS.payButton.disabled = false; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`; } finally { if (!paymentData || paymentData.status !== 'success') { MODALS.loadingModal.style.display = 'none'; PAYMENT_METHOD_ELEMENTS.payButton.disabled = false; PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`; } } } // Initialize voucher discount if (!VOUCHER_ELEMENTS.voucherDiscount) { VOUCHER_ELEMENTS.voucherDiscount = 0; } // Utility function for consistent number parsing const parsePrice = (text) => { if (!text) return 0; const cleaned = text.replace(/[^\d.-]/g, '').replace(/\./g, ''); const parsed = parseFloat(cleaned); return isNaN(parsed) ? 0 : Math.abs(parsed); }; const updateTotalDiscount = () => { // Get fresh text content from elements const productDiscountElement = document.getElementById('total-product-discount'); const voucherDiscountElement = document.getElementById('voucher-redeem-value'); const shippingDiscountElement = document.getElementById('shipping-fee'); // Parse discount values DISCOUNT_SUMMARY_ELEMENTS.productDiscount = productDiscountElement ? parsePrice(productDiscountElement.textContent.trim()) : 0; DISCOUNT_SUMMARY_ELEMENTS.voucherDiscount = voucherDiscountElement ? parsePrice(voucherDiscountElement.textContent.trim()) : 0; DISCOUNT_SUMMARY_ELEMENTS.shippingDiscount = shippingDiscountElement ? parsePrice(shippingDiscountElement.textContent.trim()) : 0; // Calculate total discount DISCOUNT_SUMMARY_ELEMENTS.totalDiscount = DISCOUNT_SUMMARY_ELEMENTS.productDiscount + DISCOUNT_SUMMARY_ELEMENTS.voucherDiscount + DISCOUNT_SUMMARY_ELEMENTS.shippingDiscount; // Format and display total discount const formattedTotalDiscount = DISCOUNT_SUMMARY_ELEMENTS.totalDiscount > 0 ? `-IDR ${DISCOUNT_SUMMARY_ELEMENTS.totalDiscount.toLocaleString('id-ID')}` : 'IDR 0'; // Update total discount display if (DISCOUNT_SUMMARY_ELEMENTS.totalDiscountDisplay) { DISCOUNT_SUMMARY_ELEMENTS.totalDiscountDisplay.textContent = formattedTotalDiscount; } }; const updateTotalPrice = () => { // Update discount summary first updateTotalDiscount(); // Get fresh text content from elements const subtotalElement = document.getElementById('subtotal_product'); const totalProductDiscountElement = document.getElementById('total-product-discount'); const shippingFeeElement = document.getElementById('original-shipping-fee'); const shippingFeeDiscountElement = document.getElementById('shipping-fee'); // Parse price values TOTAL_PRICE_SUMMARY.subtotal = subtotalElement ? parsePrice(subtotalElement.textContent.trim()) : 0; TOTAL_PRICE_SUMMARY.totalProductDiscount = totalProductDiscountElement ? parsePrice(totalProductDiscountElement.textContent.trim()) : 0; TOTAL_PRICE_SUMMARY.shippingFee = shippingFeeElement ? parsePrice(shippingFeeElement.textContent.trim()) : 0; TOTAL_PRICE_SUMMARY.shippingFeeDiscount = shippingFeeDiscountElement ? parsePrice(shippingFeeDiscountElement.textContent.trim()) : 0; // Validate subtotal if (TOTAL_PRICE_SUMMARY.subtotal === 0) { console.warn('Subtotal is 0 or invalid'); return; } // Initialize voucher discount if (!VOUCHER_ELEMENTS.voucherDiscount) { VOUCHER_ELEMENTS.voucherDiscount = 0; } // Handle voucher calculations if (VOUCHER_ELEMENTS.selectedVoucher) { VOUCHER_ELEMENTS.voucherNameElement.textContent = `(${VOUCHER_ELEMENTS.selectedVoucher.code})`; if (VOUCHER_ELEMENTS.selectedVoucher.type === "percentage") { VOUCHER_ELEMENTS.voucherDiscount = (TOTAL_PRICE_SUMMARY.subtotal * VOUCHER_ELEMENTS.selectedVoucher.value) / 100; VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.voucherDiscount.toLocaleString('id-ID')}`; } else if (VOUCHER_ELEMENTS.selectedVoucher.type === "amount") { VOUCHER_ELEMENTS.voucherDiscount = VOUCHER_ELEMENTS.selectedVoucher.value; VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.selectedVoucher.value.toLocaleString('id-ID')}`; } // Store voucher redemption data VOUCHER_ELEMENTS.voucherRedeemedValue = VOUCHER_ELEMENTS.selectedVoucher.value; VOUCHER_ELEMENTS.voucherRedeemedType = VOUCHER_ELEMENTS.selectedVoucher.type; VOUCHER_ELEMENTS.voucherRedeemedName = VOUCHER_ELEMENTS.selectedVoucher.name; VOUCHER_ELEMENTS.voucherRedeemedCode = VOUCHER_ELEMENTS.selectedVoucher.code; VOUCHER_ELEMENTS.voucherRedeemedAmount = VOUCHER_ELEMENTS.voucherDiscount; } else if (VOUCHER_ELEMENTS.isVoucherRedeemed) { VOUCHER_ELEMENTS.voucherNameElement.textContent = `(${VOUCHER_ELEMENTS.voucherRedeemedCode})`; if (VOUCHER_ELEMENTS.voucherRedeemedType === "percentage") { VOUCHER_ELEMENTS.voucherDiscount = (TOTAL_PRICE_SUMMARY.subtotal * VOUCHER_ELEMENTS.voucherRedeemedValue) / 100; VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.voucherDiscount.toLocaleString('id-ID')}`; } else if (VOUCHER_ELEMENTS.voucherRedeemedType === "amount") { VOUCHER_ELEMENTS.voucherDiscount = VOUCHER_ELEMENTS.voucherRedeemedValue; VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.voucherRedeemedValue.toLocaleString('id-ID')}`; } } // Calculate total price TOTAL_PRICE_SUMMARY.totalPrice = TOTAL_PRICE_SUMMARY.subtotal + TOTAL_PRICE_SUMMARY.shippingFee - TOTAL_PRICE_SUMMARY.shippingFeeDiscount - VOUCHER_ELEMENTS.voucherDiscount; // Calculate insurance fee SHIPPING_INSURANCE.insuranceFee = Math.ceil((TOTAL_PRICE_SUMMARY.totalPrice * 0.2 / 100) + 5000); const formattedInsuranceFee = `IDR ${SHIPPING_INSURANCE.insuranceFee.toLocaleString('id-ID')}`; const useInsuranceElement = document.getElementById('use-insurance'); if (useInsuranceElement) { useInsuranceElement.textContent = `(${formattedInsuranceFee})`; } // Apply point discount if available if (window.pointDiscount) { TOTAL_PRICE_SUMMARY.totalPrice -= window.pointDiscount; } // Handle insurance checkbox if (SHIPPING_INSURANCE.insuranceCheckbox && SHIPPING_INSURANCE.insuranceCheckbox.checked) { const insuranceFeeElement = document.getElementById('insurance-fee'); if (insuranceFeeElement) { insuranceFeeElement.textContent = formattedInsuranceFee; } if (useInsuranceElement) { useInsuranceElement.textContent = `(${formattedInsuranceFee})`; } TOTAL_PRICE_SUMMARY.totalPrice += SHIPPING_INSURANCE.insuranceFee; } else { if (SHIPPING_INSURANCE.insuranceFeeElement) { SHIPPING_INSURANCE.insuranceFeeElement.textContent = ''; } if (SHIPPING_INSURANCE.insuranceSection) { SHIPPING_INSURANCE.insuranceSection.style.display = 'none'; } } // Update total price display const totalDisplay = document.querySelector('.checkout-summary-total #final_grand_total'); if (totalDisplay) { totalDisplay.textContent = `${TOTAL_PRICE_SUMMARY.totalPrice.toLocaleString('id-ID')}`; } const savingsRow = document.querySelector('.checkout-summary-row.savings-row'); const savingsDisplay = document.getElementById('checkout-savings'); const savingsPointDiscount = window.pointDiscount ?? 0; TOTAL_PRICE_SUMMARY.totalSavings = DISCOUNT_SUMMARY_ELEMENTS.totalDiscount + savingsPointDiscount + VOUCHER_ELEMENTS.voucherDiscount; if (savingsDisplay && savingsRow) { if (TOTAL_PRICE_SUMMARY.totalSavings > 0) { savingsDisplay.textContent = `IDR ${TOTAL_PRICE_SUMMARY.totalSavings.toLocaleString('id-ID')}!`; savingsRow.style.display = 'flex'; } else { savingsRow.style.display = 'none'; } } }; const selectVoucher = (voucher, card) => { // Remove selection from other cards const cards = document.querySelectorAll('.voucher-card'); cards.forEach(c => c.classList.remove('selected')); // Add selection to clicked card card.classList.add('selected'); // Enable apply button VOUCHER_ELEMENTS.applyButton.style.display = 'block'; VOUCHER_ELEMENTS.applyButton.disabled = false; VOUCHER_ELEMENTS.redeemVoucherButton.disabled = true; VOUCHER_ELEMENTS.selectedVoucher = voucher; } // Render vouchers function renderVouchers(vouchers) { if (!vouchers || vouchers.length === 0) { VOUCHER_ELEMENTS.voucherList.innerHTML = ` <div class="empty-state"> <div class="empty-state-icon">📝</div> <p>Belum ada voucher yang tersedia</p> </div> `; return; } VOUCHER_ELEMENTS.voucherList.innerHTML = vouchers.map((voucher, index) => ` <div class="voucher-card" data-index="${index}"> <div class="voucher-info"> <div class="voucher-name">${voucher.name}</div> <div class="voucher-value">Diskon ${voucher.value}%</div> </div> <div class="radio-button"></div> </div> `).join(''); // Add click handlers to voucher cards const voucherCards = VOUCHER_ELEMENTS.voucherList.querySelectorAll('.voucher-card'); voucherCards.forEach(card => { card.addEventListener('click', function() { const index = this.getAttribute('data-index'); selectVoucher(vouchers[index], this); }); }); } function closeModal() { VOUCHER_ELEMENTS.modal.style.display = 'none'; document.body.style.overflow = 'auto'; } const populateSelectOptions = (selectEl, items, valueKey, textKey) => { items.forEach(item => { const option = document.createElement('option'); option.value = item[valueKey]; option.textContent = item[textKey]; selectEl.appendChild(option); }); }; const createFormData = (data) => { const formData = new FormData(); formData.append(CSRF_TOKEN, CSRF_HASH); Object.keys(data).forEach(key => { if (key === 'orderDetailData') { formData.append(key, JSON.stringify(data[key])); } else { formData.append(key, data[key]); } }); return formData; }; const makeRequest = async (url, data) => { const formData = createFormData(data); const response = await fetch(url, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: formData }); return response.json(); }; function toggleDiscount() { const content = document.getElementById('discount-content'); const arrow = document.getElementById('discount-arrow'); if (content.classList.contains('active')) { content.classList.remove('active'); arrow.style.transform = 'rotate(0deg)'; } else { content.classList.add('active'); arrow.style.transform = 'rotate(180deg)'; } } function showTooltip(event) { const tooltip = document.createElement('div'); tooltip.className = 'checkout-tooltip'; tooltip.textContent = event.target.dataset.tooltip; document.body.appendChild(tooltip); const triggerRect = event.target.getBoundingClientRect(); tooltip.style.top = `${triggerRect.bottom + 5}px`; tooltip.style.left = `${triggerRect.left}px`; } function hideTooltip() { const tooltip = document.querySelector('.checkout-tooltip'); if (tooltip) { tooltip.remove(); } } function showFeedback(message, type) { let iconClass = ''; if (type === 'success') { iconClass = 'fa fa-check-circle'; } else if (type === 'error') { iconClass = 'fa fa-times-circle'; } POINT_ELEMENTS.feedbackMessage.innerHTML = `<i class="${iconClass}"></i> ${message}`; POINT_ELEMENTS.feedbackMessage.className = `feedback-message ${type}`; POINT_ELEMENTS.feedbackMessage.style.display = 'flex'; } function hideFeedback() { POINT_ELEMENTS.feedbackMessage.style.display = 'none'; } function setLoadingState(loading) { if (loading) { POINT_ELEMENTS.redeemButton.classList.add('loading'); POINT_ELEMENTS.redeemButton.disabled = true; POINT_ELEMENTS.redeemButton.textContent = ''; } else { POINT_ELEMENTS.redeemButton.classList.remove('loading'); POINT_ELEMENTS.redeemButton.disabled = false; POINT_ELEMENTS.redeemButton.textContent = 'Tukar Poin'; } } function getCurrentSelectedShippingMethod() { return selectedShippingMethod; } // Helper function untuk mendapatkan semua shipping methods yang tersedia function getAllShippingMethods() { return currentShippingMethods; } // Function untuk memvalidasi apakah ada shipping method yang terpilih function validateShippingSelection() { const checkedMethod = document.querySelector('input[name="shipping_method"]:checked'); return checkedMethod && selectedShippingMethod; } function updateHiddenShippingInputs(shippingMethod) { let actualShippingFeeElement = document.getElementById('actual-shipping-fee'); if (!actualShippingFeeElement) { actualShippingFeeElement = document.createElement('input'); actualShippingFeeElement.type = 'hidden'; actualShippingFeeElement.id = 'actual-shipping-fee'; actualShippingFeeElement.name = 'actual_shipping_fee'; document.body.appendChild(actualShippingFeeElement); } actualShippingFeeElement.value = shippingMethod.final_price; let selectedMethodElement = document.getElementById('selected-shipping-method'); if (!selectedMethodElement) { selectedMethodElement = document.createElement('input'); selectedMethodElement.type = 'hidden'; selectedMethodElement.id = 'selected-shipping-method'; selectedMethodElement.name = 'selected_shipping_method'; document.body.appendChild(selectedMethodElement); } selectedMethodElement.value = shippingMethod.id; } // Helper function untuk clear hidden inputs function clearHiddenShippingInputs() { const actualShippingFeeElement = document.getElementById('actual-shipping-fee'); if (actualShippingFeeElement) { actualShippingFeeElement.value = 0; } const selectedMethodElement = document.getElementById('selected-shipping-method'); if (selectedMethodElement) { selectedMethodElement.value = ''; } } // ======== End of Helper ========= </script>