เว็บแอปตรวจสอบใบโอนเงินของธนาคาร

 





DEMO

1. สร้าง Sheet setConf

รายการข้อมูล
ชื่อบัญชีผู้รับเงินqwerty zxcvb
โฟลเดอร์ไอดีเก็บสลิปxxx-xxxxx-xxxxxx


2. สร้าง Sheet Data

#IDDateOCR DataQRCode DataSlip URL


3. สร้างไฟล์ index.html

     
<!DOCTYPE html>
<html lang="th">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>UPLOAD SLIP PAYMENT PAPER WORK</title>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Caveat:wght@700&family=K2D:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');

    body {
      margin: 0;
      padding: 0;
      font-family: 'K2D', sans-serif;
      background: linear-gradient(90deg, rgba(2, 0, 36, 1) 0%, rgba(9, 9, 121, 1) 0%, rgba(0, 212, 255, 1) 50%, rgba(9, 9, 121, 1) 100%);
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      overflow: auto;
    }


    /* Content */
    .container {
      margin: 20px 0px;
      background: rgba(9, 9, 121, 1);
      border-radius: 20px;
      padding: 40px 30px;
      max-width: 400px;
      text-align: center;
      box-shadow: 0 15px 30px rgba(0, 0, 0, 0.5);
      border: 2px solid rgba(0, 212, 255, 0.8);
    }

    .image-container {
      display: flex;
      justify-content: center;
      align-items: center;
      padding-bottom: 15px;
    }

    h1 {
      font-size: 21px;
      color: #ccc;
      margin-bottom: 20px;
    }

    a {
      color: #ddd;
      text-decoration: none;
    }

    .upload-text-label {
      font-size: 18px;
      font-weight: 600;
      color: #00d4ff;
      cursor: pointer;
    }

    #file-input {
      display: none;
    }

    .preview-container {
      margin-top: 30px;
      display: none;
    }

    .preview {
      max-width: 100%;
      border-radius: 10px;
      box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
      display: block;
    }

    .card-footer {
      color: #ddd;
      text-align: center;
      margin-top: 30px;
      font-family: 'Caveat', sans-serif;
      font-size: 18px;
      display: grid;
      align-items: center;
      justify-content: center;
    }


    /* Preloader */
    .loading-container {
      width: 100%;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      text-align: center;
      display: none;
    }

    .loading-text {
      width: 100%;
      font-size: 20px;
      font-weight: 600;
      color: rgb(255, 0, 242);
      animation: fadeText 1.5s infinite alternate;
      padding: 10px 0px;
    }

    @keyframes fadeText {
      0% {
        opacity: 0.3;
      }

      100% {
        opacity: 1;
      }
    }

    .dots {
      display: flex;
      justify-content: center;
      gap: 8px;
      margin-top: 5px;
    }

    .dot {
      width: 10px;
      height: 10px;
      border-radius: 50%;
      animation: bounce 1.5s infinite ease-in-out;
    }

    .dot:nth-child(1) {
      background: green;
      animation-delay: 0s;
    }

    .dot:nth-child(2) {
      background: red;
      animation-delay: 0.2s;
    }

    .dot:nth-child(3) {
      background: orange;
      animation-delay: 0.4s;
    }

    @keyframes bounce {

      0%,
      100% {
        transform: translateY(0);
      }

      50% {
        transform: translateY(-10px);
      }
    }


    /* Sweet alert */
    .colored-toast.swal2-icon-success {
      background-color: #a5dc86 !important;
    }

    .colored-toast.swal2-icon-error {
      background-color: #f27474 !important;
    }

    .colored-toast.swal2-icon-warning {
      background-color: #f8bb86 !important;
    }

    .colored-toast.swal2-icon-info {
      background-color: #3fc3ee !important;
    }

    .colored-toast.swal2-icon-question {
      background-color: #87adbd !important;
    }

    .colored-toast .swal2-title {
      color: white;
    }

    .colored-toast .swal2-close {
      color: white;
    }

    .colored-toast .swal2-html-container {
      color: white;
    }
  </style>
</head>

