Skip to content

SDK สำหรับ PHP ​

easyslip/sdk ช่วยให้แอป PHP ของคุณตรวจสลิปโอนเงินของธนาคารไทยและสลิป TrueMoney Wallet กับธนาคารได้ เช็กได้ว่าเงินเข้าบัญชีของคุณ ยอดเงินถูกต้อง และไม่ใช่สลิปซ้ำ รวมถึงรับผลการตรวจเบื้องหลังผ่านเว็บฮุคที่มีลายเซ็นยืนยัน ผลลัพธ์เป็น object ที่มี type ชัดเจน IDE และ PHPStan จึงเข้าใจได้ทันที และส่งคำขอผ่าน HTTP client แบบ PSR-18 ตัวไหนก็ได้ โดยใช้ Guzzle เป็นค่าเริ่มต้น

หน้านี้เป็นคู่มือฉบับเต็ม ส่วน README บน GitHub เป็นฉบับสรุป และดูซอร์สโค้ดได้ที่ EASYSLIP-CO-LTD/sdk-php

สิ่งที่ต้องมี ​

  • PHP 8.2, 8.3, 8.4 หรือ 8.5 ที่เปิด extension fileinfo ไว้ ซึ่ง PHP ส่วนใหญ่มีมาให้แล้ว
  • Composer
  • API key ของ EasySlip เข้าสู่ระบบที่ EasySlip developer portal แล้วคัดลอก API key ของแอปพลิเคชันมาใช้ ดูเรื่อง key และสาขาได้ที่การยืนยันตัวตน

การติดตั้ง ​

sh
composer require easyslip/sdk

คำสั่งนี้ติดตั้ง Guzzle 7.16 ขึ้นไปมาด้วย (ใช้ Guzzle 8.3 ขึ้นไปก็ได้) SDK ใช้ Guzzle ส่งคำขอ ถ้า Composer ถามว่าจะเชื่อถือ plugin php-http/discovery หรือไม่ ตอบแบบไหนก็ได้ เพราะ SDK หา Guzzle ได้เองโดยไม่ต้องใช้ plugin นี้

เริ่มต้นใช้งาน ​

โปรแกรมนี้ตรวจสลิปที่ลูกค้าส่งมาจ่ายออเดอร์ยอด 1,500 บาท โดยเช็กว่าเงินเข้าบัญชีรับเงินที่ลงทะเบียนไว้ ยอดเงินถูกต้อง และสลิปไม่ซ้ำ จากนั้นพิมพ์รายละเอียดการโอนออกมา

บันทึกโค้ดเป็นไฟล์ verify.php ในโฟลเดอร์โปรเจกต์ แล้ววางรูปสลิปไว้ข้างกันในชื่อ slip.jpg

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Exception\SlipNotFoundError;
use EasySlip\Input\FileInput;
use EasySlip\Input\VerifyOptions;

$easyslip = new EasySlip(); // อ่าน API key จาก EASYSLIP_API_KEY
$orderTotal = 1500; // บาท

try {
    $result = $easyslip->verify->bankByImage(FileInput::fromPath(__DIR__ . '/slip.jpg'), new VerifyOptions(
        matchAccount: true, // เงินเข้าบัญชีรับเงินของเราไหม
        matchAmount: $orderTotal, // ยอดเงินถูกไหม
        checkDuplicate: true, // เป็นสลิปซ้ำไหม
    ));
    $slip = $result->rawSlip;

    if ($result->isDuplicate) {
        echo "สลิปนี้เคยใช้แล้ว อย่ายืนยันออเดอร์ซ้ำ\n";
    } elseif (!$result->isAmountMatched) {
        echo "สลิปนี้โอน {$result->amountInSlip} บาท แต่ออเดอร์ยอด {$orderTotal} บาท\n";
    } elseif ($result->matchedAccount === null) {
        echo "สลิปนี้ไม่ได้โอนเข้าบัญชีรับเงินของเรา\n";
    } else {
        echo "ยืนยันการชำระเงินแล้ว\n";
        echo "ยอดเงิน:   {$slip->amount->amount} บาท\n";
        echo "วันที่:     {$slip->date}\n";
        echo "ผู้โอน:    {$slip->sender->account?->name->th} ({$slip->sender->bank?->short})\n";
        echo "ผู้รับ:     {$slip->receiver->account?->name->th} ({$slip->receiver->bank?->short})\n";
        echo "เลขอ้างอิง: {$slip->transRef}\n";
    }
} catch (SlipNotFoundError) {
    echo "ไม่พบรายการโอนนี้ที่ธนาคาร สลิปอาจเป็นของปลอมหรือถูกแก้ไข\n";
}

รันพร้อม API key ของคุณ

sh
EASYSLIP_API_KEY="your-api-key" php verify.php

ถ้าสลิปถูกต้อง จะได้ผลแบบนี้

text
ยืนยันการชำระเงินแล้ว
ยอดเงิน:   1500 บาท
วันที่:     2024-01-15T14:30:00+07:00
ผู้โอน:    นาย ผู้โอน ทดสอบ (KBANK)
ผู้รับ:     บริษัท ตัวอย่าง จำกัด (SCB)
เลขอ้างอิง: 68370160657749I376388B35

matchAccount เทียบบัญชีผู้รับกับบัญชีรับเงินที่คุณลงทะเบียนไว้ จึงต้องเพิ่มบัญชีรับเงินก่อน ทำได้ใน developer portal หรือใช้ bankAccounts->create() การตรวจสลิปแต่ละครั้งใช้โควต้า 1 ครั้ง

ยอดของออเดอร์ต้องดึงจากฐานข้อมูลของคุณเอง ห้ามรับค่าจากลูกค้า ยืนยันออเดอร์เฉพาะเมื่อผ่านครบทั้งสามเงื่อนไข และเก็บ rawSlip->transRef ในคอลัมน์ที่ตั้ง unique constraint ไว้ ภายใน transaction เดียวกับที่บันทึกว่าออเดอร์จ่ายแล้ว เพื่อไม่ให้คำขอสองรายการที่ใช้สลิปใบเดียวกันยืนยันออเดอร์ได้ทั้งคู่

อ่านต่อ

สร้าง client ​

สร้าง client หนึ่งตัวต่อหนึ่ง process แล้วใช้ซ้ำ ส่ง named argument เข้า new EasySlip(...) ตัวเลือกที่ไม่ได้ใส่จะอ่านจากตัวแปร environment ถ้าไม่มีก็ใช้ค่าเริ่มต้น

