บทความทั้งหมด

เรียก API ตรวจสลิปจาก PHP ด้วย cURL อย่างปลอดภัย

อ่าน 2 นาที

ไม่มีรูปภาพ
เรียก API ตรวจสลิปจาก PHP ด้วย cURL อย่างปลอดภัย

PHP ยังเป็นภาษาหลักของเว็บร้านค้าและระบบหลังบ้านจำนวนมากในไทย การเรียก API ตรวจสลิปจาก PHP ทำได้ด้วยส่วนขยาย cURL ที่มีมากับโฮสติ้งเกือบทุกเจ้า บทความนี้แสดงวิธีเรียกตามตัวอย่างในคู่มือ API ของ PPay ขยายให้ครอบคลุมการจัดการผลลัพธ์ที่ใช้ในงานจริง หากคุณใช้ WooCommerce, WHMCS หรือ Magento อยู่แล้ว ปลั๊กอินสำเร็จรูปอาจเหมาะกว่า ดูที่ หน้าปลั๊กอิน

เตรียมการ

ต้องมีส่วนขยาย curl และ json เปิดอยู่ (ส่วนใหญ่เปิดอยู่แล้ว) เก็บ API key ที่ขึ้นต้นด้วย ppk_ ใน environment variable หรือไฟล์ config นอก web root ที่เข้าถึงจากเว็บไม่ได้ และอย่า commit ลง Git ห้ามฝัง key ในไฟล์ที่ผู้เข้าชมเปิดดูได้ รูปสลิปต้องเป็น PNG หรือ JPG ไม่เกิน 5MB

ตัวอย่างพื้นฐานจากคู่มือ

<?php
$ch = curl_init(getenv("PPAY_API_BASE") . "/api/v1/verify");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer " . getenv("PPAY_API_KEY")],
    CURLOPT_POSTFIELDS     => ["file" => new CURLFile("slip.jpg", "image/jpeg", "slip.jpg")],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$data   = json_decode($body, true);

if ($status === 202) { /* queued: use $data["jobId"] */ }
elseif ($status === 200 && $data["valid"]) { echo $data["slip"]["amount"]; }

สังเกตว่า CURLFile ทำให้ cURL ส่งเป็น multipart/form-data โดยอัตโนมัติ ตัวอย่างใช้ CURLOPT_TIMEOUT 30 วินาที ซึ่งเป็นค่าที่คู่มือใช้ และกำหนด CURLOPT_RETURNTRANSFER เพื่อรับ body กลับเป็นสตริง

ห่อเป็นฟังก์ชันที่จัดการ error ครบ

ในงานจริง ควรแยกกรณี cURL ล้มเหลว (เช่นต่อเครือข่ายไม่ได้) ออกจากกรณี API ตอบ error

<?php
function ppay_verify($path) {
    $ch = curl_init(getenv("PPAY_API_BASE") . "/api/v1/verify");
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => ["Authorization: Bearer " . getenv("PPAY_API_KEY")],
        CURLOPT_POSTFIELDS     => ["file" => new CURLFile($path, "image/jpeg", "slip.jpg")],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);
    $body = curl_exec($ch);
    if ($body === false) {
        $err = curl_error($ch);
        curl_close($ch);
        return ["kind" => "network-error", "message" => $err];   // outcome unknown: check history before resending
    }
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    $data = json_decode($body, true);

    if ($status === 202) return ["kind" => "queued", "jobId" => $data["jobId"]];
    if ($status !== 200) return ["kind" => "request-error", "status" => $status, "code" => $data["code"] ?? null];
    if (!empty($data["valid"])) return ["kind" => "valid", "slip" => $data["slip"]];
    return ["kind" => ($data["result"] ?? "") === "error" ? "system-error" : "rejected", "code" => $data["code"] ?? null];
}

กรณี network-error คุณไม่รู้ว่าคำขอไปถึงเซิร์ฟเวอร์หรือไม่ จึงไม่ควรส่งสลิปใบเดิมซ้ำทันที เพราะอาจได้ DUPLICATE_SLIP และถูกหักเพิ่ม ควรตรวจหน้า "ประวัติ" ในพื้นที่สมาชิกก่อน อ่านต่อที่ การ retry โดยไม่เกิดผลซ้ำ

รับไฟล์จากฟอร์มอัปโหลดอย่างปลอดภัย

ถ้าลูกค้าอัปโหลดสลิปผ่านเว็บของคุณก่อนส่งต่อให้ API ให้ตรวจฝั่งเซิร์ฟเวอร์เสมอ ไม่เชื่อนามสกุลไฟล์หรือค่า MIME ที่เบราว์เซอร์ส่งมา

  • ตรวจขนาดไฟล์ไม่เกิน 5MB ก่อนส่งต่อ
  • ใช้ $_FILES["slip"]["tmp_name"] ที่ได้จาก is_uploaded_file ตรวจแล้ว ไม่ใช้ path ที่ผู้ใช้กำหนดเอง เพื่อกันการอ่านไฟล์อื่นในเซิร์ฟเวอร์
  • ไม่เก็บไฟล์สลิปไว้ในโฟลเดอร์ที่เปิดสาธารณะ เพราะสลิปมีข้อมูลส่วนตัว
  • ปิดปุ่มส่งฝั่งหน้าเว็บระหว่างรอผล เพื่อลดการส่งซ้ำ

ฝั่ง API เองก็ตรวจชนิดไฟล์จากเนื้อไฟล์จริงเช่นกัน ไฟล์ที่ไม่ใช่ PNG หรือ JPG จะได้ 400 INVALID_FILE

เทียบยอดก่อนอนุมัติ

<?php
$r = ppay_verify($tmpPath);
if ($r["kind"] === "valid") {
    $amount = (float)$r["slip"]["amount"];
    if ($amount >= (float)$order["total"]) {
        // mark the order as paid inside a DB transaction, only if it is still "pending"
    }
}

คู่มือระบุว่า การตัดสินว่ายอดและผู้รับตรงกับออเดอร์หรือไม่เป็นหน้าที่ของระบบคุณ และควรทำขั้นตอนอนุมัติให้ปลอดภัยแม้ถูกเรียกซ้ำ เช่น อัปเดตสถานะเฉพาะแถวที่ยังเป็น pending ดูเรื่องยอดไม่ตรงที่ ยอดในสลิปไม่ตรงกับออเดอร์

รับ Webhook ด้วย PHP

ถ้าใช้แบบคิว ให้ตรวจลายเซ็นด้วย hash_hmac และเทียบด้วย hash_equals โดยอ่านเนื้อหาดิบจาก php://input ตามที่คู่มือแสดง และตรวจว่าเวลา t ต่างจากปัจจุบันไม่เกิน 300 วินาที แล้วตอบ 200 ก่อนประมวลผลต่อ

เช็กลิสต์

  1. เก็บ key ไว้นอก web root หรือใน environment variable
  2. ตั้ง timeout และแยกกรณีเครือข่ายล้ม
  3. รองรับ 200 และ 202
  4. ไม่ส่งซ้ำอัตโนมัติเมื่อผลเป็น invalid
  5. เทียบยอดและผู้รับ แล้วอนุมัติแบบ idempotent
  6. ไม่เก็บรูปสลิปในที่สาธารณะ

ตัวอย่างและรายละเอียดอื่นอยู่ที่ คู่มือ API และถ้ายังไม่มีบัญชี เริ่มที่ สมัครสมาชิก

ลองตรวจสลิปอัตโนมัติกับร้านของคุณ

สมัครแล้วเริ่มใช้งานได้ภายในไม่กี่นาที

บทความอื่น ๆ