<body>

  <div class="container">
    <!-- Header -->
    <div class="image-container">
      <img src="https://semicon.github.io/img/LOGOKRUCHIANgrow.png" height="50" alt="logo">
    </div>
    <h1>ยืนยันสลิปการชำระเงินของธนาคาร</h1>

    <!-- Upload File -->
    <label for="file-input" class="upload-text-label">คลิกเพื่อเลือกสลิป</label>
    <input type="file" id="file-input" accept=".jpg, .jpeg, .png, image/jpeg, image/png">

    
    <div class="preview-container">
      <!-- Bank Slip -->
      <div class="image-container">
        <img id="preview" class="preview" src="" alt="ตัวอย่างภาพ">
      </div>
      
      <!-- Pre-loader -->
      <div class="loading-container" id="loading-container">
        <div class="loading-text" id="loading-text"></div>
        <div class="dots">
          <div class="dot"></div>
          <div class="dot"></div>
          <div class="dot"></div>
        </div>
      </div>
    </div>

    <!-- Copyright -->
    <div class="card-footer">
      <p>Copyleft &nbsp;&nbsp;
      <img src="https://mirrors.creativecommons.org/presskit/icons/heart.red.png" width="30" height="30" alt="cc">
      &nbsp;&nbsp;2022 -
      <script>document.write(new Date().getFullYear())</script>,
      &nbsp; by <a href="https://guruchian.blogspot.com/" target="_blank">&nbsp;Dr.Wichian Ph.</a></p>
      <p>วิเชียร พุ่มพวง</p>
    </div>
  </div>

  
  <!-- JS -->
  <script src="https://unpkg.com/jsqr/dist/jsQR.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/tesseract.js/6.0.1/tesseract.min.js"></script>
  <!-- <script src="https://cdn.jsdelivr.net/npm/tesseract.js"></script> -->
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>

  <script>
        const fileInput = document.getElementById('file-input');
        const preview = document.getElementById('preview');
        const previewContainer = document.querySelector('.preview-container');
        const loadingContainer = document.getElementById('loading-container');
        const loadingText = document.getElementById('loading-text');
 
        
        /**
         * 0 เปลี่ยนพื้นหลังเป็นขาว
         * */
        function changeBackgroundToWhite(imageData) {
            return new Promise((resolve, reject) => {
                const img = new Image();
                img.src = imageData;
                img.onload = () => {
                    const canvas = document.createElement('canvas');
                    canvas.width = img.width+50;
                    canvas.height = img.height+50;
                    const ctx = canvas.getContext('2d');

                    // เติมสีขาวให้เป็นพื้นหลังเริ่มต้น
                    ctx.fillStyle = "white";
                    ctx.fillRect(0, 0, canvas.width, canvas.height);

                    // วาดรูปทับลงบนพื้นหลังสีขาว
                    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

                    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                    const data = imageData.data;

                    // ค่า Threshold สำหรับตรวจจับพื้นหลังสีอ่อนและตัวอักษรสีเทา
                    const backgroundThreshold = 200; // สำหรับพื้นหลังสีอ่อน
                    const textThreshold = 130;       // สำหรับตัวอักษรสีเทา

                    for (let i = 0; i < data.length; i += 4) {
                        const r = data[i];
                        const g = data[i + 1];
                        const b = data[i + 2];

                        // เปลี่ยนพื้นหลังสีอ่อนเป็นสีขาว
                        if (r > backgroundThreshold && g > backgroundThreshold && b > backgroundThreshold) {
                            data[i] = 255;   // Red
                            data[i + 1] = 255; // Green
                            data[i + 2] = 255; // Blue
                            data[i + 3] = 255; // Opacity
                        }
                        // เปลี่ยนตัวอักษรสีเทาเป็นสีดำ
                        else if (r < textThreshold && g < textThreshold && b < textThreshold) {
                            data[i] = 0;     // Red
                            data[i + 1] = 0; // Green
                            data[i + 2] = 0; // Blue
                            data[i + 3] = 255; // Opacity
                        }
                    }

                    ctx.putImageData(imageData, 0, 0);
                    const newImage = canvas.toDataURL("image/png");
                    preview.src = newImage; // แสดงผลภาพที่ปรับสีแล้ว

                    // ส่งค่า newImage กลับไปเมื่อกระบวนการเสร็จสิ้น
                    resolve(newImage);
                };

                img.onerror = (error) => {
                    reject(error); // หากเกิดข้อผิดพลาด
                };
            });
        }

        
        /** 
         * 1. เรียกใช้งานเมื่อคลิกเลือกรูป
         * */
        fileInput.addEventListener('change', async (e) => {
            const file = e.target.files[0];
            if (file) {
                previewContainer.style.display = 'block';
                const reader = new FileReader();
                reader.onload = async (event) => {
                    preview.src = event.target.result;
                    loadingContainer.style.display = 'block';
                    loadingText.innerHTML = 'กำลังปรับสีภาพ...';

                    try {
                        const newImage = await changeBackgroundToWhite(event.target.result); // รอให้ปรับสีเสร็จ
                        loadingText.innerHTML = 'กำลังตรวจสอบคิวอาร์โค้ด...';
                        checkQRCode(newImage); // เรียกฟังก์ชันตรวจสอบ QR Code
                    } catch (error) {
                        console.error('เกิดข้อผิดพลาดในการปรับสีภาพ:', error);
                        Swal.fire({
                            toast: true,
                            position: 'center',
                            iconColor: 'white',
                            customClass: {
                                popup: 'colored-toast',
                            },
                            title: 'เกิดข้อผิดพลาด',
                            text: 'ไม่สามารถปรับสีภาพได้',
                            icon: 'error',
                            confirmButtonText: 'ตกลง',
                        });
                    }
                };
                reader.readAsDataURL(file);
            }
        });


        /**
         * 2. ตรวจสอบคิวอาร์โค้ด
        */
        function checkQRCode(imageData) {
            const img = new Image();
            img.src = imageData;
            img.onload = () => {
                const canvas = document.createElement('canvas');
                canvas.width = img.width;
                canvas.height = img.height;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

                const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                const qrCode = jsQR(imgData.data, canvas.width, canvas.height);

                if (qrCode && qrCode.data) {
                    const dataCode = qrCode.data;
                    // ตรวจสอบคิวอาร์โค้ด
                    const data = dataCode.includes("TH") ? dataCode.split("TH")[1] : null;

                    if (data) {
                        checkName(canvas,qrCode.data);
                    } else {
                        Swal.fire({
                            toast: true,
                            position: 'center',
                            iconColor: 'white',
                            customClass: {
                                popup: 'colored-toast',
                            },
                            title: 'พบ QR Code!',
                            html: `รหัส QR Code ไม่ถูกต้อง! <br>รหัส:  ${dataCode}`,
                            icon: 'error',
                            confirmButtonText: 'ลองใหม่',
                        }).then(() => clearForm());
                    }
                } else {
                    Swal.fire({
                        toast: true,
                        position: 'center',
                        iconColor: 'white',
                        customClass: {
                            popup: 'colored-toast',
                        },
                        title: 'ไม่พบ QR Code!',
                        text: 'สลิปไม่มี QR Code หรือ อ่าน QR Code ไม่ได้!',
                        icon: 'error',
                        confirmButtonText: 'ลองใหม่',
                    }).then(() => clearForm());
                }
            };
        }
        
        
        /**
         * 3. ตรวจสอบชื่อบัญชีของผู้รับโอน
         */
        function checkName(canvas, qrCodeStr) {
            loadingContainer.style.display = 'block';
            loadingText.innerHTML = 'กำลังตรวจสอบรหัสปลายทาง...';

            Tesseract.recognize(canvas, 'tha+eng', {
                langPath: 'https://tessdata.projectnaptha.com/4.0.0_best/',
                logger: (m) => console.log(m)
            }).then(({ data: { text } }) => {
                const imageBase64 = canvas.toDataURL("image/png").replace(/^data:image\/(png|jpg);base64,/, "");  // Convert canvas to Base64
                const slipText = text.replace(/\s/g, '');
                console.log(slipText)
                if (slipText) {
                  google.script.run.withSuccessHandler(function(res){
                    clearForm()
                    if(res.success){
                      Swal.fire({
                        toast: true,
                        position: 'center',
                        iconColor: 'white',
                        customClass: {
                          popup: 'colored-toast',
                        },
                        title: 'สำเร็จ!',
                        text: 'ข้อมูลตรงกับในระบบ',
                        icon: 'success',
                        timer: 1500,
                        showConfirmButton: false,
                        timerProgressBar: true,
                      })
                    } else {
                      Swal.fire({
                        toast: true,
                        position: 'center',
                        iconColor: 'white',
                        customClass: {
                            popup: 'colored-toast',
                        },
                        title: 'ไม่สำเร็จ!',
                        text: 'ข้อมูลไม่ตรงกับระบบ',
                        icon: 'error',
                        confirmButtonText: 'ลองใหม่',
                      })
                    }
                  }).saveToSheet({slipText, qrCodeStr, imageBase64});
                }   
            });
        }


        /**
         * 4. Clear form
         */
        function clearForm() {
          fileInput.value = ""; 
          preview.src = ""; 
          previewContainer.style.display = "none";
        }
        
  </script>