ตัวเลือกตัวแปร environmentค่าเริ่มต้นใช้ทำอะไร
apiKeyEASYSLIP_API_KEYไม่มีAPI key ของคุณ ถ้าไม่มี key เลย new EasySlip() จะโยน InputError
ไม่มีตัวเลือกEASYSLIP_API_KEY_FILEไม่มีpath ของไฟล์ที่เก็บ API key เช่น Docker secret หรือ Kubernetes secret ใช้เมื่อไม่มีทั้ง apiKey และ EASYSLIP_API_KEY
timeoutไม่มีตัวแปร30.0เวลาสูงสุดของการส่งคำขอแต่ละครั้ง หน่วยวินาที
maxRetriesไม่มีตัวแปร2จำนวนครั้งที่ลองใหม่เมื่อคำขอล้มเหลว ตั้งได้ 0 ถึง 10 ใส่ 0 เพื่อปิด
httpClientไม่มีตัวแปรGuzzle clientclient แบบ PSR-18 ที่ SDK ใช้ส่งคำขอ
requestFactoryไม่มีตัวแปรหาให้ด้วย php-http/discoveryfactory แบบ PSR-17 ที่ SDK ใช้สร้างคำขอ
v2BaseUrlEASYSLIP_V2_BASE_URLhttps://api.easyslip.com/v2ที่อยู่ของ API v2
v1BaseUrlEASYSLIP_V1_BASE_URLhttps://api.easyslip.com/v1ที่อยู่ของ API v1
sleepไม่มีตัวแปรusleep()ฟังก์ชันที่ใช้รอระหว่างการลองใหม่และระหว่างการเช็กงาน รับเวลารอเป็นวินาที ในเทสต์ส่งฟังก์ชันที่คืนค่าทันทีได้
php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;

$easyslip = new EasySlip(
    timeout: 15.0, // ให้เวลาแต่ละครั้ง 15 วินาที
    maxRetries: 1,
);
echo $easyslip->getInfo()->application->name, "\n";

EasySlip กับ Client เป็นคลาสเดียวกัน จะเขียน new Client(...) ก็ได้ ถ้าใช้เฟรมเวิร์ก ให้ลงทะเบียน instance เดียวไว้ใน service container

ใช้ HTTP client ของคุณเอง ​

ส่ง client แบบ PSR-18 ของคุณเข้าไปได้ เช่น เพื่อเก็บ log ส่งผ่าน proxy หรือบันทึกคำขอในเทสต์ ตัวอย่างนี้ครอบ Guzzle ไว้และเก็บ log ทุกคำขอ

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use GuzzleHttp\Client as Guzzle;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

final class LoggingClient implements ClientInterface
{
    public function __construct(private ClientInterface $inner) {}

    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        $started = microtime(true);
        $response = $this->inner->sendRequest($request);
        error_log(sprintf('%s %s %d %.0f ms', $request->getMethod(), $request->getUri(), $response->getStatusCode(), (microtime(true) - $started) * 1000));
        return $response;
    }
}

$easyslip = new EasySlip(httpClient: new LoggingClient(new Guzzle(['timeout' => 30, 'connect_timeout' => 10])));
echo $easyslip->getInfo()->application->name, "\n";

ถ้า httpClient เป็น Guzzle client โดยตรง SDK จะใช้ timeout กับการส่งแต่ละครั้งและปิดการ redirect ให้ ถ้าเป็น Psr18Client ของ Symfony จะใช้ timeout ให้ ส่วน client แบบอื่น เช่น wrapper ด้านบน ต้องตั้ง timeout เองและต้องไม่ follow redirect

ไม่มีอะไรต้องปิด HTTP client จะคืน connection เองเมื่อ object ถูกทำลาย หรืออย่างช้าที่สุดตอน process ของ PHP จบ

ตรวจสลิปธนาคาร ​

เลือกเมธอดตามข้อมูลที่ลูกค้าส่งมา ทั้งสี่เมธอดเรียก POST /verify/bank คืนผลการตรวจแบบเดียวกัน และรับตัวเลือกชุดเดียวกัน

ข้อมูลที่มีเมธอดเอกสาร API
ไฟล์รูป ไฟล์ที่อัปโหลดมา หรือ streamverify->bankByImage($file)อัปโหลดรูป
ข้อความในคิวอาร์โค้ดของสลิปที่อ่านด้วยตัวสแกนverify->bankByPayload($payload)Payload
URL แบบ https ของรูปสลิปที่เปิดได้จากภายนอกverify->bankByUrl($url)URL
รูปที่เข้ารหัสเป็น base64 จะมี data: นำหน้าหรือไม่ก็ได้verify->bankByBase64($base64)Base64
php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;

$easyslip = new EasySlip();

$result = $easyslip->verify->bankByUrl('https://example.com/slips/1001.jpg');
echo $result->rawSlip->transRef, ' ', $result->amountInSlip, "\n";

payload คือข้อความที่เข้ารหัสอยู่ในคิวอาร์โค้ดของสลิป ไม่ใช่เลขอ้างอิงที่พิมพ์บนสลิป ถ้าแอปของคุณสแกนคิวอาร์เองได้ bankByPayload() จะเร็วที่สุดเพราะไม่ต้องอัปโหลดอะไร

รูปแบบรูปที่รับได้ ​

bankByImage() และ truewalletByImage() รับ FileInput ซึ่งสร้างได้ด้วย

  • FileInput::fromPath() จาก path ของไฟล์
  • FileInput::fromBytes() จาก string ที่เป็น bytes ของไฟล์
  • FileInput::fromStream() จาก stream resource ที่เปิดไว้
  • FileInput::fromPsrStream() จาก stream แบบ PSR-7 เช่น ไฟล์ที่อัปโหลดผ่านเฟรมเวิร์ก

รูปต้องเป็น JPEG, PNG, GIF หรือ WebP ขนาดไม่เกิน 4 MB SDK ตรวจจากเนื้อไฟล์ ไม่ได้ดูจากชื่อไฟล์ และจะโยน InputError ถ้าไฟล์ว่าง ใหญ่เกิน 4 MB หรือไม่ใช่รูป

หน้านี้รับสลิปที่อัปโหลดมาทางฟอร์มในช่องชื่อ slip ถ้าเป็น PHP ธรรมดา ไฟล์ที่อัปโหลดจะอยู่ใน $_FILES

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Exception\InvalidImageError;
use EasySlip\Input\FileInput;
use EasySlip\Input\VerifyOptions;

$upload = $_FILES['slip'] ?? null;
$tmpName = is_array($upload) ? ($upload['tmp_name'] ?? null) : null;
if (!is_string($tmpName) || !is_uploaded_file($tmpName)) {
    http_response_code(400);
    echo 'กรุณาแนบรูปสลิปในช่อง "slip"';
    exit;
}

$easyslip = new EasySlip();
try {
    $result = $easyslip->verify->bankByImage(FileInput::fromPath($tmpName), new VerifyOptions(checkDuplicate: true));
    echo "เลขอ้างอิง {$result->rawSlip->transRef} ยอด {$result->amountInSlip} บาท";
} catch (InvalidImageError) {
    http_response_code(422);
    echo 'กรุณาอัปโหลดรูปสลิปที่ชัดเจน';
}

ถ้าเฟรมเวิร์กให้ไฟล์ที่อัปโหลดเป็นแบบ PSR-7 ให้ส่ง FileInput::fromPsrStream($upload->getStream())

