Fiat Currency Asynchronous Notification

# Asynchronous Notification of Collection

This interface is provided by the merchant. The platform requests this interface when the collection is successful When an HTTP request is received, please respond with httpcode=200 (HTTP Response Status Code). Otherwise, 16 notifications will be repeatedly sent within 5 hours.

⚠️⚠️Note: Users might only pay part of the amount, so it is necessary to check the 'actual payment amount'.⚠️⚠️

# Best Practice

  1. To determine successful payment, the orderStatus is 1.
  2. For partial payment determination: orderStatus is 3. Merchants should handle this according to their own business logic.
  3. On refund determination: orderStatus is 2. This situation generally occurs in case of customer complaints and bank risk control returns. Merchants should record related information and handle it according to their own business processes (a callback success will be received before getting this callback status).
  4. If the merchant's business logic fails to process, please change the HTTP response status code to 400 or 500 (non-200).
  5. Cheezeepay may send multiple callbacks with the same status. The merchant should handle this compatibility accordingly. If the merchant's own business has been processed successfully, they must return the HTTP response status code to 200.
  6. Please ensure to verify the callback IP➕signature verification.
Parameter Type Required Description Example
merchantId string Y Merchant ID CH10001165
mchOrderNo string Y Merchant Order Number 20240123172337
platOrderNo string Y Platform Order Number 1749724564009521152
orderStatus int Y Order Status 1-Success 2-Refund 3-Partial Payment
payAmount string Y Order Actual Payment Amount 800
amountCurrency string Y Order Amount Currency THB
fee string Y Handling Fee 80
feeCurrency string Y Handling Fee Currency THB
gmtEnd long Y Completion Time (Timestamp: Milliseconds) 1706003885000
sign string Y Signature FMZBnLfSfsWu1ZhHXBFifsexm9dGB5ZFt3RmVZ
AkU9Ck1vE5tzMZGHkOon8mfqK8yhd9E7gQuiWX
yqppWr6rlASviTinBEAjV5y3OqK0piA8bNyhGpR/W
4XPAaDKFyQ54IUbwypZwcBRJ9i5jKkTvOFh5YC+
TWFHkxfA0sK47xrixodVnHe+88hWSR3/oQdCMnN
4eGQN69IbpFILvC+PXQBfpuWTzHMPENuClm3nq
5mK/k7o5Nha9drHyJbPBO54t3Z+5L/aju7lfA9OgyV0
Ss1BHmhyugSx8gyMMSo1uDL1LYWWNmMgj8VO
BUeHNSFPK3Cio7Ko0Bh1TreV7bCa1A==

# Asynchronous Notification Message Example

{
  "merchantId":"CH10001165",
  "mchOrderNo":"20240123172337",
  "platOrderNo":"1749724564009521152",
  "orderStatus":1,
  "payAmount":"800",
  "amountCurrency":"THB",
  "fee":"80",
  "feeCurrency":"THB",
  "gmtEnd":1706003885000,
  "sign":"FMZBnLfSfsWu1ZhHXBFifsexm9dGB5ZFt3RmVZAkU9Ck1vE5tzMZGHkOon8mfqK8yhd9E7gQuiWXyqppWr6rlASviTinBEAjV5y3OqK0piA8bNyhGpR/W4XPAaDKFyQ54IUbwypZwcBRJ9i5jKkTvOFh5YC+TWFHkxfA0sK47xrixodVnHe+88hWSR3/oQdCMnN4eGQN69IbpFILvC+PXQBfpuWTzHMPENuClm3nq5mK/k7o5Nha9drHyJbPBO54t3Z+5L/aju7lfA9OgyV0Ss1BHmhyugSx8gyMMSo1uDL1LYWWNmMgj8VOBUeHNSFPK3Cio7Ko0Bh1TreV7bCa1A=="
}

# Asynchronous Notification Code Implementation Demo

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@RestController
@RequestMapping("callback")
public class TestCallbackController {

    /**
     * Platform public key
     */
    public static String PLATFORM_PUB_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dad35S74jfLPbHJh8P0jDHiTvkxwrtITK97ovVu19B24UdiHyHoEZgtNlS6alFQj1ULQ71d6EPh2rWCNkS2b5HGQXwDYBtwvesVQ8h4Sf3eVPTTLGw3BS7Os4vtDEN6BezMdv3sUG2N5i6JF+5H4CQTq3MD2Cx6u/Cv7oFOdFqeDT0AH+TR7uyZxn69OtkJaHHr834EUcdShJKKMQtbC11WCcut7ilDUgdvZnThiVTq7cfl8mcC9FDKcQ9bMWamScWIB5cJQdUW23Kr0c1NvZlpgPS8U5VODM4Uc4muHJPD2cJmquuJ+4AGP36rEk27lUB3h7B6JI1QGiuh1yyPDwIDAQAB";

    /**
     * payInCallback
     * @param paramMap
     * @param response
     */
    @PostMapping("payIn")
    public void payInCallback(@RequestBody Map<String, Object> paramMap, HttpServletResponse response) throws Exception{
        boolean verifyResult = CheeseTradeRSAUtil.verifySign(paramMap, PLATFORM_PUB_KEY);
        if (verifyResult) {
            //Signature verification successful
            //Order notification business processing logic
            String merchantId = (String)paramMap.get("merchantId");
            String mchOrderNo = (String)paramMap.get("mchOrderNo");
            String platOrderNo = (String)paramMap.get("platOrderNo");
            Integer orderStatus = (Integer)paramMap.get("orderStatus");
            String payAmount = (String)paramMap.get("payAmount");
            String amountCurrency = (String)paramMap.get("amountCurrency");
            String fee = (String)paramMap.get("fee");
            String feeCurrency = (String)paramMap.get("feeCurrency");
            Long gmtEnd = (Long)paramMap.get("gmtEnd");
        } else {
            //Signature verification failed
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }
    }

}
<?php
//This code has been verified in PHP5.6 version
class TestCallbackController 
{

