diff --git a/.env.sample b/.env.sample index ff79a32..13a80bb 100644 --- a/.env.sample +++ b/.env.sample @@ -12,5 +12,9 @@ REDIRECT_URL=https:// ## Redirect URI when the transaction fails ERROR_REDIRECT_URL=https:// +## Personal Access Token if you use $client->getTransactionDetail(), this token should be set. +## See https://app.tendopay.ph/merchants/api-settings +MERCHANT_PERSONAL_ACCESS_TOKEN= + ## If you have own sandbox #SANDBOX_HOST_URL=https:// diff --git a/samples/notify.php b/samples/notify.php new file mode 100644 index 0000000..d73568e --- /dev/null +++ b/samples/notify.php @@ -0,0 +1,57 @@ +'; + array_map('print_r', func_get_args()); + echo ''; +} + +use TendoPay\SDK\Constants; +use TendoPay\SDK\Models\NotifyRequest; +use TendoPay\SDK\TendoPayClient; + + +$client = new TendoPayClient(); +$client->enableSandBox(); + +try { + $notifyRequest = new NotifyRequest($_REQUEST); + $transaction = $client->getTransactionDetail($notifyRequest->getTransactionNumber()); + + $merchant_order_id = $transaction->getMerchantOrderId(); + $status = $transaction->getStatus(); + $amount = $transaction->getAmount(); + + // dump(compact('merchant_order_id', 'status', 'amount')); + // Search Merchant side transaction by $transaction->getMerchantOrderId() + // Check if the transaction is already processed + // The process should stop here if this transaction is already done. + // return 200 if this is a duplicated notification + + + switch ($status) { + case Constants::PURCHASE_TRANSACTION_SUCCESS: + // The transaction is successfully completed + // Do merchant job here + break; + case Constants::PURCHASE_TRANSACTION_FAILURE: + // The transaction is unsuccessfully completed. + // Do merchant job here + break; + case Constants::CANCEL_TRANSACTION_SUCCESS: + // the previous transaction is successfully cancelled + // Do merchant job here + break; + } + + // After all merchant side process done, return 200 + http_response_code(200); +} catch (Exception $e) { + // other wise return error + dump($e->getMessage()); + http_response_code(500); +} + + diff --git a/src/Constants.php b/src/Constants.php index 285f6db..fa9c6f6 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -24,11 +24,16 @@ class Constants public const AUTHORIZATION_ENDPOINT_URI = 'payments/api/v1/authTokenRequest'; public const DESCRIPTION_ENDPOINT_URI = 'payments/api/v1/paymentDescription'; public const BEARER_TOKEN_ENDPOINT_URI = 'oauth/token'; - public const ORDER_STATUS_TRANSITION_ENDPOINT_URL = 'payments/api/v1/orderUpdate'; + public const ORDER_STATUS_TRANSITION_ENDPOINT_URI = 'payments/api/v1/orderUpdate'; + /** + * Gets the transaction detail endpoint uri + */ + public const TRANSACTION_DETAIL_ENDPOINT_URI = '/merchants/api/v1/transactions/{transactionNumber}'; public const TENDOPAY_ICON = 'https://s3.ca-central-1.amazonaws.com/candydigital/images/tendopay/tp-icon-32x32.png'; public const TENDOPAY_FAQ = 'https://tendopay.ph/page-faq.html'; + /** * Below public constant names are used as keys of data send to or received from TP API */ @@ -53,6 +58,37 @@ class Constants public const STATUS_SUCCESS = 'success'; public const STATUS_FAILURE = 'failure'; + /** + * Notification Parameters + */ + public const TRANSACTION_STATUS = 'status'; + public const NOTIFIED_AT = 'notified_at'; + public const MERCHANT_ID = 'merchant_id'; + public const MERCHANT_ORDER_ID = 'merchant_order_id'; + public const AMOUNT = 'amount'; + public const CREATED_AT = 'created_at'; + + + /** + * Purchase Transaction successfully completed + */ + public const PURCHASE_TRANSACTION_SUCCESS = 'PTOK'; + + /** + * Purchase Transaction not successfully completed + */ + public const PURCHASE_TRANSACTION_FAILURE = 'PTNG'; + + /** + * Purchase Transaction has canceled + */ + public const PURCHASE_TRANSACTION_CANCELED = 'PTCA'; + + /** + * Cancel previous purchase transaction successfully completed + */ + public const CANCEL_TRANSACTION_SUCCESS = 'CTOK'; + /** * Below public constants are the keys of description object that is being sent during request to Description Endpoint */ @@ -153,4 +189,17 @@ private static function is_sandbox_enabled(): bool return (bool)getenv('TENDOPAY_SANDBOX_ENABLED', true); } + /** + * Gets the transaction detail endpoint uri + * @param string $transactionNumber + * @return string + */ + public static function getTransactionDetailEndpointURI($transactionNumber): string + { + return str_replace( + '{transactionNumber}', + $transactionNumber, + self::TRANSACTION_DETAIL_ENDPOINT_URI + ); + } } diff --git a/src/Models/NotifyRequest.php b/src/Models/NotifyRequest.php new file mode 100644 index 0000000..7529b11 --- /dev/null +++ b/src/Models/NotifyRequest.php @@ -0,0 +1,82 @@ +transactionNumber = $request[Constants::TRANSACTION_NO_PARAM] ?? ''; + $this->status = $request[Constants::TRANSACTION_STATUS] ?? ''; + $this->notifiedAt = $request[Constants::NOTIFIED_AT] ?? ''; + + if (!$this->transactionNumber || + !$this->status || + !$this->notifiedAt) { + throw new TendoPayParameterException(sprintf('%s, %s, %s are required.', + Constants::TRANSACTION_NO_PARAM, + Constants::TRANSACTION_STATUS, + Constants::NOTIFIED_AT + )); + } + } + + /** + * @return mixed|string + */ + public function getTransactionNumber() + { + return $this->transactionNumber; + } + + /** + * @return mixed|string + */ + public function getStatus() + { + return $this->status; + } + + /** + * @return mixed|string + */ + public function getNotifiedAt() + { + return $this->notifiedAt; + } +} diff --git a/src/Models/Transaction.php b/src/Models/Transaction.php new file mode 100644 index 0000000..22d266b --- /dev/null +++ b/src/Models/Transaction.php @@ -0,0 +1,98 @@ +merchantId = $response[Constants::MERCHANT_ID] ?? null; + $this->merchantOrderId = $response[Constants::MERCHANT_ORDER_ID] ?? null; + $this->amount = $response[Constants::AMOUNT] ?? null; + $this->transactionNumber = $response[Constants::TRANSACTION_NO_PARAM] ?? null; + $this->status = $response[Constants::TRANSACTION_STATUS] ?? null; + $this->createdAt = $response[Constants::CREATED_AT] ?? null; + + if (!$this->transactionNumber || !$this->merchantOrderId) { + throw new TendoPayParameterException(sprintf('%s, %s cannot be null', + Constants::TRANSACTION_NO_PARAM, + Constants::MERCHANT_ORDER_ID + )); + } + } + + + /** + * @return null + */ + public function getMerchantId() + { + return $this->merchantId; + } + + /** + * @return null + */ + public function getMerchantOrderId() + { + return $this->merchantOrderId; + } + + /** + * @return null + */ + public function getAmount() + { + return $this->amount; + } + + /** + * @return null + */ + public function getTransactionNumber() + { + return $this->transactionNumber; + } + + /** + * @return null + */ + public function getCreatedAt() + { + return $this->createdAt; + } + + /** + * @return null + */ + public function getStatus() + { + return $this->status; + } + +} diff --git a/src/TendoPayClient.php b/src/TendoPayClient.php index 16f7801..22e1f9f 100644 --- a/src/TendoPayClient.php +++ b/src/TendoPayClient.php @@ -3,12 +3,16 @@ namespace TendoPay\SDK; use Dotenv\Dotenv; +use Exception; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; +use InvalidArgumentException; use TendoPay\SDK\Exception\TendoPayConnectionException; use TendoPay\SDK\Exception\VerifyTransactionException; use TendoPay\SDK\Models\AccessToken; use TendoPay\SDK\Models\Payment; +use TendoPay\SDK\Models\PurchaseTransaction; +use TendoPay\SDK\Models\Transaction; use TendoPay\SDK\Models\VerifyTransactionRequest; use TendoPay\SDK\Models\VerifyTransactionResponse; use TendoPay\SDK\Traits\TendoPayHelper; @@ -135,13 +139,18 @@ protected function request($method, $requestURI, $params, $headers = []): string } /** + * @param bool $usePersonalAccessToken * @return array * @throws TendoPayConnectionException */ - protected function getAuthorizationHeader(): array + protected function getAuthorizationHeader($usePersonalAccessToken = false): array { + $accessToken = $usePersonalAccessToken ? + $this->getPersonalAccessToken() : + $this->getAccessToken(); + return [ - 'Authorization' => 'Bearer ' . $this->getAccessToken(), + 'Authorization' => 'Bearer ' . $accessToken, ]; } @@ -171,6 +180,19 @@ protected function getAccessToken(): string return $this->accessToken->getToken(); } + /** + * Return Personal Access Token + * @return string + */ + protected function getPersonalAccessToken(): string + { + $token = (string)getenv('MERCHANT_PERSONAL_ACCESS_TOKEN', true); + if (!$token) { + throw new InvalidArgumentException('MERCHANT_PERSONAL_ACCESS_TOKEN does not exists'); + } + return $token; + } + /** * @param $params * @return mixed|string @@ -333,6 +355,23 @@ public static function isCallbackRequest($request): bool ); } + /** + * Retrieve Transaction Details with the transactionNumber + * @param $transactionNumber + * @return Transaction + * @throws Exception + */ + public function getTransactionDetail($transactionNumber): Transaction + { + $params = []; + $response = $this->request('GET', + Constants::getTransactionDetailEndpointURI($transactionNumber), + $params, + $this->getAuthorizationHeader(true)); + + $transaction = json_decode($response, true); + return new Transaction($transaction); + } }