├── .drone.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── Midtrans.php ├── Midtrans ├── ApiRequestor.php ├── Config.php ├── CoreApi.php ├── Notification.php ├── Sanitizer.php ├── Snap.php ├── SnapApiRequestor.php └── Transaction.php ├── README.md ├── SnapBi ├── SnapBi.php ├── SnapBiApiRequestor.php └── SnapBiConfig.php ├── composer.json ├── data └── cacert.pem ├── examples ├── core-api │ ├── checkout-process.php │ ├── checkout.php │ ├── tokenization-process.php │ └── transaction-manipulation.php ├── notification-handler.php ├── snap-bi │ ├── midtrans-webhook-php │ │ └── notification.php │ ├── snap-bi-cancel.php │ ├── snap-bi-direct-debit-payment.php │ ├── snap-bi-qris-payment.php │ ├── snap-bi-refund.php │ ├── snap-bi-status.php │ └── snap-bi-va-creation.php ├── snap-redirect │ ├── checkout-process.php │ └── index.php └── snap │ ├── checkout-process-simple-version.php │ ├── checkout-process.php │ └── index.php ├── maintaining.md ├── phpunit.xml ├── test_bootstrap.php └── tests ├── MT_Tests.php ├── MidtransApiRequestorTest.php ├── MidtransConfigTest.php ├── MidtransCoreApiTest.php ├── MidtransNotificationTest.php ├── MidtransSanitizerTest.php ├── MidtransSnapApiRequestorTest.php ├── MidtransSnapTest.php ├── MidtransTransactionTest.php ├── integration ├── CoreApiIntegrationTest.php ├── IntegrationTest.php ├── NotificationIntegrationTest.php ├── SnapIntegrationTest.php └── TransactionIntegrationTest.php └── utility ├── MtFixture.php └── fixture └── mt_charge.json /.drone.yml: -------------------------------------------------------------------------------- 1 | image: bradrydzewski/php:5.5 2 | script: 3 | - echo "Hello World" 4 | notify: 5 | email: 6 | recipients: 7 | - radityo.shaddiqa@veritrans.co.id -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | vendor/ 4 | .idea 5 | .DS_STORE -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 7.4 4 | - 7.2 5 | - 7.0 6 | - 5.6 7 | # - 5.5 # PHP version too old 8 | # - 5.4 # PHP version too old 9 | # - hhvm # HHVM officially drop PHP support 10 | 11 | before_script: 12 | - composer install 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Midtrans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Midtrans.php: -------------------------------------------------------------------------------- 1 | = 5.4 required'); 7 | } 8 | 9 | // Check PHP Curl & json decode capabilities. 10 | if (!function_exists('curl_init') || !function_exists('curl_exec')) { 11 | throw new Exception('Midtrans needs the CURL PHP extension.'); 12 | } 13 | if (!function_exists('json_decode')) { 14 | throw new Exception('Midtrans needs the JSON PHP extension.'); 15 | } 16 | 17 | // Configurations 18 | require_once 'Midtrans/Config.php'; 19 | 20 | // Midtrans API Resources 21 | require_once 'Midtrans/Transaction.php'; 22 | 23 | // Plumbing 24 | require_once 'Midtrans/ApiRequestor.php'; 25 | require_once 'Midtrans/Notification.php'; 26 | require_once 'Midtrans/CoreApi.php'; 27 | require_once 'Midtrans/Snap.php'; 28 | require_once 'SnapBi/SnapBi.php'; 29 | require_once 'SnapBi/SnapBiApiRequestor.php'; 30 | require_once 'SnapBi/SnapBiConfig.php'; 31 | 32 | // Sanitization 33 | require_once 'Midtrans/Sanitizer.php'; 34 | -------------------------------------------------------------------------------- /Midtrans/ApiRequestor.php: -------------------------------------------------------------------------------- 1 | $url, 98 | CURLOPT_HTTPHEADER => array( 99 | 'Content-Type: application/json', 100 | 'Accept: application/json', 101 | 'User-Agent: midtrans-php-v2.6.2', 102 | 'Authorization: Basic ' . base64_encode($server_key . ':') 103 | ), 104 | CURLOPT_RETURNTRANSFER => 1 105 | ); 106 | 107 | // Set append notification to header 108 | if (Config::$appendNotifUrl) Config::$curlOptions[CURLOPT_HTTPHEADER][] = 'X-Append-Notification: ' . Config::$appendNotifUrl; 109 | // Set override notification to header 110 | if (Config::$overrideNotifUrl) Config::$curlOptions[CURLOPT_HTTPHEADER][] = 'X-Override-Notification: ' . Config::$overrideNotifUrl; 111 | // Set payment idempotency-key to header 112 | if (Config::$paymentIdempotencyKey) Config::$curlOptions[CURLOPT_HTTPHEADER][] = 'Idempotency-Key: ' . Config::$paymentIdempotencyKey; 113 | 114 | // merging with Config::$curlOptions 115 | if (count(Config::$curlOptions)) { 116 | // We need to combine headers manually, because it's array and it will no be merged 117 | if (Config::$curlOptions[CURLOPT_HTTPHEADER]) { 118 | $mergedHeaders = array_merge($curl_options[CURLOPT_HTTPHEADER], Config::$curlOptions[CURLOPT_HTTPHEADER]); 119 | $headerOptions = array(CURLOPT_HTTPHEADER => $mergedHeaders); 120 | } else { 121 | $mergedHeaders = array(); 122 | $headerOptions = array(CURLOPT_HTTPHEADER => $mergedHeaders); 123 | } 124 | 125 | $curl_options = array_replace_recursive($curl_options, Config::$curlOptions, $headerOptions); 126 | } 127 | 128 | if ($method != 'GET') { 129 | 130 | if ($data_hash) { 131 | $body = json_encode($data_hash); 132 | $curl_options[CURLOPT_POSTFIELDS] = $body; 133 | } else { 134 | $curl_options[CURLOPT_POSTFIELDS] = ''; 135 | } 136 | 137 | if ($method == 'POST') { 138 | $curl_options[CURLOPT_POST] = 1; 139 | } elseif ($method == 'PATCH') { 140 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH'); 141 | } 142 | } 143 | 144 | curl_setopt_array($ch, $curl_options); 145 | 146 | // For testing purpose 147 | if (class_exists('\Midtrans\MT_Tests') && MT_Tests::$stubHttp) { 148 | $result = self::processStubed($curl_options, $url, $server_key, $data_hash, $method); 149 | } else { 150 | $result = curl_exec($ch); 151 | // curl_close($ch); 152 | } 153 | 154 | 155 | if ($result === false) { 156 | throw new Exception('CURL Error: ' . curl_error($ch), curl_errno($ch)); 157 | } else { 158 | try { 159 | $result_array = json_decode($result); 160 | } catch (Exception $e) { 161 | throw new Exception("API Request Error unable to json_decode API response: ".$result . ' | Request url: '.$url); 162 | } 163 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 164 | if (isset($result_array->status_code) && $result_array->status_code >= 401 && $result_array->status_code != 407) { 165 | throw new Exception('Midtrans API is returning API error. HTTP status code: ' . $result_array->status_code . ' API response: ' . $result, $result_array->status_code); 166 | } elseif ($httpCode >= 400) { 167 | throw new Exception('Midtrans API is returning API error. HTTP status code: ' . $httpCode . ' API response: ' . $result, $httpCode); 168 | } else { 169 | return $result_array; 170 | } 171 | } 172 | } 173 | 174 | private static function processStubed($curl, $url, $server_key, $data_hash, $method) 175 | { 176 | MT_Tests::$lastHttpRequest = array( 177 | "url" => $url, 178 | "server_key" => $server_key, 179 | "data_hash" => $data_hash, 180 | $method => $method, 181 | "curl" => $curl 182 | ); 183 | 184 | return MT_Tests::$stubHttpResponse; 185 | } 186 | } -------------------------------------------------------------------------------- /Midtrans/Config.php: -------------------------------------------------------------------------------- 1 | 'credit_card' 23 | ); 24 | 25 | if (isset($params['item_details'])) { 26 | $gross_amount = 0; 27 | foreach ($params['item_details'] as $item) { 28 | $gross_amount += $item['quantity'] * $item['price']; 29 | } 30 | $payloads['transaction_details']['gross_amount'] = $gross_amount; 31 | } 32 | 33 | $payloads = array_replace_recursive($payloads, $params); 34 | 35 | if (Config::$isSanitized) { 36 | Sanitizer::jsonRequest($payloads); 37 | } 38 | 39 | return ApiRequestor::post( 40 | Config::getBaseUrl() . '/v2/charge', 41 | Config::$serverKey, 42 | $payloads 43 | ); 44 | } 45 | 46 | /** 47 | * Capture pre-authorized transaction 48 | * 49 | * @param string $param Order ID or transaction ID, that you want to capture 50 | * @return mixed 51 | * @throws Exception 52 | */ 53 | public static function capture($param) 54 | { 55 | $payloads = array( 56 | 'transaction_id' => $param, 57 | ); 58 | 59 | return ApiRequestor::post( 60 | Config::getBaseUrl() . '/v2/capture', 61 | Config::$serverKey, 62 | $payloads 63 | ); 64 | } 65 | 66 | /** 67 | * Do `/v2/card/register` API request to Core API 68 | * 69 | * @param $cardNumber 70 | * @param $expMoth 71 | * @param $expYear 72 | * @return mixed 73 | * @throws Exception 74 | */ 75 | public static function cardRegister($cardNumber, $expMoth, $expYear) 76 | { 77 | $path = "/card/register?card_number=" . $cardNumber 78 | . "&card_exp_month=" . $expMoth 79 | . "&card_exp_year=" . $expYear 80 | . "&client_key=" . Config::$clientKey; 81 | 82 | return ApiRequestor::get( 83 | Config::getBaseUrl() . "/v2" . $path, 84 | Config::$clientKey, 85 | false 86 | ); 87 | } 88 | 89 | /** 90 | * Do `/v2/token` API request to Core API 91 | * 92 | * @param $cardNumber 93 | * @param $expMoth 94 | * @param $expYear 95 | * @param $cvv 96 | * @return mixed 97 | * @throws Exception 98 | */ 99 | public static function cardToken($cardNumber, $expMoth, $expYear, $cvv) 100 | { 101 | $path = "/token?card_number=" . $cardNumber 102 | . "&card_exp_month=" . $expMoth 103 | . "&card_exp_year=" . $expYear 104 | . "&card_cvv=" . $cvv 105 | . "&client_key=" . Config::$clientKey; 106 | 107 | return ApiRequestor::get( 108 | Config::getBaseUrl() . "/v2" . $path, 109 | Config::$clientKey, 110 | false 111 | ); 112 | } 113 | 114 | /** 115 | * Do `/v2/point_inquiry/` API request to Core API 116 | * 117 | * @param string tokenId - tokenId of credit card (more params detail refer to: https://api-docs.midtrans.com) 118 | * @return mixed 119 | * @throws Exception 120 | */ 121 | public static function cardPointInquiry($tokenId) 122 | { 123 | return ApiRequestor::get( 124 | Config::getBaseUrl() . '/v2/point_inquiry/' . $tokenId, 125 | Config::$serverKey, 126 | false 127 | ); 128 | } 129 | 130 | /** 131 | * Create `/v2/pay/account` API request to Core API 132 | * 133 | * @param string create pay account request (more params detail refer to: https://api-docs.midtrans.com/#create-pay-account) 134 | * @return mixed 135 | * @throws Exception 136 | */ 137 | public static function linkPaymentAccount($param) 138 | { 139 | return ApiRequestor::post( 140 | Config::getBaseUrl() . '/v2/pay/account', 141 | Config::$serverKey, 142 | $param 143 | ); 144 | } 145 | 146 | /** 147 | * Do `/v2/pay/account/` API request to Core API 148 | * 149 | * @param string accountId (more params detail refer to: https://api-docs.midtrans.com/#get-pay-account) 150 | * @return mixed 151 | * @throws Exception 152 | */ 153 | public static function getPaymentAccount($accountId) 154 | { 155 | return ApiRequestor::get( 156 | Config::getBaseUrl() . '/v2/pay/account/' . $accountId, 157 | Config::$serverKey, 158 | false 159 | ); 160 | } 161 | 162 | /** 163 | * Unbind `/v2/pay/account//unbind` API request to Core API 164 | * 165 | * @param string accountId (more params detail refer to: https://api-docs.midtrans.com/#unbind-pay-account) 166 | * @return mixed 167 | * @throws Exception 168 | */ 169 | public static function unlinkPaymentAccount($accountId) 170 | { 171 | return ApiRequestor::post( 172 | Config::getBaseUrl() . '/v2/pay/account/' . $accountId . '/unbind', 173 | Config::$serverKey, 174 | false 175 | ); 176 | } 177 | 178 | /** 179 | * Create `/v1/subscription` API request to Core API 180 | * 181 | * @param string create subscription request (more params detail refer to: https://api-docs.midtrans.com/#create-subscription) 182 | * @return mixed 183 | * @throws Exception 184 | */ 185 | public static function createSubscription($param) 186 | { 187 | return ApiRequestor::post( 188 | Config::getBaseUrl() . '/v1/subscriptions', 189 | Config::$serverKey, 190 | $param 191 | ); 192 | } 193 | 194 | /** 195 | * Do `/v1/subscription/` API request to Core API 196 | * 197 | * @param string get subscription request (more params detail refer to: https://api-docs.midtrans.com/#get-subscription) 198 | * @return mixed 199 | * @throws Exception 200 | */ 201 | public static function getSubscription($SubscriptionId) 202 | { 203 | return ApiRequestor::get( 204 | Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId, 205 | Config::$serverKey, 206 | false 207 | ); 208 | } 209 | 210 | /** 211 | * Do disable `/v1/subscription//disable` API request to Core API 212 | * 213 | * @param string disable subscription request (more params detail refer to: https://api-docs.midtrans.com/#disable-subscription) 214 | * @return mixed 215 | * @throws Exception 216 | */ 217 | public static function disableSubscription($SubscriptionId) 218 | { 219 | return ApiRequestor::post( 220 | Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId . '/disable', 221 | Config::$serverKey, 222 | false 223 | ); 224 | } 225 | 226 | /** 227 | * Do enable `/v1/subscription//enable` API request to Core API 228 | * 229 | * @param string enable subscription request (more params detail refer to: https://api-docs.midtrans.com/#enable-subscription) 230 | * @return mixed 231 | * @throws Exception 232 | */ 233 | public static function enableSubscription($SubscriptionId) 234 | { 235 | return ApiRequestor::post( 236 | Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId . '/enable', 237 | Config::$serverKey, 238 | false 239 | ); 240 | } 241 | 242 | /** 243 | * Do update subscription `/v1/subscription/` API request to Core API 244 | * 245 | * @param string update subscription request (more params detail refer to: https://api-docs.midtrans.com/#update-subscription) 246 | * @return mixed 247 | * @throws Exception 248 | */ 249 | public static function updateSubscription($SubscriptionId, $param) 250 | { 251 | return ApiRequestor::patch( 252 | Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId, 253 | Config::$serverKey, 254 | $param 255 | ); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /Midtrans/Notification.php: -------------------------------------------------------------------------------- 1 | order_id; 16 | * echo $notif->transaction_status; 17 | * ``` 18 | */ 19 | class Notification 20 | { 21 | private $response; 22 | 23 | public function __construct($input_source = "php://input") 24 | { 25 | $raw_notification = json_decode(file_get_contents($input_source), true); 26 | $status_response = Transaction::status($raw_notification['transaction_id']); 27 | $this->response = $status_response; 28 | } 29 | 30 | public function __get($name) 31 | { 32 | if (isset($this->response->$name)) { 33 | return $this->response->$name; 34 | } 35 | } 36 | 37 | public function getResponse() 38 | { 39 | return $this->response; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Midtrans/Sanitizer.php: -------------------------------------------------------------------------------- 1 | filters = array(); 19 | } 20 | 21 | /** 22 | * Validates and modify data 23 | * 24 | * @param mixed[] $json 25 | */ 26 | public static function jsonRequest(&$json) 27 | { 28 | $keys = array('item_details', 'customer_details'); 29 | foreach ($keys as $key) { 30 | if (!isset($json[$key])) continue; 31 | $camel = static::upperCamelize($key); 32 | $function = "field$camel"; 33 | static::$function($json[$key]); 34 | } 35 | } 36 | 37 | private static function fieldItemDetails(&$items) 38 | { 39 | foreach ($items as &$item) { 40 | $id = new self; 41 | $item['id'] = $id 42 | ->maxLength(50) 43 | ->apply($item['id']); 44 | $name = new self; 45 | $item['name'] = $name 46 | ->maxLength(50) 47 | ->apply($item['name']); 48 | } 49 | } 50 | 51 | private static function fieldCustomerDetails(&$field) 52 | { 53 | if (isset($field['first_name'])) { 54 | $first_name = new self; 55 | $field['first_name'] = $first_name->maxLength(255)->apply($field['first_name']); 56 | } 57 | 58 | if (isset($field['last_name'])) { 59 | $last_name = new self; 60 | $field['last_name'] = $last_name->maxLength(255)->apply($field['last_name']); 61 | } 62 | 63 | if (isset($field['email'])) { 64 | $email = new self; 65 | $field['email'] = $email->maxLength(255)->apply($field['email']); 66 | } 67 | 68 | if (isset($field['phone'])) { 69 | $phone = new self; 70 | $field['phone'] = $phone->maxLength(255)->apply($field['phone']); 71 | } 72 | 73 | if (!empty($field['billing_address']) || !empty($field['shipping_address'])) { 74 | $keys = array('billing_address', 'shipping_address'); 75 | foreach ($keys as $key) { 76 | if (!isset($field[$key])) continue; 77 | 78 | $camel = static::upperCamelize($key); 79 | $function = "field$camel"; 80 | static::$function($field[$key]); 81 | } 82 | } 83 | 84 | } 85 | 86 | private static function fieldBillingAddress(&$field) 87 | { 88 | $fields = array( 89 | 'first_name' => 255, 90 | 'last_name' => 255, 91 | 'address' => 255, 92 | 'city' => 255, 93 | 'country_code' => 3 94 | ); 95 | 96 | foreach ($fields as $key => $value) { 97 | if (isset($field[$key])) { 98 | $self = new self; 99 | $field[$key] = $self 100 | ->maxLength($value) 101 | ->apply($field[$key]); 102 | } 103 | } 104 | 105 | if (isset($field['postal_code'])) { 106 | $postal_code = new self; 107 | $field['postal_code'] = $postal_code 108 | ->whitelist('A-Za-z0-9\\- ') 109 | ->maxLength(10) 110 | ->apply($field['postal_code']); 111 | } 112 | if (isset($field['phone'])) { 113 | static::fieldPhone($field['phone']); 114 | } 115 | } 116 | 117 | private static function fieldShippingAddress(&$field) 118 | { 119 | static::fieldBillingAddress($field); 120 | } 121 | 122 | private static function fieldPhone(&$field) 123 | { 124 | $plus = substr($field, 0, 1) === '+'; 125 | $self = new self; 126 | $field = $self 127 | ->whitelist('\\d\\-\\(\\) ') 128 | ->maxLength(19) 129 | ->apply($field); 130 | 131 | if ($plus) $field = '+' . $field; 132 | $self = new self; 133 | $field = $self 134 | ->maxLength(19) 135 | ->apply($field); 136 | } 137 | 138 | private function maxLength($length) 139 | { 140 | $this->filters[] = function ($input) use ($length) { 141 | return substr($input, 0, $length); 142 | }; 143 | return $this; 144 | } 145 | 146 | private function whitelist($regex) 147 | { 148 | $this->filters[] = function ($input) use ($regex) { 149 | return preg_replace("/[^$regex]/", '', $input); 150 | }; 151 | return $this; 152 | } 153 | 154 | private function apply($input) 155 | { 156 | foreach ($this->filters as $filter) { 157 | $input = call_user_func($filter, $input); 158 | } 159 | return $input; 160 | } 161 | 162 | private static function upperCamelize($string) 163 | { 164 | return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Midtrans/Snap.php: -------------------------------------------------------------------------------- 1 | array( 23 | * 'order_id' => rand(), 24 | * 'gross_amount' => 10000, 25 | * ) 26 | * ); 27 | * $paymentUrl = Snap::getSnapToken($params); 28 | * ``` 29 | * 30 | * @param array $params Payment options 31 | * @return string Snap token. 32 | * @throws Exception curl error or midtrans error 33 | */ 34 | public static function getSnapToken($params) 35 | { 36 | return (Snap::createTransaction($params)->token); 37 | } 38 | 39 | /** 40 | * Create Snap URL payment 41 | * 42 | * Example: 43 | * 44 | * ```php 45 | * 46 | * namespace Midtrans; 47 | * 48 | * $params = array( 49 | * 'transaction_details' => array( 50 | * 'order_id' => rand(), 51 | * 'gross_amount' => 10000, 52 | * ) 53 | * ); 54 | * $paymentUrl = Snap::getSnapUrl($params); 55 | * ``` 56 | * 57 | * @param array $params Payment options 58 | * @return string Snap redirect url. 59 | * @throws Exception curl error or midtrans error 60 | */ 61 | public static function getSnapUrl($params) 62 | { 63 | return (Snap::createTransaction($params)->redirect_url); 64 | } 65 | 66 | /** 67 | * Create Snap payment page, with this version returning full API response 68 | * 69 | * Example: 70 | * 71 | * ```php 72 | * $params = array( 73 | * 'transaction_details' => array( 74 | * 'order_id' => rand(), 75 | * 'gross_amount' => 10000, 76 | * ) 77 | * ); 78 | * $paymentUrl = Snap::getSnapToken($params); 79 | * ``` 80 | * 81 | * @param array $params Payment options 82 | * @return object Snap response (token and redirect_url). 83 | * @throws Exception curl error or midtrans error 84 | */ 85 | public static function createTransaction($params) 86 | { 87 | $payloads = array( 88 | 'credit_card' => array( 89 | // 'enabled_payments' => array('credit_card'), 90 | 'secure' => Config::$is3ds 91 | ) 92 | ); 93 | 94 | if (isset($params['item_details'])) { 95 | $gross_amount = 0; 96 | foreach ($params['item_details'] as $item) { 97 | $gross_amount += $item['quantity'] * $item['price']; 98 | } 99 | $params['transaction_details']['gross_amount'] = $gross_amount; 100 | } 101 | 102 | if (Config::$isSanitized) { 103 | Sanitizer::jsonRequest($params); 104 | } 105 | 106 | $params = array_replace_recursive($payloads, $params); 107 | 108 | return ApiRequestor::post( 109 | Config::getSnapBaseUrl() . '/transactions', 110 | Config::$serverKey, 111 | $params 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Midtrans/SnapApiRequestor.php: -------------------------------------------------------------------------------- 1 | $url, 52 | CURLOPT_HTTPHEADER => array( 53 | 'Content-Type: application/json', 54 | 'Accept: application/json', 55 | 'Authorization: Basic ' . base64_encode($server_key . ':') 56 | ), 57 | CURLOPT_RETURNTRANSFER => 1, 58 | // CURLOPT_CAINFO => dirname(__FILE__) . "/../data/cacert.pem" 59 | ); 60 | 61 | // merging with Config::$curlOptions 62 | if (count(Config::$curlOptions)) { 63 | // We need to combine headers manually, because it's array and it will no be merged 64 | if (Config::$curlOptions[CURLOPT_HTTPHEADER]) { 65 | $mergedHeders = array_merge($curl_options[CURLOPT_HTTPHEADER], Config::$curlOptions[CURLOPT_HTTPHEADER]); 66 | $headerOptions = array( CURLOPT_HTTPHEADER => $mergedHeders ); 67 | } else { 68 | $mergedHeders = array(); 69 | } 70 | 71 | $curl_options = array_replace_recursive($curl_options, Config::$curlOptions, $headerOptions); 72 | } 73 | 74 | if ($post) { 75 | $curl_options[CURLOPT_POST] = 1; 76 | 77 | if ($data_hash) { 78 | $body = json_encode($data_hash); 79 | $curl_options[CURLOPT_POSTFIELDS] = $body; 80 | } else { 81 | $curl_options[CURLOPT_POSTFIELDS] = ''; 82 | } 83 | } 84 | 85 | curl_setopt_array($ch, $curl_options); 86 | 87 | // For testing purpose 88 | if (class_exists('\Midtrans\VT_Tests') && VT_Tests::$stubHttp) { 89 | $result = self::processStubed($curl_options, $url, $server_key, $data_hash, $post); 90 | $info = VT_Tests::$stubHttpStatus; 91 | } else { 92 | $result = curl_exec($ch); 93 | $info = curl_getinfo($ch); 94 | // curl_close($ch); 95 | } 96 | 97 | if ($result === false) { 98 | throw new \Exception('CURL Error: ' . curl_error($ch), curl_errno($ch)); 99 | } else { 100 | try { 101 | $result_array = json_decode($result); 102 | } catch (\Exception $e) { 103 | $message = "API Request Error unable to json_decode API response: ".$result . ' | Request url: '.$url; 104 | throw new \Exception($message); 105 | } 106 | if ($info['http_code'] != 201) { 107 | $message = 'Midtrans Error (' . $info['http_code'] . '): ' 108 | . $result . ' | Request url: '.$url; 109 | throw new \Exception($message, $info['http_code']); 110 | } else { 111 | return $result_array; 112 | } 113 | } 114 | } 115 | 116 | private static function processStubed($curl, $url, $server_key, $data_hash, $post) 117 | { 118 | VT_Tests::$lastHttpRequest = array( 119 | "url" => $url, 120 | "server_key" => $server_key, 121 | "data_hash" => $data_hash, 122 | "post" => $post, 123 | "curl" => $curl 124 | ); 125 | 126 | return VT_Tests::$stubHttpResponse; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Midtrans/Transaction.php: -------------------------------------------------------------------------------- 1 | status_code; 61 | } 62 | 63 | /** 64 | * Cancel transaction before it's settled 65 | * 66 | * @param string $id Order ID or transaction ID 67 | * 68 | * @return string 69 | * @throws Exception 70 | */ 71 | public static function cancel($id) 72 | { 73 | return ApiRequestor::post( 74 | Config::getBaseUrl() . '/v2/' . $id . '/cancel', 75 | Config::$serverKey, 76 | false 77 | )->status_code; 78 | } 79 | 80 | /** 81 | * Expire transaction before it's setteled 82 | * 83 | * @param string $id Order ID or transaction ID 84 | * 85 | * @return mixed[] 86 | * @throws Exception 87 | */ 88 | public static function expire($id) 89 | { 90 | return ApiRequestor::post( 91 | Config::getBaseUrl() . '/v2/' . $id . '/expire', 92 | Config::$serverKey, 93 | false 94 | ); 95 | } 96 | 97 | /** 98 | * Transaction status can be updated into refund 99 | * if the customer decides to cancel completed/settlement payment. 100 | * The same refund id cannot be reused again. 101 | * 102 | * @param string $id Order ID or transaction ID 103 | * 104 | * @param $params 105 | * @return mixed[] 106 | * @throws Exception 107 | */ 108 | public static function refund($id, $params) 109 | { 110 | return ApiRequestor::post( 111 | Config::getBaseUrl() . '/v2/' . $id . '/refund', 112 | Config::$serverKey, 113 | $params 114 | ); 115 | } 116 | 117 | /** 118 | * Transaction status can be updated into refund 119 | * if the customer decides to cancel completed/settlement payment. 120 | * The same refund id cannot be reused again. 121 | * 122 | * @param string $id Order ID or transaction ID 123 | * 124 | * @return mixed[] 125 | * @throws Exception 126 | */ 127 | public static function refundDirect($id, $params) 128 | { 129 | return ApiRequestor::post( 130 | Config::getBaseUrl() . '/v2/' . $id . '/refund/online/direct', 131 | Config::$serverKey, 132 | $params 133 | ); 134 | } 135 | 136 | /** 137 | * Deny method can be triggered to immediately deny card payment transaction 138 | * in which fraud_status is challenge. 139 | * 140 | * @param string $id Order ID or transaction ID 141 | * 142 | * @return mixed[] 143 | * @throws Exception 144 | */ 145 | public static function deny($id) 146 | { 147 | return ApiRequestor::post( 148 | Config::getBaseUrl() . '/v2/' . $id . '/deny', 149 | Config::$serverKey, 150 | false 151 | ); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Midtrans-PHP 2 | =============== 3 | 4 | [![PHP version](https://badge.fury.io/ph/midtrans%2Fmidtrans-php.svg)](https://badge.fury.io/ph/midtrans%2Fmidtrans-php) 5 | [![Latest Stable Version](https://poser.pugx.org/midtrans/midtrans-php/v/stable)](https://packagist.org/packages/midtrans/midtrans-php) 6 | [![Monthly Downloads](https://poser.pugx.org/midtrans/midtrans-php/d/monthly)](https://packagist.org/packages/midtrans/midtrans-php) 7 | [![Total Downloads](https://poser.pugx.org/midtrans/midtrans-php/downloads)](https://packagist.org/packages/midtrans/midtrans-php) 8 | 9 | 10 | [Midtrans](https://midtrans.com) :heart: PHP! 11 | 12 | This is the Official PHP wrapper/library for Midtrans Payment API, that is compatible with Composer. Visit [https://midtrans.com](https://midtrans.com) for more information about the product and see documentation at [http://docs.midtrans.com](https://docs.midtrans.com) for more technical details. 13 | Starting version 2.6, this library now supports Snap-bi. You can go to this [docs](https://docs.midtrans.com/reference/core-api-snap-open-api-overview) to learn more about Snap-bi. 14 | ## 1. Installation 15 | 16 | ### 1.a Composer Installation 17 | 18 | If you are using [Composer](https://getcomposer.org), you can install via composer CLI: 19 | 20 | ``` 21 | composer require midtrans/midtrans-php 22 | ``` 23 | 24 | **or** 25 | 26 | add this require line to your `composer.json` file: 27 | 28 | ```json 29 | { 30 | "require": { 31 | "midtrans/midtrans-php": "2.*" 32 | } 33 | } 34 | ``` 35 | 36 | and run `composer install` on your terminal. 37 | 38 | > **Note:** If you are using Laravel framework, in [some](https://laracasts.com/discuss/channels/general-discussion/using-non-laravel-composer-package-with-laravel?page=1#reply=461608) [case](https://stackoverflow.com/a/23675376) you also need to run `composer dumpautoload` 39 | 40 | > `/Midtrans` will then be available (auto loaded) as Object in your Laravel project. 41 | 42 | ### 1.b Manual Instalation 43 | 44 | If you are not using Composer, you can clone or [download](https://github.com/midtrans/midtrans-php/archive/master.zip) this repository. 45 | 46 | Then you should require/autoload `Midtrans.php` file on your code. 47 | 48 | ```php 49 | require_once dirname(__FILE__) . '/pathofproject/Midtrans.php'; 50 | 51 | // my code goes here 52 | ``` 53 | 54 | ## 2. How to Use 55 | 56 | ### 2.1 General Settings 57 | 58 | ```php 59 | // Set your Merchant Server Key 60 | \Midtrans\Config::$serverKey = ''; 61 | // Set to Development/Sandbox Environment (default). Set to true for Production Environment (accept real transaction). 62 | \Midtrans\Config::$isProduction = false; 63 | // Set sanitization on (default) 64 | \Midtrans\Config::$isSanitized = true; 65 | // Set 3DS transaction for credit card to true 66 | \Midtrans\Config::$is3ds = true; 67 | ``` 68 | 69 | #### Override Notification URL 70 | 71 | You can opt to change or add custom notification urls on every transaction. It can be achieved by adding additional HTTP headers into charge request. 72 | 73 | ```php 74 | // Add new notification url(s) alongside the settings on Midtrans Dashboard Portal (MAP) 75 | Config::$appendNotifUrl = "https://example.com/test1,https://example.com/test2"; 76 | // Use new notification url(s) disregarding the settings on Midtrans Dashboard Portal (MAP) 77 | Config::$overrideNotifUrl = "https://example.com/test1"; 78 | ``` 79 | 80 | [More details](https://api-docs.midtrans.com/#override-notification-url) 81 | 82 | > **Note:** When both `appendNotifUrl` and `overrideNotifUrl` are used together then only `overrideNotifUrl` will be used. 83 | 84 | > Both header can only receive up to maximum of **3 urls**. 85 | 86 | #### Idempotency-Key 87 | You can opt to add idempotency key on charge transaction. It can be achieved by adding additional HTTP headers into charge request. 88 | Is a unique value that is put on header on API request. Midtrans API accept Idempotency-Key on header to safely handle retry request 89 | without performing the same operation twice. This is helpful for cases where merchant didn't receive the response because of network issue or other unexpected error. 90 | 91 | ```php 92 | Config::$paymentIdempotencyKey = "Unique-ID"; 93 | ``` 94 | 95 | [More details](http://api-docs.midtrans.com/#idempotent-requests) 96 | 97 | ### 2.2 Choose Product/Method 98 | 99 | We have [3 different products](https://docs.midtrans.com/en/welcome/index.html) of payment that you can use: 100 | - [Snap](#22a-snap) - Customizable payment popup will appear on **your web/app** (no redirection). [doc ref](https://snap-docs.midtrans.com/) 101 | - [Snap Redirect](#22b-snap-redirect) - Customer need to be redirected to payment url **hosted by midtrans**. [doc ref](https://snap-docs.midtrans.com/) 102 | - [Core API (VT-Direct)](#22c-core-api-vt-direct) - Basic backend implementation, you can customize the frontend embedded on **your web/app** as you like (no redirection). [doc ref](https://api-docs.midtrans.com/) 103 | 104 | Choose one that you think best for your unique needs. 105 | 106 | ### 2.2.a Snap 107 | 108 | You can see Snap example [here](examples/snap). 109 | 110 | #### Get Snap Token 111 | 112 | ```php 113 | $params = array( 114 | 'transaction_details' => array( 115 | 'order_id' => rand(), 116 | 'gross_amount' => 10000, 117 | ) 118 | ); 119 | 120 | $snapToken = \Midtrans\Snap::getSnapToken($params); 121 | ``` 122 | 123 | #### Initialize Snap JS when customer click pay button 124 | 125 | ```html 126 | 127 | 128 | 129 |
JSON result will appear here after payment:
130 | 131 | 132 | 133 | 152 | 153 | 154 | ``` 155 | 156 | #### Implement Notification Handler 157 | [Refer to this section](#23-handle-http-notification) 158 | 159 | ### 2.2.b Snap Redirect 160 | 161 | You can see some Snap Redirect examples [here](examples/snap-redirect). 162 | 163 | #### Get Redirection URL of a Payment Page 164 | 165 | ```php 166 | $params = array( 167 | 'transaction_details' => array( 168 | 'order_id' => rand(), 169 | 'gross_amount' => 10000, 170 | ) 171 | ); 172 | 173 | try { 174 | // Get Snap Payment Page URL 175 | $paymentUrl = \Midtrans\Snap::createTransaction($params)->redirect_url; 176 | 177 | // Redirect to Snap Payment Page 178 | header('Location: ' . $paymentUrl); 179 | } 180 | catch (Exception $e) { 181 | echo $e->getMessage(); 182 | } 183 | ``` 184 | #### Implement Notification Handler 185 | [Refer to this section](#23-handle-http-notification) 186 | 187 | ### 2.2.c Core API (VT-Direct) 188 | 189 | You can see some Core API examples [here](examples/core-api). 190 | 191 | #### Set Client Key 192 | 193 | ```javascript 194 | MidtransNew3ds.clientKey = ""; 195 | ``` 196 | 197 | #### Checkout Page 198 | 199 | Please refer to [this file](examples/core-api/checkout.php) 200 | 201 | #### Checkout Process 202 | 203 | ##### 1. Create Transaction Details 204 | 205 | ```php 206 | $transaction_details = array( 207 | 'order_id' => time(), 208 | 'gross_amount' => 200000 209 | ); 210 | ``` 211 | 212 | ##### 2. Create Item Details, Billing Address, Shipping Address, and Customer Details (Optional) 213 | 214 | ```php 215 | // Populate items 216 | $items = array( 217 | array( 218 | 'id' => 'item1', 219 | 'price' => 100000, 220 | 'quantity' => 1, 221 | 'name' => 'Adidas f50' 222 | ), 223 | array( 224 | 'id' => 'item2', 225 | 'price' => 50000, 226 | 'quantity' => 2, 227 | 'name' => 'Nike N90' 228 | ) 229 | ); 230 | 231 | // Populate customer's billing address 232 | $billing_address = array( 233 | 'first_name' => "Andri", 234 | 'last_name' => "Setiawan", 235 | 'address' => "Karet Belakang 15A, Setiabudi.", 236 | 'city' => "Jakarta", 237 | 'postal_code' => "51161", 238 | 'phone' => "081322311801", 239 | 'country_code' => 'IDN' 240 | ); 241 | 242 | // Populate customer's shipping address 243 | $shipping_address = array( 244 | 'first_name' => "John", 245 | 'last_name' => "Watson", 246 | 'address' => "Bakerstreet 221B.", 247 | 'city' => "Jakarta", 248 | 'postal_code' => "51162", 249 | 'phone' => "081322311801", 250 | 'country_code' => 'IDN' 251 | ); 252 | 253 | // Populate customer's info 254 | $customer_details = array( 255 | 'first_name' => "Andri", 256 | 'last_name' => "Setiawan", 257 | 'email' => "test@test.com", 258 | 'phone' => "081322311801", 259 | 'billing_address' => $billing_address, 260 | 'shipping_address' => $shipping_address 261 | ); 262 | ``` 263 | 264 | ##### 3. Get Token ID from Checkout Page 265 | 266 | ```php 267 | // Token ID from checkout page 268 | $token_id = $_POST['token_id']; 269 | ``` 270 | 271 | ##### 4. Create Transaction Data 272 | 273 | ```php 274 | // Transaction data to be sent 275 | $transaction_data = array( 276 | 'payment_type' => 'credit_card', 277 | 'credit_card' => array( 278 | 'token_id' => $token_id, 279 | 'authentication'=> true, 280 | // 'bank' => 'bni', // optional to set acquiring bank 281 | // 'save_token_id' => true // optional for one/two clicks feature 282 | ), 283 | 'transaction_details' => $transaction_details, 284 | 'item_details' => $items, 285 | 'customer_details' => $customer_details 286 | ); 287 | ``` 288 | 289 | ##### 5. Charge 290 | 291 | ```php 292 | $response = \Midtrans\CoreApi::charge($transaction_data); 293 | ``` 294 | 295 | 296 | ##### 6. Credit Card 3DS Authentication 297 | 298 | The credit card charge result may contains `redirect_url` for 3DS authentication. 3DS Authentication should be handled on Frontend please refer to [API docs](https://api-docs.midtrans.com/#card-features-3d-secure) 299 | 300 | For full example on Credit Card 3DS transaction refer to: 301 | - [Core API examples](/examples/core-api/) 302 | 303 | ##### 7. Handle Transaction Status 304 | 305 | ```php 306 | // Success 307 | if($response->transaction_status == 'capture') { 308 | echo "

Transaksi berhasil.

"; 309 | echo "

Status transaksi untuk order id $response->order_id: " . 310 | "$response->transaction_status

"; 311 | 312 | echo "

Detail transaksi:

"; 313 | echo "
";
 314 |     var_dump($response);
 315 |     echo "
"; 316 | } 317 | // Deny 318 | else if($response->transaction_status == 'deny') { 319 | echo "

Transaksi ditolak.

"; 320 | echo "

Status transaksi untuk order id .$response->order_id: " . 321 | "$response->transaction_status

"; 322 | 323 | echo "

Detail transaksi:

"; 324 | echo "
";
 325 |     var_dump($response);
 326 |     echo "
"; 327 | } 328 | // Challenge 329 | else if($response->transaction_status == 'challenge') { 330 | echo "

Transaksi challenge.

"; 331 | echo "

Status transaksi untuk order id $response->order_id: " . 332 | "$response->transaction_status

"; 333 | 334 | echo "

Detail transaksi:

"; 335 | echo "
";
 336 |     var_dump($response);
 337 |     echo "
"; 338 | } 339 | // Error 340 | else { 341 | echo "

Terjadi kesalahan pada data transaksi yang dikirim.

"; 342 | echo "

Status message: [$response->status_code] " . 343 | "$response->status_message

"; 344 | 345 | echo "
";
 346 |     var_dump($response);
 347 |     echo "
"; 348 | } 349 | ``` 350 | #### 8. Implement Notification Handler 351 | [Refer to this section](#23-handle-http-notification) 352 | 353 | 354 | ### 2.3 Handle HTTP Notification 355 | 356 | Create separated web endpoint (notification url) to receive HTTP POST notification callback/webhook. 357 | HTTP notification will be sent whenever transaction status is changed. 358 | Example also available [here](examples/notification-handler.php) 359 | 360 | ```php 361 | $notif = new \Midtrans\Notification(); 362 | 363 | $transaction = $notif->transaction_status; 364 | $fraud = $notif->fraud_status; 365 | 366 | error_log("Order ID $notif->order_id: "."transaction status = $transaction, fraud staus = $fraud"); 367 | 368 | if ($transaction == 'capture') { 369 | if ($fraud == 'challenge') { 370 | // TODO Set payment status in merchant's database to 'challenge' 371 | } 372 | else if ($fraud == 'accept') { 373 | // TODO Set payment status in merchant's database to 'success' 374 | } 375 | } 376 | else if ($transaction == 'cancel') { 377 | if ($fraud == 'challenge') { 378 | // TODO Set payment status in merchant's database to 'failure' 379 | } 380 | else if ($fraud == 'accept') { 381 | // TODO Set payment status in merchant's database to 'failure' 382 | } 383 | } 384 | else if ($transaction == 'deny') { 385 | // TODO Set payment status in merchant's database to 'failure' 386 | } 387 | ``` 388 | 389 | ### 2.4 Process Transaction 390 | 391 | #### Get Transaction Status 392 | 393 | ```php 394 | $status = \Midtrans\Transaction::status($orderId); 395 | var_dump($status); 396 | ``` 397 | 398 | #### Approve Transaction 399 | If transaction fraud_status == [CHALLENGE](https://support.midtrans.com/hc/en-us/articles/202710750-What-does-CHALLENGE-status-mean-What-should-I-do-if-there-is-a-CHALLENGE-transaction-), you can approve the transaction from Merchant Dashboard, or API : 400 | 401 | ```php 402 | $approve = \Midtrans\Transaction::approve($orderId); 403 | var_dump($approve); 404 | ``` 405 | 406 | #### Cancel Transaction 407 | You can Cancel transaction with `fraud_status == CHALLENGE`, or credit card transaction with `transaction_status == CAPTURE` (before it become SETTLEMENT) 408 | ```php 409 | $cancel = \Midtrans\Transaction::cancel($orderId); 410 | var_dump($cancel); 411 | ``` 412 | 413 | #### Expire Transaction 414 | You can Expire transaction with `transaction_status == PENDING` (before it become SETTLEMENT or EXPIRE) 415 | ```php 416 | $cancel = \Midtrans\Transaction::cancel($orderId); 417 | var_dump($cancel); 418 | ``` 419 | 420 | #### Refund Transaction 421 | Refund a transaction (not all payment channel allow refund via API) 422 | You can Refund transaction with `transaction_status == settlement` 423 | ```php 424 | $params = array( 425 | 'refund_key' => 'order1-ref1', 426 | 'amount' => 10000, 427 | 'reason' => 'Item out of stock' 428 | ); 429 | $refund = \Midtrans\Transaction::refund($orderId, $params); 430 | var_dump($refund); 431 | ``` 432 | 433 | #### Direct Refund Transaction 434 | Refund a transaction via Direct Refund API 435 | You can Refund transaction with `transaction_status == settlement` 436 | ```php 437 | $params = array( 438 | 'refund_key' => 'order1-ref1', 439 | 'amount' => 10000, 440 | 'reason' => 'Item out of stock' 441 | ); 442 | $direct_refund = \Midtrans\Transaction::refundDirect($orderId, $params); 443 | var_dump($direct_refund); 444 | ``` 445 | ## 3. Snap-BI (*NEW FEATURE starting v2.6.0) 446 | Standar Nasional Open API Pembayaran, or in short SNAP, is a national payment open API standard published by Bank Indonesia. To learn more you can read this [docs](https://docs.midtrans.com/reference/core-api-snap-open-api-overview) 447 | 448 | ### 3.1 General Settings 449 | 450 | ```php 451 | //These config value are based on the header stated here https://docs.midtrans.com/reference/getting-started-1 452 | // Set to Development/Sandbox Environment (default). Set to true for Production Environment (accept real transaction). 453 | \SnapBi\Config::$isProduction = false; 454 | // Set your client id. Merchant’s client ID that will be given by Midtrans, will be used as X-CLIENT-KEY on request’s header in B2B Access Token API. 455 | \SnapBi\Config::$snapBiClientId = "YOUR CLIENT ID"; 456 | // Set your private key here, make sure to add \n on the private key, you can refer to the examples 457 | \SnapBi\Config::$snapBiPrivateKey = "YOUR PRIVATE KEY"; 458 | // Set your client secret. Merchant’s secret key that will be given by Midtrans, will be used for symmetric signature generation for Transactional API’s header. 459 | \SnapBi\Config::$snapBiClientSecret = "YOUR CLIENT SECRET"; 460 | // Set your partner id. Merchant’s partner ID that will be given by Midtrans, will be used as X-PARTNER-ID on Transactional API’s header. 461 | \SnapBi\Config::$snapBiPartnerId = "YOUR PARTNER ID"; 462 | // Set the channel id here. 463 | \SnapBi\Config::$snapBiChannelId = "CHANNEL ID"; 464 | // Enable logging to see details of the request/response make sure to disable this on production, the default is disabled. 465 | \SnapBi\Config::$enableLogging = false; 466 | // 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 467 | \SnapBi\Config::$snapBiPublicKey = "YOUR PUBLIC KEY" 468 | ``` 469 | 470 | ### 3.2 Create Payment 471 | 472 | #### 3.2.1 Direct Debit (Gopay, Dana, Shopeepay) 473 | Refer to this [docs](https://docs.midtrans.com/reference/direct-debit-api-gopay) for more detailed information about creating payment using direct debit. 474 | 475 | ```php 476 | 477 | date_default_timezone_set('Asia/Jakarta'); 478 | $time_stamp = date("c"); 479 | $date = new DateTime($time_stamp); 480 | $external_id = "uzi-order-testing" . uniqid(); 481 | // Add 10 minutes validity time 482 | $date->modify('+10 minutes'); 483 | // Format the new date 484 | $valid_until = $date->format('c'); 485 | $merchant_id = "M001234"; 486 | 487 | 488 | //create direct debit request body/ payload 489 | //you can change the payment method on the `payOptionDetails` 490 | $debitParams = array( 491 | "partnerReferenceNo" => $external_id, 492 | "chargeToken" => "", 493 | "merchantId" => $merchant_id, 494 | "urlParam" => array( 495 | array( 496 | "url" => "https://www.google.com", 497 | "type" => "PAY_RETURN", 498 | "isDeeplink" => "Y" 499 | ) 500 | ), 501 | "validUpTo" => $valid_until, 502 | "payOptionDetails" => array( 503 | array( 504 | "payMethod" => "DANA", 505 | "payOption" => "DANA", 506 | "transAmount" => array( 507 | "value" => "100.0", 508 | "currency" => "IDR" //currently we only support `IDR` 509 | ) 510 | ) 511 | ), 512 | "additionalInfo" => array( 513 | "customerDetails" => array( 514 | "phone" => "081122334455", 515 | "firstName" => "Andri", 516 | "lastName" => "Litani", 517 | "email" => "andri@litani.com", 518 | "billingAddress" => array( 519 | "firstName" => "Andri", 520 | "lastName" => "Litani", 521 | "phone" => "081122334455", 522 | "address" => "billingAddress", 523 | "city" => "billingCity", 524 | "postalCode" => "12790", 525 | "countryCode" => "CZH" 526 | ), 527 | "shippingAddress" => array( 528 | "firstName" => "Andri", 529 | "lastName" => "Litani", 530 | "phone" => "081122334455", 531 | "address" => "shippingAddress", 532 | "city" => "shippingCity", 533 | "postalCode" => "12790", 534 | "countryCode" => "CZH" 535 | ) 536 | ), 537 | "items" => array( 538 | array( 539 | "id" => "1", 540 | "price" => array( 541 | "value" => "100.00", 542 | "currency" => "IDR" 543 | ), 544 | "quantity" => 1, 545 | "name" => "Apple", 546 | "brand" => "Apple", 547 | "category" => "Subscription", 548 | "merchantName" => "amazon prime", 549 | "url" => "itemUrl" 550 | ) 551 | ), 552 | "metadata" => array() 553 | ) 554 | ); 555 | /** 556 | * Basic example 557 | * to change the payment method, you can change the value of the request body on the `payOptionDetails` 558 | * the `currency` value that we support for now is only `IDR` 559 | */ 560 | $snapBiResponse = SnapBi::directDebit() 561 | ->withBody($debitParams) 562 | ->createPayment($external_id); 563 | 564 | ``` 565 | #### 3.2.2 VA (Bank Transfer) 566 | Refer to this [docs](https://docs.midtrans.com/reference/virtual-account-api-bank-transfer) for more detailed information about VA/Bank Transfer. 567 | ```php 568 | $external_id = "uzi-order-testing" . uniqid(); 569 | $customerVaNo = "6280123456"; 570 | $merchant_id = "M001234"; 571 | 572 | $vaParams = array( 573 | "partnerServiceId"=> " 70012", 574 | "customerNo"=> $customerVaNo, 575 | "virtualAccountNo"=> " 70012" . $customerVaNo, 576 | "virtualAccountName"=> "Jokul Doe", 577 | "virtualAccountEmail"=> "jokul@email.com", 578 | "virtualAccountPhone"=> "6281828384858", 579 | "trxId"=> $external_id, 580 | "totalAmount"=> [ 581 | "value"=> "10000.00", 582 | "currency"=> "IDR" 583 | ], 584 | "additionalInfo"=> [ 585 | "merchantId"=> $merchant_id, 586 | "bank"=> "mandiri", 587 | "flags"=> [ 588 | "shouldRandomizeVaNumber"=> false 589 | ], 590 | "mandiri"=> [ 591 | "billInfo1"=> "bank_name", 592 | "billInfo2"=> "mandiri", 593 | "billInfo3"=> "Name:", 594 | "billInfo4"=> "Budi Utomo", 595 | "billInfo5"=> "Class:", 596 | "billInfo6"=> "Computer Science", 597 | "billInfo7"=> "ID:", 598 | "billInfo8"=> "VT-12345" 599 | ], 600 | "customerDetails"=> [ 601 | "firstName"=> "Jokul", 602 | "lastName"=> "Doe", 603 | "email"=> "jokul@email.com", 604 | "phone"=> "+6281828384858", 605 | "billingAddress"=> [ 606 | "firstName"=> "Jukul", 607 | "lastName"=> "Doe", 608 | "address"=> "Kalibata", 609 | "city"=> "Jakarta", 610 | "postalCode"=> "12190", 611 | "phone"=> "+6281828384858", 612 | "countryCode"=> "IDN" 613 | ], 614 | "shippingAddress"=> [ 615 | "firstName"=> "Jukul", 616 | "lastName"=> "Doe", 617 | "address"=> "Kalibata", 618 | "city"=> "Jakarta", 619 | "postalCode"=> "12190", 620 | "phone"=> "+6281828384858", 621 | "countryCode"=> "IDN" 622 | ] 623 | ], 624 | "customField"=> [ 625 | "1"=> "custom-field-1", 626 | "2"=> "custom-field-2", 627 | "3"=> "custom-field-3" 628 | ], 629 | "items"=> [ 630 | [ 631 | "id"=> "a1", 632 | "price"=> [ 633 | "value"=> "1000.00", 634 | "currency"=> "IDR" 635 | ], 636 | "quantity"=> 3, 637 | "name"=> "Apel", 638 | "brand"=> "Fuji Apple", 639 | "category"=> "Fruit", 640 | "merchantName"=> "Fruit-store" 641 | 642 | ], 643 | [ 644 | "id"=> "a2", 645 | "price"=> [ 646 | "value"=> "1000.00", 647 | "currency"=> "IDR" 648 | ], 649 | "quantity"=> 7, 650 | "name"=> "Apel Malang", 651 | "brand"=> "Fuji Apple", 652 | "category"=> "Fruit", 653 | "merchantName"=> "Fruit-store" 654 | ] 655 | ] 656 | ] 657 | ); 658 | 659 | /** 660 | * basic implementation to create payment using va 661 | */ 662 | $snapBiResponse = SnapBi::va() 663 | ->withBody($vaParams) 664 | ->createPayment($external_id); 665 | ``` 666 | #### 3.2.3 Qris 667 | Refer to this [docs](https://docs.midtrans.com/reference/mpm-api-qris) for more detailed information about Qris. 668 | ```php 669 | $external_id = "uzi-order-testing" . uniqid(); 670 | $merchant_id = "M001234"; 671 | $qrisBody = array( 672 | "partnerReferenceNo" => $external_id, 673 | "amount" => array( 674 | "value" => "1500.00", 675 | "currency" => "IDR" 676 | ), 677 | "merchantId" => $merchant_id, 678 | "validityPeriod" => "2030-07-03T12:08:56-07:00", 679 | "additionalInfo" => array( 680 | "acquirer" => "gopay", 681 | "items" => array( 682 | array( 683 | "id" => "8143fc4f-ec05-4c55-92fb-620c212f401e", 684 | "price" => array( 685 | "value" => "1500.00", 686 | "currency" => "IDR" 687 | ), 688 | "quantity" => 1, 689 | "name" => "test item name", 690 | "brand" => "test item brand", 691 | "category" => "test item category", 692 | "merchantName" => "Merchant Operation" 693 | ) 694 | ), 695 | "customerDetails" => array( 696 | "email" => "merchant-ops@midtrans.com", 697 | "firstName" => "Merchant", 698 | "lastName" => "Operation", 699 | "phone" => "+6281932358123" 700 | ), 701 | "countryCode" => "ID", 702 | "locale" => "id_ID" 703 | ) 704 | ); 705 | 706 | /** 707 | * basic implementation to create payment using Qris 708 | */ 709 | $snapBiResponse = SnapBi::qris() 710 | ->withBody($qrisBody) 711 | ->createPayment($external_id); 712 | ``` 713 | 714 | ### 3.4 Get Transaction Status 715 | Refer to this [docs](https://docs.midtrans.com/reference/get-transaction-status-api) for more detailed information about getting the transaction status. 716 | ```php 717 | $merchant_id = "M001234"; 718 | $external_id = "uzi-order-testing" . uniqid(); 719 | 720 | $directDebitStatusByExternalIdBody = array( 721 | "originalExternalId" => "uzi-order-testing66ce90ce90ee5", 722 | "originalPartnerReferenceNo" => "uzi-order-testing66ce90ce90ee5", 723 | "serviceCode" => "54", 724 | ); 725 | 726 | $directDebitStatusByReferenceBody = array( 727 | "originalReferenceNo" => "A1202408280618283vcBaAmf7RID", 728 | "serviceCode" => "54", 729 | ); 730 | 731 | $vaStatusBody = array( 732 | "partnerServiceId" => " 5818", 733 | "customerNo" => "628064192914", 734 | "virtualAccountNo" => " 5818628064192914", 735 | "inquiryRequestId" => "uzi-order-testing66dc4799e4af5", 736 | "paymentRequestId" => "uzi-order-testing66dc4799e4af5", 737 | "additionalInfo" => array( 738 | "merchantId" => $merchant_id 739 | ) 740 | ); 741 | 742 | $qrisStatusBody = array( 743 | "originalReferenceNo" => "A120240910100828anKJlXgsi6ID", 744 | "originalPartnerReferenceNo" => "uzi-order-testing66e01a9b8c6bf", 745 | "merchantId" => $merchant_id, 746 | "serviceCode" => "54" 747 | ); 748 | 749 | /** 750 | * Example code for Direct Debit getStatus using externalId 751 | */ 752 | $snapBiResponse = SnapBi::directDebit() 753 | ->withBody($statusByExternalId) 754 | ->getStatus($external_id); 755 | 756 | /** 757 | * Example code for Direct Debit getStatus using referenceNo 758 | */ 759 | $snapBiResponse = SnapBi::directDebit() 760 | ->withBody($statusByReference) 761 | ->getStatus($external_id); 762 | 763 | /** 764 | * Example code for VA (Bank Transfer) getStatus 765 | */ 766 | $snapBiResponse = SnapBi::va() 767 | ->withBody($vaStatusBody) 768 | ->getStatus($external_id); 769 | /** 770 | * 771 | * Example code for Qris getStatus 772 | */ 773 | $snapBiResponse = SnapBi::qris() 774 | ->withBody($qrisStatusBody) 775 | ->getStatus($external_id); 776 | 777 | ``` 778 | 779 | ### 3.5 Cancel Transaction 780 | Refer to this [docs](https://docs.midtrans.com/reference/cancel-api) for more detailed information about cancelling the payment. 781 | ```php 782 | $merchant_id = "M001234"; 783 | $external_id = "uzi-order-testing" . uniqid(); 784 | 785 | $directDebitCancelByReferenceBody = array( 786 | "originalReferenceNo" => "A120240902104935GBqSQK0gtQID" 787 | ); 788 | 789 | $directDebitCancelByExternalIdBody = array( 790 | "originalExternalId" => "uzi-order-testing66d5983eabc71" 791 | ); 792 | 793 | $vaCancelBody = array( 794 | "partnerServiceId" => " 5818", 795 | "customerNo" => "628014506680", 796 | "virtualAccountNo" => " 5818628014506680", 797 | "trxId" => "uzi-order-testing66dc76754bf1c", 798 | "additionalInfo" => array( 799 | "merchantId" => $merchant_id 800 | ) 801 | ); 802 | 803 | $qrisCancelBody = array( 804 | "originalReferenceNo" => "A120240910091847fYkCqhCH1XID", 805 | "merchantId" => $merchant_id, 806 | "reason" => "cancel reason", 807 | ); 808 | /** 809 | * Basic implementation to cancel transaction using referenceNo 810 | */ 811 | $snapBiResponse = SnapBi::directDebit() 812 | ->withBody($directDebitCancelByReferenceBody) 813 | ->cancel($external_id); 814 | 815 | /** 816 | * Basic implementation to cancel transaction using externalId 817 | */ 818 | $snapBiResponse = SnapBi::directDebit() 819 | ->withBody($directDebitCancelByExternalIdBody) 820 | ->cancel($external_id); 821 | 822 | /** 823 | * Basic implementation of VA (Bank Transfer) to cancel transaction 824 | */ 825 | $snapBiResponse = SnapBi::va() 826 | ->withBody($vaCancelBody) 827 | ->cancel($external_id); 828 | 829 | /** 830 | * Basic implementation of Qris to cancel transaction 831 | */ 832 | $snapBiResponse = SnapBi::qris() 833 | ->withBody($qrisCancelBody) 834 | ->cancel($external_id); 835 | ``` 836 | 837 | ### 3.6 Refund Transaction 838 | Refer to this [docs](https://docs.midtrans.com/reference/refund-api) for more detailed information about refunding the payment. 839 | 840 | ```php 841 | $merchant_id = "M001234"; 842 | $external_id = "uzi-order-testing" . uniqid(); 843 | 844 | $directDebitRefundByExternalIdBody = array( 845 | "originalExternalId" => "uzi-order-testing66cec41c7f905", 846 | "partnerRefundNo" => "uzi-order-testing66cec41c7f905" . "refund-0001".rand(), 847 | "reason" => "some-reason", 848 | "additionalInfo" => array(), 849 | "refundAmount" => array( 850 | "value" => "100.00", 851 | "currency" => "IDR" 852 | )); 853 | 854 | $directDebitRefundByReferenceBody = array( 855 | "originalReferenceNo" => "A120240828062651Y0NQMbJkDOID", 856 | "reason" => "some-reason", 857 | "additionalInfo" => array(), 858 | "refundAmount" => array( 859 | "value" => "100.00", 860 | "currency" => "IDR" 861 | )); 862 | 863 | $qrisRefundBody = array( 864 | "merchantId" => $merchant_id, 865 | "originalPartnerReferenceNo" => "uzi-order-testing66e01a9b8c6bf", 866 | "originalReferenceNo" => "A120240910100828anKJlXgsi6ID", 867 | "partnerRefundNo" => "partner-refund-no-". uniqid(), 868 | "reason" => "refund reason", 869 | "refundAmount" => array( 870 | "value" => "1500.00", 871 | "currency" => "IDR" 872 | ), 873 | "additionalInfo" => array( 874 | "foo" => "bar" 875 | ) 876 | ); 877 | /** 878 | * Example code for refund using externalId 879 | */ 880 | $snapBiResponse = SnapBi::directDebit() 881 | ->withBody($directDebitRefundByExternalIdBody) 882 | ->refund($external_id); 883 | 884 | /** 885 | * Example code for refund using reference no 886 | */ 887 | $snapBiResponse = SnapBi::directDebit() 888 | ->withBody($directDebitRefundByReferenceBody) 889 | ->refund($external_id); 890 | 891 | /** 892 | * Example code for refund using Qris 893 | */ 894 | $snapBiResponse = SnapBi::qris() 895 | ->withBody($qrisRefundBody) 896 | ->refund($external_id); 897 | 898 | ``` 899 | 900 | ### 3.7 Adding additional header / override the header 901 | 902 | You can add or override the header value, by utilizing the `->withAccessTokenHeader` or `->withTransactionHeader` method chain. 903 | Refer to this [docs](https://docs.midtrans.com/reference/core-api-snap-open-api-overview) to see the header value required by Snap-Bi , and see the default header on each payment method 904 | 905 | ```php 906 | /** 907 | * Example code for Direct Debit refund using additional header 908 | */ 909 | $snapBiResponse = SnapBi::directDebit() 910 | ->withAccessTokenHeader([ 911 | "debug-id"=> "va debug id", 912 | "X-DEVICE-ID"=>"va device id" 913 | ]) 914 | ->withTransactionHeader([ 915 | "debug-id"=> "va debug id", 916 | "X-DEVICE-ID"=>"va device id" 917 | ]) 918 | ->withBody($directDebitRefundByExternalIdBody) 919 | ->refund($external_id); 920 | /** 921 | * Example code for using additional header on creating payment using VA 922 | */ 923 | $snapBiResponse = SnapBi::va() 924 | ->withAccessTokenHeader([ 925 | "debug-id"=> "va debug id", 926 | "X-DEVICE-ID"=>"va device id" 927 | ]) 928 | ->withTransactionHeader([ 929 | "debug-id"=> "va debug id", 930 | "X-DEVICE-ID"=>"va device id" 931 | ]) 932 | ->withBody($vaParams) 933 | ->createPayment($external_id); 934 | ``` 935 | 936 | ### 3.8 Reusing Access Token 937 | 938 | If you've saved your previous access token and wanted to re-use it, you can do it by utilizing the `->withAccessToken`. 939 | 940 | ```php 941 | /** 942 | * Example reusing your existing accessToken by using ->withAccessToken 943 | */ 944 | $snapBiResponse = SnapBi::va() 945 | ->withAccessToken("your-access-token") 946 | ->withBody($vaParams) 947 | ->createPayment($external_id); 948 | 949 | ``` 950 | 951 | ### 3.9 Payment Notification 952 | To implement Snap-Bi Payment Notification you can refer to this [docs](https://docs.midtrans.com/reference/payment-notification-api) 953 | To verify the webhook notification that you recieve you can use this method below 954 | ```php 955 | 956 | //the request body/ payload sent by the webhook 957 | $payload = json_decode( 958 | { 959 | "originalPartnerReferenceNo": "uzi-order-testing67039fa9da813", 960 | "originalReferenceNo": "A120241007084530GSXji4Q5OdID", 961 | "merchantId": "G653420184", 962 | "amount": { 963 | "value": "10000.00", 964 | "currency": "IDR" 965 | }, 966 | "latestTransactionStatus": "03", 967 | "transactionStatusDesc": "PENDING", 968 | "additionalInfo": { 969 | "refundHistory": [], 970 | "userPaymentDetails": [] 971 | } 972 | }; 973 | 974 | // to get the signature value, you need to retrieve it from the webhook header called X-Signature 975 | $xSignature = "CgjmAyC9OZ3pB2JhBRDihL939kS86LjP1VLD1R7LgI4JkvYvskUQrPXgjhrZqU2SFkfPmLtSbcEUw21pg2nItQ0KoX582Y6Tqg4Mn45BQbxo4LTPzkZwclD4WI+aCYePQtUrXpJSTM8D32lSJQQndlloJfzoD6Rh24lNb+zjUpc+YEi4vMM6MBmS26PpCm/7FZ7/OgsVh9rlSNUsuQ/1QFpldA0F8bBNWSW4trwv9bE1NFDzliHrRAnQXrT/J3chOg5qqH0+s3E6v/W21hIrBYZVDTppyJPtTOoCWeuT1Tk9XI2HhSDiSuI3pevzLL8FLEWY/G4M5zkjm/9056LTDw=="; 976 | 977 | // to get the timeStamp value, you need to retrieve it from the webhook header called X-Timestamp 978 | $xTimeStamp = "2024-10-07T15:45:22+07:00"; 979 | 980 | // the url path is based on the webhook url of the payment method for example for direct debit is `/v1.0/debit/notify` 981 | $notificationUrlPath = "/v1.0/debit/notify" 982 | /** 983 | * Example verifying the webhook notification 984 | */ 985 | $isVerified = SnapBi::notification() 986 | ->withBody($payload) 987 | ->withSignature($xSignature) 988 | ->withTimeStamp($xTimeStamp) 989 | ->withNotificationUrlPath($notificationUrlPath) 990 | ->isWebhookNotificationVerified() 991 | 992 | ``` 993 | 994 | ## Unit Test 995 | ### Integration Test (sandbox real transactions) 996 | Please change server key and client key on `phpunit.xml` to your own. 997 | 998 | ### All Test 999 | `vendor/bin/phpunit` 1000 | 1001 | ### Specific Test 1002 | `vendor/bin/phpunit tests/integration/CoreApiIntegrationTest.php` 1003 | 1004 | ## Contributing 1005 | 1006 | ### Developing e-commerce plug-ins 1007 | 1008 | There are several guides that must be taken care of when you develop new plugins. 1009 | 1010 | 1. __Handling currency other than IDR.__ Midtrans `v1` and `v2` currently accepts payments in Indonesian Rupiah only. As a corrolary, there is a validation on the server to check whether the item prices are in integer or not. As much as you are tempted to round-off the price, DO NOT do that! Always prepare when your system uses currencies other than IDR, convert them to IDR accordingly, and only round the price AFTER that. 1011 | 1012 | 2. Consider using the __auto-sanitization__ feature. 1013 | -------------------------------------------------------------------------------- /SnapBi/SnapBi.php: -------------------------------------------------------------------------------- 1 | paymentMethod = $paymentMethod; 46 | $this->timeStamp = date("c"); 47 | } 48 | 49 | /** 50 | * this method chain is used to start Direct Debit (Gopay, Shopeepay, Dana) related transaction 51 | */ 52 | 53 | public static function directDebit() 54 | { 55 | return new self("directDebit"); 56 | } 57 | 58 | /** 59 | * this method chain is used to start VA(Bank Transfer) related transaction 60 | */ 61 | public static function va() 62 | { 63 | return new self("va"); 64 | } 65 | /** 66 | * this method chain is used to start Qris related transaction 67 | */ 68 | public static function qris() 69 | { 70 | return new self("qris"); 71 | } 72 | /** 73 | * this method chain is used to verify webhook notification 74 | */ 75 | public static function notification(){ 76 | return new self(""); 77 | } 78 | /** 79 | * this method chain is used to add additional header during access token request 80 | */ 81 | public function withAccessTokenHeader(array $headers) 82 | { 83 | $this->accessTokenHeader = array_merge($this->accessTokenHeader, $headers); 84 | return $this; 85 | } 86 | 87 | /** 88 | * this method chain is used to add additional header during transaction process (create payment/ get status/ refund/ cancel) 89 | */ 90 | public function withTransactionHeader(array $headers) 91 | { 92 | $this->transactionHeader = array_merge($this->transactionHeader, $headers); 93 | return $this; 94 | } 95 | 96 | /** 97 | * this method chain is used to supply access token that you already have, and want to re-use 98 | */ 99 | public function withAccessToken($accessToken) 100 | { 101 | $this->accessToken = $accessToken; 102 | return $this; 103 | } 104 | 105 | /** 106 | * this method chain is used to supply the request body/ payload 107 | */ 108 | public function withBody($body) 109 | { 110 | $this->body = $body; 111 | return $this; 112 | } 113 | 114 | /** 115 | * These method chains below are config related method chain that can be used as an option 116 | */ 117 | public function withPrivateKey($privateKey) 118 | { 119 | SnapBiConfig::$snapBiPrivateKey = $privateKey; 120 | return $this; 121 | } 122 | 123 | public function withClientId($clientId) 124 | { 125 | SnapBiConfig::$snapBiClientId = $clientId; 126 | return $this; 127 | } 128 | 129 | public function withClientSecret($clientSecret) 130 | { 131 | SnapBiConfig::$snapBiClientSecret = $clientSecret; 132 | return $this; 133 | } 134 | 135 | public function withPartnerId($partnerId) 136 | { 137 | SnapBiConfig::$snapBiPartnerId = $partnerId; 138 | return $this; 139 | } 140 | 141 | public function withChannelId($channelId) 142 | { 143 | SnapBiConfig::$snapBiChannelId = $channelId; 144 | return $this; 145 | } 146 | 147 | public function withDeviceId($deviceId) 148 | { 149 | $this->deviceId = $deviceId; 150 | return $this; 151 | } 152 | 153 | public function withDebuglId($debugId) 154 | { 155 | $this->debugId = $debugId; 156 | return $this; 157 | } 158 | 159 | public function withSignature($signature) 160 | { 161 | $this->signature = $signature; 162 | return $this; 163 | } 164 | public function withTimeStamp($timeStamp) 165 | { 166 | $this->timestamp = $timeStamp; 167 | return $this; 168 | } 169 | public function withNotificationUrlPath($notificationUrlPath) 170 | { 171 | $this->notificationUrlPath = $notificationUrlPath; 172 | return $this; 173 | } 174 | 175 | /** 176 | * these method chain is used to execute create payment 177 | */ 178 | public function createPayment($externalId) 179 | { 180 | $this->apiPath = $this->setupCreatePaymentApiPath($this->paymentMethod); 181 | return $this->createConnection($externalId); 182 | } 183 | 184 | /** 185 | * these method chain is used to cancel the transaction 186 | */ 187 | public function cancel($externalId) 188 | { 189 | $this->apiPath = $this->setupCancelApiPath($this->paymentMethod); 190 | return $this->createConnection($externalId); 191 | } 192 | 193 | /** 194 | * these method chain is used to refund the transaction 195 | */ 196 | public function refund($externalId) 197 | { 198 | $this->apiPath = $this->setupRefundApiPath($this->paymentMethod); 199 | return $this->createConnection($externalId); 200 | } 201 | 202 | /** 203 | * these method chain is used to get the status of the transaction 204 | */ 205 | public function getStatus($externalId) 206 | { 207 | $this->apiPath = $this->setupGetStatusApiPath($this->paymentMethod); 208 | return $this->createConnection($externalId); 209 | } 210 | 211 | 212 | /** 213 | * these method chain is used to get the access token 214 | */ 215 | public function getAccessToken() 216 | { 217 | $snapBiAccessTokenHeader = $this->buildAccessTokenHeader($this->timeStamp); 218 | $openApiPayload = array( 219 | 'grant_type' => 'client_credentials', 220 | ); 221 | return SnapBiApiRequestor::remoteCall(SnapBiConfig::getSnapBiTransactionBaseUrl() . self::ACCESS_TOKEN, $snapBiAccessTokenHeader, $openApiPayload); 222 | } 223 | 224 | /** 225 | * @throws Exception 226 | */ 227 | public function isWebhookNotificationVerified(){ 228 | if (!SnapBiConfig::$snapBiPublicKey){ 229 | throw new Exception( 230 | 'The public key is null, You need to set the public key from SnapBiConfig.' . 231 | 'For more details contact support at support@midtrans.com if you have any questions.' 232 | ); 233 | } 234 | $notificationHttpMethod = "POST"; 235 | $minifiedNotificationBodyJsonString = json_encode($this->body); 236 | $hashedNotificationBodyJsonString = hash('sha256', $minifiedNotificationBodyJsonString); 237 | $rawStringDataToVerifyAgainstSignature = $notificationHttpMethod . ':' . $this->notificationUrlPath . ':' . $hashedNotificationBodyJsonString . ':' . $this->timestamp; 238 | $isSignatureVerified = openssl_verify( 239 | $rawStringDataToVerifyAgainstSignature, 240 | base64_decode($this->signature), 241 | SnapBiConfig::$snapBiPublicKey, 242 | OPENSSL_ALGO_SHA256 243 | ); 244 | return $isSignatureVerified === 1; 245 | } 246 | private function createConnection($externalId = null) 247 | { 248 | // Attempt to get the access token if it's not already set 249 | if (!$this->accessToken) { 250 | $access_token_response = $this->getAccessToken(); 251 | 252 | // If getting the access token failed, return the response from getAccessToken 253 | if (!isset($access_token_response->accessToken)) { 254 | return $access_token_response; 255 | } 256 | // Set the access token if it was successfully retrieved 257 | $this->accessToken = $access_token_response->accessToken; 258 | } 259 | // Proceed with the payment creation if access token is available 260 | $snapBiTransactionHeader = $this->buildSnapBiTransactionHeader($externalId, $this->timeStamp); 261 | // Make the remote call and return the response 262 | return SnapBiApiRequestor::remoteCall(SnapBiConfig::getSnapBiTransactionBaseUrl() . $this->apiPath, $snapBiTransactionHeader, $this->body); 263 | } 264 | 265 | public static function getSymmetricSignatureHmacSh512($accessToken, $requestBody, $method, $path, $clientSecret, $timeStamp) 266 | { 267 | // Minify and hash the request body 268 | $minifiedBody = json_encode($requestBody, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 269 | $hashedBody = hash('sha256', $minifiedBody, true); // Get binary digest 270 | $hexEncodedHash = bin2hex($hashedBody); 271 | $lowercaseHexHash = strtolower($hexEncodedHash); 272 | // Construct the payload 273 | $payload = strtoupper($method) . ":" . $path . ":" . $accessToken . ":" . $lowercaseHexHash . ":" . $timeStamp; 274 | // Generate HMAC using SHA512 275 | $hmac = hash_hmac('sha512', $payload, $clientSecret, true); 276 | // Encode the result to Base64 277 | return base64_encode($hmac); 278 | } 279 | 280 | public static function getAsymmetricSignatureSha256WithRsa($client_id, $x_time_stamp, $private_key) 281 | { 282 | $stringToSign = $client_id . "|" . $x_time_stamp; 283 | $binarySignature = null; 284 | openssl_sign($stringToSign, $binarySignature, $private_key, OPENSSL_ALGO_SHA256); 285 | return base64_encode($binarySignature); 286 | } 287 | 288 | /** 289 | * @param $externalId 290 | * @param $timeStamp 291 | * @return array 292 | */ 293 | private function buildSnapBiTransactionHeader($externalId, $timeStamp) 294 | { 295 | $snapBiTransactionHeader = array( 296 | "Content-Type" => "application/json", 297 | "Accept" => "application/json", 298 | "X-PARTNER-ID" => SnapBiConfig::$snapBiPartnerId, 299 | "X-EXTERNAL-ID" => $externalId, 300 | "X-DEVICE-ID" => $this->deviceId, 301 | "CHANNEL-ID" => SnapBiConfig::$snapBiChannelId, 302 | "debug-id" => $this->debugId, 303 | "Authorization" => "Bearer " . $this->accessToken, 304 | "X-TIMESTAMP" => $timeStamp, 305 | "X-SIGNATURE" => SnapBi::getSymmetricSignatureHmacSh512( 306 | $this->accessToken, 307 | $this->body, 308 | "post", 309 | $this->apiPath, 310 | SnapBiConfig::$snapBiClientSecret, 311 | $timeStamp 312 | ), 313 | ); 314 | //if withTransactionHeader is used, the header will be merged with the default header 315 | if (isset($this->transactionHeader)) { 316 | $snapBiTransactionHeader = array_merge($snapBiTransactionHeader, $this->transactionHeader); 317 | } 318 | return $snapBiTransactionHeader; 319 | } 320 | 321 | /** 322 | * @param $timeStamp 323 | * @return array 324 | */ 325 | private function buildAccessTokenHeader($timeStamp) 326 | { 327 | $snapBiAccessTokenHeader = array( 328 | "Content-Type" => "application/json", 329 | "Accept" => "application/json", 330 | "X-CLIENT-KEY" => SnapBiConfig::$snapBiClientId, 331 | "X-SIGNATURE" => SnapBi::getAsymmetricSignatureSha256WithRsa(SnapBiConfig::$snapBiClientId, $timeStamp, SnapBiConfig::$snapBiPrivateKey), 332 | "X-TIMESTAMP" => $timeStamp, 333 | "debug-id" => $this->debugId 334 | ); 335 | //if withAccessTokenHeader is used, the header will be merged with the default header 336 | if (isset($this->accessTokenHeader)) { 337 | $snapBiAccessTokenHeader = array_merge($snapBiAccessTokenHeader, $this->accessTokenHeader); 338 | } 339 | return $snapBiAccessTokenHeader; 340 | } 341 | 342 | private function setupCreatePaymentApiPath($paymentMethod) 343 | { 344 | switch ($paymentMethod) { 345 | case "va": 346 | return self::CREATE_VA; 347 | case "qris": 348 | return self::QRIS_PAYMENT; 349 | default: 350 | return self::PAYMENT_HOST_TO_HOST; 351 | } 352 | } 353 | private function setupRefundApiPath($paymentMethod) 354 | { 355 | switch ($paymentMethod) { 356 | case "qris": 357 | return self::QRIS_REFUND; 358 | default: 359 | return self::DEBIT_REFUND; 360 | } 361 | } 362 | 363 | private function setupCancelApiPath($paymentMethod) 364 | { 365 | switch ($paymentMethod) { 366 | case "va": 367 | return self::VA_CANCEL; 368 | case "qris": 369 | return self::QRIS_CANCEL; 370 | default: 371 | return self::DEBIT_CANCEL; 372 | } 373 | } 374 | 375 | private function setupGetStatusApiPath($paymentMethod) 376 | { 377 | switch ($paymentMethod) { 378 | case "va": 379 | return self::VA_STATUS; 380 | case "qris": 381 | return self::QRIS_STATUS; 382 | default: 383 | return self::DEBIT_STATUS; 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /SnapBi/SnapBiApiRequestor.php: -------------------------------------------------------------------------------- 1 | $value) { 16 | $curlHeaders[] = "$key: $value"; 17 | } 18 | 19 | $payload_json = json_encode($body); 20 | 21 | if (SnapBiConfig::$enableLogging){ 22 | echo sprintf("Request Body: \n%s\n", $payload_json); 23 | } 24 | 25 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 26 | curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders); 27 | curl_setopt($ch, CURLOPT_POST, true); 28 | curl_setopt($ch, CURLOPT_POSTFIELDS, $payload_json); 29 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 30 | curl_setopt($ch, CURLOPT_VERBOSE, SnapBiConfig::$enableLogging); 31 | 32 | $response = curl_exec($ch); 33 | 34 | if (curl_errno($ch)) { 35 | throw new Exception(curl_error($ch)); 36 | } 37 | $jsonResponse = json_decode($response); 38 | curl_close($ch); 39 | if (SnapBiConfig::$enableLogging){ 40 | echo sprintf("Response Body: \n%s\n", $response); 41 | } 42 | 43 | return $jsonResponse; 44 | } 45 | } -------------------------------------------------------------------------------- /SnapBi/SnapBiConfig.php: -------------------------------------------------------------------------------- 1 | =5.4", 28 | "ext-curl": "*", 29 | "ext-json": "*", 30 | "ext-openssl": "*" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "5.7.*", 34 | "psy/psysh": "0.4.*" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Midtrans\\": "Midtrans/", 39 | "SnapBi\\": "SnapBi/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Midtrans\\":[ 45 | "tests/", 46 | "test/integration", 47 | "tests/utility", 48 | "tests/utility/fixture" 49 | ] 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/core-api/checkout-process.php: -------------------------------------------------------------------------------- 1 | '; 11 | // Uncomment for append and override notification URL 12 | // Config::$appendNotifUrl = "https://example.com"; 13 | // Config::$overrideNotifUrl = "https://example.com"; 14 | 15 | // non-relevant function only used for demo/example purpose 16 | printExampleWarningMessage(); 17 | 18 | // Uncomment for production environment 19 | // Config::$isProduction = true; 20 | 21 | // Uncomment to enable sanitization 22 | // Config::$isSanitized = true; 23 | 24 | // Uncomment to enable idempotency-key, more details: (http://api-docs.midtrans.com/#idempotent-requests) 25 | // Config::$paymentIdempotencyKey = "Unique-ID"; 26 | 27 | $transaction_details = array( 28 | 'order_id' => time(), 29 | 'gross_amount' => 200000 30 | ); 31 | 32 | // Populate items 33 | $items = array( 34 | array( 35 | 'id' => 'item1', 36 | 'price' => 100000, 37 | 'quantity' => 1, 38 | 'name' => 'Adidas f50' 39 | ), 40 | array( 41 | 'id' => 'item2', 42 | 'price' => 50000, 43 | 'quantity' => 2, 44 | 'name' => 'Nike N90' 45 | )); 46 | 47 | // Populate customer's billing address 48 | $billing_address = array( 49 | 'first_name' => "Andri", 50 | 'last_name' => "Setiawan", 51 | 'address' => "Karet Belakang 15A, Setiabudi.", 52 | 'city' => "Jakarta", 53 | 'postal_code' => "51161", 54 | 'phone' => "081322311801", 55 | 'country_code' => 'IDN' 56 | ); 57 | 58 | // Populate customer's shipping address 59 | $shipping_address = array( 60 | 'first_name' => "John", 61 | 'last_name' => "Watson", 62 | 'address' => "Bakerstreet 221B.", 63 | 'city' => "Jakarta", 64 | 'postal_code' => "51162", 65 | 'phone' => "081322311801", 66 | 'country_code' => 'IDN' 67 | ); 68 | 69 | // Populate customer's info 70 | $customer_details = array( 71 | 'first_name' => "Andri", 72 | 'last_name' => "Setiawan", 73 | 'email' => "andri@setiawan.com", 74 | 'phone' => "081322311801", 75 | 'billing_address' => $billing_address, 76 | 'shipping_address' => $shipping_address 77 | ); 78 | 79 | // Token ID from checkout page 80 | $token_id = $_POST['token_id']; 81 | $authentication = isset($_POST['secure']); 82 | $save_token_id = isset($_POST['save_cc']); 83 | 84 | // Transaction data to be sent 85 | $transaction_data = array( 86 | 'payment_type' => 'credit_card', 87 | 'credit_card' => array( 88 | 'token_id' => $token_id, 89 | 'authentication' => $authentication, 90 | // 'bank' => 'bni', // optional acquiring bank 91 | 'save_token_id' => $save_token_id 92 | ), 93 | 'transaction_details' => $transaction_details, 94 | 'item_details' => $items, 95 | 'customer_details' => $customer_details 96 | ); 97 | 98 | try { 99 | $response = CoreApi::charge($transaction_data); 100 | header('Content-Type: application/json'); 101 | echo json_encode($response); 102 | } catch (\Exception $e) { 103 | echo $e->getMessage(); 104 | } 105 | 106 | function printExampleWarningMessage() { 107 | if (strpos(Config::$serverKey, 'your ') != false ) { 108 | echo ""; 109 | echo "

Please set your server key from sandbox

"; 110 | echo "In file: " . __FILE__; 111 | echo "
"; 112 | echo "
"; 113 | echo htmlspecialchars('Config::$serverKey = \'\';'); 114 | die(); 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /examples/core-api/checkout.php: -------------------------------------------------------------------------------- 1 | Settings -> Access keys 11 | Config::$clientKey = ''; 12 | 13 | // non-relevant function only used for demo/example purpose 14 | printExampleWarningMessage(); 15 | 16 | function printExampleWarningMessage() { 17 | if (strpos(Config::$clientKey, 'your ') != false ) { 18 | echo ""; 19 | echo "

Please set your client key from sandbox

"; 20 | echo "In file: " . __FILE__; 21 | echo "
"; 22 | echo "
"; 23 | echo htmlspecialchars('Config::$clientKey = \'\';'); 24 | die(); 25 | } 26 | } 27 | ?> 28 | 29 | 30 | 31 | Checkout 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |

Checkout

41 |
42 |
43 | Checkout 44 | Field that may be presented to customer: 45 |

46 | 47 | 48 |

49 |

50 | 51 | 52 | / 53 | 54 |

55 |

56 | 57 | 58 |

59 |

60 | 61 | 62 |

63 | Fields that shouldn't be presented to the customer: 64 |

65 | 66 | 67 |

68 | 69 | 70 |
71 |
72 | 73 | 74 |
 75 |         Testing cards:
 76 | 
 77 |         For 3D Secure:
 78 |         Visa        4811 1111 1111 1114
 79 |         MasterCard  5211 1111 1111 1117
 80 | 
 81 |         For Non 3D Secure:
 82 |         Visa success      4011 1111 1111 1112
 83 |         Visa challenge    4111 1111 1111 1111
 84 |         Visa deny by FDS  4211 1111 1111 1110
 85 | 
 86 |         MasterCard success      5481 1611 1111 1081
 87 |         MasterCard challenge    5110 1111 1111 1119
 88 |         MasterCard deny by FDS  5210 1111 1111 1118
 89 | 
 90 |         
91 |
92 | 93 | 94 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /examples/core-api/tokenization-process.php: -------------------------------------------------------------------------------- 1 | Settings -> Access keys 9 | Config::$serverKey = ''; 10 | 11 | // non-relevant function only used for demo/example purpose 12 | printExampleWarningMessage(); 13 | 14 | // define variables and set to empty values 15 | $number = ""; 16 | 17 | if ($_SERVER["REQUEST_METHOD"] == "POST") { 18 | $number = ($_POST["number"]); 19 | } 20 | 21 | // required 22 | $params = array( 23 | "payment_type" => "gopay", 24 | "gopay_partner" => array( 25 | "phone_number" => $number, 26 | "redirect_url" => "https://www.google.com" 27 | ) 28 | ); 29 | 30 | $response = ''; 31 | try { 32 | $response = CoreApi::linkPaymentAccount($params); 33 | } catch (\Exception $e) { 34 | echo $e->getMessage(); 35 | die(); 36 | } 37 | 38 | function printExampleWarningMessage() { 39 | if (strpos(Config::$serverKey, 'your ') != false ) { 40 | echo ""; 41 | echo "

Please set your server key from sandbox

"; 42 | echo "In file: " . __FILE__; 43 | echo "
"; 44 | echo "
"; 45 | echo htmlspecialchars('Config::$serverKey = \'\';'); 46 | die(); 47 | } 48 | } 49 | 50 | ?> 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |

Simple Gopay Tokenization

59 |
"> 60 | Phone number: 61 |

62 | 63 |
64 | 65 | 66 | Result get pay account:"; 68 | echo json_encode($response, JSON_UNESCAPED_SLASHES); 69 | echo "
"; 70 | ?> 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /examples/core-api/transaction-manipulation.php: -------------------------------------------------------------------------------- 1 | Settings -> Access keys 9 | Config::$serverKey = ''; 10 | 11 | // non-relevant function only used for demo/example purpose 12 | printExampleWarningMessage(); 13 | 14 | $orderId = ''; 15 | // Get transaction status to Midtrans API 16 | $status = ''; 17 | try { 18 | $status = Transaction::status($orderId); 19 | } catch (\Exception $e) { 20 | echo $e->getMessage(); 21 | die(); 22 | } 23 | 24 | echo '
';
25 | echo json_encode($status);
26 | 
27 | function printExampleWarningMessage() {
28 |     if (strpos(Config::$serverKey, 'your ') != false ) {
29 |         echo "";
30 |         echo "

Please set your server key from sandbox

"; 31 | echo "In file: " . __FILE__; 32 | echo "
"; 33 | echo "
"; 34 | echo htmlspecialchars('Config::$serverKey = \'\';'); 35 | die(); 36 | } 37 | } 38 | 39 | 40 | // Approve a transaction that is in Challenge status 41 | // $approve = Transaction::approve($orderId); 42 | // var_dump($approve); 43 | 44 | // Cancel a transaction 45 | // $cancel = Transaction::cancel($orderId); 46 | // var_dump($cancel); 47 | 48 | // Expire a transaction 49 | // $expire = Transaction::expire($orderId); 50 | // var_dump($expire); 51 | -------------------------------------------------------------------------------- /examples/notification-handler.php: -------------------------------------------------------------------------------- 1 | '; 11 | 12 | // non-relevant function only used for demo/example purpose 13 | printExampleWarningMessage(); 14 | 15 | try { 16 | $notif = new Notification(); 17 | } 18 | catch (\Exception $e) { 19 | exit($e->getMessage()); 20 | } 21 | 22 | $notif = $notif->getResponse(); 23 | $transaction = $notif->transaction_status; 24 | $type = $notif->payment_type; 25 | $order_id = $notif->order_id; 26 | $fraud = $notif->fraud_status; 27 | 28 | if ($transaction == 'capture') { 29 | // For credit card transaction, we need to check whether transaction is challenge by FDS or not 30 | if ($type == 'credit_card') { 31 | if ($fraud == 'challenge') { 32 | // TODO set payment status in merchant's database to 'Challenge by FDS' 33 | // TODO merchant should decide whether this transaction is authorized or not in MAP 34 | echo "Transaction order_id: " . $order_id ." is challenged by FDS"; 35 | } else { 36 | // TODO set payment status in merchant's database to 'Success' 37 | echo "Transaction order_id: " . $order_id ." successfully captured using " . $type; 38 | } 39 | } 40 | } else if ($transaction == 'settlement') { 41 | // TODO set payment status in merchant's database to 'Settlement' 42 | echo "Transaction order_id: " . $order_id ." successfully transfered using " . $type; 43 | } else if ($transaction == 'pending') { 44 | // TODO set payment status in merchant's database to 'Pending' 45 | echo "Waiting customer to finish transaction order_id: " . $order_id . " using " . $type; 46 | } else if ($transaction == 'deny') { 47 | // TODO set payment status in merchant's database to 'Denied' 48 | echo "Payment using " . $type . " for transaction order_id: " . $order_id . " is denied."; 49 | } else if ($transaction == 'expire') { 50 | // TODO set payment status in merchant's database to 'expire' 51 | echo "Payment using " . $type . " for transaction order_id: " . $order_id . " is expired."; 52 | } else if ($transaction == 'cancel') { 53 | // TODO set payment status in merchant's database to 'Denied' 54 | echo "Payment using " . $type . " for transaction order_id: " . $order_id . " is canceled."; 55 | } 56 | 57 | function printExampleWarningMessage() { 58 | if ($_SERVER['REQUEST_METHOD'] != 'POST') { 59 | echo 'Notification-handler are not meant to be opened via browser / GET HTTP method. It is used to handle Midtrans HTTP POST notification / webhook.'; 60 | } 61 | if (strpos(Config::$serverKey, 'your ') != false ) { 62 | echo ""; 63 | echo "

Please set your server key from sandbox

"; 64 | echo "In file: " . __FILE__; 65 | echo "
"; 66 | echo "
"; 67 | echo htmlspecialchars('Config::$serverKey = \'\';'); 68 | die(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/snap-bi/midtrans-webhook-php/notification.php: -------------------------------------------------------------------------------- 1 | "Only POST requests are allowed"]); 11 | exit; 12 | } 13 | 14 | // Read all headers 15 | $headers = getallheaders(); 16 | 17 | // Example: Get a specific header (like 'X-Signature') 18 | $xSignature = isset($headers['X-Signature']) ? $headers['X-Signature'] : "kosong"; 19 | $xTimeStamp = isset($headers['X-Timestamp']) ? $headers['X-Timestamp'] : "kosong"; 20 | $requestUri = $_SERVER['REQUEST_URI']; 21 | 22 | // Extract everything after '/notification.php' 23 | $afterNotification = strstr($requestUri, '/notification.php'); 24 | $pathAfterNotification = substr($afterNotification, strlen('/notification.php')); 25 | 26 | // Read the input/payload from the request body 27 | $input = file_get_contents("php://input"); 28 | $payload = json_decode($input); 29 | 30 | if (!$input) { 31 | // Respond with an error if no payload is received 32 | header("Content-Type: application/json"); 33 | echo json_encode(["error" => "No input received"]); 34 | exit; 35 | } 36 | header("Content-Type: application/json"); 37 | $notificationUrlPath = $pathAfterNotification; 38 | 39 | $publicKeyString = "-----BEGIN PUBLIC KEY-----\nABCDefghlhuoJgoXiK21s2NIW0+uJb08sHmd/+/Cm7UH7M/oU3VE9oLhU89oOzXZgtsiw7lR8duWJ0w738NfzvkdA5pX8OYnIL+5Hfa/CxvlT4yAX/abcdEFgh\n-----END PUBLIC KEY-----\n"; 40 | SnapBiConfig::$snapBiPublicKey = $publicKeyString; 41 | 42 | 43 | try { 44 | echo json_encode([ 45 | "message" => "Webhook verified successfully", 46 | "isVerified" => SnapBi::notification() 47 | ->withBody($payload) 48 | ->withSignature($xSignature) 49 | ->withTimeStamp($xTimeStamp) 50 | ->withNotificationUrlPath($notificationUrlPath) 51 | ->isWebhookNotificationVerified(), 52 | ]); 53 | } catch (\Exception $e) { 54 | echo json_encode([ 55 | "message" => "Webhook verification error", 56 | "error" => $e->getMessage() 57 | ]); 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/snap-bi/snap-bi-cancel.php: -------------------------------------------------------------------------------- 1 | "A1202409071547203VpKvjM8MrID" 28 | ); 29 | $directDebitCancelByExternalIdBody = array( 30 | "originalExternalId" => "uzi-order-testing66dc75ab3b96c" 31 | ); 32 | 33 | $vaCancelBody = array( 34 | "partnerServiceId" => " 5818", 35 | "customerNo" => "628014506680", 36 | "virtualAccountNo" => " 5818628014506680", 37 | "trxId" => "uzi-order-testing66dc76754bf1c", 38 | "additionalInfo" => array( 39 | "merchantId" => $va_merchant_id 40 | ) 41 | ); 42 | $qrisCancelBody = array( 43 | "originalReferenceNo" => "A120241003151650FgOcD5vWIVID", 44 | "merchantId" => $merchant_id, 45 | "reason" => "cancel reason", 46 | ); 47 | 48 | 49 | $snapBiResponse = null; 50 | SnapBiConfig::$snapBiClientId = $client_id; 51 | SnapBiConfig::$snapBiPrivateKey = $private_key; 52 | SnapBiConfig::$snapBiClientSecret = $client_secret; 53 | SnapBiConfig::$snapBiPartnerId = $partner_id; 54 | SnapBiConfig::$snapBiChannelId = $channel_id; 55 | SnapBiConfig::$enableLogging = true; 56 | 57 | try { 58 | 59 | /** 60 | * Example code for SnapBI, you can uncomment and run the code. 61 | * To cancel transaction you can use externalId or referenceNo. 62 | * The difference is based on the request body/ payload. 63 | * you can refer to the variable $directDebitCancelByReferenceBody or $directDebitCancelByExternalIdBody (for direct debit) or $vaCancelBody (for va) to see the value. 64 | * 65 | * Below are example code to cancel the transaction. 66 | */ 67 | 68 | /** 69 | * Example code for Direct Debit cancel using externalId 70 | */ 71 | 72 | /** 73 | * Basic implementation of Direct Debit to cancel transaction 74 | */ 75 | $snapBiResponse = SnapBi::directDebit() 76 | ->withBody($directDebitCancelByExternalIdBody) 77 | ->cancel($external_id); 78 | 79 | /** 80 | * Example code of Direct Debit to cancel transaction using your existing access token 81 | */ 82 | $snapBiResponse = SnapBi::directDebit() 83 | ->withAccessToken("") 84 | ->withBody($directDebitCancelByExternalIdBody) 85 | ->cancel($external_id); 86 | 87 | /** 88 | * Example code of Direct Debit to cancel transaction by adding or overriding the accessTokenHeader and TranasctionHeader 89 | */ 90 | $snapBiResponse = SnapBi::directDebit() 91 | ->withBody($directDebitCancelByExternalIdBody) 92 | ->withAccessTokenHeader([ 93 | "CHANNEL-ID" => "12345" 94 | ]) 95 | ->withTransactionHeader([ 96 | "CHANNEL-ID" => "12345" 97 | ]) 98 | ->cancel($external_id); 99 | 100 | /** 101 | * Example code for Direct Debit to cancel using referenceNo 102 | */ 103 | 104 | /** 105 | * Basic implementation of Direct Debit to cancel transaction 106 | */ 107 | $snapBiResponse = SnapBi::directDebit() 108 | ->withBody($directDebitCancelByReferenceBody) 109 | ->cancel($external_id); 110 | 111 | /** 112 | * Example code of Direct Debit to cancel transaction using your existing access token 113 | */ 114 | $snapBiResponse = SnapBi::directDebit() 115 | ->withAccessToken("") 116 | ->withBody($directDebitCancelByReferenceBody) 117 | ->cancel($external_id); 118 | 119 | /** 120 | * Example code of Direct Debit to cancel transaction by adding or overriding the accessTokenHeader and TransactionHeader 121 | */ 122 | $snapBiResponse = SnapBi::directDebit() 123 | ->withBody($directDebitCancelByReferenceBody) 124 | ->withAccessTokenHeader([ 125 | "CHANNEL-ID" => "12345" 126 | ]) 127 | ->withTransactionHeader([ 128 | "CHANNEL-ID" => "12345" 129 | ]) 130 | ->cancel($external_id); 131 | 132 | /** 133 | * Example code for VA (Bank Transfer) to cancel transaction 134 | */ 135 | 136 | /** 137 | * Basic implementation of VA (Bank Transfer) to cancel transaction 138 | */ 139 | $snapBiResponse = SnapBi::va() 140 | ->withBody($vaCancelBody) 141 | ->cancel($external_id); 142 | 143 | /** 144 | * Example code of VA (Bank Transfer) to cancel transaction using your existing access token 145 | */ 146 | $snapBiResponse = SnapBi::va() 147 | ->withAccessToken("") 148 | ->withBody($vaCancelBody) 149 | ->cancel($external_id); 150 | 151 | /** 152 | * Example code of VA (Bank Transfer) to cancel transaction by adding or overriding the accessTokenHeader and TransactionHeader 153 | */ 154 | $snapBiResponse = SnapBi::va() 155 | ->withBody($vaCancelBody) 156 | ->withAccessTokenHeader([ 157 | "CHANNEL-ID" => "12345" 158 | ]) 159 | ->withTransactionHeader([ 160 | "CHANNEL-ID" => "12345" 161 | ]) 162 | ->cancel($external_id); 163 | 164 | /** 165 | * Example code for Qris to cancel transaction 166 | */ 167 | 168 | /** 169 | * Basic implementation of Qris to cancel transaction 170 | */ 171 | $snapBiResponse = SnapBi::qris() 172 | ->withBody($qrisCancelBody) 173 | ->cancel($external_id); 174 | 175 | /** 176 | * Example code of Qris to cancel transaction using your existing access token 177 | */ 178 | $snapBiResponse = SnapBi::qris() 179 | ->withAccessToken("") 180 | ->withBody($qrisCancelBody) 181 | ->cancel($external_id); 182 | 183 | /** 184 | * Example code of Qris to cancel transaction by adding or overriding the accessTokenHeader and TransactionHeader 185 | */ 186 | $snapBiResponse = SnapBi::qris() 187 | ->withBody($qrisCancelBody) 188 | ->withAccessTokenHeader([ 189 | "CHANNEL-ID" => "12345" 190 | ]) 191 | ->withTransactionHeader([ 192 | "CHANNEL-ID" => "12345" 193 | ]) 194 | ->cancel($external_id); 195 | 196 | } catch (\Exception $e) { 197 | echo $e->getMessage(); 198 | } 199 | echo "snap bi response = " . print_r($snapBiResponse, true), PHP_EOL; 200 | 201 | function generateRandomNumber() 202 | { 203 | $prefix = "6280"; // Fixed prefix 204 | $randomDigits = mt_rand(100000000, 999999999); // Generate 9 random digits 205 | return $prefix . $randomDigits; 206 | } 207 | 208 | -------------------------------------------------------------------------------- /examples/snap-bi/snap-bi-direct-debit-payment.php: -------------------------------------------------------------------------------- 1 | modify('+10 minutes'); 30 | 31 | // Format the new date 32 | $valid_until = $date->format('c'); 33 | 34 | $debitParams = array( 35 | "partnerReferenceNo" => $external_id, 36 | "chargeToken" => "", 37 | "merchantId" => $merchant_id, 38 | "urlParam" => array( 39 | array( 40 | "url" => "https://www.google.com", 41 | "type" => "PAY_RETURN", 42 | "isDeeplink" => "Y" 43 | ) 44 | ), 45 | "validUpTo" => $valid_until, 46 | "payOptionDetails" => array( 47 | array( 48 | "payMethod" => "GOPAY", 49 | "payOption" => "GOPAY_WALLET", 50 | "transAmount" => array( 51 | "value" => "10000.0", 52 | "currency" => "IDR" 53 | ) 54 | ) 55 | ), 56 | "additionalInfo" => array( 57 | "customerDetails" => array( 58 | "phone" => "081122334455", 59 | "firstName" => "Andri", 60 | "lastName" => "Litani", 61 | "email" => "andri@litani.com", 62 | "billingAddress" => array( 63 | "firstName" => "Andri", 64 | "lastName" => "Litani", 65 | "phone" => "081122334455", 66 | "address" => "billingAddress", 67 | "city" => "billingCity", 68 | "postalCode" => "12790", 69 | "countryCode" => "CZH" 70 | ), 71 | "shippingAddress" => array( 72 | "firstName" => "Andri", 73 | "lastName" => "Litani", 74 | "phone" => "081122334455", 75 | "address" => "shippingAddress", 76 | "city" => "shippingCity", 77 | "postalCode" => "12790", 78 | "countryCode" => "CZH" 79 | ) 80 | ), 81 | "items" => array( 82 | array( 83 | "id" => "1", 84 | "price" => array( 85 | "value" => "10000.00", 86 | "currency" => "IDR" 87 | ), 88 | "quantity" => 1, 89 | "name" => "Apple", 90 | "brand" => "Apple", 91 | "category" => "Subscription", 92 | "merchantName" => "amazon prime", 93 | "url" => "itemUrl" 94 | ) 95 | ), 96 | "metadata" => array() 97 | ) 98 | ); 99 | 100 | 101 | $snapBiResponse = null; 102 | SnapBiConfig::$snapBiClientId = $client_id; 103 | SnapBiConfig::$snapBiPrivateKey = $private_key; 104 | SnapBiConfig::$snapBiClientSecret = $client_secret; 105 | SnapBiConfig::$snapBiPartnerId = $partner_id; 106 | SnapBiConfig::$snapBiChannelId = $channel_id; 107 | SnapBiConfig::$enableLogging = true; 108 | 109 | try { 110 | 111 | /** 112 | * Example code for Direct Debit (gopay/ dana/ shopeepay) using Snap Bi, you can uncomment and run the code. 113 | * Below are example code to create va 114 | */ 115 | 116 | /** 117 | * Basic example 118 | * to change the payment method, you can change the value of the request body on the `payOptionDetails` 119 | */ 120 | $snapBiResponse = SnapBi::directDebit() 121 | ->withBody($debitParams) 122 | ->createPayment($external_id); 123 | 124 | /** 125 | * Example of using existing access token to create payment. You can uncomment and run the code 126 | * to change the payment method, you can change the value of the request body on the `payOptionDetails` 127 | */ 128 | $snapBiResponse = SnapBi::directDebit() 129 | ->withAccessToken("") 130 | ->withBody($debitParams) 131 | ->createPayment($external_id); 132 | 133 | /** 134 | * Example of using additional header on access token and when doing transaction header. You can uncomment and run the code 135 | * to change the payment method, you can change the value of the request body on the `payOptionDetails` 136 | */ 137 | $snapBiResponse = SnapBi::directDebit() 138 | ->withAccessTokenHeader([ 139 | "debug-id"=> "va debug id", 140 | "X-DEVICE-ID"=>"va device id", 141 | ]) 142 | ->withTransactionHeader([ 143 | "debug-id"=> "va debug id", 144 | "X-DEVICE-ID"=>"va device id", 145 | ]) 146 | ->withBody($debitParams) 147 | ->createPayment($external_id); 148 | 149 | } catch (\Exception $e) { 150 | echo $e->getMessage(); 151 | } 152 | echo "snap bi response = " . print_r($snapBiResponse, true), PHP_EOL; 153 | 154 | function generateRandomNumber() 155 | { 156 | $prefix = "6280"; // Fixed prefix 157 | $randomDigits = mt_rand(100000000, 999999999); // Generate 9 random digits 158 | return $prefix . $randomDigits; 159 | } 160 | -------------------------------------------------------------------------------- /examples/snap-bi/snap-bi-qris-payment.php: -------------------------------------------------------------------------------- 1 | modify('+10 minutes'); 30 | 31 | // Format the new date 32 | $valid_until = $date->format('c'); 33 | 34 | 35 | 36 | $qrisBody = array( 37 | "partnerReferenceNo" => $external_id, 38 | "amount" => array( 39 | "value" => "1500.00", 40 | "currency" => "IDR" 41 | ), 42 | "merchantId" => $merchant_id, 43 | "validityPeriod" => "2030-07-03T12:08:56-07:00", 44 | "additionalInfo" => array( 45 | "acquirer" => "gopay", 46 | "items" => array( 47 | array( 48 | "id" => "8143fc4f-ec05-4c55-92fb-620c212f401e", 49 | "price" => array( 50 | "value" => "1500.00", 51 | "currency" => "IDR" 52 | ), 53 | "quantity" => 1, 54 | "name" => "test item name", 55 | "brand" => "test item brand", 56 | "category" => "test item category", 57 | "merchantName" => "Merchant Operation" 58 | ) 59 | ), 60 | "customerDetails" => array( 61 | "email" => "merchant-ops@midtrans.com", 62 | "firstName" => "Merchant", 63 | "lastName" => "Operation", 64 | "phone" => "+6281932358123" 65 | ), 66 | "countryCode" => "ID", 67 | "locale" => "id_ID" 68 | ) 69 | ); 70 | 71 | 72 | $snapBiResponse = null; 73 | SnapBiConfig::$snapBiClientId = $client_id; 74 | SnapBiConfig::$snapBiPrivateKey = $private_key; 75 | SnapBiConfig::$snapBiClientSecret = $client_secret; 76 | SnapBiConfig::$snapBiPartnerId = $partner_id; 77 | SnapBiConfig::$snapBiChannelId = $channel_id; 78 | SnapBiConfig::$enableLogging = true; 79 | 80 | try { 81 | 82 | /** 83 | * Example code for Direct Debit (gopay/ dana/ shopeepay) using Snap Bi, you can uncomment and run the code. 84 | * Below are example code to create va 85 | */ 86 | 87 | /** 88 | * Basic example 89 | * to change the payment method, you can change the value of the request body on the `payOptionDetails` 90 | */ 91 | $snapBiResponse = SnapBi::qris() 92 | ->withBody($qrisBody) 93 | ->createPayment($external_id); 94 | 95 | /** 96 | * Example of using existing access token to create payment. You can uncomment and run the code 97 | * to change the payment method, you can change the value of the request body on the `payOptionDetails` 98 | */ 99 | $snapBiResponse = SnapBi::qris() 100 | ->withAccessToken("") 101 | ->withBody($qrisBody) 102 | ->createPayment($external_id); 103 | 104 | /** 105 | * Example of using additional header on access token and when doing transaction header. You can uncomment and run the code 106 | * to change the payment method, you can change the value of the request body on the `payOptionDetails` 107 | */ 108 | $snapBiResponse = SnapBi::qris() 109 | ->withAccessTokenHeader([ 110 | "debug-id"=> "va debug id", 111 | "X-DEVICE-ID"=>"va device id", 112 | ]) 113 | ->withTransactionHeader([ 114 | "debug-id"=> "va debug id", 115 | "X-DEVICE-ID"=>"va device id", 116 | ]) 117 | ->withBody($qrisBody) 118 | ->createPayment($external_id); 119 | 120 | } catch (\Exception $e) { 121 | echo $e->getMessage(); 122 | } 123 | echo "snap bi response = " . print_r($snapBiResponse, true), PHP_EOL; 124 | 125 | function generateRandomNumber() 126 | { 127 | $prefix = "6280"; // Fixed prefix 128 | $randomDigits = mt_rand(100000000, 999999999); // Generate 9 random digits 129 | return $prefix . $randomDigits; 130 | } 131 | 132 | -------------------------------------------------------------------------------- /examples/snap-bi/snap-bi-refund.php: -------------------------------------------------------------------------------- 1 | "uzi-order-testing66cec41c7f905", 30 | "partnerRefundNo" => "uzi-order-testing66cec41c7f905" . "refund-0001".rand(), 31 | "reason" => "some-reason", 32 | "additionalInfo" => array(), 33 | "refundAmount" => array( 34 | "value" => "100.00", 35 | "currency" => "IDR" 36 | )); 37 | 38 | $directDebitRefundByReferenceBody = array( 39 | "originalReferenceNo" => "A120240907120426ZsbsQvlcYBID", 40 | "reason" => "some-reason", 41 | "additionalInfo" => array(), 42 | "refundAmount" => array( 43 | "value" => "100.00", 44 | "currency" => "IDR" 45 | )); 46 | 47 | $qrisRefundBody = array( 48 | "merchantId" => $merchant_id, 49 | "originalPartnerReferenceNo" => "uzi-order-testing66feb6218257b", 50 | "originalReferenceNo" => "A1202410031520025F17xSCZWMID", 51 | "partnerRefundNo" => "partner-refund-no-". uniqid(), 52 | "reason" => "refund reason", 53 | "refundAmount" => array( 54 | "value" => "100.00", 55 | "currency" => "IDR" 56 | ), 57 | "additionalInfo" => array( 58 | "foo" => "bar" 59 | ) 60 | ); 61 | 62 | 63 | $snapBiResponse = null; 64 | SnapBiConfig::$snapBiClientId = $client_id; 65 | SnapBiConfig::$snapBiPrivateKey = $private_key; 66 | SnapBiConfig::$snapBiClientSecret = $client_secret; 67 | SnapBiConfig::$snapBiPartnerId = $partner_id; 68 | SnapBiConfig::$snapBiChannelId = $channel_id; 69 | SnapBiConfig::$enableLogging = true; 70 | 71 | try { 72 | 73 | /** 74 | * Example code for SnapBI, you can uncomment and run the code. 75 | * Below are example code to refund the transaction. 76 | * For Direct Debit, you can refund the transaction using externalId or referenceNo 77 | * The difference is based on the payload, you can refer to $directDebitRefundByExternalIdBody or $directDebitRefundByReferenceBody to see the value 78 | * For Qris refund, you can refer to $qrisRefundBody to see the value. 79 | */ 80 | 81 | 82 | /** 83 | * Example code for Direct Debit refund 84 | */ 85 | /** 86 | * Example code for Direct Debit refund using externalId 87 | */ 88 | $snapBiResponse = SnapBi::directDebit() 89 | ->withBody($directDebitRefundByExternalIdBody) 90 | ->refund($external_id); 91 | 92 | /** 93 | * Example code for Direct Debit refund using externalId by re-using access token 94 | */ 95 | $snapBiResponse = SnapBi::directDebit() 96 | ->withAccessToken("") 97 | ->withBody($directDebitRefundByExternalIdBody) 98 | ->refund($external_id); 99 | 100 | /** 101 | * Example code for Direct Debit refund using externalId by adding additional header 102 | */ 103 | $snapBiResponse = SnapBi::directDebit() 104 | ->withAccessTokenHeader([ 105 | "debug-id"=> "va debug id", 106 | "X-DEVICE-ID"=>"va device id" 107 | ]) 108 | ->withTransactionHeader([ 109 | "debug-id"=> "va debug id", 110 | "X-DEVICE-ID"=>"va device id" 111 | ]) 112 | ->withBody($directDebitRefundByExternalIdBody) 113 | ->refund($external_id); 114 | 115 | /** 116 | * Example code for Direct Debit refund using reference no 117 | */ 118 | $snapBiResponse = SnapBi::directDebit() 119 | ->withBody($directDebitRefundByReferenceBody) 120 | ->refund($external_id); 121 | 122 | /** 123 | * Example code for Direct Debit refund using reference no by re-using access token 124 | */ 125 | $snapBiResponse = SnapBi::directDebit() 126 | ->withAccessToken("") 127 | ->withBody($directDebitRefundByReferenceBody) 128 | ->refund($external_id); 129 | 130 | /** 131 | * Example code for Direct Debit refund using reference no by adding additional header 132 | */ 133 | $snapBiResponse = SnapBi::directDebit() 134 | ->withAccessTokenHeader([ 135 | "debug-id"=> "va debug id", 136 | "X-DEVICE-ID"=>"va device id" 137 | ]) 138 | ->withTransactionHeader([ 139 | "debug-id"=> "va debug id", 140 | "X-DEVICE-ID"=>"va device id" 141 | ]) 142 | ->withBody($directDebitRefundByReferenceBody) 143 | ->refund($external_id); 144 | 145 | /** 146 | * Example code for Qris refund 147 | */ 148 | /** 149 | * Example code for Qris refund basic implementation 150 | */ 151 | $snapBiResponse = SnapBi::qris() 152 | ->withBody($qrisRefundBody) 153 | ->refund($external_id); 154 | 155 | /** 156 | * Example code for Qris refund by re-using access token 157 | */ 158 | $snapBiResponse = SnapBi::qris() 159 | ->withAccessToken("") 160 | ->withBody($qrisRefundBody) 161 | ->refund($external_id); 162 | 163 | /** 164 | * Example code for Qris refund by adding additional header 165 | */ 166 | $snapBiResponse = SnapBi::qris() 167 | ->withAccessTokenHeader([ 168 | "debug-id"=> "va debug id", 169 | "X-DEVICE-ID"=>"va device id" 170 | ]) 171 | ->withTransactionHeader([ 172 | "debug-id"=> "va debug id", 173 | "X-DEVICE-ID"=>"va device id" 174 | ]) 175 | ->withBody($qrisRefundBody) 176 | ->refund($external_id); 177 | 178 | 179 | 180 | } catch (\Exception $e) { 181 | echo $e->getMessage(); 182 | } 183 | echo "snap bi response = " . print_r($snapBiResponse, true), PHP_EOL; 184 | 185 | function generateRandomNumber() 186 | { 187 | $prefix = "6280"; // Fixed prefix 188 | $randomDigits = mt_rand(100000000, 999999999); // Generate 9 random digits 189 | return $prefix . $randomDigits; 190 | } 191 | -------------------------------------------------------------------------------- /examples/snap-bi/snap-bi-status.php: -------------------------------------------------------------------------------- 1 | "uzi-order-testing66ce90ce90ee5", 30 | "originalPartnerReferenceNo" => "uzi-order-testing66ce90ce90ee5", 31 | "serviceCode" => "54" 32 | ); 33 | 34 | $directDebitStatusByReferenceBody = array( 35 | "originalReferenceNo" => "A120240907120426ZsbsQvlcYBID", 36 | "serviceCode" => "54" 37 | ); 38 | 39 | 40 | //make sure to include the spaces based on the createPayment response 41 | $vaStatusBody = array( 42 | "partnerServiceId" => " 5818", 43 | "customerNo" => "628064192914", 44 | "virtualAccountNo" => " 5818628064192914", 45 | "inquiryRequestId" => "uzi-order-testing66dc4799e4af5", 46 | "paymentRequestId" => "uzi-order-testing66dc4799e4af5", 47 | "additionalInfo" => array( 48 | "merchantId" => $va_merchant_id 49 | ) 50 | ); 51 | 52 | $qrisStatusBody = array( 53 | "originalReferenceNo" => "A120240910100828anKJlXgsi6ID", 54 | "originalPartnerReferenceNo" => "uzi-order-testing66e01a9b8c6bf", 55 | "merchantId" => $merchant_id, 56 | "serviceCode" => "54" 57 | ); 58 | 59 | $snapBiResponse = null; 60 | SnapBiConfig::$snapBiClientId = $client_id; 61 | SnapBiConfig::$snapBiPrivateKey = $private_key; 62 | SnapBiConfig::$snapBiClientSecret = $client_secret; 63 | SnapBiConfig::$snapBiPartnerId = $partner_id; 64 | SnapBiConfig::$snapBiChannelId = $channel_id; 65 | SnapBiConfig::$enableLogging = true; 66 | 67 | try { 68 | 69 | /** 70 | * Example code for SnapBI 71 | * The difference is based on the request body/ payload. 72 | * For Direct Debit you can refer to the variable $directDebitStatusByExternalIdBody or $directDebitStatusByReferenceBody to see the value. 73 | * For VA (Bank Transfer) you can refer to the variable $vaStatusBody to see the value. 74 | * For qris, you can refer to the variable $qrisStatusBody. 75 | */ 76 | 77 | /** 78 | * Example code for Direct Debit getStatus using externalId 79 | */ 80 | $snapBiResponse = SnapBi::directDebit() 81 | ->withBody($directDebitStatusByExternalIdBody) 82 | ->getStatus($external_id); 83 | 84 | /** 85 | * Example code for Direct Debit getStatus using externalId by re-using access token 86 | */ 87 | $snapBiResponse = SnapBi::directDebit() 88 | ->withAccessToken("") 89 | ->withBody($directDebitStatusByExternalIdBody) 90 | ->getStatus($external_id); 91 | 92 | /** 93 | * Example code for Direct Debit getStatus using externalId by adding additional header 94 | */ 95 | $snapBiResponse = SnapBi::directDebit() 96 | ->withAccessTokenHeader([ 97 | "CHANNEL-ID" => "12345" 98 | ]) 99 | ->withTransactionHeader([ 100 | "CHANNEL-ID" => "12345" 101 | ]) 102 | ->withBody($directDebitStatusByExternalIdBody) 103 | ->getStatus($external_id); 104 | 105 | 106 | /** 107 | * Example code for Direct Debit getStatus using referenceNo 108 | */ 109 | $snapBiResponse = SnapBi::directDebit() 110 | ->withBody($directDebitStatusByReferenceBody) 111 | ->getStatus($external_id); 112 | 113 | $snapBiResponse = SnapBi::va() 114 | ->withBody($directDebitStatusByReferenceBody) 115 | ->getStatus($external_id); 116 | 117 | /** 118 | * Example code for Direct Debit getStatus using referenceNo by re-using access token 119 | */ 120 | $snapBiResponse = SnapBi::directDebit() 121 | ->withAccessToken("") 122 | ->withBody($directDebitStatusByReferenceBody) 123 | ->getStatus($external_id); 124 | 125 | /** 126 | * Example code for Direct Debit getStatus using referenceNo by adding additional header 127 | */ 128 | $snapBiResponse = SnapBi::directDebit() 129 | ->withAccessTokenHeader([ 130 | "CHANNEL-ID" => "12345" 131 | ]) 132 | ->withTransactionHeader([ 133 | "CHANNEL-ID" => "12345" 134 | ]) 135 | ->withBody($directDebitStatusByReferenceBody) 136 | ->getStatus($external_id); 137 | 138 | /** 139 | * Example code for VA getStatus 140 | */ 141 | $snapBiResponse = SnapBi::va() 142 | ->withBody($vaStatusBody) 143 | ->getStatus($external_id); 144 | 145 | /** 146 | * Example code for VA getStatus by re-using access token 147 | */ 148 | $snapBiResponse = SnapBi::va() 149 | ->withBody($vaStatusBody) 150 | ->withAccessToken("") 151 | ->getStatus($external_id); 152 | 153 | /** 154 | * Example code for VA getStatus by adding additional header 155 | */ 156 | $snapBiResponse = SnapBi::va() 157 | ->withBody($vaStatusBody) 158 | ->withAccessTokenHeader([ 159 | "CHANNEL-ID" => "12345" 160 | ]) 161 | ->withTransactionHeader([ 162 | "CHANNEL-ID" => "12345" 163 | ]) 164 | ->getStatus($external_id); 165 | 166 | /** 167 | * Example code for Qris getStatus 168 | */ 169 | $snapBiResponse = SnapBi::qris() 170 | ->withBody($qrisStatusBody) 171 | ->getStatus($external_id); 172 | 173 | /** 174 | * Example code for Qris getStatus by re-using access token 175 | */ 176 | $snapBiResponse = SnapBi::qris() 177 | ->withBody($qrisStatusBody) 178 | ->withAccessToken("") 179 | ->getStatus($external_id); 180 | 181 | /** 182 | * Example code for Qris getStatus by adding additional header 183 | */ 184 | $snapBiResponse = SnapBi::qris() 185 | ->withBody($qrisStatusBody) 186 | ->withAccessTokenHeader([ 187 | "CHANNEL-ID" => "12345" 188 | ]) 189 | ->withTransactionHeader([ 190 | "CHANNEL-ID" => "12345" 191 | ]) 192 | ->getStatus($external_id); 193 | 194 | } catch (\Exception $e) { 195 | echo $e->getMessage(); 196 | } 197 | echo "snap bi response = " . print_r($snapBiResponse, true), PHP_EOL; 198 | 199 | function generateRandomNumber() 200 | { 201 | $prefix = "6280"; // Fixed prefix 202 | $randomDigits = mt_rand(100000000, 999999999); // Generate 9 random digits 203 | return $prefix . $randomDigits; 204 | } 205 | -------------------------------------------------------------------------------- /examples/snap-bi/snap-bi-va-creation.php: -------------------------------------------------------------------------------- 1 | " 1234", 28 | "customerNo"=> "0000000000", 29 | "virtualAccountNo"=> " 12340000000000", 30 | "virtualAccountName"=> "Jokul Doe", 31 | "virtualAccountEmail"=> "jokul@email.com", 32 | "virtualAccountPhone"=> "6281828384858", 33 | "trxId"=> $external_id, 34 | "totalAmount"=> [ 35 | "value"=> "10000.00", 36 | "currency"=> "IDR" 37 | ], 38 | "additionalInfo"=> [ 39 | "merchantId"=> $merchant_id, 40 | "bank"=> "bca", 41 | "flags"=> [ 42 | "shouldRandomizeVaNumber"=> true 43 | ], 44 | "customerDetails"=> [ 45 | "firstName"=> "Jokul", 46 | "lastName"=> "Doe", 47 | "email"=> "jokul@email.com", 48 | "phone"=> "+6281828384858", 49 | "billingAddress"=> [ 50 | "firstName"=> "Jukul", 51 | "lastName"=> "Doe", 52 | "address"=> "Kalibata", 53 | "city"=> "Jakarta", 54 | "postalCode"=> "12190", 55 | "phone"=> "+6281828384858", 56 | "countryCode"=> "IDN" 57 | ], 58 | "shippingAddress"=> [ 59 | "firstName"=> "Jukul", 60 | "lastName"=> "Doe", 61 | "address"=> "Kalibata", 62 | "city"=> "Jakarta", 63 | "postalCode"=> "12190", 64 | "phone"=> "+6281828384858", 65 | "countryCode"=> "IDN" 66 | ] 67 | ], 68 | "customField"=> [ 69 | "1"=> "custom-field-1", 70 | "2"=> "custom-field-2", 71 | "3"=> "custom-field-3" 72 | ], 73 | "items"=> [ 74 | [ 75 | "id"=> "a1", 76 | "price"=> [ 77 | "value"=> "1000.00", 78 | "currency"=> "IDR" 79 | ], 80 | "quantity"=> 3, 81 | "name"=> "Apel", 82 | "brand"=> "Fuji Apple", 83 | "category"=> "Fruit", 84 | "merchantName"=> "Fruit-store" 85 | 86 | ], 87 | [ 88 | "id"=> "a2", 89 | "price"=> [ 90 | "value"=> "1000.00", 91 | "currency"=> "IDR" 92 | ], 93 | "quantity"=> 7, 94 | "name"=> "Apel Malang", 95 | "brand"=> "Fuji Apple", 96 | "category"=> "Fruit", 97 | "merchantName"=> "Fruit-store" 98 | ] 99 | ] 100 | ] 101 | ); 102 | 103 | 104 | $snapBiResponse = null; 105 | SnapBiConfig::$snapBiClientId = $client_id; 106 | SnapBiConfig::$snapBiPrivateKey = $private_key; 107 | SnapBiConfig::$snapBiClientSecret = $client_secret; 108 | SnapBiConfig::$snapBiPartnerId = $partner_id; 109 | SnapBiConfig::$snapBiChannelId = $channel_id; 110 | SnapBiConfig::$enableLogging = true; 111 | 112 | try { 113 | 114 | /** 115 | * Example code for SnapBI, you can uncomment and run the code. 116 | * Below are example code to create va 117 | */ 118 | 119 | /** 120 | * basic implementation to create payment using va 121 | */ 122 | $snapBiResponse = SnapBi::va() 123 | ->withBody($vaParams) 124 | ->createPayment($external_id); 125 | 126 | /** 127 | * You can re-use your existing accessToken by using ->withAccessToken 128 | */ 129 | $snapBiResponse = SnapBi::va() 130 | ->withAccessToken("") 131 | ->withBody($vaParams) 132 | ->createPayment($external_id); 133 | 134 | /** 135 | * Adding custom header during accessToken request by using ->withAccessTokenHeader 136 | */ 137 | $snapBiResponse = SnapBi::va() 138 | ->withAccessTokenHeader([ 139 | "debug-id"=> "va debug id", 140 | "X-DEVICE-ID"=>"va device id" 141 | ]) 142 | ->withBody($vaParams) 143 | ->createPayment($external_id); 144 | 145 | /** 146 | * Adding custom header during transaction process request by using ->withTransactionHeader 147 | */ 148 | $snapBiResponse = SnapBi::va() 149 | ->withTransactionHeader([ 150 | "debug-id"=> "va debug id", 151 | "X-DEVICE-ID"=>"va device id" 152 | ]) 153 | ->withBody($vaParams) 154 | ->createPayment($external_id); 155 | 156 | /** 157 | * Adding custom header during both access token & transaction process request by using ->withAccessTokenHeader ->withTransactionHeader 158 | */ 159 | $snapBiResponse = SnapBi::va() 160 | ->withAccessTokenHeader([ 161 | "debug-id"=> "va debug id", 162 | "X-DEVICE-ID"=>"va device id" 163 | ]) 164 | ->withTransactionHeader([ 165 | "debug-id"=> "va debug id", 166 | "X-DEVICE-ID"=>"va device id" 167 | ]) 168 | ->withBody($vaParams) 169 | ->createPayment($external_id); 170 | 171 | } catch (\Exception $e) { 172 | echo $e->getMessage(); 173 | } 174 | echo "snap bi response = " . print_r($snapBiResponse, true), PHP_EOL; 175 | 176 | function generateRandomNumber() 177 | { 178 | $prefix = "6280"; // Fixed prefix 179 | $randomDigits = mt_rand(100000000, 999999999); // Generate 9 random digits 180 | return $prefix . $randomDigits; 181 | } 182 | -------------------------------------------------------------------------------- /examples/snap-redirect/checkout-process.php: -------------------------------------------------------------------------------- 1 | Settings -> Access keys 11 | Config::$serverKey = ''; 12 | 13 | // non-relevant function only used for demo/example purpose 14 | printExampleWarningMessage(); 15 | 16 | // Uncomment for production environment 17 | // Config::$isProduction = true; 18 | 19 | // Uncomment to enable sanitization 20 | // Config::$isSanitized = true; 21 | 22 | // Uncomment to enable 3D-Secure 23 | // Config::$is3ds = true; 24 | 25 | // Required 26 | $transaction_details = array( 27 | 'order_id' => rand(), 28 | 'gross_amount' => 145000, // no decimal allowed for creditcard 29 | ); 30 | 31 | // Optional 32 | $item1_details = array( 33 | 'id' => 'a1', 34 | 'price' => 50000, 35 | 'quantity' => 2, 36 | 'name' => "Apple" 37 | ); 38 | 39 | // Optional 40 | $item2_details = array( 41 | 'id' => 'a2', 42 | 'price' => 45000, 43 | 'quantity' => 1, 44 | 'name' => "Orange" 45 | ); 46 | 47 | // Optional 48 | $item_details = array ($item1_details, $item2_details); 49 | 50 | // Optional 51 | $billing_address = array( 52 | 'first_name' => "Andri", 53 | 'last_name' => "Litani", 54 | 'address' => "Mangga 20", 55 | 'city' => "Jakarta", 56 | 'postal_code' => "16602", 57 | 'phone' => "081122334455", 58 | 'country_code' => 'IDN' 59 | ); 60 | 61 | // Optional 62 | $shipping_address = array( 63 | 'first_name' => "Obet", 64 | 'last_name' => "Supriadi", 65 | 'address' => "Manggis 90", 66 | 'city' => "Jakarta", 67 | 'postal_code' => "16601", 68 | 'phone' => "08113366345", 69 | 'country_code' => 'IDN' 70 | ); 71 | 72 | // Optional 73 | $customer_details = array( 74 | 'first_name' => "Andri", 75 | 'last_name' => "Litani", 76 | 'email' => "andri@litani.com", 77 | 'phone' => "081122334455", 78 | 'billing_address' => $billing_address, 79 | 'shipping_address' => $shipping_address 80 | ); 81 | 82 | // Fill SNAP API parameter 83 | $params = array( 84 | 'transaction_details' => $transaction_details, 85 | 'customer_details' => $customer_details, 86 | 'item_details' => $item_details, 87 | ); 88 | 89 | try { 90 | // Get Snap Payment Page URL 91 | $paymentUrl = Snap::createTransaction($params)->redirect_url; 92 | 93 | // Redirect to Snap Payment Page 94 | header('Location: ' . $paymentUrl); 95 | } 96 | catch (\Exception $e) { 97 | echo $e->getMessage(); 98 | } 99 | 100 | function printExampleWarningMessage() { 101 | if (strpos(Config::$serverKey, 'your ') != false ) { 102 | echo ""; 103 | echo "

Please set your server key from sandbox

"; 104 | echo "In file: " . __FILE__; 105 | echo "
"; 106 | echo "
"; 107 | echo htmlspecialchars('Config::$serverKey = \'\';'); 108 | die(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/snap-redirect/index.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /examples/snap/checkout-process-simple-version.php: -------------------------------------------------------------------------------- 1 | Settings -> Access keys 11 | Config::$serverKey = ''; 12 | Config::$clientKey = ''; 13 | 14 | // non-relevant function only used for demo/example purpose 15 | printExampleWarningMessage(); 16 | 17 | // Uncomment for production environment 18 | // Config::$isProduction = true; 19 | Config::$isSanitized = Config::$is3ds = true; 20 | 21 | // Required 22 | $transaction_details = array( 23 | 'order_id' => rand(), 24 | 'gross_amount' => 94000, // no decimal allowed for creditcard 25 | ); 26 | // Optional 27 | $item_details = array ( 28 | array( 29 | 'id' => 'a1', 30 | 'price' => 94000, 31 | 'quantity' => 1, 32 | 'name' => "Apple" 33 | ), 34 | ); 35 | // Optional 36 | $customer_details = array( 37 | 'first_name' => "Andri", 38 | 'last_name' => "Litani", 39 | 'email' => "andri@litani.com", 40 | 'phone' => "081122334455", 41 | 'billing_address' => $billing_address, 42 | 'shipping_address' => $shipping_address 43 | ); 44 | // Fill transaction details 45 | $transaction = array( 46 | 'transaction_details' => $transaction_details, 47 | 'customer_details' => $customer_details, 48 | 'item_details' => $item_details, 49 | ); 50 | 51 | $snap_token = ''; 52 | try { 53 | $snap_token = Snap::getSnapToken($transaction); 54 | } 55 | catch (\Exception $e) { 56 | echo $e->getMessage(); 57 | } 58 | echo "snapToken = ".$snap_token; 59 | 60 | function printExampleWarningMessage() { 61 | if (strpos(Config::$serverKey, 'your ') != false ) { 62 | echo ""; 63 | echo "

Please set your server key from sandbox

"; 64 | echo "In file: " . __FILE__; 65 | echo "
"; 66 | echo "
"; 67 | echo htmlspecialchars('Config::$serverKey = \'\';'); 68 | die(); 69 | } 70 | } 71 | 72 | ?> 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/snap/checkout-process.php: -------------------------------------------------------------------------------- 1 | Settings -> Access keys 11 | Config::$serverKey = ''; 12 | Config::$clientKey = ''; 13 | 14 | // non-relevant function only used for demo/example purpose 15 | printExampleWarningMessage(); 16 | 17 | // Uncomment for production environment 18 | // Config::$isProduction = true; 19 | 20 | // Enable sanitization 21 | Config::$isSanitized = true; 22 | 23 | // Enable 3D-Secure 24 | Config::$is3ds = true; 25 | 26 | // Uncomment for append and override notification URL 27 | // Config::$appendNotifUrl = "https://example.com"; 28 | // Config::$overrideNotifUrl = "https://example.com"; 29 | 30 | // Required 31 | $transaction_details = array( 32 | 'order_id' => rand(), 33 | 'gross_amount' => 94000, // no decimal allowed for creditcard 34 | ); 35 | 36 | // Optional 37 | $item1_details = array( 38 | 'id' => 'a1', 39 | 'price' => 18000, 40 | 'quantity' => 3, 41 | 'name' => "Apple" 42 | ); 43 | 44 | // Optional 45 | $item2_details = array( 46 | 'id' => 'a2', 47 | 'price' => 20000, 48 | 'quantity' => 2, 49 | 'name' => "Orange" 50 | ); 51 | 52 | // Optional 53 | $item_details = array ($item1_details, $item2_details); 54 | 55 | // Optional 56 | $billing_address = array( 57 | 'first_name' => "Andri", 58 | 'last_name' => "Litani", 59 | 'address' => "Mangga 20", 60 | 'city' => "Jakarta", 61 | 'postal_code' => "16602", 62 | 'phone' => "081122334455", 63 | 'country_code' => 'IDN' 64 | ); 65 | 66 | // Optional 67 | $shipping_address = array( 68 | 'first_name' => "Obet", 69 | 'last_name' => "Supriadi", 70 | 'address' => "Manggis 90", 71 | 'city' => "Jakarta", 72 | 'postal_code' => "16601", 73 | 'phone' => "08113366345", 74 | 'country_code' => 'IDN' 75 | ); 76 | 77 | // Optional 78 | $customer_details = array( 79 | 'first_name' => "Andri", 80 | 'last_name' => "Litani", 81 | 'email' => "andri@litani.com", 82 | 'phone' => "081122334455", 83 | 'billing_address' => $billing_address, 84 | 'shipping_address' => $shipping_address 85 | ); 86 | 87 | // Optional, remove this to display all available payment methods 88 | $enable_payments = array('credit_card','cimb_clicks','mandiri_clickpay','echannel'); 89 | 90 | // Fill transaction details 91 | $transaction = array( 92 | 'enabled_payments' => $enable_payments, 93 | 'transaction_details' => $transaction_details, 94 | 'customer_details' => $customer_details, 95 | 'item_details' => $item_details, 96 | ); 97 | 98 | $snap_token = ''; 99 | try { 100 | $snap_token = Snap::getSnapToken($transaction); 101 | } 102 | catch (\Exception $e) { 103 | echo $e->getMessage(); 104 | } 105 | 106 | echo "snapToken = ".$snap_token; 107 | 108 | function printExampleWarningMessage() { 109 | if (strpos(Config::$serverKey, 'your ') != false ) { 110 | echo ""; 111 | echo "

Please set your server key from sandbox

"; 112 | echo "In file: " . __FILE__; 113 | echo "
"; 114 | echo "
"; 115 | echo htmlspecialchars('Config::$serverKey = \'\';'); 116 | die(); 117 | } 118 | } 119 | 120 | ?> 121 | 122 | 123 | 124 | 125 | 126 |
JSON result will appear here after payment:
127 | 128 | 129 | 130 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /examples/snap/index.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Selected Items:

6 |
    7 |
  • Jeruk 2 kg x @20000
  • 8 |
  • Apel 3 kg x @18000
  • 9 |
10 | 11 |

Total: Rp 94.000,00

12 | 13 |
14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /maintaining.md: -------------------------------------------------------------------------------- 1 | > Warning: This note is for developer/maintainer of this package only 2 | 3 | ## Updating Package 4 | 5 | - Make your changes 6 | - Update `version` value on `composer.json` 7 | - Update library version header on `ApiRequestor.php` 8 | - Commit and push changes to Github master branch 9 | - Create a [Github Release](https://github.com/Midtrans/midtrans-php/releases) with the target version 10 | - Github Release and Master Branch is automatically synced to [the Packagist version](https://packagist.org/packages/midtrans/midtrans-php) 11 | - Because of configured integration on Github & Packagist 12 | - To edit integration config sign in with the Midtrans Packagist Account 13 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | ./tests/integration 17 | 18 | 19 | ./tests/ 20 | 21 | 22 | ./tests/integration 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test_bootstrap.php: -------------------------------------------------------------------------------- 1 | "URL", 23 | CURLOPT_HTTPHEADER => "HTTPHEADER", 24 | CURLOPT_RETURNTRANSFER => "RETURNTRANSFER", 25 | CURLOPT_CAINFO => "CAINFO", 26 | CURLOPT_POST => "POST", 27 | CURLOPT_POSTFIELDS => "POSTFIELDS", 28 | CURLOPT_PROXY => "PROXY" 29 | ); 30 | 31 | $options = array(); 32 | foreach (MT_Tests::$lastHttpRequest["curl"] as $intValue => $value) { 33 | $key = $consts[$intValue] ? $consts[$intValue] : $intValue; 34 | $options[$key] = $value; 35 | } 36 | 37 | return $options; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tests/MidtransApiRequestorTest.php: -------------------------------------------------------------------------------- 1 | array( "User-Agent: testing lib" ), 15 | CURLOPT_PROXY => "http://proxy.com" 16 | ); 17 | 18 | $resp = ApiRequestor::post("http://proxy.com", "dummy", ""); 19 | 20 | $fields = MT_Tests::lastReqOptions(); 21 | $this->assertTrue(in_array("User-Agent: testing lib", $fields["HTTPHEADER"])); 22 | $this->assertTrue(in_array('Content-Type: application/json', $fields["HTTPHEADER"])); 23 | 24 | $this->assertEquals("http://proxy.com", $fields["PROXY"]); 25 | } 26 | 27 | public function tearDown() 28 | { 29 | MT_Tests::reset(); 30 | Config::$curlOptions = array(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /tests/MidtransConfigTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 12 | Config::getBaseUrl(), 13 | Config::SANDBOX_BASE_URL 14 | ); 15 | 16 | Config::$isProduction = true; 17 | $this->assertEquals(Config::PRODUCTION_BASE_URL, Config::getBaseUrl()); 18 | } 19 | 20 | public function tearDown() 21 | { 22 | Config::$isProduction = false; 23 | } 24 | } -------------------------------------------------------------------------------- /tests/MidtransCoreApiTest.php: -------------------------------------------------------------------------------- 1 | array( 22 | 'order_id' => "Order-111", 23 | 'gross_amount' => 10000, 24 | ) 25 | ); 26 | 27 | $charge = CoreApi::charge($params); 28 | 29 | $this->assertEquals($charge->status_code, "200"); 30 | 31 | $this->assertEquals( 32 | MT_Tests::$lastHttpRequest["url"], 33 | "https://api.sandbox.midtrans.com/v2/charge" 34 | ); 35 | 36 | $fields = MT_Tests::lastReqOptions(); 37 | $this->assertEquals($fields["POST"], 1); 38 | $this->assertEquals( 39 | $fields["POSTFIELDS"], 40 | '{"payment_type":"credit_card","transaction_details":{"order_id":"Order-111","gross_amount":10000}}' 41 | ); 42 | $this->assertTrue(in_array('X-Append-Notification: https://example.com', $fields["HTTPHEADER"])); 43 | $this->assertTrue(in_array('X-Override-Notification: https://example.com', $fields["HTTPHEADER"])); 44 | $this->assertTrue(in_array('Idempotency-Key: 123456', $fields["HTTPHEADER"])); 45 | } 46 | 47 | public function testRealConnectWithInvalidKey() 48 | { 49 | Config::$serverKey = 'invalid-server-key'; 50 | $params = array( 51 | 'transaction_details' => array( 52 | 'order_id' => rand(), 53 | 'gross_amount' => 10000, 54 | ) 55 | ); 56 | 57 | try { 58 | $paymentUrl = CoreApi::charge($params); 59 | } catch (\Exception $error) { 60 | $this->assertContains("Midtrans API is returning API error. HTTP status code: 401", $error->getMessage()); 61 | } 62 | } 63 | 64 | public function testCapture() 65 | { 66 | MT_Tests::$stubHttp = true; 67 | MT_Tests::$stubHttpResponse = '{ 68 | "status_code": "200", 69 | "status_message": "Success, Credit Card capture transaction is successful", 70 | "transaction_id": "1ac1a089d-a587-40f1-a936-a7770667d6dd", 71 | "order_id": "A27550", 72 | "payment_type": "credit_card", 73 | "transaction_time": "2014-08-25 10:20:54", 74 | "transaction_status": "capture", 75 | "fraud_status": "accept", 76 | "masked_card": "481111-1114", 77 | "bank": "bni", 78 | "approval_code": "1408937217061", 79 | "gross_amount": "55000.00" 80 | }'; 81 | 82 | $capture = CoreApi::capture("A27550"); 83 | 84 | $this->assertEquals($capture->status_code, "200"); 85 | 86 | $this->assertEquals("https://api.sandbox.midtrans.com/v2/capture", MT_Tests::$lastHttpRequest["url"]); 87 | 88 | $fields = MT_Tests::lastReqOptions(); 89 | $this->assertEquals(1, $fields["POST"]); 90 | $this->assertEquals('{"transaction_id":"A27550"}', $fields["POSTFIELDS"]); 91 | } 92 | 93 | public function tearDown() 94 | { 95 | MT_Tests::reset(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/MidtransNotificationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("capture", $notif->transaction_status); 37 | $this->assertEquals("credit_card", $notif->payment_type); 38 | $this->assertEquals("2014040745", $notif->order_id); 39 | $this->assertEquals("2700", $notif->gross_amount); 40 | 41 | unlink($tmpfname); 42 | } 43 | 44 | public function tearDown() 45 | { 46 | MT_Tests::reset(); 47 | } 48 | } -------------------------------------------------------------------------------- /tests/MidtransSanitizerTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(false, isset($params['customer_details'])); 18 | } 19 | 20 | public function testSanitizeWithoutOptionalCustDetails() 21 | { 22 | $params = MtChargeFixture::build('vtweb'); 23 | unset($params['customer_details']['first_name']); 24 | unset($params['customer_details']['last_name']); 25 | unset($params['customer_details']['email']); 26 | unset($params['customer_details']['billing_address']); 27 | unset($params['customer_details']['shipping_address']); 28 | 29 | Sanitizer::jsonRequest($params); 30 | 31 | $this->assertEquals(false, isset($params['customer_details']['first_name'])); 32 | $this->assertEquals(false, isset($params['customer_details']['last_name'])); 33 | $this->assertEquals(false, isset($params['customer_details']['email'])); 34 | $this->assertEquals(false, isset($params['customer_details']['billing_address'])); 35 | $this->assertEquals(false, isset($params['customer_details']['shipping_address'])); 36 | } 37 | 38 | public function testSanitizeWithoutOptionalInBillingAddress() 39 | { 40 | $params = MtChargeFixture::build('vtweb'); 41 | unset($params['customer_details']['billing_address']['first_name']); 42 | unset($params['customer_details']['billing_address']['last_name']); 43 | unset($params['customer_details']['billing_address']['phone']); 44 | unset($params['customer_details']['billing_address']['address']); 45 | unset($params['customer_details']['billing_address']['city']); 46 | unset($params['customer_details']['billing_address']['postal_code']); 47 | unset($params['customer_details']['billing_address']['country_code']); 48 | 49 | Sanitizer::jsonRequest($params); 50 | 51 | $this->assertEquals(false, isset($params['customer_details']['billing_address']['first_name'])); 52 | $this->assertEquals(false, isset($params['customer_details']['billing_address']['last_name'])); 53 | $this->assertEquals(false, isset($params['customer_details']['billing_address']['phone'])); 54 | $this->assertEquals(false, isset($params['customer_details']['billing_address']['address'])); 55 | $this->assertEquals(false, isset($params['customer_details']['billing_address']['city'])); 56 | $this->assertEquals(false, isset($params['customer_details']['billing_address']['postal_code'])); 57 | $this->assertEquals(false, isset($params['customer_details']['billing_address']['country_code'])); 58 | } 59 | 60 | public function testSanitizeWithoutOptionalInShippingAddress() 61 | { 62 | $params = MtChargeFixture::build('vtweb'); 63 | unset($params['customer_details']['shipping_address']['first_name']); 64 | unset($params['customer_details']['shipping_address']['last_name']); 65 | unset($params['customer_details']['shipping_address']['phone']); 66 | unset($params['customer_details']['shipping_address']['address']); 67 | unset($params['customer_details']['shipping_address']['city']); 68 | unset($params['customer_details']['shipping_address']['postal_code']); 69 | unset($params['customer_details']['shipping_address']['country_code']); 70 | 71 | Sanitizer::jsonRequest($params); 72 | 73 | $this->assertEquals(false, isset($params['customer_details']['shipping_address']['first_name'])); 74 | $this->assertEquals(false, isset($params['customer_details']['shipping_address']['last_name'])); 75 | $this->assertEquals(false, isset($params['customer_details']['shipping_address']['phone'])); 76 | $this->assertEquals(false, isset($params['customer_details']['shipping_address']['address'])); 77 | $this->assertEquals(false, isset($params['customer_details']['shipping_address']['city'])); 78 | $this->assertEquals(false, isset($params['customer_details']['shipping_address']['postal_code'])); 79 | $this->assertEquals(false, isset($params['customer_details']['shipping_address']['country_code'])); 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /tests/MidtransSnapApiRequestorTest.php: -------------------------------------------------------------------------------- 1 | 201); 13 | 14 | Config::$curlOptions = array( 15 | CURLOPT_HTTPHEADER => array( "User-Agent: testing lib" ), 16 | CURLOPT_PROXY => "http://proxy.com" 17 | ); 18 | 19 | $resp = ApiRequestor::post("http://example.com", "dummy", ""); 20 | 21 | $fields = MT_Tests::lastReqOptions(); 22 | $this->assertTrue(in_array("User-Agent: testing lib", $fields["HTTPHEADER"])); 23 | $this->assertTrue(in_array('Content-Type: application/json', $fields["HTTPHEADER"])); 24 | 25 | $this->assertEquals("http://proxy.com", $fields["PROXY"]); 26 | } 27 | 28 | public function tearDown() 29 | { 30 | MT_Tests::reset(); 31 | Config::$curlOptions = array(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /tests/MidtransSnapTest.php: -------------------------------------------------------------------------------- 1 | 201); 16 | 17 | $params = array( 18 | 'transaction_details' => array( 19 | 'order_id' => "Order-111", 20 | 'gross_amount' => 10000, 21 | ) 22 | ); 23 | 24 | $tokenId = Snap::getSnapToken($params); 25 | 26 | $this->assertEquals("abcdefghijklmnopqrstuvwxyz", $tokenId); 27 | 28 | $this->assertEquals( 29 | "https://app.sandbox.midtrans.com/snap/v1/transactions", 30 | MT_Tests::$lastHttpRequest["url"] 31 | ); 32 | 33 | $this->assertEquals( 34 | 'MyVerySecretKey', 35 | MT_Tests::$lastHttpRequest["server_key"] 36 | ); 37 | 38 | $fields = MT_Tests::lastReqOptions(); 39 | 40 | $this->assertEquals(1, $fields["POST"]); 41 | $this->assertTrue(in_array('X-Append-Notification: https://example.com', $fields["HTTPHEADER"])); 42 | $this->assertTrue(in_array('X-Override-Notification: https://example.com', $fields["HTTPHEADER"])); 43 | $this->assertEquals( 44 | $fields["POSTFIELDS"], 45 | '{"credit_card":{"secure":false},' . 46 | '"transaction_details":{"order_id":"Order-111","gross_amount":10000}}' 47 | ); 48 | } 49 | 50 | public function testGrossAmount() 51 | { 52 | $params = array( 53 | 'transaction_details' => array( 54 | 'order_id' => rand() 55 | ), 56 | 'item_details' => array( array( 'price' => 10000, 'quantity' => 5 ) ) 57 | ); 58 | 59 | MT_Tests::$stubHttp = true; 60 | MT_Tests::$stubHttpResponse = '{ "token": "abcdefghijklmnopqrstuvwxyz" }'; 61 | MT_Tests::$stubHttpStatus = array('http_code' => 201); 62 | 63 | $tokenId = Snap::getSnapToken($params); 64 | 65 | $this->assertEquals( 66 | 50000, 67 | MT_Tests::$lastHttpRequest['data_hash']['transaction_details']['gross_amount'] 68 | ); 69 | } 70 | 71 | public function testOverrideParams() 72 | { 73 | $params = array( 74 | 'echannel' => array( 75 | 'bill_info1' => 'bill_value1' 76 | ) 77 | ); 78 | 79 | MT_Tests::$stubHttp = true; 80 | MT_Tests::$stubHttpResponse = '{ "token": "abcdefghijklmnopqrstuvwxyz" }'; 81 | MT_Tests::$stubHttpStatus = array('http_code' => 201); 82 | 83 | $tokenId = Snap::getSnapToken($params); 84 | 85 | $this->assertEquals( 86 | array('bill_info1' => 'bill_value1'), 87 | MT_Tests::$lastHttpRequest['data_hash']['echannel'] 88 | ); 89 | } 90 | 91 | public function testRealConnect() 92 | { 93 | $params = array( 94 | 'transaction_details' => array( 95 | 'order_id' => rand(), 96 | 'gross_amount' => 10000, 97 | ) 98 | ); 99 | 100 | try { 101 | $tokenId = Snap::getSnapToken($params); 102 | } catch (\Exception $error) { 103 | $errorHappen = true; 104 | $this->assertContains( 105 | "authorized", 106 | $error->getMessage() 107 | ); 108 | } 109 | 110 | $this->assertTrue($errorHappen); 111 | } 112 | 113 | public function tearDown() 114 | { 115 | MT_Tests::reset(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /tests/MidtransTransactionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("200", $status->status_code); 30 | $this->assertEquals("Order-111", $status->order_id); 31 | $this->assertEquals("1416550071152", $status->approval_code); 32 | 33 | $this->assertEquals( 34 | "https://api.sandbox.midtrans.com/v2/Order-111/status", 35 | MT_Tests::$lastHttpRequest["url"] 36 | ); 37 | 38 | $fields = MT_Tests::lastReqOptions(); 39 | $this->assertFalse(isset($fields['POST'])); 40 | $this->assertFalse(isset($fields['POSTFIELDS'])); 41 | } 42 | 43 | public function testFailureStatus() 44 | { 45 | Config::$serverKey = 'MyVerySecretKey'; 46 | MT_Tests::$stubHttp = true; 47 | MT_Tests::$stubHttpResponse = '{ 48 | "status_code": "404", 49 | "status_message": "The requested resource is not found" 50 | }'; 51 | 52 | try { 53 | $status = Transaction::status("Order-111"); 54 | } catch (\Exception $error) { 55 | $errorHappen = true; 56 | $this->assertEquals(404, $error->getCode()); 57 | } 58 | 59 | $this->assertTrue($errorHappen); 60 | MT_Tests::reset(); 61 | } 62 | 63 | public function testRealStatus() 64 | { 65 | Config::$serverKey = 'MyVerySecretKey'; 66 | try { 67 | $status = Transaction::status("Order-111"); 68 | } catch (\Exception $error) { 69 | $errorHappen = true; 70 | $this->assertContains("Midtrans API is returning API error. HTTP status code: 401", $error->getMessage()); 71 | } 72 | 73 | $this->assertTrue($errorHappen); 74 | } 75 | 76 | public function testApprove() 77 | { 78 | Config::$serverKey = 'MyVerySecretKey'; 79 | MT_Tests::$stubHttp = true; 80 | MT_Tests::$stubHttpResponse = '{ 81 | "status_code": "200", 82 | "status_message": "Success, transaction is approved", 83 | "transaction_id": "2af158d4-b82e-46ac-808b-be19aaa96ce3", 84 | "masked_card": "451111-1117", 85 | "order_id": "Order-111", 86 | "payment_type": "credit_card", 87 | "transaction_time": "2014-11-27 10:05:10", 88 | "transaction_status": "capture", 89 | "fraud_status": "accept", 90 | "approval_code": "1416550071152", 91 | "bank": "bni", 92 | "gross_amount": "10000.00" 93 | }'; 94 | 95 | $approve = Transaction::approve("Order-111"); 96 | 97 | $this->assertEquals("200", $approve); 98 | 99 | $this->assertEquals( 100 | "https://api.sandbox.midtrans.com/v2/Order-111/approve", 101 | MT_Tests::$lastHttpRequest["url"] 102 | ); 103 | 104 | $fields = MT_Tests::lastReqOptions(); 105 | $this->assertEquals(1, $fields["POST"]); 106 | $this->assertEquals(null, $fields["POSTFIELDS"]); 107 | } 108 | 109 | public function testCancel() 110 | { 111 | Config::$serverKey = 'MyVerySecretKey'; 112 | MT_Tests::$stubHttp = true; 113 | MT_Tests::$stubHttpResponse = '{ 114 | "status_code": "200", 115 | "status_message": "Success, transaction is canceled", 116 | "transaction_id": "2af158d4-b82e-46ac-808b-be19aaa96ce3", 117 | "masked_card": "451111-1117", 118 | "order_id": "Order-111", 119 | "payment_type": "credit_card", 120 | "transaction_time": "2014-11-27 10:05:10", 121 | "transaction_status": "cancel", 122 | "fraud_status": "accept", 123 | "approval_code": "1416550071152", 124 | "bank": "bni", 125 | "gross_amount": "10000.00" 126 | }'; 127 | 128 | $cancel = Transaction::cancel("Order-111"); 129 | 130 | $this->assertEquals("200", $cancel); 131 | 132 | $this->assertEquals( 133 | "https://api.sandbox.midtrans.com/v2/Order-111/cancel", 134 | MT_Tests::$lastHttpRequest["url"] 135 | ); 136 | 137 | $fields = MT_Tests::lastReqOptions(); 138 | $this->assertEquals(1, $fields["POST"]); 139 | $this->assertEquals(null, $fields["POSTFIELDS"]); 140 | } 141 | 142 | public function testExpire() 143 | { 144 | Config::$serverKey = 'MyVerySecretKey'; 145 | MT_Tests::$stubHttp = true; 146 | MT_Tests::$stubHttpResponse = '{ 147 | "status_code": "407", 148 | "status_message": "Success, transaction has expired", 149 | "transaction_id": "2af158d4-b82e-46ac-808b-be19aaa96ce3", 150 | "order_id": "Order-111", 151 | "payment_type": "echannel", 152 | "transaction_time": "2014-11-27 10:05:10", 153 | "transaction_status": "expire", 154 | "gross_amount": "10000.00" 155 | }'; 156 | 157 | $expire = Transaction::expire("Order-111"); 158 | 159 | $this->assertEquals("407", $expire->status_code); 160 | $this->assertEquals("Success, transaction has expired", $expire->status_message); 161 | 162 | $this->assertEquals( 163 | "https://api.sandbox.midtrans.com/v2/Order-111/expire", 164 | MT_Tests::$lastHttpRequest["url"] 165 | ); 166 | 167 | $fields = MT_Tests::lastReqOptions(); 168 | $this->assertEquals(1, $fields["POST"]); 169 | $this->assertEquals(null, $fields["POSTFIELDS"]); 170 | } 171 | 172 | public function testRefund() 173 | { 174 | Config::$serverKey = 'MyVerySecretKey'; 175 | MT_Tests::$stubHttp = true; 176 | MT_Tests::$stubHttpResponse = '{ 177 | "status_code": "200", 178 | "status_message": "Success, refund request is approved", 179 | "transaction_id": "447e846a-403e-47db-a5da-d7f3f06375d6", 180 | "order_id": "Order-111", 181 | "payment_type": "credit_card", 182 | "transaction_time": "2015-06-15 13:36:24", 183 | "transaction_status": "refund", 184 | "gross_amount": "10000.00", 185 | "refund_chargeback_id": 1, 186 | "refund_amount": "10000.00", 187 | "refund_key": "reference1" 188 | }'; 189 | 190 | $params = array( 191 | 'refund_key' => 'reference1', 192 | 'amount' => 10000, 193 | 'reason' => 'Item out of stock' 194 | ); 195 | $refund = Transaction::refund("Order-111",$params); 196 | 197 | $this->assertEquals("200", $refund->status_code); 198 | 199 | $this->assertEquals( 200 | "https://api.sandbox.midtrans.com/v2/Order-111/refund", 201 | MT_Tests::$lastHttpRequest["url"] 202 | ); 203 | 204 | $fields = MT_Tests::lastReqOptions(); 205 | $this->assertEquals(1, $fields["POST"]); 206 | $this->assertEquals( 207 | '{"refund_key":"reference1","amount":10000,"reason":"Item out of stock"}', 208 | $fields["POSTFIELDS"]); 209 | } 210 | 211 | public function testRefundDirect() 212 | { 213 | Config::$serverKey = 'MyVerySecretKey'; 214 | MT_Tests::$stubHttp = true; 215 | MT_Tests::$stubHttpResponse = '{ 216 | "status_code": "200", 217 | "status_message": "Success, refund request is approved", 218 | "transaction_id": "447e846a-403e-47db-a5da-d7f3f06375d6", 219 | "order_id": "Order-111", 220 | "payment_type": "credit_card", 221 | "transaction_time": "2015-06-15 13:36:24", 222 | "transaction_status": "refund", 223 | "gross_amount": "10000.00", 224 | "refund_chargeback_id": 1, 225 | "refund_amount": "10000.00", 226 | "refund_key": "reference1" 227 | }'; 228 | 229 | $params = array( 230 | 'refund_key' => 'reference1', 231 | 'amount' => 10000, 232 | 'reason' => 'Item out of stock' 233 | ); 234 | $refund = Transaction::refundDirect("Order-111", $params); 235 | $this->assertEquals("200", $refund->status_code); 236 | 237 | $this->assertEquals( 238 | "https://api.sandbox.midtrans.com/v2/Order-111/refund/online/direct", 239 | MT_Tests::$lastHttpRequest["url"] 240 | ); 241 | $fields = MT_Tests::lastReqOptions(); 242 | $this->assertEquals(1, $fields["POST"]); 243 | $this->assertEquals( 244 | '{"refund_key":"reference1","amount":10000,"reason":"Item out of stock"}', 245 | $fields["POSTFIELDS"]); 246 | } 247 | 248 | public function testDeny() 249 | { 250 | Config::$serverKey = 'MyVerySecretKey'; 251 | MT_Tests::$stubHttp = true; 252 | MT_Tests::$stubHttpResponse = '{ 253 | "status_code" : "200", 254 | "status_message" : "Success, transaction is denied", 255 | "transaction_id" : "ca297170-be4c-45ed-9dc9-be5ba99d30ee", 256 | "masked_card" : "451111-1117", 257 | "order_id" : "Order-111", 258 | "payment_type" : "credit_card", 259 | "transaction_time" : "2014-10-31 14:46:44", 260 | "transaction_status" : "deny", 261 | "fraud_status" : "deny", 262 | "bank" : "bni", 263 | "gross_amount" : "30000.00" 264 | }'; 265 | 266 | $deny = Transaction::deny("Order-111"); 267 | 268 | $this->assertEquals("200", $deny->status_code); 269 | 270 | $this->assertEquals( 271 | "https://api.sandbox.midtrans.com/v2/Order-111/deny", 272 | MT_Tests::$lastHttpRequest["url"] 273 | ); 274 | 275 | $fields = MT_Tests::lastReqOptions(); 276 | $this->assertEquals(1, $fields["POST"]); 277 | $this->assertEquals(null, $fields["POSTFIELDS"]); 278 | } 279 | 280 | public function tearDown() 281 | { 282 | MT_Tests::reset(); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tests/integration/CoreApiIntegrationTest.php: -------------------------------------------------------------------------------- 1 | payment_type = $payment_type; 19 | $this->charge_params = MtChargeFixture::build($payment_type, $payment_data); 20 | } 21 | 22 | public function testCardRegister() 23 | { 24 | $this->charge_response = CoreApi::cardRegister("4811111111111114", "12", "2026"); 25 | $this->assertEquals('200', $this->charge_response->status_code); 26 | } 27 | 28 | public function testCardToken() 29 | { 30 | $this->charge_response = CoreApi::cardToken("4811111111111114", "12", "2026", "123"); 31 | $this->assertEquals('200', $this->charge_response->status_code); 32 | } 33 | 34 | public function testCardPointInquiry() 35 | { 36 | $this->charge_response = CoreApi::cardToken("4617006959746656", "12", "2026", "123"); 37 | $cardPointResponse = CoreApi::cardPointInquiry($this->charge_response->token_id); 38 | $this->assertEquals('200', $cardPointResponse->status_code); 39 | } 40 | 41 | public function testChargeCimbClicks() 42 | { 43 | $this->prepareChargeParams( 44 | 'cimb_clicks', 45 | array( 46 | "description" => "Item Descriptions", 47 | ) 48 | ); 49 | $this->charge_response = CoreApi::charge($this->charge_params); 50 | $this->assertEquals('pending', $this->charge_response->transaction_status); 51 | $this->assertTrue(isset($this->charge_response->redirect_url)); 52 | } 53 | 54 | public function testChargePermataVa() 55 | { 56 | $this->prepareChargeParams( 57 | 'bank_transfer', 58 | array( 59 | "bank" => "permata", 60 | ) 61 | ); 62 | $this->charge_response = CoreApi::charge($this->charge_params); 63 | $this->assertEquals('pending', $this->charge_response->transaction_status); 64 | $this->assertTrue(isset($this->charge_response->permata_va_number)); 65 | } 66 | 67 | public function testChargeEPayBri() 68 | { 69 | $this->prepareChargeParams('bri_epay'); 70 | $this->charge_response = CoreApi::charge($this->charge_params); 71 | $this->assertEquals('pending', $this->charge_response->transaction_status); 72 | $this->assertTrue(isset($this->charge_response->redirect_url)); 73 | } 74 | 75 | public function testChargeMandiriBillPayment() 76 | { 77 | $this->prepareChargeParams( 78 | 'echannel', 79 | array( 80 | "bill_info1" => "Payment for:", 81 | "bill_info2" => "Item descriptions", 82 | ) 83 | ); 84 | $this->charge_response = CoreApi::charge($this->charge_params); 85 | $this->assertEquals('pending', $this->charge_response->transaction_status); 86 | } 87 | 88 | public function testChargeIndomaret() 89 | { 90 | $this->prepareChargeParams( 91 | 'cstore', 92 | array( 93 | "store" => "indomaret", 94 | "message" => "Item descriptions", 95 | ) 96 | ); 97 | $this->charge_response = CoreApi::charge($this->charge_params); 98 | $this->assertEquals('pending', $this->charge_response->transaction_status); 99 | $this->assertTrue(isset($this->charge_response->payment_code)); 100 | } 101 | 102 | public function testChargeGopay() 103 | { 104 | $this->prepareChargeParams( 105 | 'gopay', 106 | array( 107 | "enable_callback" => true, 108 | "callback_url" => "someapps://callback", 109 | ) 110 | ); 111 | $this->charge_response = CoreApi::charge($this->charge_params); 112 | $this->assertEquals('201', $this->charge_response->status_code); 113 | $this->assertEquals('pending', $this->charge_response->transaction_status); 114 | } 115 | 116 | public function testCreateSubscription() 117 | { 118 | $param = array( 119 | "name" => "Monthly_2021", 120 | "amount" => "10000", 121 | "currency" => "IDR", 122 | "payment_type" => "credit_card", 123 | "token" => "dummy", 124 | "schedule" => array( 125 | "interval" => 1, 126 | "interval_unit" => "month", 127 | "max_interval" => "12", 128 | "start_time" => "2022-08-17 10:00:01 +0700" 129 | ), 130 | "metadata" => array( 131 | "description" => "Recurring payment for user a" 132 | ), 133 | "customer_details" => array( 134 | "first_name" => "John", 135 | "last_name" => "Doe", 136 | "email" => "johndoe@gmail.com", 137 | "phone_number" => "+628987654321" 138 | ) 139 | ); 140 | $this->charge_response = CoreApi::createSubscription($param); 141 | $this->assertEquals('active', $this->charge_response->status); 142 | $subscription_id = $this->charge_response->id; 143 | return $subscription_id; 144 | } 145 | 146 | /** 147 | * @depends testCreateSubscription 148 | */ 149 | public function testGetSubscription($subscription_id) 150 | { 151 | $this->charge_response = CoreApi::getSubscription($subscription_id); 152 | $this->assertEquals('active', $this->charge_response->status); 153 | } 154 | 155 | /** 156 | * @depends testCreateSubscription 157 | */ 158 | public function testDisableSubscription($subscription_id) 159 | { 160 | $this->charge_response = CoreApi::disableSubscription($subscription_id); 161 | $this->assertContains('Subscription is updated.', $this->charge_response->status_message); 162 | } 163 | 164 | /** 165 | * @depends testCreateSubscription 166 | */ 167 | public function testEnableSubscription($subscription_id) 168 | { 169 | $this->charge_response = CoreApi::enableSubscription($subscription_id); 170 | $this->assertContains('Subscription is updated.', $this->charge_response->status_message); 171 | } 172 | 173 | /** 174 | * @depends testCreateSubscription 175 | */ 176 | public function testUpdateSubscription($subscription_id) 177 | { 178 | $param = array( 179 | "name" => "Monthly_2021", 180 | "amount" => "25000", 181 | "currency" => "IDR", 182 | "token" => "dummy", 183 | "schedule" => array( 184 | "interval" => 1 185 | ) 186 | ); 187 | 188 | $this->charge_response = CoreApi::updateSubscription($subscription_id, $param); 189 | $this->assertContains('Subscription is updated.', $this->charge_response->status_message); 190 | } 191 | 192 | public function testGetSubscriptionWithNonExistAccount() 193 | { 194 | try { 195 | $this->charge_response = CoreApi::getSubscription("dummy"); 196 | } catch (\Exception $e) { 197 | $this->assertContains("Midtrans API is returning API error.", $e->getMessage()); 198 | } 199 | } 200 | 201 | public function testDisableSubscriptionWithNonExistAccount() 202 | { 203 | try { 204 | $this->charge_response = CoreApi::disableSubscription("dummy"); 205 | } catch (\Exception $e) { 206 | $this->assertContains("Midtrans API is returning API error.", $e->getMessage()); 207 | } 208 | } 209 | 210 | public function testEnableSubscriptionWithNonExistAccount() 211 | { 212 | try { 213 | $this->charge_response = CoreApi::enableSubscription("dummy"); 214 | } catch (\Exception $e) { 215 | $this->assertContains("Midtrans API is returning API error.", $e->getMessage()); 216 | } 217 | } 218 | 219 | public function testUpdateSubscriptionWithNonExistAccount() 220 | { 221 | $param = array( 222 | "name" => "Monthly_2021", 223 | "amount" => "25000", 224 | "currency" => "IDR", 225 | "token" => "dummy", 226 | "schedule" => array( 227 | "interval" => 1 228 | ) 229 | ); 230 | 231 | try { 232 | $this->charge_response = CoreApi::updateSubscription("dummy", $param); 233 | } catch (\Exception $e) { 234 | $this->assertContains("Midtrans API is returning API error.", $e->getMessage()); 235 | } 236 | } 237 | 238 | public function testCreatePayAccount() 239 | { 240 | $params = array( 241 | "payment_type" => "gopay", 242 | "gopay_partner" => array( 243 | "phone_number" => 874567446788, 244 | "redirect_url" => "https://www.google.com" 245 | ) 246 | ); 247 | $this->charge_response = CoreApi::linkPaymentAccount($params); 248 | $this->assertEquals('201', $this->charge_response->status_code); 249 | $this->assertEquals('PENDING', $this->charge_response->account_status); 250 | $account_id = $this->charge_response->account_id; 251 | return $account_id; 252 | } 253 | 254 | /** 255 | * @depends testCreatePayAccount 256 | */ 257 | public function testGetPaymentAccount($account_id) 258 | { 259 | $this->charge_response = CoreApi::getPaymentAccount($account_id); 260 | $this->assertEquals('201', $this->charge_response->status_code); 261 | $this->assertEquals('PENDING', $this->charge_response->account_status); 262 | } 263 | 264 | 265 | public function testGetPaymentAccountWithNonExistAccount() 266 | { 267 | try { 268 | $this->charge_response = CoreApi::getPaymentAccount("dummy"); 269 | } catch (\Exception $e) { 270 | $this->assertContains("Midtrans API is returning API error.", $e->getMessage()); 271 | } 272 | } 273 | 274 | public function testUnlinkPaymentAccountWithNonExistAccount() 275 | { 276 | try { 277 | $this->charge_response = CoreApi::unlinkPaymentAccount("dummy"); 278 | } catch (\Exception $e) { 279 | $this->assertContains("Account doesn't exist.", $e->getMessage()); 280 | } 281 | } 282 | } -------------------------------------------------------------------------------- /tests/integration/IntegrationTest.php: -------------------------------------------------------------------------------- 1 | status_response = Transaction::status($charge_response->transaction_id); 22 | } 23 | 24 | public function testValidBriEPayNotification() 25 | { 26 | // Assume status response is similar to HTTP(s) notification 27 | $tmpfname = tempnam(sys_get_temp_dir(), "test"); 28 | file_put_contents($tmpfname, json_encode($this->status_response)); 29 | 30 | $notif = new Notification($tmpfname); 31 | 32 | $this->assertEquals($notif->status_code, "201"); 33 | $this->assertEquals($notif->transaction_status, "pending"); 34 | $this->assertEquals($notif->payment_type, "bri_epay"); 35 | $this->assertEquals($notif->order_id, $this->status_response->order_id); 36 | $this->assertEquals($notif->transaction_id, $this->status_response->transaction_id); 37 | $this->assertEquals($notif->gross_amount, $this->status_response->gross_amount); 38 | 39 | unlink($tmpfname); 40 | } 41 | 42 | public function testFraudulentBriEPayNotification() 43 | { 44 | /* 45 | As a fraudster, I want the merchant to think that I finished e-Pay BRI payment 46 | 1. alter transaction status, from pending to settlement 47 | 2. alter status code, from 201 to 200 48 | */ 49 | $this->status_response->transaction_status = "settlement"; 50 | $this->status_response->status_code = "200"; 51 | 52 | $tmpfname = tempnam(sys_get_temp_dir(), "test"); 53 | file_put_contents($tmpfname, json_encode($this->status_response)); 54 | 55 | $notif = new Notification($tmpfname); 56 | 57 | /* 58 | Merchant should not be tricked... thanks to Get Status API 59 | */ 60 | $this->assertEquals("201", $notif->status_code); 61 | $this->assertEquals("pending", $notif->transaction_status); 62 | $this->assertEquals("bri_epay", $notif->payment_type); 63 | $this->assertEquals($this->status_response->order_id, $notif->order_id); 64 | $this->assertEquals($this->status_response->transaction_id, $notif->transaction_id); 65 | $this->assertEquals($this->status_response->gross_amount, $notif->gross_amount); 66 | 67 | unlink($tmpfname); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/integration/SnapIntegrationTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(isset($token_id)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/integration/TransactionIntegrationTest.php: -------------------------------------------------------------------------------- 1 | "permata", 20 | ) 21 | ); 22 | $charge_response = CoreApi::charge($charge_params); 23 | $status_response = Transaction::status($charge_response->transaction_id); 24 | 25 | $this->assertEquals('201', $status_response->status_code); 26 | $this->assertEquals('pending', $status_response->transaction_status); 27 | $this->assertEquals($charge_params['transaction_details']['order_id'], $status_response->order_id); 28 | $this->assertEquals($charge_params['transaction_details']['gross_amount'], $status_response->gross_amount); 29 | $this->assertEquals($charge_response->transaction_id, $status_response->transaction_id); 30 | $this->assertEquals($charge_response->transaction_time, $status_response->transaction_time); 31 | $this->assertEquals('Success, transaction is found', $status_response->status_message); 32 | 33 | $this->assertTrue(isset($status_response->signature_key)); 34 | } 35 | 36 | public function testCancelPermataVa() 37 | { 38 | $charge_params = MtChargeFixture::build( 39 | 'bank_transfer', 40 | array( 41 | "bank" => "permata", 42 | ) 43 | ); 44 | $charge_response = CoreApi::charge($charge_params); 45 | $cancel_status_code = Transaction::cancel($charge_response->transaction_id); 46 | 47 | $this->assertEquals('200', $cancel_status_code); 48 | } 49 | 50 | public function testExpirePermataVa() 51 | { 52 | $charge_params = MtChargeFixture::build( 53 | 'bank_transfer', 54 | array( 55 | "bank" => "permata", 56 | ) 57 | ); 58 | $charge_response = CoreApi::charge($charge_params); 59 | $expire = Transaction::expire($charge_response->transaction_id); 60 | 61 | $this->assertEquals('407', $expire->status_code); 62 | 63 | // Verify transaction via API 64 | $txn_status = Transaction::status($charge_response->transaction_id); 65 | $this->assertEquals("407", $txn_status->status_code); 66 | $this->assertEquals("expire", $txn_status->transaction_status); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/utility/MtFixture.php: -------------------------------------------------------------------------------- 1 |