Add remaining project files (exclude ignored folders)

This commit is contained in:
WD - Dev
2025-07-05 15:11:40 +07:00
parent a96eb2b958
commit b440b80882
4697 changed files with 1365702 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
image: bradrydzewski/php:5.5
script:
- echo "Hello World"
notify:
email:
recipients:
- radityo.shaddiqa@veritrans.co.id
+5
View File
@@ -0,0 +1,5 @@
composer.lock
composer.phar
vendor/
.idea
.DS_STORE
+12
View File
@@ -0,0 +1,12 @@
language: php
php:
- 7.4
- 7.2
- 7.0
- 5.6
# - 5.5 # PHP version too old
# - 5.4 # PHP version too old
# - hhvm # HHVM officially drop PHP support
before_script:
- composer install
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Midtrans
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+30
View File
@@ -0,0 +1,30 @@
<?php
/**
* Check PHP version.
*/
if (version_compare(PHP_VERSION, '5.4', '<')) {
throw new Exception('PHP version >= 5.4 required');
}
// Check PHP Curl & json decode capabilities.
if (!function_exists('curl_init') || !function_exists('curl_exec')) {
throw new Exception('Midtrans needs the CURL PHP extension.');
}
if (!function_exists('json_decode')) {
throw new Exception('Midtrans needs the JSON PHP extension.');
}
// Configurations
require_once 'Midtrans/Config.php';
// Midtrans API Resources
require_once 'Midtrans/Transaction.php';
// Plumbing
require_once 'Midtrans/ApiRequestor.php';
require_once 'Midtrans/Notification.php';
require_once 'Midtrans/CoreApi.php';
require_once 'Midtrans/Snap.php';
// Sanitization
require_once 'Midtrans/Sanitizer.php';
+186
View File
@@ -0,0 +1,186 @@
<?php
namespace Midtrans;
use Exception;
/**
* Send request to Midtrans API
* Better don't use this class directly, please use CoreApi, Snap, and Transaction instead
*/
class ApiRequestor
{
/**
* Send GET request
*
* @param string $url
* @param string $server_key
* @param mixed[] $data_hash
* @return mixed
* @throws Exception
*/
public static function get($url, $server_key, $data_hash)
{
return self::remoteCall($url, $server_key, $data_hash, 'GET');
}
/**
* Send POST request
*
* @param string $url
* @param string $server_key
* @param mixed[] $data_hash
* @return mixed
* @throws Exception
*/
public static function post($url, $server_key, $data_hash)
{
return self::remoteCall($url, $server_key, $data_hash, 'POST');
}
/**
* Send PATCH request
*
* @param string $url
* @param string $server_key
* @param mixed[] $data_hash
* @return mixed
* @throws Exception
*/
public static function patch($url, $server_key, $data_hash)
{
return self::remoteCall($url, $server_key, $data_hash, 'PATCH');
}
/**
* Actually send request to API server
*
* @param string $url
* @param string $server_key
* @param mixed[] $data_hash
* @param bool $post
* @return mixed
* @throws Exception
*/
public static function remoteCall($url, $server_key, $data_hash, $method)
{
$ch = curl_init();
if (!$server_key) {
throw new Exception(
'The ServerKey/ClientKey is null, You need to set the server-key from Config. Please double-check Config and ServerKey key. ' .
'You can check from the Midtrans Dashboard. ' .
'See https://docs.midtrans.com/en/midtrans-account/overview?id=retrieving-api-access-keys ' .
'for the details or contact support at support@midtrans.com if you have any questions.'
);
} else {
if ($server_key == "") {
throw new Exception(
'The ServerKey/ClientKey is invalid, as it is an empty string. Please double-check your ServerKey key. ' .
'You can check from the Midtrans Dashboard. ' .
'See https://docs.midtrans.com/en/midtrans-account/overview?id=retrieving-api-access-keys ' .
'for the details or contact support at support@midtrans.com if you have any questions.'
);
} elseif (preg_match('/\s/',$server_key)) {
throw new Exception(
'The ServerKey/ClientKey is contains white-space. Please double-check your API key. Please double-check your ServerKey key. ' .
'You can check from the Midtrans Dashboard. ' .
'See https://docs.midtrans.com/en/midtrans-account/overview?id=retrieving-api-access-keys ' .
'for the details or contact support at support@midtrans.com if you have any questions.'
);
}
}
$curl_options = array(
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'Accept: application/json',
'User-Agent: midtrans-php-v2.5.2',
'Authorization: Basic ' . base64_encode($server_key . ':')
),
CURLOPT_RETURNTRANSFER => 1
);
// Set append notification to header
if (Config::$appendNotifUrl) Config::$curlOptions[CURLOPT_HTTPHEADER][] = 'X-Append-Notification: ' . Config::$appendNotifUrl;
// Set override notification to header
if (Config::$overrideNotifUrl) Config::$curlOptions[CURLOPT_HTTPHEADER][] = 'X-Override-Notification: ' . Config::$overrideNotifUrl;
// Set payment idempotency-key to header
if (Config::$paymentIdempotencyKey) Config::$curlOptions[CURLOPT_HTTPHEADER][] = 'Idempotency-Key: ' . Config::$paymentIdempotencyKey;
// merging with Config::$curlOptions
if (count(Config::$curlOptions)) {
// We need to combine headers manually, because it's array and it will no be merged
if (Config::$curlOptions[CURLOPT_HTTPHEADER]) {
$mergedHeaders = array_merge($curl_options[CURLOPT_HTTPHEADER], Config::$curlOptions[CURLOPT_HTTPHEADER]);
$headerOptions = array(CURLOPT_HTTPHEADER => $mergedHeaders);
} else {
$mergedHeaders = array();
$headerOptions = array(CURLOPT_HTTPHEADER => $mergedHeaders);
}
$curl_options = array_replace_recursive($curl_options, Config::$curlOptions, $headerOptions);
}
if ($method != 'GET') {
if ($data_hash) {
$body = json_encode($data_hash);
$curl_options[CURLOPT_POSTFIELDS] = $body;
} else {
$curl_options[CURLOPT_POSTFIELDS] = '';
}
if ($method == 'POST') {
$curl_options[CURLOPT_POST] = 1;
} elseif ($method == 'PATCH') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
}
}
curl_setopt_array($ch, $curl_options);
// For testing purpose
if (class_exists('\Midtrans\MT_Tests') && MT_Tests::$stubHttp) {
$result = self::processStubed($curl_options, $url, $server_key, $data_hash, $method);
} else {
$result = curl_exec($ch);
// curl_close($ch);
}
if ($result === false) {
throw new Exception('CURL Error: ' . curl_error($ch), curl_errno($ch));
} else {
try {
$result_array = json_decode($result);
} catch (Exception $e) {
throw new Exception("API Request Error unable to json_decode API response: ".$result . ' | Request url: '.$url);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (isset($result_array->status_code) && $result_array->status_code >= 401 && $result_array->status_code != 407) {
throw new Exception('Midtrans API is returning API error. HTTP status code: ' . $result_array->status_code . ' API response: ' . $result, $result_array->status_code);
} elseif ($httpCode >= 400) {
throw new Exception('Midtrans API is returning API error. HTTP status code: ' . $httpCode . ' API response: ' . $result, $httpCode);
} else {
return $result_array;
}
}
}
private static function processStubed($curl, $url, $server_key, $data_hash, $method)
{
MT_Tests::$lastHttpRequest = array(
"url" => $url,
"server_key" => $server_key,
"data_hash" => $data_hash,
$method => $method,
"curl" => $curl
);
return MT_Tests::$stubHttpResponse;
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
namespace Midtrans;
/**
* Midtrans Configuration
*/
class Config
{
/**
* Your merchant's server key
*
* @static
*/
public static $serverKey;
/**
* Your merchant's client key
*
* @static
*/
public static $clientKey;
/**
* True for production
* false for sandbox mode
*
* @static
*/
public static $isProduction = false;
/**
* Set it true to enable 3D Secure by default
*
* @static
*/
public static $is3ds = false;
/**
* Set Append URL notification
*
* @static
*/
public static $appendNotifUrl;
/**
* Set Override URL notification
*
* @static
*/
public static $overrideNotifUrl;
/**
* Set Payment IdempotencyKey
* for details (http://api-docs.midtrans.com/#idempotent-requests)
*
* @static
*/
public static $paymentIdempotencyKey;
/**
* Enable request params sanitizer (validate and modify charge request params).
* See Midtrans_Sanitizer for more details
*
* @static
*/
public static $isSanitized = false;
/**
* Default options for every request
*
* @static
*/
public static $curlOptions = array();
const SANDBOX_BASE_URL = 'https://api.sandbox.midtrans.com';
const PRODUCTION_BASE_URL = 'https://api.midtrans.com';
const SNAP_SANDBOX_BASE_URL = 'https://app.sandbox.midtrans.com/snap/v1';
const SNAP_PRODUCTION_BASE_URL = 'https://app.midtrans.com/snap/v1';
/**
* Get baseUrl
*
* @return string Midtrans API URL, depends on $isProduction
*/
public static function getBaseUrl()
{
return Config::$isProduction ?
Config::PRODUCTION_BASE_URL : Config::SANDBOX_BASE_URL;
}
/**
* Get snapBaseUrl
*
* @return string Snap API URL, depends on $isProduction
*/
public static function getSnapBaseUrl()
{
return Config::$isProduction ?
Config::SNAP_PRODUCTION_BASE_URL : Config::SNAP_SANDBOX_BASE_URL;
}
}
+257
View File
@@ -0,0 +1,257 @@
<?php
namespace Midtrans;
use Exception;
/**
* Provide charge and capture functions for Core API
*/
class CoreApi
{
/**
* Create transaction.
*
* @param mixed[] $params Transaction options
* @return mixed
* @throws Exception
*/
public static function charge($params)
{
$payloads = array(
'payment_type' => 'credit_card'
);
if (isset($params['item_details'])) {
$gross_amount = 0;
foreach ($params['item_details'] as $item) {
$gross_amount += $item['quantity'] * $item['price'];
}
$payloads['transaction_details']['gross_amount'] = $gross_amount;
}
$payloads = array_replace_recursive($payloads, $params);
if (Config::$isSanitized) {
Sanitizer::jsonRequest($payloads);
}
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/charge',
Config::$serverKey,
$payloads
);
}
/**
* Capture pre-authorized transaction
*
* @param string $param Order ID or transaction ID, that you want to capture
* @return mixed
* @throws Exception
*/
public static function capture($param)
{
$payloads = array(
'transaction_id' => $param,
);
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/capture',
Config::$serverKey,
$payloads
);
}
/**
* Do `/v2/card/register` API request to Core API
*
* @param $cardNumber
* @param $expMoth
* @param $expYear
* @return mixed
* @throws Exception
*/
public static function cardRegister($cardNumber, $expMoth, $expYear)
{
$path = "/card/register?card_number=" . $cardNumber
. "&card_exp_month=" . $expMoth
. "&card_exp_year=" . $expYear
. "&client_key=" . Config::$clientKey;
return ApiRequestor::get(
Config::getBaseUrl() . "/v2" . $path,
Config::$clientKey,
false
);
}
/**
* Do `/v2/token` API request to Core API
*
* @param $cardNumber
* @param $expMoth
* @param $expYear
* @param $cvv
* @return mixed
* @throws Exception
*/
public static function cardToken($cardNumber, $expMoth, $expYear, $cvv)
{
$path = "/token?card_number=" . $cardNumber
. "&card_exp_month=" . $expMoth
. "&card_exp_year=" . $expYear
. "&card_cvv=" . $cvv
. "&client_key=" . Config::$clientKey;
return ApiRequestor::get(
Config::getBaseUrl() . "/v2" . $path,
Config::$clientKey,
false
);
}
/**
* Do `/v2/point_inquiry/<tokenId>` API request to Core API
*
* @param string tokenId - tokenId of credit card (more params detail refer to: https://api-docs.midtrans.com)
* @return mixed
* @throws Exception
*/
public static function cardPointInquiry($tokenId)
{
return ApiRequestor::get(
Config::getBaseUrl() . '/v2/point_inquiry/' . $tokenId,
Config::$serverKey,
false
);
}
/**
* Create `/v2/pay/account` API request to Core API
*
* @param string create pay account request (more params detail refer to: https://api-docs.midtrans.com/#create-pay-account)
* @return mixed
* @throws Exception
*/
public static function linkPaymentAccount($param)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/pay/account',
Config::$serverKey,
$param
);
}
/**
* Do `/v2/pay/account/<accountId>` API request to Core API
*
* @param string accountId (more params detail refer to: https://api-docs.midtrans.com/#get-pay-account)
* @return mixed
* @throws Exception
*/
public static function getPaymentAccount($accountId)
{
return ApiRequestor::get(
Config::getBaseUrl() . '/v2/pay/account/' . $accountId,
Config::$serverKey,
false
);
}
/**
* Unbind `/v2/pay/account/<accountId>/unbind` API request to Core API
*
* @param string accountId (more params detail refer to: https://api-docs.midtrans.com/#unbind-pay-account)
* @return mixed
* @throws Exception
*/
public static function unlinkPaymentAccount($accountId)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/pay/account/' . $accountId . '/unbind',
Config::$serverKey,
false
);
}
/**
* Create `/v1/subscription` API request to Core API
*
* @param string create subscription request (more params detail refer to: https://api-docs.midtrans.com/#create-subscription)
* @return mixed
* @throws Exception
*/
public static function createSubscription($param)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v1/subscriptions',
Config::$serverKey,
$param
);
}
/**
* Do `/v1/subscription/<subscription_id>` API request to Core API
*
* @param string get subscription request (more params detail refer to: https://api-docs.midtrans.com/#get-subscription)
* @return mixed
* @throws Exception
*/
public static function getSubscription($SubscriptionId)
{
return ApiRequestor::get(
Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId,
Config::$serverKey,
false
);
}
/**
* Do disable `/v1/subscription/<subscription_id>/disable` API request to Core API
*
* @param string disable subscription request (more params detail refer to: https://api-docs.midtrans.com/#disable-subscription)
* @return mixed
* @throws Exception
*/
public static function disableSubscription($SubscriptionId)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId . '/disable',
Config::$serverKey,
false
);
}
/**
* Do enable `/v1/subscription/<subscription_id>/enable` API request to Core API
*
* @param string enable subscription request (more params detail refer to: https://api-docs.midtrans.com/#enable-subscription)
* @return mixed
* @throws Exception
*/
public static function enableSubscription($SubscriptionId)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId . '/enable',
Config::$serverKey,
false
);
}
/**
* Do update subscription `/v1/subscription/<subscription_id>` API request to Core API
*
* @param string update subscription request (more params detail refer to: https://api-docs.midtrans.com/#update-subscription)
* @return mixed
* @throws Exception
*/
public static function updateSubscription($SubscriptionId, $param)
{
return ApiRequestor::patch(
Config::getBaseUrl() . '/v1/subscriptions/' . $SubscriptionId,
Config::$serverKey,
$param
);
}
}
@@ -0,0 +1,41 @@
<?php
namespace Midtrans;
/**
* Read raw post input and parse as JSON. Provide getters for fields in notification object
*
* Example:
*
* ```php
*
* namespace Midtrans;
*
* $notif = new Notification();
* echo $notif->order_id;
* echo $notif->transaction_status;
* ```
*/
class Notification
{
private $response;
public function __construct($input_source = "php://input")
{
$raw_notification = json_decode(file_get_contents($input_source), true);
$status_response = Transaction::status($raw_notification['transaction_id']);
$this->response = $status_response;
}
public function __get($name)
{
if (isset($this->response->$name)) {
return $this->response->$name;
}
}
public function getResponse()
{
return $this->response;
}
}
+166
View File
@@ -0,0 +1,166 @@
<?php
namespace Midtrans;
/**
* Request params filters.
*
* It truncate fields that have length limit, remove not allowed characters from other fields
*
* This feature is optional, you can control it with Config::$isSanitized (default: false)
*/
class Sanitizer
{
private $filters;
public function __construct()
{
$this->filters = array();
}
/**
* Validates and modify data
*
* @param mixed[] $json
*/
public static function jsonRequest(&$json)
{
$keys = array('item_details', 'customer_details');
foreach ($keys as $key) {
if (!isset($json[$key])) continue;
$camel = static::upperCamelize($key);
$function = "field$camel";
static::$function($json[$key]);
}
}
private static function fieldItemDetails(&$items)
{
foreach ($items as &$item) {
$id = new self;
$item['id'] = $id
->maxLength(50)
->apply($item['id']);
$name = new self;
$item['name'] = $name
->maxLength(50)
->apply($item['name']);
}
}
private static function fieldCustomerDetails(&$field)
{
if (isset($field['first_name'])) {
$first_name = new self;
$field['first_name'] = $first_name->maxLength(255)->apply($field['first_name']);
}
if (isset($field['last_name'])) {
$last_name = new self;
$field['last_name'] = $last_name->maxLength(255)->apply($field['last_name']);
}
if (isset($field['email'])) {
$email = new self;
$field['email'] = $email->maxLength(255)->apply($field['email']);
}
if (isset($field['phone'])) {
$phone = new self;
$field['phone'] = $phone->maxLength(255)->apply($field['phone']);
}
if (!empty($field['billing_address']) || !empty($field['shipping_address'])) {
$keys = array('billing_address', 'shipping_address');
foreach ($keys as $key) {
if (!isset($field[$key])) continue;
$camel = static::upperCamelize($key);
$function = "field$camel";
static::$function($field[$key]);
}
}
}
private static function fieldBillingAddress(&$field)
{
$fields = array(
'first_name' => 255,
'last_name' => 255,
'address' => 255,
'city' => 255,
'country_code' => 3
);
foreach ($fields as $key => $value) {
if (isset($field[$key])) {
$self = new self;
$field[$key] = $self
->maxLength($value)
->apply($field[$key]);
}
}
if (isset($field['postal_code'])) {
$postal_code = new self;
$field['postal_code'] = $postal_code
->whitelist('A-Za-z0-9\\- ')
->maxLength(10)
->apply($field['postal_code']);
}
if (isset($field['phone'])) {
static::fieldPhone($field['phone']);
}
}
private static function fieldShippingAddress(&$field)
{
static::fieldBillingAddress($field);
}
private static function fieldPhone(&$field)
{
$plus = substr($field, 0, 1) === '+';
$self = new self;
$field = $self
->whitelist('\\d\\-\\(\\) ')
->maxLength(19)
->apply($field);
if ($plus) $field = '+' . $field;
$self = new self;
$field = $self
->maxLength(19)
->apply($field);
}
private function maxLength($length)
{
$this->filters[] = function ($input) use ($length) {
return substr($input, 0, $length);
};
return $this;
}
private function whitelist($regex)
{
$this->filters[] = function ($input) use ($regex) {
return preg_replace("/[^$regex]/", '', $input);
};
return $this;
}
private function apply($input)
{
foreach ($this->filters as $filter) {
$input = call_user_func($filter, $input);
}
return $input;
}
private static function upperCamelize($string)
{
return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace Midtrans;
use Exception;
/**
* Create Snap payment page and return snap token
*/
class Snap
{
/**
* Create Snap payment page
*
* Example:
*
* ```php
*
* namespace Midtrans;
*
* $params = array(
* 'transaction_details' => array(
* 'order_id' => rand(),
* 'gross_amount' => 10000,
* )
* );
* $paymentUrl = Snap::getSnapToken($params);
* ```
*
* @param array $params Payment options
* @return string Snap token.
* @throws Exception curl error or midtrans error
*/
public static function getSnapToken($params)
{
return (Snap::createTransaction($params)->token);
}
/**
* Create Snap URL payment
*
* Example:
*
* ```php
*
* namespace Midtrans;
*
* $params = array(
* 'transaction_details' => array(
* 'order_id' => rand(),
* 'gross_amount' => 10000,
* )
* );
* $paymentUrl = Snap::getSnapUrl($params);
* ```
*
* @param array $params Payment options
* @return string Snap redirect url.
* @throws Exception curl error or midtrans error
*/
public static function getSnapUrl($params)
{
return (Snap::createTransaction($params)->redirect_url);
}
/**
* Create Snap payment page, with this version returning full API response
*
* Example:
*
* ```php
* $params = array(
* 'transaction_details' => array(
* 'order_id' => rand(),
* 'gross_amount' => 10000,
* )
* );
* $paymentUrl = Snap::getSnapToken($params);
* ```
*
* @param array $params Payment options
* @return object Snap response (token and redirect_url).
* @throws Exception curl error or midtrans error
*/
public static function createTransaction($params)
{
$payloads = array(
'credit_card' => array(
// 'enabled_payments' => array('credit_card'),
'secure' => Config::$is3ds
)
);
if (isset($params['item_details'])) {
$gross_amount = 0;
foreach ($params['item_details'] as $item) {
$gross_amount += $item['quantity'] * $item['price'];
}
$params['transaction_details']['gross_amount'] = $gross_amount;
}
if (Config::$isSanitized) {
Sanitizer::jsonRequest($params);
}
$params = array_replace_recursive($payloads, $params);
return ApiRequestor::post(
Config::getSnapBaseUrl() . '/transactions',
Config::$serverKey,
$params
);
}
}
@@ -0,0 +1,128 @@
<?php
namespace Midtrans;
/**
* Send request to Snap API
* Better don't use this class directly, use Snap
* @deprecated this class already deprecated. We will deleted on the next major release. We have been centralized the
* requestor via ApiRequestor.php
*/
class SnapApiRequestor
{
/**
* Send GET request
*
* @param string $url
* @param string $server_key
* @param mixed[] $data_hash
*/
public static function get($url, $server_key, $data_hash)
{
return self::remoteCall($url, $server_key, $data_hash, false);
}
/**
* Send POST request
*
* @param string $url
* @param string $server_key
* @param mixed[] $data_hash
*/
public static function post($url, $server_key, $data_hash)
{
return self::remoteCall($url, $server_key, $data_hash, true);
}
/**
* Actually send request to API server
*
* @param string $url
* @param string $server_key
* @param mixed[] $data_hash
* @param bool $post
*/
public static function remoteCall($url, $server_key, $data_hash, $post = true)
{
$ch = curl_init();
$curl_options = array(
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'Accept: application/json',
'Authorization: Basic ' . base64_encode($server_key . ':')
),
CURLOPT_RETURNTRANSFER => 1,
// CURLOPT_CAINFO => dirname(__FILE__) . "/../data/cacert.pem"
);
// merging with Config::$curlOptions
if (count(Config::$curlOptions)) {
// We need to combine headers manually, because it's array and it will no be merged
if (Config::$curlOptions[CURLOPT_HTTPHEADER]) {
$mergedHeders = array_merge($curl_options[CURLOPT_HTTPHEADER], Config::$curlOptions[CURLOPT_HTTPHEADER]);
$headerOptions = array( CURLOPT_HTTPHEADER => $mergedHeders );
} else {
$mergedHeders = array();
}
$curl_options = array_replace_recursive($curl_options, Config::$curlOptions, $headerOptions);
}
if ($post) {
$curl_options[CURLOPT_POST] = 1;
if ($data_hash) {
$body = json_encode($data_hash);
$curl_options[CURLOPT_POSTFIELDS] = $body;
} else {
$curl_options[CURLOPT_POSTFIELDS] = '';
}
}
curl_setopt_array($ch, $curl_options);
// For testing purpose
if (class_exists('\Midtrans\VT_Tests') && VT_Tests::$stubHttp) {
$result = self::processStubed($curl_options, $url, $server_key, $data_hash, $post);
$info = VT_Tests::$stubHttpStatus;
} else {
$result = curl_exec($ch);
$info = curl_getinfo($ch);
// curl_close($ch);
}
if ($result === false) {
throw new \Exception('CURL Error: ' . curl_error($ch), curl_errno($ch));
} else {
try {
$result_array = json_decode($result);
} catch (\Exception $e) {
$message = "API Request Error unable to json_decode API response: ".$result . ' | Request url: '.$url;
throw new \Exception($message);
}
if ($info['http_code'] != 201) {
$message = 'Midtrans Error (' . $info['http_code'] . '): '
. $result . ' | Request url: '.$url;
throw new \Exception($message, $info['http_code']);
} else {
return $result_array;
}
}
}
private static function processStubed($curl, $url, $server_key, $data_hash, $post)
{
VT_Tests::$lastHttpRequest = array(
"url" => $url,
"server_key" => $server_key,
"data_hash" => $data_hash,
"post" => $post,
"curl" => $curl
);
return VT_Tests::$stubHttpResponse;
}
}
+153
View File
@@ -0,0 +1,153 @@
<?php
namespace Midtrans;
use Exception;
/**
* API methods to get transaction status, approve and cancel transactions
*/
class Transaction
{
/**
* Retrieve transaction status
*
* @param string $id Order ID or transaction ID
*
* @return mixed[]
* @throws Exception
*/
public static function status($id)
{
return ApiRequestor::get(
Config::getBaseUrl() . '/v2/' . $id . '/status',
Config::$serverKey,
false
);
}
/**
* Retrieve B2B transaction status
*
* @param string $id Order ID or transaction ID
*
* @return mixed[]
* @throws Exception
*/
public static function statusB2b($id)
{
return ApiRequestor::get(
Config::getBaseUrl() . '/v2/' . $id . '/status/b2b',
Config::$serverKey,
false
);
}
/**
* Approve challenge transaction
*
* @param string $id Order ID or transaction ID
*
* @return string
* @throws Exception
*/
public static function approve($id)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/' . $id . '/approve',
Config::$serverKey,
false
)->status_code;
}
/**
* Cancel transaction before it's settled
*
* @param string $id Order ID or transaction ID
*
* @return string
* @throws Exception
*/
public static function cancel($id)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/' . $id . '/cancel',
Config::$serverKey,
false
)->status_code;
}
/**
* Expire transaction before it's setteled
*
* @param string $id Order ID or transaction ID
*
* @return mixed[]
* @throws Exception
*/
public static function expire($id)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/' . $id . '/expire',
Config::$serverKey,
false
);
}
/**
* Transaction status can be updated into refund
* if the customer decides to cancel completed/settlement payment.
* The same refund id cannot be reused again.
*
* @param string $id Order ID or transaction ID
*
* @param $params
* @return mixed[]
* @throws Exception
*/
public static function refund($id, $params)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/' . $id . '/refund',
Config::$serverKey,
$params
);
}
/**
* Transaction status can be updated into refund
* if the customer decides to cancel completed/settlement payment.
* The same refund id cannot be reused again.
*
* @param string $id Order ID or transaction ID
*
* @return mixed[]
* @throws Exception
*/
public static function refundDirect($id, $params)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/' . $id . '/refund/online/direct',
Config::$serverKey,
$params
);
}
/**
* Deny method can be triggered to immediately deny card payment transaction
* in which fraud_status is challenge.
*
* @param string $id Order ID or transaction ID
*
* @return mixed[]
* @throws Exception
*/
public static function deny($id)
{
return ApiRequestor::post(
Config::getBaseUrl() . '/v2/' . $id . '/deny',
Config::$serverKey,
false
);
}
}
+464
View File
@@ -0,0 +1,464 @@
Midtrans-PHP
===============
[![PHP version](https://badge.fury.io/ph/midtrans%2Fmidtrans-php.svg)](https://badge.fury.io/ph/midtrans%2Fmidtrans-php)
[![Latest Stable Version](https://poser.pugx.org/midtrans/midtrans-php/v/stable)](https://packagist.org/packages/midtrans/midtrans-php)
[![Monthly Downloads](https://poser.pugx.org/midtrans/midtrans-php/d/monthly)](https://packagist.org/packages/midtrans/midtrans-php)
[![Total Downloads](https://poser.pugx.org/midtrans/midtrans-php/downloads)](https://packagist.org/packages/midtrans/midtrans-php)
<!-- [![Build Status](https://travis-ci.org/midtrans/midtrans-php.svg)](https://travis-ci.org/midtrans/midtrans-php) -->
[Midtrans](https://midtrans.com) :heart: PHP!
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.
## 1. Installation
### 1.a Composer Installation
If you are using [Composer](https://getcomposer.org), you can install via composer CLI:
```
composer require midtrans/midtrans-php
```
**or**
add this require line to your `composer.json` file:
```json
{
"require": {
"midtrans/midtrans-php": "2.*"
}
}
```
and run `composer install` on your terminal.
> **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`
> `/Midtrans` will then be available (auto loaded) as Object in your Laravel project.
### 1.b Manual Instalation
If you are not using Composer, you can clone or [download](https://github.com/midtrans/midtrans-php/archive/master.zip) this repository.
Then you should require/autoload `Midtrans.php` file on your code.
```php
require_once dirname(__FILE__) . '/pathofproject/Midtrans.php';
// my code goes here
```
## 2. How to Use
### 2.1 General Settings
```php
// Set your Merchant Server Key
\Midtrans\Config::$serverKey = '<your server key>';
// Set to Development/Sandbox Environment (default). Set to true for Production Environment (accept real transaction).
\Midtrans\Config::$isProduction = false;
// Set sanitization on (default)
\Midtrans\Config::$isSanitized = true;
// Set 3DS transaction for credit card to true
\Midtrans\Config::$is3ds = true;
```
#### Override Notification URL
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.
```php
// Add new notification url(s) alongside the settings on Midtrans Dashboard Portal (MAP)
Config::$appendNotifUrl = "https://example.com/test1,https://example.com/test2";
// Use new notification url(s) disregarding the settings on Midtrans Dashboard Portal (MAP)
Config::$overrideNotifUrl = "https://example.com/test1";
```
[More details](https://api-docs.midtrans.com/#override-notification-url)
> **Note:** When both `appendNotifUrl` and `overrideNotifUrl` are used together then only `overrideNotifUrl` will be used.
> Both header can only receive up to maximum of **3 urls**.
#### Idempotency-Key
You can opt to add idempotency key on charge transaction. It can be achieved by adding additional HTTP headers into charge request.
Is a unique value that is put on header on API request. Midtrans API accept Idempotency-Key on header to safely handle retry request
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.
```php
Config::$paymentIdempotencyKey = "Unique-ID";
```
[More details](http://api-docs.midtrans.com/#idempotent-requests)
### 2.2 Choose Product/Method
We have [3 different products](https://docs.midtrans.com/en/welcome/index.html) of payment that you can use:
- [Snap](#22a-snap) - Customizable payment popup will appear on **your web/app** (no redirection). [doc ref](https://snap-docs.midtrans.com/)
- [Snap Redirect](#22b-snap-redirect) - Customer need to be redirected to payment url **hosted by midtrans**. [doc ref](https://snap-docs.midtrans.com/)
- [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/)
Choose one that you think best for your unique needs.
### 2.2.a Snap
You can see Snap example [here](examples/snap).
#### Get Snap Token
```php
$params = array(
'transaction_details' => array(
'order_id' => rand(),
'gross_amount' => 10000,
)
);
$snapToken = \Midtrans\Snap::getSnapToken($params);
```
#### Initialize Snap JS when customer click pay button
```html
<html>
<body>
<button id="pay-button">Pay!</button>
<pre><div id="result-json">JSON result will appear here after payment:<br></div></pre>
<!-- TODO: Remove ".sandbox" from script src URL for production environment. Also input your client key in "data-client-key" -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="<Set your ClientKey here>"></script>
<script type="text/javascript">
document.getElementById('pay-button').onclick = function(){
// SnapToken acquired from previous step
snap.pay('<?=$snapToken?>', {
// Optional
onSuccess: function(result){
/* You may add your own js here, this is just example */ document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
},
// Optional
onPending: function(result){
/* You may add your own js here, this is just example */ document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
},
// Optional
onError: function(result){
/* You may add your own js here, this is just example */ document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
}
});
};
</script>
</body>
</html>
```
#### Implement Notification Handler
[Refer to this section](#23-handle-http-notification)
### 2.2.b Snap Redirect
You can see some Snap Redirect examples [here](examples/snap-redirect).
#### Get Redirection URL of a Payment Page
```php
$params = array(
'transaction_details' => array(
'order_id' => rand(),
'gross_amount' => 10000,
)
);
try {
// Get Snap Payment Page URL
$paymentUrl = \Midtrans\Snap::createTransaction($params)->redirect_url;
// Redirect to Snap Payment Page
header('Location: ' . $paymentUrl);
}
catch (Exception $e) {
echo $e->getMessage();
}
```
#### Implement Notification Handler
[Refer to this section](#23-handle-http-notification)
### 2.2.c Core API (VT-Direct)
You can see some Core API examples [here](examples/core-api).
#### Set Client Key
```javascript
MidtransNew3ds.clientKey = "<your client key>";
```
#### Checkout Page
Please refer to [this file](examples/core-api/checkout.php)
#### Checkout Process
##### 1. Create Transaction Details
```php
$transaction_details = array(
'order_id' => time(),
'gross_amount' => 200000
);
```
##### 2. Create Item Details, Billing Address, Shipping Address, and Customer Details (Optional)
```php
// Populate items
$items = array(
array(
'id' => 'item1',
'price' => 100000,
'quantity' => 1,
'name' => 'Adidas f50'
),
array(
'id' => 'item2',
'price' => 50000,
'quantity' => 2,
'name' => 'Nike N90'
)
);
// Populate customer's billing address
$billing_address = array(
'first_name' => "Andri",
'last_name' => "Setiawan",
'address' => "Karet Belakang 15A, Setiabudi.",
'city' => "Jakarta",
'postal_code' => "51161",
'phone' => "081322311801",
'country_code' => 'IDN'
);
// Populate customer's shipping address
$shipping_address = array(
'first_name' => "John",
'last_name' => "Watson",
'address' => "Bakerstreet 221B.",
'city' => "Jakarta",
'postal_code' => "51162",
'phone' => "081322311801",
'country_code' => 'IDN'
);
// Populate customer's info
$customer_details = array(
'first_name' => "Andri",
'last_name' => "Setiawan",
'email' => "test@test.com",
'phone' => "081322311801",
'billing_address' => $billing_address,
'shipping_address' => $shipping_address
);
```
##### 3. Get Token ID from Checkout Page
```php
// Token ID from checkout page
$token_id = $_POST['token_id'];
```
##### 4. Create Transaction Data
```php
// Transaction data to be sent
$transaction_data = array(
'payment_type' => 'credit_card',
'credit_card' => array(
'token_id' => $token_id,
'authentication'=> true,
// 'bank' => 'bni', // optional to set acquiring bank
// 'save_token_id' => true // optional for one/two clicks feature
),
'transaction_details' => $transaction_details,
'item_details' => $items,
'customer_details' => $customer_details
);
```
##### 5. Charge
```php
$response = \Midtrans\CoreApi::charge($transaction_data);
```
##### 6. Credit Card 3DS Authentication
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)
For full example on Credit Card 3DS transaction refer to:
- [Core API examples](/examples/core-api/)
##### 7. Handle Transaction Status
```php
// Success
if($response->transaction_status == 'capture') {
echo "<p>Transaksi berhasil.</p>";
echo "<p>Status transaksi untuk order id $response->order_id: " .
"$response->transaction_status</p>";
echo "<h3>Detail transaksi:</h3>";
echo "<pre>";
var_dump($response);
echo "</pre>";
}
// Deny
else if($response->transaction_status == 'deny') {
echo "<p>Transaksi ditolak.</p>";
echo "<p>Status transaksi untuk order id .$response->order_id: " .
"$response->transaction_status</p>";
echo "<h3>Detail transaksi:</h3>";
echo "<pre>";
var_dump($response);
echo "</pre>";
}
// Challenge
else if($response->transaction_status == 'challenge') {
echo "<p>Transaksi challenge.</p>";
echo "<p>Status transaksi untuk order id $response->order_id: " .
"$response->transaction_status</p>";
echo "<h3>Detail transaksi:</h3>";
echo "<pre>";
var_dump($response);
echo "</pre>";
}
// Error
else {
echo "<p>Terjadi kesalahan pada data transaksi yang dikirim.</p>";
echo "<p>Status message: [$response->status_code] " .
"$response->status_message</p>";
echo "<pre>";
var_dump($response);
echo "</pre>";
}
```
#### 8. Implement Notification Handler
[Refer to this section](#23-handle-http-notification)
### 2.3 Handle HTTP Notification
Create separated web endpoint (notification url) to receive HTTP POST notification callback/webhook.
HTTP notification will be sent whenever transaction status is changed.
Example also available [here](examples/notification-handler.php)
```php
$notif = new \Midtrans\Notification();
$transaction = $notif->transaction_status;
$fraud = $notif->fraud_status;
error_log("Order ID $notif->order_id: "."transaction status = $transaction, fraud staus = $fraud");
if ($transaction == 'capture') {
if ($fraud == 'challenge') {
// TODO Set payment status in merchant's database to 'challenge'
}
else if ($fraud == 'accept') {
// TODO Set payment status in merchant's database to 'success'
}
}
else if ($transaction == 'cancel') {
if ($fraud == 'challenge') {
// TODO Set payment status in merchant's database to 'failure'
}
else if ($fraud == 'accept') {
// TODO Set payment status in merchant's database to 'failure'
}
}
else if ($transaction == 'deny') {
// TODO Set payment status in merchant's database to 'failure'
}
```
### 2.4 Process Transaction
#### Get Transaction Status
```php
$status = \Midtrans\Transaction::status($orderId);
var_dump($status);
```
#### Approve Transaction
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 :
```php
$approve = \Midtrans\Transaction::approve($orderId);
var_dump($approve);
```
#### Cancel Transaction
You can Cancel transaction with `fraud_status == CHALLENGE`, or credit card transaction with `transaction_status == CAPTURE` (before it become SETTLEMENT)
```php
$cancel = \Midtrans\Transaction::cancel($orderId);
var_dump($cancel);
```
#### Expire Transaction
You can Expire transaction with `transaction_status == PENDING` (before it become SETTLEMENT or EXPIRE)
```php
$cancel = \Midtrans\Transaction::cancel($orderId);
var_dump($cancel);
```
#### Refund Transaction
Refund a transaction (not all payment channel allow refund via API)
You can Refund transaction with `transaction_status == settlement`
```php
$params = array(
'refund_key' => 'order1-ref1',
'amount' => 10000,
'reason' => 'Item out of stock'
);
$refund = \Midtrans\Transaction::refund($orderId, $params);
var_dump($refund);
```
#### Direct Refund Transaction
Refund a transaction via Direct Refund API
You can Refund transaction with `transaction_status == settlement`
```php
$params = array(
'refund_key' => 'order1-ref1',
'amount' => 10000,
'reason' => 'Item out of stock'
);
$direct_refund = \Midtrans\Transaction::refundDirect($orderId, $params);
var_dump($direct_refund);
```
## Unit Test
### Integration Test (sandbox real transactions)
Please change server key and client key on `phpunit.xml` to your own.
### All Test
`vendor/bin/phpunit`
### Specific Test
`vendor/bin/phpunit tests/integration/CoreApiIntegrationTest.php`
## Contributing
### Developing e-commerce plug-ins
There are several guides that must be taken care of when you develop new plugins.
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.
2. Consider using the __auto-sanitization__ feature.
+46
View File
@@ -0,0 +1,46 @@
{
"name": "midtrans/midtrans-php",
"description": "PHP Wrapper for Midtrans Payment API.",
"homepage": "https://midtrans.com",
"version": "2.5.2",
"type": "library",
"license":"MIT",
"authors": [
{
"name": "Andri Setiawan",
"email": "andri.setiawan@veritrans.co.id"
},
{
"name": "Alvin Litani",
"email": "alvin.litani@veritrans.co.id"
},
{
"name": "Ismail Faruqi",
"email": "ismail.faruqi@veritrans.co.id"
}
],
"require": {
"php": ">=5.4",
"ext-curl": "*",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "5.7.*",
"psy/psysh": "0.4.*"
},
"autoload": {
"psr-4": {
"Midtrans\\": "Midtrans/"
}
},
"autoload-dev": {
"psr-4": {
"Midtrans\\":[
"tests/",
"test/integration",
"tests/utility",
"tests/utility/fixture"
]
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,117 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
// Please refer to this docs:
// https://docs.midtrans.com/en/core-api/credit-card?id=_2-sending-transaction-data-to-charge-api
namespace Midtrans;
require_once dirname(__FILE__) . '/../../Midtrans.php';
Config::$serverKey = '<your server key>';
// Uncomment for append and override notification URL
// Config::$appendNotifUrl = "https://example.com";
// Config::$overrideNotifUrl = "https://example.com";
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
// Uncomment for production environment
// Config::$isProduction = true;
// Uncomment to enable sanitization
// Config::$isSanitized = true;
// Uncomment to enable idempotency-key, more details: (http://api-docs.midtrans.com/#idempotent-requests)
// Config::$paymentIdempotencyKey = "Unique-ID";
$transaction_details = array(
'order_id' => time(),
'gross_amount' => 200000
);
// Populate items
$items = array(
array(
'id' => 'item1',
'price' => 100000,
'quantity' => 1,
'name' => 'Adidas f50'
),
array(
'id' => 'item2',
'price' => 50000,
'quantity' => 2,
'name' => 'Nike N90'
));
// Populate customer's billing address
$billing_address = array(
'first_name' => "Andri",
'last_name' => "Setiawan",
'address' => "Karet Belakang 15A, Setiabudi.",
'city' => "Jakarta",
'postal_code' => "51161",
'phone' => "081322311801",
'country_code' => 'IDN'
);
// Populate customer's shipping address
$shipping_address = array(
'first_name' => "John",
'last_name' => "Watson",
'address' => "Bakerstreet 221B.",
'city' => "Jakarta",
'postal_code' => "51162",
'phone' => "081322311801",
'country_code' => 'IDN'
);
// Populate customer's info
$customer_details = array(
'first_name' => "Andri",
'last_name' => "Setiawan",
'email' => "andri@setiawan.com",
'phone' => "081322311801",
'billing_address' => $billing_address,
'shipping_address' => $shipping_address
);
// Token ID from checkout page
$token_id = $_POST['token_id'];
$authentication = isset($_POST['secure']);
$save_token_id = isset($_POST['save_cc']);
// Transaction data to be sent
$transaction_data = array(
'payment_type' => 'credit_card',
'credit_card' => array(
'token_id' => $token_id,
'authentication' => $authentication,
// 'bank' => 'bni', // optional acquiring bank
'save_token_id' => $save_token_id
),
'transaction_details' => $transaction_details,
'item_details' => $items,
'customer_details' => $customer_details
);
try {
$response = CoreApi::charge($transaction_data);
header('Content-Type: application/json');
echo json_encode($response);
} catch (\Exception $e) {
echo $e->getMessage();
}
function printExampleWarningMessage() {
if (strpos(Config::$serverKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your server key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$serverKey = \'<your server key>\';');
die();
}
}
@@ -0,0 +1,201 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
// Please refer to this docs:
// https://docs.midtrans.com/en/core-api/credit-card?id=_1-getting-the-card-token
namespace Midtrans;
require_once dirname(__FILE__) . '/../../Midtrans.php';
// Set Your server key
// can find in Merchant Portal -> Settings -> Access keys
Config::$clientKey = '<your client key>';
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
function printExampleWarningMessage() {
if (strpos(Config::$clientKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your client key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$clientKey = \'<your client key>\';');
die();
}
}
?>
<html>
<head>
<title>Checkout</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/featherlight/1.7.12/featherlight.min.css">
</head>
<body>
<script id="midtrans-script" type="text/javascript" src="https://api.midtrans.com/v2/assets/js/midtrans-new-3ds.min.js" data-environment="sandbox" data-client-key="<?php echo Config::$clientKey;?>"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/featherlight/1.7.12/featherlight.min.js"></script>
<h1>Checkout</h1>
<form action="checkout-process.php" method="POST" id="payment-form">
<fieldset>
<legend>Checkout</legend>
<small><strong>Field that may be presented to customer:</strong></small>
<p>
<label>Card Number</label>
<input class="card-number" name="card-number" value="4811 1111 1111 1114" size="23" type="text" autocomplete="off" />
</p>
<p>
<label>Expiration (MM/YYYY)</label>
<input class="card-expiry-month" name="card-expiry-month" value="12" placeholder="MM" size="2" type="text" />
<span> / </span>
<input class="card-expiry-year" name="card-expiry-year" value="2025" placeholder="YYYY" size="4" type="text" />
</p>
<p>
<label>CVV</label>
<input class="card-cvv" name="card-cvv" value="123" size="4" type="password" autocomplete="off" />
</p>
<p>
<label>Save credit card</label>
<input type="checkbox" id="save_cc" name="save_cc" value="true">
</p>
<small><strong>Fields that shouldn't be presented to the customer:</strong></small>
<p>
<label>3D Secure</label>
<input type="checkbox" id="secure" name="secure" value="true" checked>
</p>
<input id="token_id" name="token_id" type="hidden" />
<button class="submit-button" type="submit">Submit Payment</button>
</fieldset>
</form>
<code>
<pre>
<b>Testing cards:</b>
<b>For 3D Secure:</b>
Visa 4811 1111 1111 1114
MasterCard 5211 1111 1111 1117
<b>For Non 3D Secure:</b>
Visa success 4011 1111 1111 1112
Visa challenge 4111 1111 1111 1111
Visa deny by FDS 4211 1111 1111 1110
MasterCard success 5481 1611 1111 1081
MasterCard challenge 5110 1111 1111 1119
MasterCard deny by FDS 5210 1111 1111 1118
</pre>
</code>
<!-- Javascript for token generation -->
<script type="text/javascript">
$(function () {
// open the console log to check the flow
// 3ds new flow:
// 1. get token_id
// 2. send token_id to backend
// 3. initial charge from backend to midtrans api
// 4. open redirect_url
var options = {
performAuthentication: function(redirect_url){
openDialog(redirect_url);
},
onSuccess: function(response){
console.log('success');
console.log('response:',response);
closeDialog();
},
onFailure: function(response){
console.log('fail');
console.log('response:',response);
closeDialog();
alert(response.status_message);
$('button').removeAttr("disabled");
},
onPending: function(response){
console.log('pending');
console.log('response:',response);
closeDialog();
}
};
function openDialog(url) {
$.featherlight({
iframe: url,
iframeMaxWidth: '80%',
iframeWidth: 700,
iframeHeight: 500,
closeOnClick: false,
closeOnEsc: false,
closeIcon:''
});
}
function closeDialog() {
$.featherlight.close();
}
$(".submit-button").click(function (event) {
var card = {
"card_number": $(".card-number").val(),
"card_exp_month": $(".card-expiry-month").val(),
"card_exp_year": $(".card-expiry-year").val(),
"card_cvv": $(".card-cvv").val()
};
event.preventDefault();
$(this).attr("disabled", "disabled");
console.log('1. get token_id');
MidtransNew3ds.getCardToken(card, getCardTokenCallback);
return false;
});
// callback functions
var getCardTokenCallback = {
onSuccess: function(response) {
// Success to get card token_id, implement as you wish here
console.log('Success to get card token_id, response:', response);
var token_id = response.token_id;
$("#token_id").val(token_id);
console.log('This is the card token_id:', token_id);
// Implement sending the token_id to backend to proceed to next step
console.log('2. send token_id to backend');
// send token_id, save_cc and secure params
// we send secure param for sample, in production, you should define transaction is secure/not in backend
// we recommend always use secure=true
// data: $("#token_id, #save_cc, #secure").serialize()
$.ajax({
type: 'POST',
url: 'checkout-process.php',
data: $("#token_id, #save_cc, #secure").serialize(),
success: function(response){
console.log('3. response charge from backend:', response);
if (response.redirect_url){
console.log('4. open redirect_url');
MidtransNew3ds.authenticate(response.redirect_url, options);
}
},
error: function(xhr, status, error){
console.error(xhr);
}
});
},
onFailure: function(response) {
// Fail to get card token_id, implement as you wish here
console.log('Fail to get card token_id, response:', response);
closeDialog();
$('button').removeAttr("disabled");
}
};
});
</script>
</body>
</html>
@@ -0,0 +1,74 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
namespace Midtrans;
require_once dirname(__FILE__) . '/../../Midtrans.php';
// Set Your server key
// can find in Merchant Portal -> Settings -> Access keys
Config::$serverKey = '<your server key>';
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
// define variables and set to empty values
$number = "";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$number = ($_POST["number"]);
}
// required
$params = array(
"payment_type" => "gopay",
"gopay_partner" => array(
"phone_number" => $number,
"redirect_url" => "https://www.google.com"
)
);
$response = '';
try {
$response = CoreApi::linkPaymentAccount($params);
} catch (\Exception $e) {
echo $e->getMessage();
die();
}
function printExampleWarningMessage() {
if (strpos(Config::$serverKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your server key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$serverKey = \'<your server key>\';');
die();
}
}
?>
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<h2>Simple Gopay Tokenization</h2>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
Phone number: <input type="text" name="number">
<br><br>
<input type="submit" name="submit" value="Submit">
</form>
<?php
echo "<h2>Result get pay account:</h2>";
echo json_encode($response, JSON_UNESCAPED_SLASHES);
echo "<br>";
?>
</body>
</html>
@@ -0,0 +1,50 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
namespace Midtrans;
require_once dirname(__FILE__) . '/../../Midtrans.php';
// Set Your server key
// can find in Merchant Portal -> Settings -> Access keys
Config::$serverKey = '<your server key>';
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
$orderId = '<your order id / transaction id>';
// Get transaction status to Midtrans API
$status = '';
try {
$status = Transaction::status($orderId);
} catch (\Exception $e) {
echo $e->getMessage();
die();
}
echo '<pre>';
echo json_encode($status);
function printExampleWarningMessage() {
if (strpos(Config::$serverKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your server key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$serverKey = \'<your server key>\';');
die();
}
}
// Approve a transaction that is in Challenge status
// $approve = Transaction::approve($orderId);
// var_dump($approve);
// Cancel a transaction
// $cancel = Transaction::cancel($orderId);
// var_dump($cancel);
// Expire a transaction
// $expire = Transaction::expire($orderId);
// var_dump($expire);
@@ -0,0 +1,70 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
// Please refer to this docs for sample HTTP notifications:
// https://docs.midtrans.com/en/after-payment/http-notification?id=sample-of-different-payment-channels
namespace Midtrans;
require_once dirname(__FILE__) . '/../Midtrans.php';
Config::$isProduction = false;
Config::$serverKey = '<your server key>';
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
try {
$notif = new Notification();
}
catch (\Exception $e) {
exit($e->getMessage());
}
$notif = $notif->getResponse();
$transaction = $notif->transaction_status;
$type = $notif->payment_type;
$order_id = $notif->order_id;
$fraud = $notif->fraud_status;
if ($transaction == 'capture') {
// For credit card transaction, we need to check whether transaction is challenge by FDS or not
if ($type == 'credit_card') {
if ($fraud == 'challenge') {
// TODO set payment status in merchant's database to 'Challenge by FDS'
// TODO merchant should decide whether this transaction is authorized or not in MAP
echo "Transaction order_id: " . $order_id ." is challenged by FDS";
} else {
// TODO set payment status in merchant's database to 'Success'
echo "Transaction order_id: " . $order_id ." successfully captured using " . $type;
}
}
} else if ($transaction == 'settlement') {
// TODO set payment status in merchant's database to 'Settlement'
echo "Transaction order_id: " . $order_id ." successfully transfered using " . $type;
} else if ($transaction == 'pending') {
// TODO set payment status in merchant's database to 'Pending'
echo "Waiting customer to finish transaction order_id: " . $order_id . " using " . $type;
} else if ($transaction == 'deny') {
// TODO set payment status in merchant's database to 'Denied'
echo "Payment using " . $type . " for transaction order_id: " . $order_id . " is denied.";
} else if ($transaction == 'expire') {
// TODO set payment status in merchant's database to 'expire'
echo "Payment using " . $type . " for transaction order_id: " . $order_id . " is expired.";
} else if ($transaction == 'cancel') {
// TODO set payment status in merchant's database to 'Denied'
echo "Payment using " . $type . " for transaction order_id: " . $order_id . " is canceled.";
}
function printExampleWarningMessage() {
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
echo 'Notification-handler are not meant to be opened via browser / GET HTTP method. It is used to handle Midtrans HTTP POST notification / webhook.';
}
if (strpos(Config::$serverKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your server key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$serverKey = \'<your server key>\';');
die();
}
}
@@ -0,0 +1,110 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
// Please refer to this docs for snap-redirect:
// https://docs.midtrans.com/en/snap/integration-guide?id=alternative-way-to-display-snap-payment-page-via-redirect
namespace Midtrans;
require_once dirname(__FILE__) . '/../../Midtrans.php';
// Set Your server key
// can find in Merchant Portal -> Settings -> Access keys
Config::$serverKey = '<your server key>';
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
// Uncomment for production environment
// Config::$isProduction = true;
// Uncomment to enable sanitization
// Config::$isSanitized = true;
// Uncomment to enable 3D-Secure
// Config::$is3ds = true;
// Required
$transaction_details = array(
'order_id' => rand(),
'gross_amount' => 145000, // no decimal allowed for creditcard
);
// Optional
$item1_details = array(
'id' => 'a1',
'price' => 50000,
'quantity' => 2,
'name' => "Apple"
);
// Optional
$item2_details = array(
'id' => 'a2',
'price' => 45000,
'quantity' => 1,
'name' => "Orange"
);
// Optional
$item_details = array ($item1_details, $item2_details);
// Optional
$billing_address = array(
'first_name' => "Andri",
'last_name' => "Litani",
'address' => "Mangga 20",
'city' => "Jakarta",
'postal_code' => "16602",
'phone' => "081122334455",
'country_code' => 'IDN'
);
// Optional
$shipping_address = array(
'first_name' => "Obet",
'last_name' => "Supriadi",
'address' => "Manggis 90",
'city' => "Jakarta",
'postal_code' => "16601",
'phone' => "08113366345",
'country_code' => 'IDN'
);
// Optional
$customer_details = array(
'first_name' => "Andri",
'last_name' => "Litani",
'email' => "andri@litani.com",
'phone' => "081122334455",
'billing_address' => $billing_address,
'shipping_address' => $shipping_address
);
// Fill SNAP API parameter
$params = array(
'transaction_details' => $transaction_details,
'customer_details' => $customer_details,
'item_details' => $item_details,
);
try {
// Get Snap Payment Page URL
$paymentUrl = Snap::createTransaction($params)->redirect_url;
// Redirect to Snap Payment Page
header('Location: ' . $paymentUrl);
}
catch (\Exception $e) {
echo $e->getMessage();
}
function printExampleWarningMessage() {
if (strpos(Config::$serverKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your server key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$serverKey = \'<your server key>\';');
die();
}
}
@@ -0,0 +1,7 @@
<?php
$base = $_SERVER['REQUEST_URI'];
?>
<form action="<?php echo $base ?>checkout-process.php" method="GET">
<input type="submit" value="Pay with Snap Redirect">
</form>
@@ -0,0 +1,104 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
// Please refer to this docs for snap popup:
// https://docs.midtrans.com/en/snap/integration-guide?id=integration-steps-overview
namespace Midtrans;
require_once dirname(__FILE__) . '/../../Midtrans.php';
// Set Your server key
// can find in Merchant Portal -> Settings -> Access keys
Config::$serverKey = 'SB-Mid-server-RxyojFyK90t-HrWwtDeV0QCO';
Config::$clientKey = 'SB-Mid-client-NhMJk2e-7C27nGkV';
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
// Uncomment for production environment
// Config::$isProduction = true;
Config::$isSanitized = Config::$is3ds = true;
// Required
$transaction_details = array(
'order_id' => rand(),
'gross_amount' => 94000, // no decimal allowed for creditcard
);
// Optional
$item_details = array (
array(
'id' => 'a1',
'price' => 94000,
'quantity' => 1,
'name' => "Apple"
),
);
// Optional
$customer_details = array(
'first_name' => "Andri",
'last_name' => "Litani",
'email' => "andri@litani.com",
'phone' => "081122334455"
);
// Fill transaction details
$transaction = array(
'transaction_details' => $transaction_details,
'customer_details' => $customer_details,
'item_details' => $item_details,
);
$snap_token = '';
try {
$snap_token = Snap::getSnapToken($transaction);
}
catch (\Exception $e) {
echo $e->getMessage();
}
echo "snapToken = ".$snap_token;
function printExampleWarningMessage() {
if (strpos(Config::$serverKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your server key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$serverKey = \'<your server key>\';');
die();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PAYMENT </title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
</head>
<body>
<br>
<br>
<div class="container">
<div class="card">
<div class="card-body">
<p>Registrasi Berhasil, Selesaikan Pembayaran Sekarang</p>
<button id="pay-button" class="btn btn-primary">PILIH METODE PEMBAYARAN</button>
<!-- TODO: Remove ".sandbox" from script src URL for production environment. Also input your client key in "data-client-key" -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="<?php echo Config::$clientKey;?>"></script>
<script type="text/javascript">
document.getElementById('pay-button').onclick = function(){
// SnapToken acquired from previous step
snap.pay('<?php echo $snap_token?>');
};
</script>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
</body>
</html>
@@ -0,0 +1,150 @@
<?php
// This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely.
// Please refer to this docs for snap popup:
// https://docs.midtrans.com/en/snap/integration-guide?id=integration-steps-overview
namespace Midtrans;
require_once dirname(__FILE__) . '/../../Midtrans.php';
// Set Your server key
// can find in Merchant Portal -> Settings -> Access keys
Config::$serverKey = '<your server key>';
Config::$clientKey = '<your client key>';
// non-relevant function only used for demo/example purpose
printExampleWarningMessage();
// Uncomment for production environment
// Config::$isProduction = true;
// Enable sanitization
Config::$isSanitized = true;
// Enable 3D-Secure
Config::$is3ds = true;
// Uncomment for append and override notification URL
// Config::$appendNotifUrl = "https://example.com";
// Config::$overrideNotifUrl = "https://example.com";
// Required
$transaction_details = array(
'order_id' => rand(),
'gross_amount' => 94000, // no decimal allowed for creditcard
);
// Optional
$item1_details = array(
'id' => 'a1',
'price' => 18000,
'quantity' => 3,
'name' => "Apple"
);
// Optional
$item2_details = array(
'id' => 'a2',
'price' => 20000,
'quantity' => 2,
'name' => "Orange"
);
// Optional
$item_details = array ($item1_details, $item2_details);
// Optional
$billing_address = array(
'first_name' => "Andri",
'last_name' => "Litani",
'address' => "Mangga 20",
'city' => "Jakarta",
'postal_code' => "16602",
'phone' => "081122334455",
'country_code' => 'IDN'
);
// Optional
$shipping_address = array(
'first_name' => "Obet",
'last_name' => "Supriadi",
'address' => "Manggis 90",
'city' => "Jakarta",
'postal_code' => "16601",
'phone' => "08113366345",
'country_code' => 'IDN'
);
// Optional
$customer_details = array(
'first_name' => "Andri",
'last_name' => "Litani",
'email' => "andri@litani.com",
'phone' => "081122334455",
'billing_address' => $billing_address,
'shipping_address' => $shipping_address
);
// Optional, remove this to display all available payment methods
$enable_payments = array('credit_card','cimb_clicks','mandiri_clickpay','echannel');
// Fill transaction details
$transaction = array(
'enabled_payments' => $enable_payments,
'transaction_details' => $transaction_details,
'customer_details' => $customer_details,
'item_details' => $item_details,
);
$snap_token = '';
try {
$snap_token = Snap::getSnapToken($transaction);
}
catch (\Exception $e) {
echo $e->getMessage();
}
echo "snapToken = ".$snap_token;
function printExampleWarningMessage() {
if (strpos(Config::$serverKey, 'your ') != false ) {
echo "<code>";
echo "<h4>Please set your server key from sandbox</h4>";
echo "In file: " . __FILE__;
echo "<br>";
echo "<br>";
echo htmlspecialchars('Config::$serverKey = \'<your server key>\';');
die();
}
}
?>
<!DOCTYPE html>
<html>
<body>
<button id="pay-button">Pay!</button>
<pre><div id="result-json">JSON result will appear here after payment:<br></div></pre>
<!-- TODO: Remove ".sandbox" from script src URL for production environment. Also input your client key in "data-client-key" -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="<?php echo Config::$clientKey;?>"></script>
<script type="text/javascript">
document.getElementById('pay-button').onclick = function(){
// SnapToken acquired from previous step
snap.pay('<?php echo $snap_token?>', {
// Optional
onSuccess: function(result){
/* You may add your own js here, this is just example */ document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
},
// Optional
onPending: function(result){
/* You may add your own js here, this is just example */ document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
},
// Optional
onError: function(result){
/* You may add your own js here, this is just example */ document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
}
});
};
</script>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
<?php
$base = $_SERVER['REQUEST_URI'];
?>
<h3>Selected Items:</h3>
<ul>
<li>Jeruk 2 kg x @20000</li>
<li>Apel 3 kg x @18000</li>
</ul>
<h4>Total: Rp 94.000,00</h4>
<form action="<?php echo $base ?>checkout-process.php" method="POST">
<input type="hidden" name="amount" value="94000"/>
<input type="submit" value="Confirm">
</form>
+12
View File
@@ -0,0 +1,12 @@
> Warning: This note is for developer/maintainer of this package only
## Updating Package
- Make your changes
- Update `version` value on `composer.json`
- Update library version header on `ApiRequestor.php`
- Commit and push changes to Github master branch
- Create a [Github Release](https://github.com/Midtrans/midtrans-php/releases) with the target version
- Github Release and Master Branch is automatically synced to [the Packagist version](https://packagist.org/packages/midtrans/midtrans-php)
- Because of configured integration on Github & Packagist
- To edit integration config sign in with the Midtrans Packagist Account
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="test_bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Unit Test">
<directory>./tests/</directory>
<exclude>./tests/integration</exclude>
</testsuite>
<testsuite name="All">
<directory>./tests/</directory>
</testsuite>
<testsuite name="Integration Test">
<directory>./tests/integration</directory>
</testsuite>
</testsuites>
<php>
<env name="SERVER_KEY" value="SB-Mid-server-GwUP_WGbJPXsDzsNEBRs8IYA"/>
<env name="CLIENT_KEY" value="SB-Mid-client-61XuGAwQ8Bj8LxSS"/>
</php>
</phpunit>
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* Include test library if you are using composer
* Example: Psysh (debugging library similar to pry in Ruby)
*/
require_once dirname(__FILE__) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/Midtrans.php';
require_once dirname(__FILE__) . '/tests/Mt_Tests.php';
require_once dirname(__FILE__) . '/tests/utility/MtFixture.php';
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace Midtrans;
class MT_Tests
{
public static $stubHttp = false;
public static $stubHttpResponse;
public static $stubHttpStatus;
public static $lastHttpRequest;
public static function reset()
{
MT_Tests::$stubHttp = false;
MT_Tests::$stubHttpResponse = null;
MT_Tests::$lastHttpRequest = null;
}
public static function lastReqOptions()
{
$consts = array(
CURLOPT_URL => "URL",
CURLOPT_HTTPHEADER => "HTTPHEADER",
CURLOPT_RETURNTRANSFER => "RETURNTRANSFER",
CURLOPT_CAINFO => "CAINFO",
CURLOPT_POST => "POST",
CURLOPT_POSTFIELDS => "POSTFIELDS",
CURLOPT_PROXY => "PROXY"
);
$options = array();
foreach (MT_Tests::$lastHttpRequest["curl"] as $intValue => $value) {
$key = $consts[$intValue] ? $consts[$intValue] : $intValue;
$options[$key] = $value;
}
return $options;
}
}
@@ -0,0 +1,33 @@
<?php
namespace Midtrans;
class MidtransApiRequestorTest extends \PHPUnit_Framework_TestCase
{
public function testConfigOptionsOverrideCurlOptions()
{
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{ "status_code": "200" }';
Config::$curlOptions = array(
CURLOPT_HTTPHEADER => array( "User-Agent: testing lib" ),
CURLOPT_PROXY => "http://proxy.com"
);
$resp = ApiRequestor::post("http://proxy.com", "dummy", "");
$fields = MT_Tests::lastReqOptions();
$this->assertTrue(in_array("User-Agent: testing lib", $fields["HTTPHEADER"]));
$this->assertTrue(in_array('Content-Type: application/json', $fields["HTTPHEADER"]));
$this->assertEquals("http://proxy.com", $fields["PROXY"]);
}
public function tearDown()
{
MT_Tests::reset();
Config::$curlOptions = array();
}
}
@@ -0,0 +1,24 @@
<?php
namespace Midtrans;
class MidtransConfigTest extends \PHPUnit_Framework_TestCase
{
public function testReturnBaseUrl()
{
Config::$isProduction = false;
$this->assertEquals(
Config::getBaseUrl(),
Config::SANDBOX_BASE_URL
);
Config::$isProduction = true;
$this->assertEquals(Config::PRODUCTION_BASE_URL, Config::getBaseUrl());
}
public function tearDown()
{
Config::$isProduction = false;
}
}
@@ -0,0 +1,97 @@
<?php
namespace Midtrans;
class MidtransCoreApiTest extends \PHPUnit_Framework_TestCase
{
public function testCharge()
{
Config::$appendNotifUrl = "https://example.com";
Config::$overrideNotifUrl = "https://example.com";
Config::$paymentIdempotencyKey = "123456";
Config::$serverKey = "dummy";
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": 200,
"redirect_url": "http://host.com/pay"
}';
$params = array(
'transaction_details' => array(
'order_id' => "Order-111",
'gross_amount' => 10000,
)
);
$charge = CoreApi::charge($params);
$this->assertEquals($charge->status_code, "200");
$this->assertEquals(
MT_Tests::$lastHttpRequest["url"],
"https://api.sandbox.midtrans.com/v2/charge"
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals($fields["POST"], 1);
$this->assertEquals(
$fields["POSTFIELDS"],
'{"payment_type":"credit_card","transaction_details":{"order_id":"Order-111","gross_amount":10000}}'
);
$this->assertTrue(in_array('X-Append-Notification: https://example.com', $fields["HTTPHEADER"]));
$this->assertTrue(in_array('X-Override-Notification: https://example.com', $fields["HTTPHEADER"]));
$this->assertTrue(in_array('Idempotency-Key: 123456', $fields["HTTPHEADER"]));
}
public function testRealConnectWithInvalidKey()
{
Config::$serverKey = 'invalid-server-key';
$params = array(
'transaction_details' => array(
'order_id' => rand(),
'gross_amount' => 10000,
)
);
try {
$paymentUrl = CoreApi::charge($params);
} catch (\Exception $error) {
$this->assertContains("Midtrans API is returning API error. HTTP status code: 401", $error->getMessage());
}
}
public function testCapture()
{
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "200",
"status_message": "Success, Credit Card capture transaction is successful",
"transaction_id": "1ac1a089d-a587-40f1-a936-a7770667d6dd",
"order_id": "A27550",
"payment_type": "credit_card",
"transaction_time": "2014-08-25 10:20:54",
"transaction_status": "capture",
"fraud_status": "accept",
"masked_card": "481111-1114",
"bank": "bni",
"approval_code": "1408937217061",
"gross_amount": "55000.00"
}';
$capture = CoreApi::capture("A27550");
$this->assertEquals($capture->status_code, "200");
$this->assertEquals("https://api.sandbox.midtrans.com/v2/capture", MT_Tests::$lastHttpRequest["url"]);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertEquals('{"transaction_id":"A27550"}', $fields["POSTFIELDS"]);
}
public function tearDown()
{
MT_Tests::reset();
}
}
@@ -0,0 +1,48 @@
<?php
namespace Midtrans;
require_once dirname(__FILE__) . '/../Midtrans.php';
define(
'TEST_CAPTURE_JSON', '{
"status_code" : "200",
"status_message" : "Midtrans payment notification",
"transaction_id" : "826acc53-14e0-4ae7-95e2-845bf0311579",
"order_id" : "2014040745",
"payment_type" : "credit_card",
"transaction_time" : "2014-04-07 16:22:36",
"transaction_status" : "capture",
"fraud_status" : "accept",
"masked_card" : "411111-1111",
"gross_amount" : "2700"
}'
);
class MidtransNotificationTest extends \PHPUnit_Framework_TestCase
{
public function testCanWorkWithJSON()
{
$tmpfname = tempnam(sys_get_temp_dir(), "midtrans_test");
file_put_contents($tmpfname, TEST_CAPTURE_JSON);
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = TEST_CAPTURE_JSON;
Config::$serverKey = 'dummy';
$notif = new Notification($tmpfname);
$this->assertEquals("capture", $notif->transaction_status);
$this->assertEquals("credit_card", $notif->payment_type);
$this->assertEquals("2014040745", $notif->order_id);
$this->assertEquals("2700", $notif->gross_amount);
unlink($tmpfname);
}
public function tearDown()
{
MT_Tests::reset();
}
}
@@ -0,0 +1,82 @@
<?php
namespace Midtrans;
use Midtrans\utility\MtChargeFixture;
class MidtransSanitizerTest extends \PHPUnit_Framework_TestCase
{
public function testSanitizeWithoutOptionalRequest()
{
$params = MtChargeFixture::build('vtweb');
unset($params['customer_details']);
Sanitizer::jsonRequest($params);
$this->assertEquals(false, isset($params['customer_details']));
}
public function testSanitizeWithoutOptionalCustDetails()
{
$params = MtChargeFixture::build('vtweb');
unset($params['customer_details']['first_name']);
unset($params['customer_details']['last_name']);
unset($params['customer_details']['email']);
unset($params['customer_details']['billing_address']);
unset($params['customer_details']['shipping_address']);
Sanitizer::jsonRequest($params);
$this->assertEquals(false, isset($params['customer_details']['first_name']));
$this->assertEquals(false, isset($params['customer_details']['last_name']));
$this->assertEquals(false, isset($params['customer_details']['email']));
$this->assertEquals(false, isset($params['customer_details']['billing_address']));
$this->assertEquals(false, isset($params['customer_details']['shipping_address']));
}
public function testSanitizeWithoutOptionalInBillingAddress()
{
$params = MtChargeFixture::build('vtweb');
unset($params['customer_details']['billing_address']['first_name']);
unset($params['customer_details']['billing_address']['last_name']);
unset($params['customer_details']['billing_address']['phone']);
unset($params['customer_details']['billing_address']['address']);
unset($params['customer_details']['billing_address']['city']);
unset($params['customer_details']['billing_address']['postal_code']);
unset($params['customer_details']['billing_address']['country_code']);
Sanitizer::jsonRequest($params);
$this->assertEquals(false, isset($params['customer_details']['billing_address']['first_name']));
$this->assertEquals(false, isset($params['customer_details']['billing_address']['last_name']));
$this->assertEquals(false, isset($params['customer_details']['billing_address']['phone']));
$this->assertEquals(false, isset($params['customer_details']['billing_address']['address']));
$this->assertEquals(false, isset($params['customer_details']['billing_address']['city']));
$this->assertEquals(false, isset($params['customer_details']['billing_address']['postal_code']));
$this->assertEquals(false, isset($params['customer_details']['billing_address']['country_code']));
}
public function testSanitizeWithoutOptionalInShippingAddress()
{
$params = MtChargeFixture::build('vtweb');
unset($params['customer_details']['shipping_address']['first_name']);
unset($params['customer_details']['shipping_address']['last_name']);
unset($params['customer_details']['shipping_address']['phone']);
unset($params['customer_details']['shipping_address']['address']);
unset($params['customer_details']['shipping_address']['city']);
unset($params['customer_details']['shipping_address']['postal_code']);
unset($params['customer_details']['shipping_address']['country_code']);
Sanitizer::jsonRequest($params);
$this->assertEquals(false, isset($params['customer_details']['shipping_address']['first_name']));
$this->assertEquals(false, isset($params['customer_details']['shipping_address']['last_name']));
$this->assertEquals(false, isset($params['customer_details']['shipping_address']['phone']));
$this->assertEquals(false, isset($params['customer_details']['shipping_address']['address']));
$this->assertEquals(false, isset($params['customer_details']['shipping_address']['city']));
$this->assertEquals(false, isset($params['customer_details']['shipping_address']['postal_code']));
$this->assertEquals(false, isset($params['customer_details']['shipping_address']['country_code']));
}
}
@@ -0,0 +1,34 @@
<?php
namespace Midtrans;
class MidtransSnapApiRequestorTest extends \PHPUnit_Framework_TestCase
{
public function testConfigOptionsOverrideCurlOptions()
{
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{ "status_code": "200" }';
MT_Tests::$stubHttpStatus = array('http_code' => 201);
Config::$curlOptions = array(
CURLOPT_HTTPHEADER => array( "User-Agent: testing lib" ),
CURLOPT_PROXY => "http://proxy.com"
);
$resp = ApiRequestor::post("http://example.com", "dummy", "");
$fields = MT_Tests::lastReqOptions();
$this->assertTrue(in_array("User-Agent: testing lib", $fields["HTTPHEADER"]));
$this->assertTrue(in_array('Content-Type: application/json', $fields["HTTPHEADER"]));
$this->assertEquals("http://proxy.com", $fields["PROXY"]);
}
public function tearDown()
{
MT_Tests::reset();
Config::$curlOptions = array();
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace Midtrans;
class MidtransSnapTest extends \PHPUnit_Framework_TestCase
{
public function testGetSnapToken()
{
Config::$serverKey = 'MyVerySecretKey';
Config::$appendNotifUrl = "https://example.com";
Config::$overrideNotifUrl = "https://example.com";
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{ "token": "abcdefghijklmnopqrstuvwxyz" }';
MT_Tests::$stubHttpStatus = array('http_code' => 201);
$params = array(
'transaction_details' => array(
'order_id' => "Order-111",
'gross_amount' => 10000,
)
);
$tokenId = Snap::getSnapToken($params);
$this->assertEquals("abcdefghijklmnopqrstuvwxyz", $tokenId);
$this->assertEquals(
"https://app.sandbox.midtrans.com/snap/v1/transactions",
MT_Tests::$lastHttpRequest["url"]
);
$this->assertEquals(
'MyVerySecretKey',
MT_Tests::$lastHttpRequest["server_key"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertTrue(in_array('X-Append-Notification: https://example.com', $fields["HTTPHEADER"]));
$this->assertTrue(in_array('X-Override-Notification: https://example.com', $fields["HTTPHEADER"]));
$this->assertEquals(
$fields["POSTFIELDS"],
'{"credit_card":{"secure":false},' .
'"transaction_details":{"order_id":"Order-111","gross_amount":10000}}'
);
}
public function testGrossAmount()
{
$params = array(
'transaction_details' => array(
'order_id' => rand()
),
'item_details' => array( array( 'price' => 10000, 'quantity' => 5 ) )
);
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{ "token": "abcdefghijklmnopqrstuvwxyz" }';
MT_Tests::$stubHttpStatus = array('http_code' => 201);
$tokenId = Snap::getSnapToken($params);
$this->assertEquals(
50000,
MT_Tests::$lastHttpRequest['data_hash']['transaction_details']['gross_amount']
);
}
public function testOverrideParams()
{
$params = array(
'echannel' => array(
'bill_info1' => 'bill_value1'
)
);
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{ "token": "abcdefghijklmnopqrstuvwxyz" }';
MT_Tests::$stubHttpStatus = array('http_code' => 201);
$tokenId = Snap::getSnapToken($params);
$this->assertEquals(
array('bill_info1' => 'bill_value1'),
MT_Tests::$lastHttpRequest['data_hash']['echannel']
);
}
public function testRealConnect()
{
$params = array(
'transaction_details' => array(
'order_id' => rand(),
'gross_amount' => 10000,
)
);
try {
$tokenId = Snap::getSnapToken($params);
} catch (\Exception $error) {
$errorHappen = true;
$this->assertContains(
"authorized",
$error->getMessage()
);
}
$this->assertTrue($errorHappen);
}
public function tearDown()
{
MT_Tests::reset();
}
}
@@ -0,0 +1,284 @@
<?php
namespace Midtrans;
class MidtransTransactionTest extends \PHPUnit_Framework_TestCase
{
public function testStatus()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "200",
"status_message": "Success, transaction found",
"transaction_id": "e3b8c383-55b4-4223-bd77-15c48c0245ca",
"masked_card": "481111-1114",
"order_id": "Order-111",
"payment_type": "credit_card",
"transaction_time": "2014-11-21 13:07:50",
"transaction_status": "settlement",
"fraud_status": "accept",
"approval_code": "1416550071152",
"signature_key": "4ef8218aad5b64bae2ec9d6b0f0a0b059b88bd...",
"bank": "mandiri",
"gross_amount": "10000.00"
}';
$status = Transaction::status("Order-111");
$this->assertEquals("200", $status->status_code);
$this->assertEquals("Order-111", $status->order_id);
$this->assertEquals("1416550071152", $status->approval_code);
$this->assertEquals(
"https://api.sandbox.midtrans.com/v2/Order-111/status",
MT_Tests::$lastHttpRequest["url"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertFalse(isset($fields['POST']));
$this->assertFalse(isset($fields['POSTFIELDS']));
}
public function testFailureStatus()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "404",
"status_message": "The requested resource is not found"
}';
try {
$status = Transaction::status("Order-111");
} catch (\Exception $error) {
$errorHappen = true;
$this->assertEquals(404, $error->getCode());
}
$this->assertTrue($errorHappen);
MT_Tests::reset();
}
public function testRealStatus()
{
Config::$serverKey = 'MyVerySecretKey';
try {
$status = Transaction::status("Order-111");
} catch (\Exception $error) {
$errorHappen = true;
$this->assertContains("Midtrans API is returning API error. HTTP status code: 401", $error->getMessage());
}
$this->assertTrue($errorHappen);
}
public function testApprove()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "200",
"status_message": "Success, transaction is approved",
"transaction_id": "2af158d4-b82e-46ac-808b-be19aaa96ce3",
"masked_card": "451111-1117",
"order_id": "Order-111",
"payment_type": "credit_card",
"transaction_time": "2014-11-27 10:05:10",
"transaction_status": "capture",
"fraud_status": "accept",
"approval_code": "1416550071152",
"bank": "bni",
"gross_amount": "10000.00"
}';
$approve = Transaction::approve("Order-111");
$this->assertEquals("200", $approve);
$this->assertEquals(
"https://api.sandbox.midtrans.com/v2/Order-111/approve",
MT_Tests::$lastHttpRequest["url"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertEquals(null, $fields["POSTFIELDS"]);
}
public function testCancel()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "200",
"status_message": "Success, transaction is canceled",
"transaction_id": "2af158d4-b82e-46ac-808b-be19aaa96ce3",
"masked_card": "451111-1117",
"order_id": "Order-111",
"payment_type": "credit_card",
"transaction_time": "2014-11-27 10:05:10",
"transaction_status": "cancel",
"fraud_status": "accept",
"approval_code": "1416550071152",
"bank": "bni",
"gross_amount": "10000.00"
}';
$cancel = Transaction::cancel("Order-111");
$this->assertEquals("200", $cancel);
$this->assertEquals(
"https://api.sandbox.midtrans.com/v2/Order-111/cancel",
MT_Tests::$lastHttpRequest["url"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertEquals(null, $fields["POSTFIELDS"]);
}
public function testExpire()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "407",
"status_message": "Success, transaction has expired",
"transaction_id": "2af158d4-b82e-46ac-808b-be19aaa96ce3",
"order_id": "Order-111",
"payment_type": "echannel",
"transaction_time": "2014-11-27 10:05:10",
"transaction_status": "expire",
"gross_amount": "10000.00"
}';
$expire = Transaction::expire("Order-111");
$this->assertEquals("407", $expire->status_code);
$this->assertEquals("Success, transaction has expired", $expire->status_message);
$this->assertEquals(
"https://api.sandbox.midtrans.com/v2/Order-111/expire",
MT_Tests::$lastHttpRequest["url"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertEquals(null, $fields["POSTFIELDS"]);
}
public function testRefund()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "200",
"status_message": "Success, refund request is approved",
"transaction_id": "447e846a-403e-47db-a5da-d7f3f06375d6",
"order_id": "Order-111",
"payment_type": "credit_card",
"transaction_time": "2015-06-15 13:36:24",
"transaction_status": "refund",
"gross_amount": "10000.00",
"refund_chargeback_id": 1,
"refund_amount": "10000.00",
"refund_key": "reference1"
}';
$params = array(
'refund_key' => 'reference1',
'amount' => 10000,
'reason' => 'Item out of stock'
);
$refund = Transaction::refund("Order-111",$params);
$this->assertEquals("200", $refund->status_code);
$this->assertEquals(
"https://api.sandbox.midtrans.com/v2/Order-111/refund",
MT_Tests::$lastHttpRequest["url"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertEquals(
'{"refund_key":"reference1","amount":10000,"reason":"Item out of stock"}',
$fields["POSTFIELDS"]);
}
public function testRefundDirect()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code": "200",
"status_message": "Success, refund request is approved",
"transaction_id": "447e846a-403e-47db-a5da-d7f3f06375d6",
"order_id": "Order-111",
"payment_type": "credit_card",
"transaction_time": "2015-06-15 13:36:24",
"transaction_status": "refund",
"gross_amount": "10000.00",
"refund_chargeback_id": 1,
"refund_amount": "10000.00",
"refund_key": "reference1"
}';
$params = array(
'refund_key' => 'reference1',
'amount' => 10000,
'reason' => 'Item out of stock'
);
$refund = Transaction::refundDirect("Order-111", $params);
$this->assertEquals("200", $refund->status_code);
$this->assertEquals(
"https://api.sandbox.midtrans.com/v2/Order-111/refund/online/direct",
MT_Tests::$lastHttpRequest["url"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertEquals(
'{"refund_key":"reference1","amount":10000,"reason":"Item out of stock"}',
$fields["POSTFIELDS"]);
}
public function testDeny()
{
Config::$serverKey = 'MyVerySecretKey';
MT_Tests::$stubHttp = true;
MT_Tests::$stubHttpResponse = '{
"status_code" : "200",
"status_message" : "Success, transaction is denied",
"transaction_id" : "ca297170-be4c-45ed-9dc9-be5ba99d30ee",
"masked_card" : "451111-1117",
"order_id" : "Order-111",
"payment_type" : "credit_card",
"transaction_time" : "2014-10-31 14:46:44",
"transaction_status" : "deny",
"fraud_status" : "deny",
"bank" : "bni",
"gross_amount" : "30000.00"
}';
$deny = Transaction::deny("Order-111");
$this->assertEquals("200", $deny->status_code);
$this->assertEquals(
"https://api.sandbox.midtrans.com/v2/Order-111/deny",
MT_Tests::$lastHttpRequest["url"]
);
$fields = MT_Tests::lastReqOptions();
$this->assertEquals(1, $fields["POST"]);
$this->assertEquals(null, $fields["POSTFIELDS"]);
}
public function tearDown()
{
MT_Tests::reset();
}
}
@@ -0,0 +1,282 @@
<?php
namespace Midtrans\integration;
use Midtrans\CoreApi;
use Midtrans\utility\MtChargeFixture;
require_once 'IntegrationTest.php';
class CoreApiIntegrationTest extends IntegrationTest
{
private $payment_type;
private $charge_params;
private $charge_response;
public function prepareChargeParams($payment_type, $payment_data = null)
{
$this->payment_type = $payment_type;
$this->charge_params = MtChargeFixture::build($payment_type, $payment_data);
}
public function testCardRegister()
{
$this->charge_response = CoreApi::cardRegister("4811111111111114", "12", "2026");
$this->assertEquals('200', $this->charge_response->status_code);
}
public function testCardToken()
{
$this->charge_response = CoreApi::cardToken("4811111111111114", "12", "2026", "123");
$this->assertEquals('200', $this->charge_response->status_code);
}
public function testCardPointInquiry()
{
$this->charge_response = CoreApi::cardToken("4617006959746656", "12", "2026", "123");
$cardPointResponse = CoreApi::cardPointInquiry($this->charge_response->token_id);
$this->assertEquals('200', $cardPointResponse->status_code);
}
public function testChargeCimbClicks()
{
$this->prepareChargeParams(
'cimb_clicks',
array(
"description" => "Item Descriptions",
)
);
$this->charge_response = CoreApi::charge($this->charge_params);
$this->assertEquals('pending', $this->charge_response->transaction_status);
$this->assertTrue(isset($this->charge_response->redirect_url));
}
public function testChargePermataVa()
{
$this->prepareChargeParams(
'bank_transfer',
array(
"bank" => "permata",
)
);
$this->charge_response = CoreApi::charge($this->charge_params);
$this->assertEquals('pending', $this->charge_response->transaction_status);
$this->assertTrue(isset($this->charge_response->permata_va_number));
}
public function testChargeEPayBri()
{
$this->prepareChargeParams('bri_epay');
$this->charge_response = CoreApi::charge($this->charge_params);
$this->assertEquals('pending', $this->charge_response->transaction_status);
$this->assertTrue(isset($this->charge_response->redirect_url));
}
public function testChargeMandiriBillPayment()
{
$this->prepareChargeParams(
'echannel',
array(
"bill_info1" => "Payment for:",
"bill_info2" => "Item descriptions",
)
);
$this->charge_response = CoreApi::charge($this->charge_params);
$this->assertEquals('pending', $this->charge_response->transaction_status);
}
public function testChargeIndomaret()
{
$this->prepareChargeParams(
'cstore',
array(
"store" => "indomaret",
"message" => "Item descriptions",
)
);
$this->charge_response = CoreApi::charge($this->charge_params);
$this->assertEquals('pending', $this->charge_response->transaction_status);
$this->assertTrue(isset($this->charge_response->payment_code));
}
public function testChargeGopay()
{
$this->prepareChargeParams(
'gopay',
array(
"enable_callback" => true,
"callback_url" => "someapps://callback",
)
);
$this->charge_response = CoreApi::charge($this->charge_params);
$this->assertEquals('201', $this->charge_response->status_code);
$this->assertEquals('pending', $this->charge_response->transaction_status);
}
public function testCreateSubscription()
{
$param = array(
"name" => "Monthly_2021",
"amount" => "10000",
"currency" => "IDR",
"payment_type" => "credit_card",
"token" => "dummy",
"schedule" => array(
"interval" => 1,
"interval_unit" => "month",
"max_interval" => "12",
"start_time" => "2022-08-17 10:00:01 +0700"
),
"metadata" => array(
"description" => "Recurring payment for user a"
),
"customer_details" => array(
"first_name" => "John",
"last_name" => "Doe",
"email" => "johndoe@gmail.com",
"phone_number" => "+628987654321"
)
);
$this->charge_response = CoreApi::createSubscription($param);
$this->assertEquals('active', $this->charge_response->status);
$subscription_id = $this->charge_response->id;
return $subscription_id;
}
/**
* @depends testCreateSubscription
*/
public function testGetSubscription($subscription_id)
{
$this->charge_response = CoreApi::getSubscription($subscription_id);
$this->assertEquals('active', $this->charge_response->status);
}
/**
* @depends testCreateSubscription
*/
public function testDisableSubscription($subscription_id)
{
$this->charge_response = CoreApi::disableSubscription($subscription_id);
$this->assertContains('Subscription is updated.', $this->charge_response->status_message);
}
/**
* @depends testCreateSubscription
*/
public function testEnableSubscription($subscription_id)
{
$this->charge_response = CoreApi::enableSubscription($subscription_id);
$this->assertContains('Subscription is updated.', $this->charge_response->status_message);
}
/**
* @depends testCreateSubscription
*/
public function testUpdateSubscription($subscription_id)
{
$param = array(
"name" => "Monthly_2021",
"amount" => "25000",
"currency" => "IDR",
"token" => "dummy",
"schedule" => array(
"interval" => 1
)
);
$this->charge_response = CoreApi::updateSubscription($subscription_id, $param);
$this->assertContains('Subscription is updated.', $this->charge_response->status_message);
}
public function testGetSubscriptionWithNonExistAccount()
{
try {
$this->charge_response = CoreApi::getSubscription("dummy");
} catch (\Exception $e) {
$this->assertContains("Midtrans API is returning API error.", $e->getMessage());
}
}
public function testDisableSubscriptionWithNonExistAccount()
{
try {
$this->charge_response = CoreApi::disableSubscription("dummy");
} catch (\Exception $e) {
$this->assertContains("Midtrans API is returning API error.", $e->getMessage());
}
}
public function testEnableSubscriptionWithNonExistAccount()
{
try {
$this->charge_response = CoreApi::enableSubscription("dummy");
} catch (\Exception $e) {
$this->assertContains("Midtrans API is returning API error.", $e->getMessage());
}
}
public function testUpdateSubscriptionWithNonExistAccount()
{
$param = array(
"name" => "Monthly_2021",
"amount" => "25000",
"currency" => "IDR",
"token" => "dummy",
"schedule" => array(
"interval" => 1
)
);
try {
$this->charge_response = CoreApi::updateSubscription("dummy", $param);
} catch (\Exception $e) {
$this->assertContains("Midtrans API is returning API error.", $e->getMessage());
}
}
public function testCreatePayAccount()
{
$params = array(
"payment_type" => "gopay",
"gopay_partner" => array(
"phone_number" => 874567446788,
"redirect_url" => "https://www.google.com"
)
);
$this->charge_response = CoreApi::linkPaymentAccount($params);
$this->assertEquals('201', $this->charge_response->status_code);
$this->assertEquals('PENDING', $this->charge_response->account_status);
$account_id = $this->charge_response->account_id;
return $account_id;
}
/**
* @depends testCreatePayAccount
*/
public function testGetPaymentAccount($account_id)
{
$this->charge_response = CoreApi::getPaymentAccount($account_id);
$this->assertEquals('201', $this->charge_response->status_code);
$this->assertEquals('PENDING', $this->charge_response->account_status);
}
public function testGetPaymentAccountWithNonExistAccount()
{
try {
$this->charge_response = CoreApi::getPaymentAccount("dummy");
} catch (\Exception $e) {
$this->assertContains("Midtrans API is returning API error.", $e->getMessage());
}
}
public function testUnlinkPaymentAccountWithNonExistAccount()
{
try {
$this->charge_response = CoreApi::unlinkPaymentAccount("dummy");
} catch (\Exception $e) {
$this->assertContains("Account doesn't exist.", $e->getMessage());
}
}
}
@@ -0,0 +1,21 @@
<?php
namespace Midtrans\integration;
use Midtrans\Config;
abstract class IntegrationTest extends \PHPUnit_Framework_TestCase
{
public static function setUpBeforeClass()
{
Config::$serverKey = getenv('SERVER_KEY');
Config::$clientKey = getenv('CLIENT_KEY');
Config::$isProduction = false;
}
public function tearDown()
{
// One second interval to avoid throttle
sleep(1);
}
}
@@ -0,0 +1,69 @@
<?php
namespace Midtrans\integration;
use Midtrans\CoreApi;
use Midtrans\Notification;
use Midtrans\Transaction;
use Midtrans\utility\MtChargeFixture;
require_once 'IntegrationTest.php';
class NotificationIntegrationTest extends IntegrationTest
{
private $status_response;
public function setUp()
{
$charge_params = MtChargeFixture::build('bri_epay');
$charge_response = CoreApi::charge($charge_params);
$this->status_response = Transaction::status($charge_response->transaction_id);
}
public function testValidBriEPayNotification()
{
// Assume status response is similar to HTTP(s) notification
$tmpfname = tempnam(sys_get_temp_dir(), "test");
file_put_contents($tmpfname, json_encode($this->status_response));
$notif = new Notification($tmpfname);
$this->assertEquals($notif->status_code, "201");
$this->assertEquals($notif->transaction_status, "pending");
$this->assertEquals($notif->payment_type, "bri_epay");
$this->assertEquals($notif->order_id, $this->status_response->order_id);
$this->assertEquals($notif->transaction_id, $this->status_response->transaction_id);
$this->assertEquals($notif->gross_amount, $this->status_response->gross_amount);
unlink($tmpfname);
}
public function testFraudulentBriEPayNotification()
{
/*
As a fraudster, I want the merchant to think that I finished e-Pay BRI payment
1. alter transaction status, from pending to settlement
2. alter status code, from 201 to 200
*/
$this->status_response->transaction_status = "settlement";
$this->status_response->status_code = "200";
$tmpfname = tempnam(sys_get_temp_dir(), "test");
file_put_contents($tmpfname, json_encode($this->status_response));
$notif = new Notification($tmpfname);
/*
Merchant should not be tricked... thanks to Get Status API
*/
$this->assertEquals("201", $notif->status_code);
$this->assertEquals("pending", $notif->transaction_status);
$this->assertEquals("bri_epay", $notif->payment_type);
$this->assertEquals($this->status_response->order_id, $notif->order_id);
$this->assertEquals($this->status_response->transaction_id, $notif->transaction_id);
$this->assertEquals($this->status_response->gross_amount, $notif->gross_amount);
unlink($tmpfname);
}
}
@@ -0,0 +1,19 @@
<?php
namespace Midtrans\integration;
use Midtrans\Snap;
use Midtrans\utility\MtChargeFixture;
require_once 'IntegrationTest.php';
class SnapIntegrationTest extends IntegrationTest
{
public function testSnapToken()
{
$charge_params = MtChargeFixture::build('vtweb');
$token_id = Snap::getSnapToken($charge_params);
$this->assertTrue(isset($token_id));
}
}
@@ -0,0 +1,68 @@
<?php
namespace Midtrans\integration;
use Midtrans\CoreApi;
use Midtrans\Transaction;
use Midtrans\utility\MtChargeFixture;
require_once 'IntegrationTest.php';
class TransactionIntegrationTest extends IntegrationTest
{
public function testStatusPermataVa()
{
$charge_params = MtChargeFixture::build(
'bank_transfer',
array(
"bank" => "permata",
)
);
$charge_response = CoreApi::charge($charge_params);
$status_response = Transaction::status($charge_response->transaction_id);
$this->assertEquals('201', $status_response->status_code);
$this->assertEquals('pending', $status_response->transaction_status);
$this->assertEquals($charge_params['transaction_details']['order_id'], $status_response->order_id);
$this->assertEquals($charge_params['transaction_details']['gross_amount'], $status_response->gross_amount);
$this->assertEquals($charge_response->transaction_id, $status_response->transaction_id);
$this->assertEquals($charge_response->transaction_time, $status_response->transaction_time);
$this->assertEquals('Success, transaction is found', $status_response->status_message);
$this->assertTrue(isset($status_response->signature_key));
}
public function testCancelPermataVa()
{
$charge_params = MtChargeFixture::build(
'bank_transfer',
array(
"bank" => "permata",
)
);
$charge_response = CoreApi::charge($charge_params);
$cancel_status_code = Transaction::cancel($charge_response->transaction_id);
$this->assertEquals('200', $cancel_status_code);
}
public function testExpirePermataVa()
{
$charge_params = MtChargeFixture::build(
'bank_transfer',
array(
"bank" => "permata",
)
);
$charge_response = CoreApi::charge($charge_params);
$expire = Transaction::expire($charge_response->transaction_id);
$this->assertEquals('407', $expire->status_code);
// Verify transaction via API
$txn_status = Transaction::status($charge_response->transaction_id);
$this->assertEquals("407", $txn_status->status_code);
$this->assertEquals("expire", $txn_status->transaction_status);
}
}
@@ -0,0 +1,31 @@
<?php
namespace Midtrans\utility;
class MtFixture
{
protected static function readFixture($filename)
{
$charge_json = file_get_contents(__DIR__ . '/fixture/' . $filename);
$charge_template_params = json_decode($charge_json, true);
$charge_template_params['transaction_details']['order_id'] = rand();
return $charge_template_params;
}
}
class MtChargeFixture extends MtFixture
{
public static function build($payment_type, $payment_data = null)
{
$charge_params = self::readFixture('mt_charge.json');
if (!is_null($payment_type)) {
$charge_params['payment_type'] = $payment_type;
}
if (!is_null($payment_data)) {
$charge_params[$payment_type] = $payment_data;
}
return $charge_params;
}
}
@@ -0,0 +1,45 @@
{
"transaction_details": {
"order_id": "C17550",
"gross_amount": 145000
},
"item_details" : [
{
"id": "a1",
"price": 50000,
"quantity": 2,
"name": "Apel"
},
{
"id": "a2",
"price": 45000,
"quantity": 1,
"name": "Jeruk"
}
],
"customer_details": {
"first_name": "Andri",
"last_name": "Litani",
"email": "andri@litani.com",
"phone": "081122334455",
"billing_address": {
"first_name": "Andri",
"last_name": "Litani",
"address": "Mangga 20",
"city": "Jakarta",
"postal_code": "16602",
"phone": "081122334455",
"country_code": "IDN"
},
"shipping_address": {
"first_name": "Obet",
"last_name": "Supriadi",
"address": "Manggis 90",
"city": "Jakarta",
"postal_code": "16601",
"phone": "08113366345",
"country_code": "IDN"
}
}
}