เช็กยอดเงิน บัญชีรับเงิน และสลิปซ้ำ ​

เปิดการตรวจที่ต้องการด้วย named argument ของ VerifyOptions ตัวเลือกแต่ละตัวจะเพิ่มฟิลด์ในผลการตรวจ ถ้าไม่ใส่ก็จะไม่มีฟิลด์นั้น เมื่อเงื่อนไขไม่ผ่าน API ยังคืนข้อมูลสลิปมาให้ตามปกติ โค้ดของคุณเป็นคนตัดสินว่าจะรับยอดหรือไม่

ตัวเลือกตรวจอะไรฟิลด์ในผลการตรวจเมื่อไม่ผ่าน
matchAmount: 1500ยอดเงินในสลิปตรงกับยอดที่คาดไว้ หน่วยเป็นบาทamountInOrder, isAmountMatchedisAmountMatched เป็น false
matchAccount: trueผู้รับเงินเป็นบัญชีรับเงินที่ลงทะเบียนไว้matchedAccountmatchedAccount เป็น null
checkDuplicate: trueสลิปนี้เคยถูกตรวจมาแล้วisDuplicateisDuplicate เป็น true
remark: 'Order 1001'ไม่ได้ตรวจอะไร แค่บันทึกโน้ตยาว 1 ถึง 255 ตัวอักษรไว้กับการตรวจครั้งนี้remarkไม่มีกรณีไม่ผ่าน
php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Input\FileInput;
use EasySlip\Input\VerifyOptions;

$easyslip = new EasySlip();

$result = $easyslip->verify->bankByImage(FileInput::fromPath('slip.jpg'), new VerifyOptions(
    remark: 'Order 1001',
    matchAccount: true,
    matchAmount: 1500,
    checkDuplicate: true,
));
$accepted = $result->isAmountMatched === true && $result->matchedAccount !== null && $result->isDuplicate === false;
echo $accepted ? 'รับยอด' : 'ไม่รับยอด', ' ', $result->remark, "\n";

ถ้าสาขาเดียวกันเคยตรวจสลิปนี้แล้ว API จะคืนผลเดิมที่บันทึกไว้และไม่ใช้โควต้า ถ้าเป็นสาขาอื่นที่เคยตรวจ API จะตรวจกับธนาคารใหม่และใช้โควต้า ทั้งสองกรณี isDuplicate เป็น true

matchAccount เทียบเฉพาะบัญชีที่ผูกกับสาขาของ API key ถ้าสลิปที่ควรผ่านกลับได้ matchedAccount เป็น null ให้ตรวจว่าบัญชีรับเงินนั้นผูกกับสาขานี้แล้ว

สลิป TrueMoney Wallet ​

สลิป TrueMoney Wallet ใช้งานแบบเดียวกันและรับตัวเลือกชุดเดียวกัน เมธอดกลุ่มนี้เรียก POST /verify/truewallet และข้อมูลสลิปในผลการตรวจสั้นกว่า

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Input\FileInput;
use EasySlip\Input\VerifyOptions;

$easyslip = new EasySlip();

$result = $easyslip->verify->truewalletByImage(FileInput::fromPath('wallet-receipt.png'), new VerifyOptions(
    matchAmount: 500,
    checkDuplicate: true,
));
echo $result->rawSlip->transactionId, ' ', $result->rawSlip->amount, ' ', $result->rawSlip->receiver->name, "\n";

ใช้ verify->truewalletByUrl() เมื่อมี URL ของรูป และ verify->truewalletByBase64() เมื่อมี base64 สลิป TrueMoney Wallet ไม่มีเมธอดแบบ payload

ข้อมูลบัญชีและโควต้าคงเหลือ ​

getInfo() เรียก GET /info แล้วคืนข้อมูลแอปพลิเคชัน สาขา และโควต้า ถ้าแพ็กเกจไม่จำกัดโควต้า quota->remaining จะเป็น null

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;

$easyslip = new EasySlip();

$info = $easyslip->getInfo();
$quota = $info->application->quota;
echo "{$info->application->name}, สาขา {$info->branch->name}\n";
echo 'ใช้ไป ', $quota->used, ' คงเหลือ ', $quota->remaining ?? 'ไม่จำกัด', "\n";

บัญชีรับเงิน ​

matchAccount เทียบผู้รับเงินในสลิปกับบัญชีกลุ่มนี้ บัญชีรับเงินเป็นของบริการ (service) และแต่ละสาขาจะเทียบเฉพาะบัญชีที่ผูกกับสาขานั้น bankAccounts->create() เพิ่มบัญชีแล้วผูกกับสาขาของ API key ให้ทันที ดูรายละเอียดแต่ละ endpoint ได้ที่บัญชีธนาคาร

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Enum\AccountType;
use EasySlip\Input\CreateBankAccountParams;
use EasySlip\Input\ListBankAccountsParams;
use EasySlip\Input\UpdateBankAccountParams;

$easyslip = new EasySlip();

$account = $easyslip->bankAccounts->create(new CreateBankAccountParams(
    bankCode: '004', // ดูรหัสธนาคารได้จาก $easyslip->banks->list()
    bankNumber: '1234567890',
    nameTh: 'บริษัท ตัวอย่าง จำกัด',
    nameEn: 'EXAMPLE CO., LTD.',
    type: AccountType::JURISTIC,
));
$easyslip->bankAccounts->update($account->id, new UpdateBankAccountParams(nameEn: 'EXAMPLE COMPANY LIMITED'));

// บัญชีที่ผูกกับสาขานี้ทีละหน้า
$page = $easyslip->bankAccounts->list(new ListBankAccountsParams(page: 1, limit: 20));
echo "ผูกกับสาขานี้ {$page->total} บัญชี\n";

// ทุกบัญชีในบริการ SDK ดึงทุกหน้าให้เอง
foreach ($easyslip->bankAccounts->iterateAll() as $item) {
    echo $item->id, ' ', $item->bankNumber, ' ', $item->nameEn, "\n";
}
เมธอดใช้ทำอะไร
bankAccounts->create($params)เพิ่มบัญชีเข้าบริการและผูกกับสาขานี้
bankAccounts->get($id)ดึงข้อมูลบัญชีเดียว
bankAccounts->update($id, $params)แก้เฉพาะฟิลด์ที่ส่งมา ส่ง extraVerify: null เพื่อล้างค่านั้น
bankAccounts->link($id)ผูกบัญชีของบริการกับสาขานี้ ผูกซ้ำก็ไม่เป็นไร
bankAccounts->unlink($id)ยกเลิกการผูกกับสาขานี้ บัญชียังอยู่ในบริการ
bankAccounts->list($params)ดึงบัญชีที่ผูกกับสาขานี้หนึ่งหน้า page เริ่มที่ 1 และ limit อยู่ระหว่าง 1 ถึง 100 (ค่าเริ่มต้น 20)
bankAccounts->listAll($params)ดึงบัญชีทั้งหมดของบริการหนึ่งหน้า แบ่งหน้าแบบเดียวกัน
bankAccounts->iterate($params)ไล่ดูทุกบัญชีที่ผูกกับสาขานี้ ดึงหน้าละ 100 บัญชี
bankAccounts->iterateAll($params)ไล่ดูทุกบัญชีของบริการ ดึงหน้าละ 100 บัญชี