    /**
     * Platform public key
     */
    const PLATFORM_PUB_KEY = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyVqixSZsLJ75yL7r1SwFn0QsOrDAIv4JU6PCIyLwH5Y1m7QY3nm5k+D4VX9qNvTq9R6CT9fPuXH8AVfx7iDCahHA0j//SiTUNlRqgXZVwrhYoytW4PqSl8+QkMimS6Mm9zPC8JsyaUrv0DfIfVxLPZ42JMrKo6+/neIORTduhyO8VYcfNbFAa+fzKl1B0dmXyvat9BmMDYexKPtqKb3WkXKzAERPSizdbib6GvpV6boEnEDlyLT32rtWNEb26NWvmyiaQjBb2J32sDHSPga5kOT8L1dyi3YA4T2JtHMCAMyw9hPz7tjtl1XwSBsJ3h4q9Zra6G2K+NfPxQsBIFVgtwIDAQAB
-----END PUBLIC KEY-----';

    /**
     * payInCallback
     * @param paramMap
     * @param response
     */
    public function payInCallback($paramMap, $response) {
        //Sign
        $Sign = $paramMap["sign"];
        
        //$paramMap need remove sign
        unset($paramMap['sign']); 
        
        //Splicing callback parameters
        $keys = array_keys($paramMap);
        sort($keys);
        $str = '';
        foreach ($keys as $key) {
            $str .=  $key . '=' . $paramMap[$key] . '&';
        }
        $str = rtrim($str, '&');
        echo $str;

        //Verify the signature    
        $verifyResult = verifySign($str,$Sign ,self::PLATFORM_PUB_KEY);
        if ($verifyResult) {
        //Verification is successful, you can implement your logic
            //Signature verification successful
            //Order notification business processing logic
            $merchantId = $paramMap["merchantId"];
            $mchOrderNo = $paramMap["mchOrderNo"];
            $platOrderNo = $paramMap["platOrderNo"];
            $orderStatus = $paramMap["orderStatus"];
            $payAmount = $paramMap["payAmount"];
            $amountCurrency = $paramMap["amountCurrency"];
            $fee = $paramMap["fee"];
            $feeCurrency = $paramMap["feeCurrency"];
            $gmtEnd = $paramMap["gmtEnd"];
        //After implementing the logic, we finally need to return an HTTP200 response to us.
        } else {
            //Signature verification failed
            http_response_code(400);
        }
    }

}
//校验签名
function verifySign($data, $sign, $pubKey){
    $sign = base64_decode($sign);
    $key = openssl_pkey_get_public($pubKey);
    $result = openssl_verify($data, $sign, $key, OPENSSL_ALGO_SHA256) === 1;
    return $result;
}

?>

const express = require('express');
const bodyParser = require('body-parser');
//The functions of the introduced signature tool class (CheeseTradeRSAUtil). 
//The signature tool class can refer to the DEMO in "Signature & Verification Algorithm"
const  {verifySign}= require('./CheeseTradeRSAUtil');

const app = express();
//Parsing JSON request body
app.use(bodyParser.json()); 

// Platform public key
const platPublicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dad35S74jfLPbHJh8P0jDHiTvkxwrtITK97ovVu19B24UdiHyHoEZgtNlS6alFQj1ULQ71d6EPh2rWCNkS2b5HGQXwDYBtwvesVQ8h4Sf3eVPTTLGw3BS7Os4vtDEN6BezMdv3sUG2N5i6JF+5H4CQTq3MD2Cx6u/Cv7oFOdFqeDT0AH+TR7uyZxn69OtkJaHHr834EUcdShJKKMQtbC11WCcut7ilDUgdvZnThiVTq7cfl8mcC9FDKcQ9bMWamScWIB5cJQdUW23Kr0c1NvZlpgPS8U5VODM4Uc4muHJPD2cJmquuJ+4AGP36rEk27lUB3h7B6JI1QGiuh1yyPDwIDAQAB
-----END PUBLIC KEY-----`;

// callback interface
app.post('/callback/payIn', (req, res) => {
    const paramMap = req.body; // Get the parameters in the request body

    try {
        const verifyResult = verifySign(paramMap,platPublicKey);
        if (verifyResult) {
            // Signature verification successful
            const merchantId = paramMap.merchantId;
            const mchOrderNo = paramMap.mchOrderNo;
            const platOrderNo = paramMap.platOrderNo;
            const orderStatus = paramMap.orderStatus;
            const payAmount = paramMap.payAmount;
            const amountCurrency = paramMap.amountCurrency;
            const fee = paramMap.fee;
            const feeCurrency = paramMap.feeCurrency;
            const gmtEnd = paramMap.gmtEnd;

            // Order notification business processing logic
            console.log('Signature verification successful, order processed:', {
                merchantId,
                mchOrderNo,
                platOrderNo,
                orderStatus,
                payAmount,
                amountCurrency,
                fee,
                feeCurrency,
                gmtEnd,
            });

            // Return a successful response
            res.status(200).json({ message: 'Successful processing' });
        } else {
            // Signature verification failed
            res.status(400).json({ message: 'Signature verification failed' });
        }
    } catch (error) {
        // Handling Exceptions
        console.error('An error occurred while processing the callback:', error);
        res.status(500).json({ message: 'Internal server error' });
    }
});