</body>

</html>

    


4. สร้างไฟล์ code.gs

     
function doGet() {
  return HtmlService.createTemplateFromFile('index').evaluate()
    .setTitle("Zoom_09_02_2568_read_payment_slips")
    .setFaviconUrl("https://semicon.github.io/img/logo2small.png")
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/*** save to Sheet ***/
function saveToSheet(obj) {  // {slipText, qrCodeStr, imageBase64}
  Logger.log(obj.slipText)
  const sheetConf = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("setConf");
  const accountName = sheetConf.getRange("B2").getValue().trim();
  const folderId = sheetConf.getRange("B3").getValue()
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data");
  const blob = Utilities.newBlob(Utilities.base64Decode(obj.imageBase64), 'image/png',
               'Slip-'+obj.qrCodeStr.split("TH")[1]+'.png'); // ชื่อไฟล์
  const folder = DriveApp.getFolderById(folderId);
  const date = new Date();
  const options = { day: 'numeric', month: 'long', year: 'numeric'};
  
  const normalize = str => str.replace(/\s+/g, '');

  if (normalize(obj.slipText).includes(normalize(accountName))) {
    const file = folder.createFile(blob);
    const fileUrl = file.getUrl();

    sheet.appendRow([
      '',
      date.toLocaleDateString('th-TH', options),
      obj.slipText,
      obj.qrCodeStr,
      fileUrl
    ]);

    return { success: true };
  }else{
    return {success: false}
  }
}

    


หากถูกใจโปรดสนับสนุนเป็นกำลังใจกันด้วยนะครับ

สนับสนุนที่นี่



แสดงความคิดเห็น (0)
ใหม่กว่า เก่ากว่า