type เป็น AccountType::NATURAL สำหรับบัญชีบุคคลธรรมดา หรือ AccountType::JURISTIC สำหรับบัญชีนิติบุคคล เมธอดดึงรายการทั้งสี่ตัวรับ ListBankAccountsParams ที่กรองด้วย type และ bankCode ได้ ถ้าเพิ่มบัญชีที่มีอยู่แล้วจะได้ ConflictError

รหัสธนาคาร ​

banks->list() เรียก GET /banks แล้วคืนรายชื่อธนาคารทั้งหมดที่ API รู้จัก พร้อม code ที่ใช้เป็น bankCode รายการเดียวกันนี้ดูได้ที่รหัสธนาคาร

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;

$easyslip = new EasySlip();

foreach ($easyslip->banks->list() as $bank) {
    echo $bank->code, ' ', $bank->shortCode, ' ', $bank->nameTh, "\n";
}

ตรวจสลิปจำนวนมากเบื้องหลัง ​

งานเบื้องหลัง (job) จะตรวจสลิปที่ฝั่ง EasySlip แล้วส่งผลไปที่เว็บฮุคของคุณ เหมาะเมื่อมีสลิปจำนวนมากหรือไม่อยากรอธนาคารตอบ ต้องส่ง callbackUrl แบบ https ทุกครั้ง เว้นแต่สาขาตั้ง Default Webhook URL ไว้แล้ว ตรวจเบื้องหลังได้เฉพาะสลิปธนาคาร ดูรายละเอียด endpoint ได้ที่ Async Verify

ตัวอย่างนี้อ่านข้อความคิวอาร์ของสลิปหลายใบจาก payloads.txt บรรทัดละใบ แล้วส่งตรวจเป็นชุดเดียว

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Input\BatchOptions;
use EasySlip\Input\BatchSlip;

