fix payment

This commit is contained in:
javad committed 2026-10-07 13:39:29 +03:30
1 parent 4f7486152c
commit bb3ddd1610
47 files changed
+2137 -51

No files matched your search

+2
View File
@@ -119,3 +119,5 @@
.customer-accounts .pay-notice-entry{background:#fff;border:1px solid #dce5ed;border-radius:23px;margin:16px 0;overflow:hidden}.customer-accounts .pay-notice-entry .payment-notice{border:0;border-radius:0;margin:0}.customer-accounts .pay-notice-entry .pay-notice-permalink{display:flex;justify-content:space-between;border-top:1px solid #e7edf2;margin:0;padding:12px 20px;min-height:48px;text-decoration:none;background:#fafcfe}
.pay-identifier-control{display:flex;align-items:stretch;gap:12px}.pay-identifier-control>input{flex:1;min-width:0}.pay-identifier-control>.pay-button{gap:8px;flex-shrink:0;white-space:nowrap;font-size:14px}.pay-identifier-control>.pay-button:disabled{opacity:.6;cursor:not-allowed}.pay-staff-form #id_deposit_id{letter-spacing:.5px}.pay-staff-form #identifier-replacement p{font-size:14px}.pay-staff-form #identifier-replacement .pay-text-link{font:inherit;background:transparent;border:0;color:#526c86;min-height:44px;padding:8px 12px;cursor:pointer}.pay-staff-form #identifier-replacement[hidden]{display:none}
@media(max-width:639px){.pay-identifier-control{flex-direction:column;gap:8px}.pay-identifier-control>.pay-button{align-self:stretch}}
.pay-pagination{flex-wrap:wrap}.pay-pagination small{flex-basis:100%;text-align:center;color:#526c86}.pay-sms-panel{overflow-wrap:anywhere}.pay-sms-panel>form>.pay-button{width:100%;white-space:normal;line-height:1.8}.pay-sms-panel button:disabled{opacity:.6;cursor:not-allowed}.pay-sms-record{padding:16px 0;border-top:1px solid #e7edf2}.pay-sms-record header{display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap}.pay-sms-record p{margin:8px 0;font-size:13px;line-height:1.9}.pay-sms-record time{font-size:12px;color:#526c86}.pay-sms-panel .pay-status{white-space:normal;max-width:100%;line-height:1.8}.pay-sms-record summary{cursor:pointer;font-size:13px;padding:10px 0;color:#17435d}.pay-sms-body{font-size:13px;line-height:1.9;background:#f0f8fc;padding:12px;border-radius:10px}.pay-sms-attempt{padding:10px 0;border-top:1px dashed #dce5ed}.pay-sms-error{color:#9f1f1f}
.receipt-upload{padding:18px;margin:16px 0;border:1px solid #c7dfeb;border-radius:16px;background:#f0f8fc;color:#17435d;overflow-wrap:anywhere}.receipt-upload[hidden]{display:none}.receipt-upload p{margin:8px 0;font-size:14px}.receipt-upload small{display:block;color:#526c86}.receipt-upload-meter{display:flex;align-items:center;gap:12px;margin-top:12px}.receipt-upload-meter progress{width:100%;flex:1;min-width:0;height:12px;accent-color:#087d8b;direction:ltr}.receipt-upload-meter span{min-width:44px;text-align:center;font-weight:700;font-variant-numeric:tabular-nums}.customer-receipt-actions button:disabled{opacity:.65;cursor:wait}.customer-receipt-form.is-uploading input,.customer-receipt-form.is-uploading select,.customer-receipt-form.is-uploading textarea{opacity:.75}
+1
View File
@@ -1,4 +1,5 @@
/* Temporary payment access follows the Chaharsu style guide. */
.pay-page-share{margin:12px 0 18px;padding-top:16px;border-top:1px solid #e7edf2}.pay-page-share>.pay-button{display:flex;gap:10px;max-width:100%;min-height:48px;white-space:normal;text-align:start;line-height:1.8}.pay-page-share>.pay-button:focus-visible{outline:3px solid #1977ce;outline-offset:3px}.pay-page-share>.pay-button:disabled{opacity:.65;cursor:wait}.pay-page-share p{margin:8px 0 0;color:#526c86;font-size:13px;line-height:1.9}.pay-page-share .pay-share-feedback:empty{display:none}@media(max-width:639px){.pay-page-share>.pay-button{width:100%;justify-content:center}}
.pay-share-url{overflow-wrap:anywhere;font-size:14px;line-height:1.8;padding:12px;border:1px solid #dce5ed;border-radius:12px;background:#f6fbff}.pay-detail-aside .pay-card:has(.pay-share-url) form{margin-top:12px}
.pay-link-status{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px 16px;margin-bottom:24px;padding:14px 18px;border:1px solid #cde6f7;border-radius:14px;background:#f6fbff;color:#08376b;line-height:1.8}
.pay-link-status>span{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700}.pay-link-status .cs-icon{width:20px;height:20px;flex-shrink:0}.pay-link-status small{color:#526c86;font-size:14px}.pay-link-session-label{font-size:14px;color:#526c86}
+1 -1
View File
@@ -48,7 +48,7 @@
const data = await response.json();
if (request !== controller || customer.value !== selectedCustomer) return;
if (!response.ok) {failureMessage = data.error || 'شناسه تولید نشد؛ دوباره تلاش کنید.'; throw new Error('generation');}
if (data.customer !== selectedCustomer || !/^[0-9]{13}$/.test(data.identifier)) {failureMessage = 'پاسخ سیستم معتبر نیست؛ دوباره تلاش کنید.'; throw new Error('generation');}
if (data.customer !== selectedCustomer || !/^9[0-9]{11}$/.test(data.identifier)) {failureMessage = 'پاسخ سیستم معتبر نیست؛ دوباره تلاش کنید.'; throw new Error('generation');}
if (input.value.trim() && input.value !== data.identifier) {
pending = data.identifier; candidate.textContent = pending; replacement.hidden = false;
status.textContent = 'شناسه آماده است؛ برای جایگزینی مقدار فعلی، دکمه جایگزینی را بزنید.';
+16
View File
@@ -13,6 +13,22 @@ async function copy(text){
}
}
document.querySelectorAll('[data-copy]').forEach(button=>button.addEventListener('click',()=>copy(button.dataset.copy)));
document.querySelectorAll('[data-notice-sms]').forEach(form=>form.addEventListener('submit',event=>{
if(form.dataset.sending==='true'){event.preventDefault();return;}
form.dataset.sending='true';const button=form.querySelector('button');button.disabled=true;button.textContent='در حال ثبت درخواست ارسال…';
}));
document.querySelectorAll('[data-share-link]').forEach(form=>form.addEventListener('submit',async event=>{
event.preventDefault();
const button=form.querySelector('button'),feedback=form.querySelector('[role="status"]');
button.disabled=true;button.setAttribute('aria-busy','true');feedback.textContent='در حال آماده‌سازی لینک…';
try{
const response=await fetch(form.action,{method:'POST',body:new FormData(form),credentials:'same-origin',cache:'no-store',headers:{'Accept':'application/json'}});
if(!response.ok || !response.headers.get('Content-Type')?.includes('application/json'))throw new Error('share unavailable');
const data=await response.json();
await copy(data.url);feedback.textContent='لینک آماده است؛ می‌توانید آن را برای دیگران ارسال کنید.';
}catch{feedback.textContent='دریافت لینک ممکن نشد. دوباره تلاش کنید یا لینک پرداخت دریافتی را باز کنید.';}
finally{button.disabled=false;button.removeAttribute('aria-busy');}
}));
const identifiers=document.querySelector('#identifier-accounts'),account=document.querySelector('#id_account'),deposit=document.querySelector('#id_deposit_id');
if(identifiers && account && deposit){const ids=JSON.parse(identifiers.textContent).map(String);function update(){deposit.required=ids.includes(account.value);document.querySelector('#identifier-help').textContent=deposit.required?'این حساب شناسه‌دار است؛ شناسه واریز مشتری الزامی است.':'شناسه واریز در صورت اعلام بانک وارد شود.';}account.addEventListener('change',update);update();}
const badge=document.querySelector('[data-payment-count]');
+8 -5
View File
@@ -42,8 +42,8 @@ fields.forEach(field=>{const controls=field.querySelectorAll('input,select,texta
let busy=false;
function showErrors(errors){
fields.forEach(field=>{const name=field.dataset.field,message=errors.get(name)||'',error=field.querySelector('.field-error');error.textContent=message;error.hidden=!message;field.querySelectorAll('input,select,textarea').forEach(control=>control.setAttribute('aria-invalid',String(Boolean(message))));});
const summary=document.querySelector('#receipt-errors'),list=summary.querySelector('ul');list.replaceChildren();errors.forEach((message,name)=>{const item=document.createElement('li'),link=document.createElement('a');link.href='#id_'+name;link.textContent=message;link.onclick=event=>{event.preventDefault();q(name).focus();q(name).scrollIntoView({block:'center'});};item.append(link);list.append(item);});summary.hidden=!errors.size;
if(errors.size){const first=q(errors.keys().next().value);first.focus();first.scrollIntoView({block:'center'});}
const summary=document.querySelector('#receipt-errors'),list=summary.querySelector('ul');list.replaceChildren();errors.forEach((message,name)=>{const item=document.createElement('li'),control=q(name);if(control && typeof control.focus==='function'){const link=document.createElement('a');link.href='#'+control.id;link.textContent=message;link.onclick=event=>{event.preventDefault();control.focus();control.scrollIntoView({block:'center'});};item.append(link);}else item.textContent=message;list.append(item);});summary.hidden=!errors.size;
if(errors.size){const first=q(errors.keys().next().value);if(first && typeof first.focus==='function'){first.focus();first.scrollIntoView({block:'center'});}else{summary.tabIndex=-1;summary.focus();summary.scrollIntoView({block:'center'});}}
}
async function validateImage(file){
if(!file)return 'تصویر فیش واریز را انتخاب کنید.';
@@ -53,7 +53,7 @@ async function validateImage(file){
}
let revision=0;form.addEventListener('input',()=>revision++);form.addEventListener('change',()=>revision++);
form.addEventListener('submit',async event=>{
event.preventDefault();if(busy)return;busy=true;button.disabled=true;const version=revision,errors=new Map();
event.preventDefault();if(busy)return;busy=true;button.disabled=true;button.textContent='در حال بررسی تصویر…';document.querySelector('#form-status').textContent='';const version=revision,errors=new Map();let navigating=false;
try{
const value=digits(q('paid_amount').value).trim();if(!/^\d+$/.test(value) && !/^\d{1,3}(?:[,٬ ]\d{3})+$/.test(value))errors.set('paid_amount','مبلغ را به ریال و با عدد صحیح وارد کنید.');else{const amount=BigInt(value.replace(/[,٬ ]/g,''));if(amount<=0n || amount>9007199254740991n)errors.set('paid_amount','مبلغ باید مثبت و در محدوده مجاز باشد.');}
if(q('receipt_mode').value==='linked' && !q('notice_id').value)errors.set('notice_id','اعلامیه را انتخاب کنید یا واریز مستقل را بزنید.');
@@ -63,8 +63,11 @@ form.addEventListener('submit',async event=>{
const imageError=await validateImage(q('receipt_image').files[0]);if(imageError)errors.set('receipt_image',imageError);
if(version!==revision){document.querySelector('#form-status').textContent='اطلاعات تغییر کرده است؛ دوباره ارسال را بزنید.';return;}
showErrors(errors);if(errors.size)return;
button.textContent='در حال ثبت…';HTMLFormElement.prototype.submit.call(form);return;
}finally{busy=false;if(button.textContent!=='در حال ثبت…')button.disabled=false;}
const result=await window.ReceiptUpload.send(form);
if(result.redirect_url){const destination=new URL(result.redirect_url,location.href);if(destination.origin!==location.origin)throw new Error('پاسخ ثبت فیش معتبر نیست.');navigating=true;location.assign(destination.href);return;}
showErrors(new Map(Object.entries(result.errors).map(([name,items])=>[name,items.map(item=>item.message).join(' ')])));
}catch(error){showErrors(new Map([['__all__',error.message || 'ارسال فیش انجام نشد؛ دوباره تلاش کنید.']]));}
finally{if(!navigating){busy=false;button.disabled=false;button.textContent='ثبت و ارسال فیش';}}
});
if(document.querySelector('#receipt-errors').hidden===false){const field=form.querySelector('[aria-invalid=true]');if(field){field.focus();field.scrollIntoView({block:'center'});}}
})();
+65
View File
@@ -0,0 +1,65 @@
/* Real multipart upload progress; registration remains authoritative on the server. */
(() => {
'use strict';
const number=new Intl.NumberFormat('fa-IR',{maximumFractionDigits:1});
function bytes(value){return value>=1048576?number.format(value/1048576)+' مگابایت':number.format(value/1024)+' کیلوبایت';}
function duration(seconds){return seconds>=60?number.format(Math.ceil(seconds/60))+' دقیقه':number.format(Math.max(1,Math.ceil(seconds)))+' ثانیه';}
function send(form){
const payload=new FormData(form),panel=document.querySelector('#receipt-upload');
const title=document.querySelector('#receipt-upload-title'),meter=document.querySelector('#receipt-upload-progress');
const percent=document.querySelector('#receipt-upload-percent'),amount=document.querySelector('#receipt-upload-bytes'),time=document.querySelector('#receipt-upload-time');
const button=document.querySelector('#submit-receipt');
const controls=[...form.querySelectorAll('input,select,textarea,button')].map(control=>[control,control.disabled]);
controls.forEach(([control])=>{control.disabled=true;});form.classList.add('is-uploading');button.setAttribute('aria-busy','true');
panel.hidden=false;title.textContent='در حال ارسال فیش…';button.textContent='در حال ارسال…';
meter.value=0;percent.textContent='۰٪';amount.textContent='در انتظار شروع ارسال';
const started=performance.now();let loaded=0,total=0,lastProgress=started,processing=false;
function updateTime(){
if(processing)return;
const elapsed=(performance.now()-started)/1000;
const estimate=loaded>0 && elapsed>=1 && total>loaded;
time.textContent='زمان سپری‌شده: '+number.format(Math.floor(elapsed))+' ثانیه'+(performance.now()-lastProgress>5000?' · منتظر ادامه ارسال؛ سرعت اتصال تغییر کرده است.':estimate?' · حدود '+duration((total-loaded)*elapsed/loaded)+' دیگر برای ارسال':' · در حال محاسبه زمان باقی‌مانده…');
}
function process(){
processing=true;meter.value=100;percent.textContent='۱۰۰٪';
title.textContent='ارسال کامل شد؛ در حال بررسی و ثبت فیش…';button.textContent='در حال ثبت…';
time.textContent='منتظر تأیید ثبت از سرور؛ زمان این مرحله به سرعت پردازش بستگی دارد.';
}
updateTime();panel.scrollIntoView({block:'nearest'});
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest(),timer=setInterval(updateTime,1000);let settled=false;
function finish(error,result){
if(settled)return;settled=true;clearInterval(timer);
if(!result?.redirect_url){
controls.forEach(([control,disabled])=>{control.disabled=disabled;});form.classList.remove('is-uploading');button.removeAttribute('aria-busy');
title.textContent='ثبت فیش انجام نشد';time.textContent='اطلاعات و تصویر حفظ شده‌اند؛ پس از رفع مشکل دوباره ارسال کنید.';
}
if(error)reject(error);else resolve(result);
}
xhr.upload.addEventListener('progress',event=>{
loaded=event.loaded;total=event.lengthComputable?event.total:0;lastProgress=performance.now();
amount.textContent='ارسال‌شده: '+bytes(loaded)+(total?' از '+bytes(total):'');
if(total){const value=Math.min(100,Math.floor(loaded/total*100));meter.value=value;percent.textContent=number.format(value)+'٪';}
else{meter.removeAttribute('value');percent.textContent='در حال ارسال';}
updateTime();
});
xhr.upload.addEventListener('load',process);
xhr.addEventListener('load',()=>{
const result=xhr.response;
if(xhr.status>=200 && xhr.status<300 && result?.redirect_url){finish(null,result);return;}
if(xhr.status===422 && result?.errors){finish(null,result);return;}
const message=[401,403,410].includes(xhr.status)?'دسترسی یا نشست شما معتبر نیست؛ صفحه را دوباره باز کنید.':xhr.status===413?'حجم تصویر بیش از حد مجاز سرور است؛ تصویر کوچک‌تری انتخاب کنید.':'ثبت فیش تأیید نشد؛ دوباره تلاش کنید. در صورت تکرار، وضعیت فیش‌های خود را بررسی کنید.';
finish(new Error(message));
});
xhr.addEventListener('error',()=>finish(new Error('ارتباط با سرور قطع شد؛ اتصال اینترنت را بررسی و دوباره ارسال کنید.')));
xhr.addEventListener('abort',()=>finish(new Error('ارسال متوقف شد؛ دوباره تلاش کنید.')));
try{
xhr.open('POST',form.action || location.href);xhr.responseType='json';
xhr.setRequestHeader('Accept','application/json');
xhr.setRequestHeader('X-CSRFToken',payload.get('csrfmiddlewaretoken'));
xhr.send(payload);
}catch(error){finish(error);}
});
}
window.ReceiptUpload=Object.freeze({send});
})();