Skip to content

Commit

Permalink
Merge pull request #105 from Midtrans/snapbi-webhook-verification
Browse files Browse the repository at this point in the history
Snapbi webhook verification
  • Loading branch information
uziwuzzy authored Oct 8, 2024
2 parents ebbe912 + 654aa75 commit a24b48c
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 35 deletions.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,9 @@ Standar Nasional Open API Pembayaran, or in short SNAP, is a national payment op
// Set the channel id here.
\SnapBi\Config::$snapBiChannelId = "CHANNEL ID";
// Enable logging to see details of the request/response make sure to disable this on production, the default is disabled.
SnapBiConfig::$enableLogging = false;
\SnapBi\Config::$enableLogging = false;
// Set your public key here if you want to verify your webhook notification, make sure to add \n on the public key, you can refer to the examples
\SnapBi\Config::$snapBiPublicKey = "YOUR PUBLIC KEY"
```

### 3.2 Create Payment
Expand Down Expand Up @@ -948,6 +950,46 @@ $snapBiResponse = SnapBi::va()

### 3.9 Payment Notification
To implement Snap-Bi Payment Notification you can refer to this [docs](https://docs.midtrans.com/reference/payment-notification-api)
To verify the webhook notification that you recieve you can use this method below
```php

//the request body/ payload sent by the webhook
$payload = json_decode(
{
"originalPartnerReferenceNo": "uzi-order-testing67039fa9da813",
"originalReferenceNo": "A120241007084530GSXji4Q5OdID",
"merchantId": "G653420184",
"amount": {
"value": "10000.00",
"currency": "IDR"
},
"latestTransactionStatus": "03",
"transactionStatusDesc": "PENDING",
"additionalInfo": {
"refundHistory": [],
"userPaymentDetails": []
}
};

// to get the signature value, you need to retrieve it from the webhook header called X-Signature
$xSignature = "CgjmAyC9OZ3pB2JhBRDihL939kS86LjP1VLD1R7LgI4JkvYvskUQrPXgjhrZqU2SFkfPmLtSbcEUw21pg2nItQ0KoX582Y6Tqg4Mn45BQbxo4LTPzkZwclD4WI+aCYePQtUrXpJSTM8D32lSJQQndlloJfzoD6Rh24lNb+zjUpc+YEi4vMM6MBmS26PpCm/7FZ7/OgsVh9rlSNUsuQ/1QFpldA0F8bBNWSW4trwv9bE1NFDzliHrRAnQXrT/J3chOg5qqH0+s3E6v/W21hIrBYZVDTppyJPtTOoCWeuT1Tk9XI2HhSDiSuI3pevzLL8FLEWY/G4M5zkjm/9056LTDw==";

// to get the timeStamp value, you need to retrieve it from the webhook header called X-Timestamp
$xTimeStamp = "2024-10-07T15:45:22+07:00";

// the url path is based on the webhook url of the payment method for example for direct debit is `/v1.0/debit/notify`
$notificationUrlPath = "/v1.0/debit/notify"
/**
* Example verifying the webhook notification
*/
$isVerified = SnapBi::notification()
->withBody($payload)
->withSignature($xSignature)
->withTimeStamp($xTimeStamp)
->withNotificationUrlPath($notificationUrlPath)
->isWebhookNotificationVerified()

```

## Unit Test
### Integration Test (sandbox real transactions)
Expand Down
52 changes: 50 additions & 2 deletions SnapBi/SnapBi.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace SnapBi;

use Exception;

/**
* Provide Snap-Bi functionalities (create transaction, refund, cancel, get status)
*/
Expand Down Expand Up @@ -34,6 +36,9 @@ class SnapBi
private $deviceId;
private $debugId;
private $timeStamp;
private $signature;
private $timestamp;
private $notificationUrlPath;

public function __construct($paymentMethod)
{
Expand Down Expand Up @@ -64,7 +69,12 @@ public static function qris()
{
return new self("qris");
}

/**
* this method chain is used to verify webhook notification
*/
public static function notification(){
return new self("");
}
/**
* this method chain is used to add additional header during access token request
*/
Expand Down Expand Up @@ -95,7 +105,7 @@ public function withAccessToken($accessToken)
/**
* this method chain is used to supply the request body/ payload
*/
public function withBody(array $body)
public function withBody($body)
{
$this->body = $body;
return $this;
Expand Down Expand Up @@ -146,6 +156,22 @@ public function withDebuglId($debugId)
return $this;
}

public function withSignature($signature)
{
$this->signature = $signature;
return $this;
}
public function withTimeStamp($timeStamp)
{
$this->timestamp = $timeStamp;
return $this;
}
public function withNotificationUrlPath($notificationUrlPath)
{
$this->notificationUrlPath = $notificationUrlPath;
return $this;
}

/**
* these method chain is used to execute create payment
*/
Expand Down Expand Up @@ -195,6 +221,28 @@ public function getAccessToken()
return SnapBiApiRequestor::remoteCall(SnapBiConfig::getSnapBiTransactionBaseUrl() . self::ACCESS_TOKEN, $snapBiAccessTokenHeader, $openApiPayload);
}

/**
* @throws Exception
*/
public function isWebhookNotificationVerified(){
if (!SnapBiConfig::$snapBiPublicKey){
throw new Exception(
'The public key is null, You need to set the public key from SnapBiConfig.' .
'For more details contact support at [email protected] if you have any questions.'
);
}
$notificationHttpMethod = "POST";
$minifiedNotificationBodyJsonString = json_encode($this->body);
$hashedNotificationBodyJsonString = hash('sha256', $minifiedNotificationBodyJsonString);
$rawStringDataToVerifyAgainstSignature = $notificationHttpMethod . ':' . $this->notificationUrlPath . ':' . $hashedNotificationBodyJsonString . ':' . $this->timestamp;
$isSignatureVerified = openssl_verify(
$rawStringDataToVerifyAgainstSignature,
base64_decode($this->signature),
SnapBiConfig::$snapBiPublicKey,
OPENSSL_ALGO_SHA256
);
return $isSignatureVerified === 1;
}
private function createConnection($externalId = null)
{
// Attempt to get the access token if it's not already set
Expand Down
1 change: 1 addition & 0 deletions SnapBi/SnapBiConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SnapBiConfig
public static $snapBiPartnerId;
public static $snapBiChannelId;
public static $enableLogging = false;
public static $snapBiPublicKey;

const SNAP_BI_SANDBOX_BASE_URL = 'https://merchants.sbx.midtrans.com';
const SNAP_BI_PRODUCTION_BASE_URL = 'https://merchants.midtrans.com';
Expand Down
60 changes: 60 additions & 0 deletions examples/snap-bi/midtrans-webhook-php/notification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
// notification.php

namespace SnapBi;

require_once dirname(__FILE__) . '/../../../Midtrans.php';
// Only allow POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header("HTTP/1.1 405 Method Not Allowed");
echo json_encode(["error" => "Only POST requests are allowed"]);
exit;
}

// Read all headers
$headers = getallheaders();

// Example: Get a specific header (like 'X-Signature')
$xSignature = isset($headers['X-Signature']) ? $headers['X-Signature'] : "kosong";
$xTimeStamp = isset($headers['X-Timestamp']) ? $headers['X-Timestamp'] : "kosong";
$requestUri = $_SERVER['REQUEST_URI'];

// Extract everything after '/notification.php'
$afterNotification = strstr($requestUri, '/notification.php');
$pathAfterNotification = substr($afterNotification, strlen('/notification.php'));

// Read the input/payload from the request body
$input = file_get_contents("php://input");
$payload = json_decode($input);

if (!$input) {
// Respond with an error if no payload is received
header("Content-Type: application/json");
echo json_encode(["error" => "No input received"]);
exit;
}
header("Content-Type: application/json");
$notificationUrlPath = $pathAfterNotification;

$publicKeyString = "-----BEGIN PUBLIC KEY-----\nABCDefghlhuoJgoXiK21s2NIW0+uJb08sHmd/+/Cm7UH7M/oU3VE9oLhU89oOzXZgtsiw7lR8duWJ0w738NfzvkdA5pX8OYnIL+5Hfa/CxvlT4yAX/abcdEFgh\n-----END PUBLIC KEY-----\n";
SnapBiConfig::$snapBiPublicKey = $publicKeyString;


try {
echo json_encode([
"message" => "Webhook verified successfully",
"isVerified" => SnapBi::notification()
->withBody($payload)
->withSignature($xSignature)
->withTimeStamp($xTimeStamp)
->withNotificationUrlPath($notificationUrlPath)
->isWebhookNotificationVerified(),
]);
} catch (\Exception $e) {
echo json_encode([
"message" => "Webhook verification error",
"error" => $e->getMessage()
]);
}


2 changes: 1 addition & 1 deletion examples/snap-bi/snap-bi-cancel.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
)
);
$qrisCancelBody = array(
"originalReferenceNo" => "A120240910091847fYkCqhCH1XID",
"originalReferenceNo" => "A120241003151650FgOcD5vWIVID",
"merchantId" => $merchant_id,
"reason" => "cancel reason",
);
Expand Down
11 changes: 5 additions & 6 deletions examples/snap-bi/snap-bi-direct-debit-payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
"validUpTo" => $valid_until,
"payOptionDetails" => array(
array(
"payMethod" => "DANA",
"payOption" => "DANA",
"payMethod" => "GOPAY",
"payOption" => "GOPAY_WALLET",
"transAmount" => array(
"value" => "100.0",
"value" => "10000.0",
"currency" => "IDR"
)
)
Expand Down Expand Up @@ -82,7 +82,7 @@
array(
"id" => "1",
"price" => array(
"value" => "100.00",
"value" => "10000.00",
"currency" => "IDR"
),
"quantity" => 1,
Expand All @@ -103,9 +103,8 @@
SnapBiConfig::$snapBiPrivateKey = $private_key;
SnapBiConfig::$snapBiClientSecret = $client_secret;
SnapBiConfig::$snapBiPartnerId = $partner_id;
SnapBiConfig::$snapBiChannelId = $partner_id;
SnapBiConfig::$snapBiChannelId = $channel_id;
SnapBiConfig::$enableLogging = false;
SnapBiConfig::$enableLogging = true;

try {

Expand Down
2 changes: 0 additions & 2 deletions examples/snap-bi/snap-bi-qris-payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
*/

$client_id = "Zabcdefg-MIDTRANS-CLIENT-SNAP";

//make sure to add 3 newline "\n" to your private key as shown below
$private_key = "-----BEGIN PRIVATE KEY-----\nABCDEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7Zk6kJjqamLddaN1lK03XJW3vi5zOSA7V+5eSiYeM9tCOGouJewN/Py58wgvRh7OMAMm1IbSZpAbcZbBa1=\n-----END PRIVATE KEY-----\n";
$client_secret = "ABcdefghiSrLJPgKRXqdjaSAuj5WDAbeaXAX8Vn7CWGHuBCfFgABCDVqRLvNZf8BaqPGKaksMjrZDrZqzZEbaA1AYFwBewIWCqLZr4PuvuLBqfTmYIzAbCakHKejABCa";
$partner_id = "partner-id";
$merchant_id = "M001234";
$channel_id = "12345";


date_default_timezone_set('Asia/Jakarta');
$time_stamp = date("c");
$date = new DateTime($time_stamp);
Expand Down
8 changes: 3 additions & 5 deletions examples/snap-bi/snap-bi-refund.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
*/

$client_id = "Zabcdefg-MIDTRANS-CLIENT-SNAP";

//make sure to add 3 newline "\n" to your private key as shown below
$private_key = "-----BEGIN PRIVATE KEY-----\nABCDEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7Zk6kJjqamLddaN1lK03XJW3vi5zOSA7V+5eSiYeM9tCOGouJewN/Py58wgvRh7OMAMm1IbSZpAbcZbBa1=\n-----END PRIVATE KEY-----\n";
$client_secret = "ABcdefghiSrLJPgKRXqdjaSAuj5WDAbeaXAX8Vn7CWGHuBCfFgABCDVqRLvNZf8BaqPGKaksMjrZDrZqzZEbaA1AYFwBewIWCqLZr4PuvuLBqfTmYIzAbCakHKejABCa";
$partner_id = "partner-id";
$merchant_id = "M001234";
$channel_id = "12345";


$external_id = "uzi-order-testing" . uniqid();
date_default_timezone_set('Asia/Jakarta');
$time_stamp = date("c");
Expand All @@ -48,12 +46,12 @@

$qrisRefundBody = array(
"merchantId" => $merchant_id,
"originalPartnerReferenceNo" => "uzi-order-testing66e01a9b8c6bf",
"originalReferenceNo" => "A120240910100828anKJlXgsi6ID",
"originalPartnerReferenceNo" => "uzi-order-testing66feb6218257b",
"originalReferenceNo" => "A1202410031520025F17xSCZWMID",
"partnerRefundNo" => "partner-refund-no-". uniqid(),
"reason" => "refund reason",
"refundAmount" => array(
"value" => "1500.00",
"value" => "100.00",
"currency" => "IDR"
),
"additionalInfo" => array(
Expand Down
24 changes: 6 additions & 18 deletions examples/snap-bi/snap-bi-va-creation.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,20 @@
*/

$client_id = "Zabcdefg-MIDTRANS-CLIENT-SNAP";

//make sure to add 3 newline "\n" to your private key as shown below
$private_key = "-----BEGIN PRIVATE KEY-----\nABCDEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7Zk6kJjqamLddaN1lK03XJW3vi5zOSA7V+5eSiYeM9tCOGouJewN/Py58wgvRh7OMAMm1IbSZpAbcZbBa1=\n-----END PRIVATE KEY-----\n";
$client_secret = "ABcdefghiSrLJPgKRXqdjaSAuj5WDAbeaXAX8Vn7CWGHuBCfFgABCDVqRLvNZf8BaqPGKaksMjrZDrZqzZEbaA1AYFwBewIWCqLZr4PuvuLBqfTmYIzAbCakHKejABCa";
$partner_id = "partner-id";
$merchant_id = "M001234";

$channel_id = "12345";

$external_id = "uzi-order-testing" . uniqid();
$customerVaNo = generateRandomNumber();

$vaParams = array(
"partnerServiceId"=> " 70012",
"customerNo"=> $customerVaNo,
"virtualAccountNo"=> " 70012" . $customerVaNo,
"partnerServiceId"=> " 1234",
"customerNo"=> "0000000000",
"virtualAccountNo"=> " 12340000000000",
"virtualAccountName"=> "Jokul Doe",
"virtualAccountEmail"=> "[email protected]",
"virtualAccountPhone"=> "6281828384858",
Expand All @@ -39,19 +37,9 @@
],
"additionalInfo"=> [
"merchantId"=> $merchant_id,
"bank"=> "mandiri",
"bank"=> "bca",
"flags"=> [
"shouldRandomizeVaNumber"=> false
],
"mandiri"=> [
"billInfo1"=> "bank_name",
"billInfo2"=> "mandiri",
"billInfo3"=> "Name:",
"billInfo4"=> "Budi Utomo",
"billInfo5"=> "Class:",
"billInfo6"=> "Computer Science",
"billInfo7"=> "ID:",
"billInfo8"=> "VT-12345"
"shouldRandomizeVaNumber"=> true
],
"customerDetails"=> [
"firstName"=> "Jokul",
Expand Down Expand Up @@ -118,8 +106,8 @@
SnapBiConfig::$snapBiPrivateKey = $private_key;
SnapBiConfig::$snapBiClientSecret = $client_secret;
SnapBiConfig::$snapBiPartnerId = $partner_id;
SnapBiConfig::$snapBiChannelId = $partner_id;
SnapBiConfig::$snapBiChannelId = $channel_id;
SnapBiConfig::$enableLogging = true;

try {

Expand Down

0 comments on commit a24b48c

Please sign in to comment.