$easyslip = new EasySlip();
$payloads = file('payloads.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];

$batch = $easyslip->jobs->submitBankBatch(
    array_map(static fn(string $payload): BatchSlip => new BatchSlip($payload, checkDuplicate: true), $payloads),
    new BatchOptions(callbackUrl: 'https://example.com/webhooks/easyslip'),
);

// เว็บฮุคจะได้รับผลทุกใบ หรือจะรอผลของแต่ละงานตรงนี้ก็ได้
foreach ($batch->jobs as $submitted) {
    $job = $easyslip->jobs->waitForJob($submitted->jobId);
    echo $job->jobId, ' ', $job->status, ' ', $job->result?->status, ' ', $job->result?->data?->amountInSlip, "\n";
}
เมธอดใช้ทำอะไร
jobs->submitBank($payload, $options)ส่งสลิปหนึ่งใบ AsyncVerifyOptions รับตัวเลือกเหมือน VerifyOptions และเพิ่ม callbackUrl
jobs->submitBankBatch($slips, $options)ส่งสลิป 1 ถึง 100 ใบในคำขอเดียว BatchSlip แต่ละใบตั้งตัวเลือกของตัวเองได้ และ BatchOptions เก็บ callbackUrl
jobs->submitBankMany($slips, $options)ส่งสลิปกี่ใบก็ได้ โดยแบ่งเป็นชุดละ 100 ใบแล้วส่งทีละชุด
jobs->get($jobId)ดึงสถานะงาน และผลการตรวจเมื่องานเสร็จ
jobs->waitForJob($jobId, $options)เช็กงานทุก interval วินาที (ค่าเริ่มต้น 2) จนงานเป็น done หรือ failed รอนานสุด timeout วินาที (ค่าเริ่มต้น 600 หรือ 10 นาที) ส่ง WaitOptions เพื่อเปลี่ยนค่าเหล่านี้

สลิปแต่ละใบในงานเบื้องหลังใช้โควต้าตอนที่ถูกตรวจ เหมือนการตรวจโดยตรง การเช็กงานด้วย jobs->get() หรือ waitForJob() ไม่ใช้โควต้าและไม่ส่งสลิปไปตรวจซ้ำ API เก็บงานไว้ประมาณ 7 วัน ถ้าเว็บฮุคพลาดผลไป ก็ยังอ่านผลได้ด้วย jobs->get()

งานจะผ่านสถานะ queued และ retrying ไปจนเป็น done หรือ failed สถานะ done แปลว่าธนาคารตอบแล้ว โดย result->status เป็น success พร้อมผลการตรวจใน result->data หรือเป็น not_found เมื่อลองครบแล้วธนาคารก็ยังไม่มีรายการโอนที่ตรงกัน ส่วน failed แปลว่าตรวจไม่สำเร็จ และ error->code บอกสาเหตุ เช่น QUOTA_EXCEEDED

ถ้า submitBankMany() หยุดกลางทาง จะโยน BatchSubmitError ชุดที่อยู่ใน $error->submitted ถูกรับไปแล้วและยังทำงานต่อ ให้ส่งใหม่เฉพาะสลิปตั้งแต่ชุด $error->failedChunk เป็นต้นไป

ตรวจสลิปหลายใบพร้อมกัน ​

แต่ละเมธอดรอคำตอบจาก API ก่อนแล้วจึงคืนค่า และ SDK ไม่มี API แบบ asynchronous ถ้าต้องตรวจสลิปจำนวนมากโดยไม่ให้คำขอของหน้าเว็บค้างรอ ให้ส่งเป็นงานเบื้องหลังแล้วรับผลทางเว็บฮุค หรือตรวจใน queue worker อย่ายิงคำขอพร้อมกันมากเกินไป เพราะ API จำกัดจำนวนคำขอต่อช่วงเวลา

สร้างคิวอาร์รับเงิน ​

v1->generateQr() สร้างคิวอาร์ PromptPay และคิวอาร์ชำระบิลให้ลูกค้าสแกนจ่ายได้ การสร้างคิวอาร์ไม่ใช้โควต้า amount มีหน่วยเป็นบาท ใส่ได้ตั้งแต่ 0.01 ถึง 9,999,999,999.99 ทศนิยมไม่เกิน 2 ตำแหน่ง ถ้าไม่ใส่ ผู้จ่ายจะกรอกยอดเอง

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Enum\QrType;
use EasySlip\Input\QrRequest;

$easyslip = new EasySlip();

// PromptPay เข้าเบอร์โทร ยอด 250 บาท
$promptPay = $easyslip->v1->generateQr(new QrRequest(QrType::PROMPTPAY, msisdn: '0812345678', amount: 250));
file_put_contents('promptpay.png', $promptPay->imageBytes());

// ชำระบิลเข้า Biller ID ของคุณ ใช้เลขใบแจ้งหนี้เป็น Ref1
$bill = $easyslip->v1->generateQr(new QrRequest(
    QrType::PROMPTPAY,
    billerId: '010555512345601',
    ref1: 'INV1001',
    amount: 1500,
));
file_put_contents('bill.png', $bill->imageBytes());
typeฟิลด์ที่ต้องใส่ฟิลด์ที่ใส่หรือไม่ก็ได้
QrType::PROMPTPAYอย่างใดอย่างหนึ่ง: msisdn (เบอร์โทร), natId (เลขบัตรประชาชนหรือเลขผู้เสียภาษี), eWalletId หรือ billerId ถ้าใช้ billerId ต้องมี ref1 ด้วยamount และใส่ ref2 ได้เมื่อใช้ billerId
QrType::KSHOPref1 คือรหัสร้าน ขึ้นต้นด้วย KB ตามด้วยตัวเลข 12 หลักamount
QrType::MAE_MANEEref1 คือรหัสร้าน 15 หลักamount
QrType::TUNGNGERNref1 คือรหัสร้านค้า และ merchantName คือชื่อร้านภาษาอังกฤษamount

type QrType::BILL_PAYMENT แบบเดิมยังใช้ได้ ถ้าใช้ billerId เดียวกันจะได้คิวอาร์เดียวกับ QrType::PROMPTPAY ผลลัพธ์มี payload ซึ่งเป็นข้อความในคิวอาร์ด้วย ถ้าต้องการวาดคิวอาร์เอง

API v1 แบบเดิม ​

ใช้ $easyslip->v1 เฉพาะระบบที่เชื่อมกับ API v1 อยู่แล้ว โค้ดใหม่ควรใช้เมธอด v2 ด้านบน เมธอดของ v1 คืนข้อมูลสลิปโดยตรง และเมื่อใส่ checkDuplicate: true แล้วเจอสลิปซ้ำจะโยน DuplicateSlipError

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Exception\DuplicateSlipError;
use EasySlip\Input\FileInput;
use EasySlip\Input\V1VerifyOptions;

$easyslip = new EasySlip();

try {
    $slip = $easyslip->v1->verifyBankByImage(FileInput::fromPath('slip.jpg'), new V1VerifyOptions(checkDuplicate: true));
    echo $slip->transRef, ' ', $slip->amount->amount, "\n";
} catch (DuplicateSlipError $error) {
    echo 'สลิปนี้เคยใช้แล้ว วันที่โอน: ', $error->slip?->date, "\n";
}

API v1 ยังมี verifyBankByPayload(), verifyBankByUrl(), verifyBankByBase64(), verifyTruemoneyByImage() และ getMe() ดูสิ่งที่เปลี่ยนใน v2 ได้ที่เปรียบเทียบเวอร์ชัน

ผลการตรวจ ​

การตรวจแบบ v2 คืนผลของเงื่อนไขที่คุณขอไว้ที่ชั้นบนสุด และข้อมูลสลิปตามที่ธนาคารส่งมาไว้ใน rawSlip ดูโครงสร้างเต็มได้ที่เอกสาร API

สลิปธนาคาร ​

verify->bankBy...() คืน BankSlipResult

propertyชนิดความหมายตัวอย่าง
amountInSlipfloatยอดเงินที่โอน หน่วยเป็นบาท1500.0
isAmountMatched?boolยอดเงินตรงกับ matchAmount หรือไม่ มีเฉพาะเมื่อใส่ matchAmount ถ้าไม่ใส่จะเป็น nulltrue
amountInOrder?floatค่า matchAmount ที่ส่งไป มีเฉพาะเมื่อใส่ matchAmount1500.0
matchedAccount?MatchedAccountบัญชีรับเงินของคุณที่สลิปโอนเข้า หรือ null มีเฉพาะเมื่อใส่ matchAccountbankNumber เป็น '123-4-56789-0'
isDuplicate?boolสลิปนี้เคยถูกตรวจแล้วหรือไม่ มีเฉพาะเมื่อใส่ checkDuplicatefalse
remark?stringค่า remark ที่ส่งไป มีเฉพาะเมื่อส่ง'Order 1001'
rawSlip->transRefstringเลขอ้างอิงรายการของธนาคาร แต่ละรายการโอนไม่ซ้ำกัน เก็บไว้เพื่อกันสลิปซ้ำได้'68370160657749I376388B35'
rawSlip->datestringเวลาที่โอน รูปแบบ ISO 8601 พร้อมเขตเวลาไทย'2024-01-15T14:30:00+07:00'
rawSlip->amount->amountfloatยอดเงิน หน่วยเป็นบาท1500.0
rawSlip->fee?floatค่าธรรมเนียม เมื่อธนาคารแจ้งมา0.0
rawSlip->sender->bank?SlipBankธนาคารผู้โอน: id (รหัสธนาคาร), name และ short'004', 'กสิกรไทย', 'KBANK'
rawSlip->sender->account->nameAccountNameชื่อผู้โอนภาษาไทย (th) และอังกฤษ (en) เมื่อธนาคารแสดง'นาย ผู้โอน ทดสอบ', 'MR. SENDER TEST'
rawSlip->sender->account->bank?AccountNumberเลขบัญชีผู้โอน (account) และ type ธนาคารปิดบางหลักไว้'BANKAC', '123-4-xxxxx-5'
rawSlip->receiver->bank?SlipBankธนาคารผู้รับ รูปแบบเดียวกับผู้โอน'014', 'ไทยพาณิชย์', 'SCB'
rawSlip->receiver->account->nameAccountNameชื่อผู้รับ'บริษัท ตัวอย่าง จำกัด'
rawSlip->receiver->account->bank?AccountNumberเลขบัญชีผู้รับ ปิดบางหลักไว้'BANKAC', 'xxx-x-x5678-x'
rawSlip->receiver->account->proxy?AccountNumberPromptPay ที่ใช้รับเงิน เมื่อโอนผ่าน PromptPay'MSISDN', '08xxxxxxxx89'
rawSlip->ref1, ref2, ref3?stringเลขอ้างอิงของการชำระบิล เป็นค่าว่างสำหรับการโอนทั่วไป''

rawSlip->sender->account และ rawSlip->receiver->account อาจเป็น null เมื่อธนาคารไม่ได้ส่งข้อมูลบัญชีมา จึงควรอ่านด้วย ?-> แบบในตัวอย่างเริ่มต้นใช้งาน

สลิป TrueMoney Wallet ​

verify->truewalletBy...() คืน TrueWalletResult ฟิลด์ชั้นบนสุดเหมือนสลิปธนาคาร ส่วน rawSlip มีข้อมูลดังนี้

propertyชนิดความหมายตัวอย่าง
rawSlip->transactionIdstringเลขรายการของ TrueMoney'50012345678901'
rawSlip->datestringเวลาที่ชำระ รูปแบบ ISO 8601'2026-10-26T14:30:00+07:00'
rawSlip->amountfloatยอดเงิน หน่วยเป็นบาท500.0
rawSlip->sender->namestringชื่อผู้โอน'นาย ผู้โอน ทดสอบ'
rawSlip->receiver->namestringชื่อผู้รับ'นาย รับเงิน ทดสอบ'
rawSlip->receiver->phonestringเบอร์โทรผู้รับ ปิดบางหลักไว้'08x-xxx-4567'

งานเบื้องหลังและ event ของเว็บฮุค ​

jobs->get() และ waitForJob() คืน Job ส่วนเว็บฮุคได้รับ WebhookEvent

propertyJobWebhookEventความหมาย
jobIdมีมีID ของงานที่ submitBank() หรือ submitBankBatch() คืนมา
batchIdมีมีID ของชุด เมื่อสลิปถูกส่งมาเป็นชุด ถ้าไม่ใช่จะเป็น null
statusqueued, processing, retrying, done หรือ failedsuccess, not_found หรือ failedงานอยู่ขั้นไหน หรือจบด้วยผลอะไร
result->statusมีไม่มีsuccess หรือ not_found เมื่องานเป็น done แล้ว
result->data, dataมีมีBankSlipResult แบบเดียวกับการเรียกตรง เมื่อตรวจสลิปได้
error->code, error->messageมีมีสาเหตุที่ตรวจไม่สำเร็จ เมื่อสถานะเป็น failed
timestampไม่มีมีเวลาที่ได้ผล

ข้อมูลประกอบของ response ​

ผลการตรวจทุกตัวมี property meta ที่เก็บ HTTP status, requestId, ตัวนับ rateLimit และ headers ของ response ค่านี้ไม่ได้เป็นส่วนของข้อมูล json_encode($result) จึงได้ข้อมูลตามที่ API ส่งมาโดยไม่มี meta แนบ meta->requestId มาด้วยเมื่อติดต่อซัพพอร์ตเรื่องการเรียกครั้งนั้น

การจัดการ error ​

exception ทุกตัวที่ SDK โยนเป็น EasySlipError ซึ่ง extends RuntimeException และอยู่ใน namespace EasySlip\Exception ทั้งหมด ส่วน exception ที่มาจาก API เป็น ApiError ซึ่งมี status, code (เช่น SLIP_NOT_FOUND), getMessage(), requestId และ retryAfter บางคลาสเป็นคลาสย่อยของอีกคลาส ได้แก่ QuotaExceededError เป็น PermissionDeniedError ส่วน InvalidImageError, InvalidUrlError และ DuplicateSlipError เป็น InvalidRequestError ส่วน SlipNotFoundError และ SlipPendingError เป็น NotFoundError ส่วน ServiceUnavailableError เป็น ServerError และ RequestTimeoutError เป็น NetworkError ดูความหมายของ code ทุกตัวที่ API คืนได้ที่รหัส Error

ถ้า SDK รู้จัก code ของ error จะเลือกคลาสจาก code ก่อน ถ้าไม่รู้จักจะเลือกจาก HTTP status ในตาราง และ 4xx อื่นจะได้ ApiError

คลาส exceptionHTTP statusเกิดเมื่อควรทำอย่างไร
AuthenticationError401ไม่มี API key, key ผิด หรือ key ถูกยกเลิกแล้วตรวจ key ที่ส่งเข้าไปหรือ EASYSLIP_API_KEY ถ้าจำเป็นให้สร้าง key ใหม่ใน developer portal
PermissionDeniedError403key ถูกต้องแต่ไม่มีสิทธิ์ทำคำขอนี้ เช่น สาขาถูกปิด แพ็กเกจหมดอายุ หรือ IP ของเซิร์ฟเวอร์ไม่อยู่ในรายการที่อนุญาตดู $error->code แล้วแก้การตั้งค่านั้นใน developer portal
QuotaExceededError403โควต้าหมด คำขอนี้ยังไม่ถูกหักโควต้าเติมโควต้าหรือเปลี่ยนแพ็กเกจ แล้วส่งคำขอใหม่
InvalidRequestError400API ไม่รับค่าบางอย่างในคำขอ เช่น ข้อความคิวอาร์หรือค่าตัวเลือกแก้ค่าที่ $error->getMessage() บอก ส่งคำขอเดิมซ้ำจะได้ error เดิม
InvalidImageError400อ่านคิวอาร์ของสลิปในรูปไม่ได้ ชนิดไฟล์ไม่รองรับ หรือไฟล์ใหญ่เกินไปขอให้ลูกค้าส่งรูป JPEG, PNG, GIF หรือ WebP ที่ชัดและไม่เกิน 4 MB
InvalidUrlError400URL ของรูปหรือ callbackUrl ชี้ไปที่ที่อยู่ภายใน ไม่ได้ใช้ http หรือ https หรือเปิดไม่ได้ใช้ URL แบบ https ที่เปิดได้จากภายนอก
DuplicateSlipError400เฉพาะ API v1 ที่ใส่ checkDuplicate: true: สลิปนี้เคยถูกตรวจแล้ว $error->slip คือผลการตรวจครั้งแรกถือว่าได้รับยอดนี้ไปแล้ว อย่ายืนยันซ้ำ
NotFoundError404ไม่พบบัญชีธนาคาร งาน หรือรายการที่ขอตรวจ ID ที่ส่งเข้าไป
SlipNotFoundError404ธนาคารไม่มีรายการโอนที่ตรงกับสลิปนี้อย่ารับยอด และขอสลิปตัวจริงจากลูกค้า
SlipPendingError404ธนาคารยังไม่เผยแพร่รายการโอนนี้ มักเกิดกับสลิปธนาคารกรุงเทพที่เพิ่งโอนรอประมาณหนึ่งนาทีแล้วตรวจใหม่
ConflictError409บัญชีรับเงินที่กำลังเพิ่มมีอยู่แล้วใช้บัญชีเดิม หาได้ด้วย bankAccounts->iterateAll()
RateLimitError429ส่งคำขอถี่เกินไป $error->retryAfter บอกว่าต้องรอกี่วินาทีลดจำนวนคำขอที่ส่งพร้อมกัน SDK ลองใหม่ให้แล้วก่อนโยน exception นี้
ServerError500, 502, 504; 5xx อื่นยกเว้น 503เกิดปัญหาที่ฝั่ง EasySlipลองใหม่ภายหลัง ถ้ายังเกิดซ้ำให้ติดต่อซัพพอร์ตพร้อม $error->requestId
ServiceUnavailableError503บริการไม่พร้อมใช้งานชั่วคราว เช่น ช่วงต่ออายุโควต้าลองใหม่ในอีกไม่กี่นาที
ApiError4xx อื่น ๆAPI ตอบ error ด้วย status ที่ไม่มีคลาสเฉพาะเก็บ log status, code และ requestId ถ้ายังเกิดอยู่ให้ติดต่อซัพพอร์ต
NetworkErrorเครือข่ายไม่ได้รับคำตอบเพราะ DNS การเชื่อมต่อ หรือ TLS ล้มเหลวตรวจเครือข่ายหรือ proxy แล้วลองใหม่
RequestTimeoutErrorเครือข่ายไม่ได้รับคำตอบภายใน timeoutลองใหม่หรือเพิ่ม timeout การตรวจสลิปครั้งนั้นอาจถูกนับโควต้าไปแล้ว
InputErrorก่อนส่งคำขอSDK เจอปัญหาก่อนส่ง เช่น ไม่มี API key ข้อความคิวอาร์ว่าง รูปเกิน 4 MB หรือไฟล์ไม่ใช่รูป $error->code บอกสาเหตุแก้ค่าที่ส่งเข้าไป
WebhookSignatureErrorก่อนส่งคำขอเว็บฮุคไม่มีลายเซ็น หรือลายเซ็นไม่ตรงตอบเว็บฮุคนั้นด้วย status 400
JobWaitTimeoutErrorก่อนส่งคำขอwaitForJob() รอจนครบ timeout แล้ว $error->lastJob คือสถานะล่าสุดที่เห็นรอนานขึ้น หรือรอผลทางเว็บฮุคแทน
BatchSubmitErrorก่อนส่งคำขอsubmitBankMany() หยุดกลางทาง $error->submitted คือชุดที่ส่งไปแล้ว $error->failedChunk คือลำดับของชุดที่ล้มเหลว นับจาก 0 และ $error->cause คือ error ของชุดนั้นส่งชุดที่ล้มเหลวและชุดถัดไปใหม่ ชุดที่ส่งไปแล้วยังทำงานต่อ

ตัวอย่างนี้ตรวจสลิปที่ลูกค้าอัปโหลดตอนชำระเงิน แล้วแปลง error แต่ละแบบเป็นข้อความที่แสดงให้ลูกค้าเห็น

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\EasySlip;
use EasySlip\Exception\ApiError;
use EasySlip\Exception\InvalidImageError;
use EasySlip\Exception\QuotaExceededError;
use EasySlip\Exception\RateLimitError;
use EasySlip\Exception\SlipNotFoundError;
use EasySlip\Exception\SlipPendingError;
use EasySlip\Input\FileInput;
use EasySlip\Input\VerifyOptions;

function checkPayment(EasySlip $easyslip, string $uploadedFile, float $orderTotal): string
{
    try {
        $result = $easyslip->verify->bankByImage(FileInput::fromPath($uploadedFile), new VerifyOptions(
            matchAccount: true,
            matchAmount: $orderTotal,
            checkDuplicate: true,
        ));
        if ($result->isDuplicate) {
            return 'สลิปนี้ถูกใช้ไปแล้ว';
        }
        if (!$result->isAmountMatched) {
            return "สลิปนี้โอน {$result->amountInSlip} บาท";
        }
        if ($result->matchedAccount === null) {
            return 'สลิปนี้ไม่ได้โอนเข้าบัญชีของเรา';
        }
        return 'ได้รับการชำระเงินแล้ว';
    } catch (SlipNotFoundError) {
        return 'ไม่พบรายการโอนนี้ที่ธนาคาร';
    } catch (SlipPendingError) {
        return 'ธนาคารกำลังดำเนินการรายการนี้ ลองใหม่ในอีกหนึ่งนาที';
    } catch (InvalidImageError) {
        return 'กรุณาอัปโหลดรูปถ่ายหรือภาพหน้าจอสลิปที่ชัดเจน';
    } catch (QuotaExceededError | RateLimitError $error) {
        error_log("ใช้ EasySlip ถึงขีดจำกัดแล้ว: {$error->code} {$error->requestId}");
        return 'ตอนนี้ยังตรวจสลิปไม่ได้ กรุณาลองใหม่อีกครั้ง';
    } catch (ApiError $error) {
        error_log("EasySlip ตอบ error: {$error->status} {$error->code} {$error->requestId}");
        return 'ตรวจสลิปไม่สำเร็จ กรุณาลองใหม่';
    }
}

echo checkPayment(new EasySlip(), __DIR__ . '/slip.jpg', 1500), "\n";

SDK ลองใหม่ให้เมื่อไร ​

SDK ลองส่งคำขอที่ล้มเหลวใหม่ได้สูงสุด maxRetries ครั้ง (ค่าเริ่มต้น 2 ครั้ง) ก่อนลองใหม่ครั้งแรกจะรอประมาณครึ่งวินาที แล้วเพิ่มเวลารอเป็นสองเท่าทุกครั้ง ไม่เกิน 8 วินาที ถ้า API ส่ง Retry-After มา SDK จะรอตามนั้นแทน แต่ถ้าต้องรอนานกว่า 60 วินาทีจะโยน exception ทันที

คำขอลองใหม่เมื่อไม่ลองใหม่เมื่อ
การอ่านข้อมูลและการผูกบัญชี: getInfo(), banks->list(), bankAccounts->list(), listAll(), get() และ link(), jobs->get(), v1->getMe(), v1->generateQr()เครือข่ายล้มเหลว หมดเวลา หรือ API ตอบ 429, 500, 502, 503 หรือ 504API ตอบ 400, 401, 403, 404 หรือ 409
การตรวจสลิปและการส่งงานเบื้องหลัง ซึ่งใช้โควต้าAPI ตอบ 429 หรือเชื่อมต่อไม่สำเร็จก่อนคำขอจะออกจากเซิร์ฟเวอร์คำขอส่งออกไปแล้ว แล้วหมดเวลาหรือได้คำตอบ 5xx
การแก้ไขบัญชีรับเงิน: create(), update() และ unlink()API ตอบ 429 หรือเชื่อมต่อไม่สำเร็จก่อนคำขอจะออกจากเซิร์ฟเวอร์คำขอส่งออกไปแล้ว แล้วหมดเวลาหรือได้คำตอบ 5xx

การตรวจสลิปที่ส่งถึง API แล้วอาจถูกนับโควต้าไปแล้ว แม้จะไม่ได้รับคำตอบกลับมา การลองใหม่อาจทำให้ถูกหักโควต้าซ้ำ SDK จึงให้คุณเป็นคนตัดสินใจเอง ถ้าจะส่งใหม่ ให้ใส่ checkDuplicate: true ไว้ด้วย ถ้าครั้งแรกถูกนับโควต้าไปแล้ว ครั้งที่สองจะได้ผลเดิมที่บันทึกไว้พร้อม isDuplicate เป็น true และไม่ใช้โควต้า

เว็บฮุค ​

เมื่องานเบื้องหลังเสร็จ EasySlip จะส่งคำขอ POST ไปที่ callbackUrl ของคุณ ถ้าสาขาตั้ง Webhook Secret ไว้ คำขอจะมี header X-EasySlip-Signature ให้ตรวจลายเซ็นกับ body ดิบก่อนแปลงหรือเชื่อข้อมูลใด ๆ ในนั้น ดูรายละเอียดของคำขอทั้งหมดได้ที่ Webhook Callback

handler นี้เป็น PHP ธรรมดา PHP เก็บ header X-EasySlip-Signature ไว้ใน $_SERVER['HTTP_X_EASYSLIP_SIGNATURE']

php
<?php

require __DIR__ . '/vendor/autoload.php';

use EasySlip\Exception\InputError;
use EasySlip\Exception\WebhookSignatureError;
use EasySlip\Webhook;

$rawBody = (string) file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_EASYSLIP_SIGNATURE'] ?? null;
$secret = (string) getenv('EASYSLIP_WEBHOOK_SECRET');

try {
    $event = Webhook::constructEvent($rawBody, is_string($signature) ? $signature : null, $secret);
} catch (WebhookSignatureError | InputError) {
    http_response_code(400);
    echo 'invalid webhook';
    exit;
}

if ($event->status === 'success') {
    error_log("ตรวจผ่าน {$event->jobId} {$event->data?->rawSlip->transRef} {$event->data?->amountInSlip}");
} else {
    error_log("ตรวจไม่ผ่าน {$event->jobId} {$event->status} {$event->error?->code}");
}
echo 'ok';

สคริปต์นี้ตอบ 400 เมื่อไม่มีลายเซ็นหรือลายเซ็นไม่ตรง และเมื่อ body ที่ลงลายเซ็นมาไม่ใช่ event ของเว็บฮุค ถ้าใช้ Laravel ให้ส่ง $request->getContent() เป็น body ดิบ และ $request->header(Webhook::SIGNATURE_HEADER) เป็นลายเซ็น บน route ที่ไม่มีการตรวจ CSRF เช่น route ใน routes/api.php ถ้าใช้ Symfony ให้ใช้ $request->getContent() และ $request->headers->get(Webhook::SIGNATURE_HEADER) อย่าแปลง JSON แล้วแปลงกลับเป็นข้อความก่อนตรวจ เพราะ bytes จะเปลี่ยน แล้วลายเซ็นจะไม่ตรง

เมธอดใช้ทำอะไร
Webhook::constructEvent($rawBody, $signature, $secret)ตรวจลายเซ็นแล้วคืน WebhookEvent ที่แปลงแล้ว โยน WebhookSignatureError เมื่อไม่มีลายเซ็นหรือลายเซ็นไม่ตรง และโยน InputError เมื่อ body ไม่ใช่ event ที่ถูกต้อง
Webhook::verifySignature($rawBody, $signature, $secret)คืน true เมื่อลายเซ็นตรง และ false เมื่อไม่ตรง
Webhook::parseEvent($rawBody)แปลง event โดยไม่ตรวจลายเซ็น และโยน InputError เมื่อ body ไม่ใช่ event ใช้หลังจากตรวจลายเซ็นเองแล้วเท่านั้น

$secret คือ Webhook Secret ของสาขา ตั้งค่าได้ใน developer portal ถ้าไม่ได้ตั้งไว้ EasySlip จะส่ง callback มาโดยไม่มีลายเซ็น และ Webhook::constructEvent() จะไม่รับ จึงควรตั้งให้เรียบร้อยก่อนเปิดใช้งานจริง

กติกาการส่งเว็บฮุค

  • ตอบกลับด้วย status 2xx ภายใน 5 วินาที บันทึก event ไว้ก่อนแล้วค่อยทำงานที่ใช้เวลานานทีหลัง เช่น ใน queue job
  • ถ้าตอบด้วย status อื่นที่ไม่ใช่ 2xx หรือตอบไม่ทัน EasySlip จะส่ง event เดิมอีกครั้งหลังผ่านไปประมาณ 10, 30 และ 120 วินาที
  • EasySlip อาจส่ง event เดิมมาซ้ำได้ ให้ใช้ jobId ข้าม event ที่จัดการไปแล้ว
  • ถ้าส่งไม่สำเร็จทุกครั้ง jobs->get($jobId) ยังคืนผลได้ประมาณ 7 วัน

ความปลอดภัยและข้อมูลส่วนบุคคล ​

  • เก็บ API key ไว้ฝั่งเซิร์ฟเวอร์เท่านั้น อย่าใส่ในหน้าเว็บ แอปมือถือ หรือ repository สาธารณะ เพราะใครได้ key ไปก็ใช้โควต้าของคุณได้
  • อ่าน key จาก EASYSLIP_API_KEY หรือไฟล์ secret แทนการเขียนลงในโค้ด
  • SDK ส่ง key ไปใน header Authorization เท่านั้น ไม่ใส่ key ลงในข้อความของ exception และ var_dump() ของ client ก็ไม่แสดง key จึงเก็บ log ของ error ได้อย่างปลอดภัย
  • สลิปมีชื่อ เลขบัญชี และยอดเงิน ซึ่งเป็นข้อมูลส่วนบุคคล เก็บเฉพาะที่จำเป็น ดูแลเหมือนข้อมูลลูกค้าอื่น ๆ และปฏิบัติตาม พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล (PDPA)
  • ตรวจลายเซ็นของเว็บฮุคทุกครั้งก่อนนำข้อมูลไปใช้

เวอร์ชันและนโยบายการรองรับ ​

  • การออกเวอร์ชันใช้ semantic versioning เวอร์ชัน minor และ patch จะไม่ทำให้โค้ดเดิมพัง การเปลี่ยนแปลงที่กระทบโค้ดเดิมจะออกเป็นเวอร์ชัน major ใหม่ พร้อมวิธีอัปเกรดใน changelog
  • SDK รองรับ PHP เวอร์ชันที่โปรเจกต์ PHP ยังออก security fix ให้ เมื่อ PHP เวอร์ชันใดหมดอายุการดูแล SDK เวอร์ชันถัดไปอาจเลิกรองรับเวอร์ชันนั้น และจะแจ้งไว้ใน changelog
  • changelog และหน้า releases รวบรวมทุกเวอร์ชันและสิ่งที่เปลี่ยน

อ่านเพิ่มเติม ​

  • โฟลเดอร์ examples มีโปรแกรมที่รันได้จริงครบทุกเมธอด
  • ภาพรวม API v2 และรหัส Error อธิบายทุก endpoint ฟิลด์ และรหัสข้อผิดพลาด
  • คำถามเรื่องบัญชี โควต้า หรือการเรียกเก็บเงิน ติดต่อ EasySlip ได้ทาง LINE @ai-easy โทร 02-114-8806 หรือหน้าติดต่อเรา โดยแนบ requestId ของการเรียกครั้งนั้นมาด้วย
  • แจ้งบั๊กหรือขอฟีเจอร์ได้ที่ issue ห้ามโพสต์ API key หรือสลิปจริง ส่วนปัญหาด้านความปลอดภัยให้แจ้งแบบส่วนตัวตามที่ SECURITY.md อธิบายไว้

Bank Slip Verification API for Thai Banking