Skip to content

Commit

Permalink
FetchTransaction
Browse files Browse the repository at this point in the history
  • Loading branch information
recca0120 committed Nov 12, 2023
1 parent 514f757 commit ad67782
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 145 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"require-dev": {
"roave/security-advisories": "dev-latest",
"omnipay/tests": "^3.0|^4.0",
"squizlabs/php_codesniffer": "^3"
"squizlabs/php_codesniffer": "^3",
"guzzlehttp/guzzle": "^6.5|^7.0"
},
"autoload": {
"psr-4": {
Expand Down
56 changes: 45 additions & 11 deletions src/Encryptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public function __construct(string $hashKey, string $hashIv)
$this->hashIv = $hashIv;

$this->cipher = new AES('cbc');
// $this->cipher->disablePadding();
$this->cipher->setKey($this->hashKey);
$this->cipher->setIv($this->hashIv);
}
Expand All @@ -27,10 +28,10 @@ public function encrypt(array $data): string

public function decrypt(string $plainText): string
{
return $this->cipher->decrypt(hex2bin($plainText));
return self::stripPadding($this->cipher->decrypt(hex2bin($plainText)));
}

public function makeHash($data, $swap = false): string
public function tradeSha($data)
{
if (is_array($data)) {
ksort($data);
Expand All @@ -39,20 +40,53 @@ public function makeHash($data, $swap = false): string
$plainText = $data;
}

$prefix = 'HashIV='.$this->hashIv;
$suffix = 'HashKey='.$this->hashKey;
return strtoupper(hash(
"sha256",
implode('&', ['HashKey='.$this->hashKey, $plainText, 'HashIV='.$this->hashIv])
));
}

public function checkValue($data)
{
if (is_array($data)) {
ksort($data);
$plainText = http_build_query($data);
} else {
$plainText = $data;
}

return strtoupper(hash(
"sha256",
implode('&', ['IV='.$this->hashIv, $plainText, 'Key='.$this->hashKey])
));
}

if ($swap === true) {
$temp = $suffix;
$suffix = $prefix;
$prefix = $temp;
public function checkCode($data)
{
if (is_array($data)) {
ksort($data);
$plainText = http_build_query($data);
} else {
$plainText = $data;
}

return strtoupper(hash("sha256", implode('&', [$prefix, $plainText, $suffix])));
return strtoupper(hash(
"sha256",
implode('&', ['HashIV='.$this->hashIv, $plainText, 'HashKey='.$this->hashKey])
));
}

public function check(string $data, string $hashedValue): bool
private function stripPadding($value)
{
return hash_equals($hashedValue, $this->makeHash($data, true));
$pad = ord($value[($len = strlen($value)) - 1]);

return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value;
}

private function paddingIsValid($pad, $value)
{
$beforePad = strlen($value) - $pad;

return substr($value, $beforePad) === str_repeat(substr($value, -1), $pad);
}
}
8 changes: 7 additions & 1 deletion src/Gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Omnipay\NewebPay\Message\AcceptNotificationRequest;
use Omnipay\NewebPay\Message\AuthorizeRequest;
use Omnipay\NewebPay\Message\CompletePurchaseRequest;
use Omnipay\NewebPay\Message\FetchTransactionRequest;
use Omnipay\NewebPay\Message\PurchaseRequest;
use Omnipay\NewebPay\Traits\HasDefaults;

Expand Down Expand Up @@ -53,6 +54,11 @@ public function completePurchase(array $options = []): RequestInterface

public function acceptNotification(array $options = []): NotificationInterface
{
return $this->createRequest(AcceptNotificationRequest::class, []);
return $this->createRequest(AcceptNotificationRequest::class, $options);
}

public function fetchTransaction(array $options = []): RequestInterface
{
return $this->createRequest(FetchTransactionRequest::class, $options);
}
}
3 changes: 2 additions & 1 deletion src/Message/CompletePurchaseRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public function sendData($data)
protected function decrypt($data)
{
$encryptor = new Encryptor($this->getHashKey(), $this->getHashIv());
$tradeSha = $encryptor->tradeSha($data['TradeInfo']);

if (! $encryptor->check($data['TradeInfo'], $data['TradeSha'])) {
if (! hash_equals($tradeSha, $data['TradeSha'])) {
throw new InvalidResponseException();
}

Expand Down
94 changes: 94 additions & 0 deletions src/Message/FetchTransactionRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace Omnipay\NewebPay\Message;

use Omnipay\Common\Exception\InvalidRequestException;
use Omnipay\Common\Exception\InvalidResponseException;
use Omnipay\NewebPay\Encryptor;
use Omnipay\NewebPay\Traits\HasDefaults;

class FetchTransactionRequest extends AbstractRequest
{
use HasDefaults;

protected $liveEndpoint = 'https://core.newebpay.com/API/QueryTradeInfo';

protected $testEndpoint = 'https://ccore.newebpay.com/API/QueryTradeInfo';

public function getEndpoint()
{
return $this->getTestMode() ? $this->testEndpoint : $this->liveEndpoint;
}

/**
* 資料來源.
* 預設為空值。
* 1.若為複合式商店(MS5 開頭) ,此欄位為必填
* 2.複合式商店查詢請固定填入:Composite設定此參數會查詢 複合式商店旗下對應商店的訂單。
* 3.若沒有帶[Gateway]或是帶入其他參數值,則查詢一般商店代號。
*
* @param string $value
* @return static
*/
public function setGateway($value)
{
return $this->setParameter('Gateway', $value);
}

/**
* @return ?string
*/
public function getGateway()
{
return $this->getParameter('Gateway');
}

/**
* @throws InvalidRequestException
*/
public function getData()
{
$encryptor = new Encryptor($this->getHashKey(), $this->getHashIv());

return array_filter([
'MerchantID' => $this->getMerchantID(),
'Version' => $this->getVersion() ?: '1.3',
'RespondType' => $this->getRespondType(),
'CheckValue' => $encryptor->checkValue([
'Amt' => (int) $this->getAmount(),
'MerchantID' => $this->getMerchantID(),
'MerchantOrderNo' => $this->getTransactionId(),
]),
'TimeStamp' => $this->getTimeStamp(),
'MerchantOrderNo' => $this->getTransactionId(),
'Amt' => (int) $this->getAmount(),
'Gateway' => $this->getGateway(),
], static function ($value) {
return $value !== null && $value !== '';
});
}

/**
* @throws InvalidResponseException
*/
public function sendData($data)
{
$response = $this->httpClient->request('POST', $this->getEndpoint(), [
'Content-Type' => 'application/x-www-form-urlencoded',
], http_build_query($data));
$result = json_decode((string) $response->getBody(), true);

$encryptor = new Encryptor($this->getHashKey(), $this->getHashIv());

if (! hash_equals($result['Result']['CheckCode'], $encryptor->checkCode([
'MerchantID' => $result['Result']['MerchantID'],
'Amt' => $result['Result']['Amt'],
'MerchantOrderNo' => $result['Result']['MerchantOrderNo'],
'TradeNo' => $result['Result']['TradeNo'],
]))) {
throw new InvalidResponseException('Incorrect CheckCode');
}

return $this->response = new FetchTransactionResponse($this, $result);
}
}
31 changes: 31 additions & 0 deletions src/Message/FetchTransactionResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Omnipay\NewebPay\Message;

class FetchTransactionResponse extends AbstractResponse
{
public function isSuccessful()
{
return $this->getCode() === '00';
}

public function getCode()
{
return $this->data['Result']['RespondCode'];
}

public function getMessage()
{
return $this->data['Result']['RespondMsg'];
}

public function getTransactionId()
{
return $this->data['Result']['MerchantOrderNo'];
}

public function getTransactionReference()
{
return $this->data['Result']['TradeNo'];
}
}
108 changes: 1 addition & 107 deletions src/Message/PurchaseRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,68 +18,6 @@ public function getEndpoint()
return $this->getTestMode() ? $this->testEndpoint : $this->liveEndpoint;
}

/**
* 回傳格式.
* JSON 或是 String
*
* @param string $value
* @return static
*/
public function setRespondType($value)
{
return $this->setParameter('RespondType', $value);
}

/**
* @return string
*/
public function getRespondType(): string
{
return $this->getParameter('RespondType') ?: "JSON";
}

/**
* 時間戳記.
* 自從 Unix 纪元(格林威治時間 1970 年 1 月 1 日 00:00:00)到當前時間的秒數,
* 若以 PHP 程式語言為例,即為呼叫 time()函式所回傳值
* 例:2014-05-15 15:00:00 這個時間的時間戳記為 1400137200
* * 須確實帶入自 Unix 紀元到當前時間的秒數以避免交易失敗。(容許誤差值 120 秒)
*
* @param int $value
* @return static
*/
public function setTimeStamp($value)
{
return $this->setParameter('TimeStamp', $value);
}

/**
* @return int
*/
public function getTimeStamp()
{
return $this->getParameter('TimeStamp') ?: time();
}

/**
* 串接程式版本.
*
* @param string $value
* @return static
*/
public function setVersion($value)
{
return $this->setParameter('Version', $value);
}

/**
* @return string
*/
public function getVersion()
{
return $this->getParameter('Version') ?: '2.0';
}

/**
* 語系.
* 1.設定 MPG 頁面顯示的文字語系:
Expand All @@ -104,50 +42,6 @@ public function getLangType()
return $this->getParameter('LangType');
}

/**
* 商店訂單編號.
* 1.商店自訂訂單編號,限英、數字、”_ ”格式 例:201406010001
* 2.長度限制為 30 字元
* 3.同一商店中此編號不可重覆
*
* @param string $value
* @return static
*/
public function setMerchantOrderNo($value)
{
return $this->setTransactionId($value);
}

/**
* @return string
*/
public function getMerchantOrderNo()
{
return $this->getTransactionId();
}

/**
* 訂單金額.
* 1.純數字不含符號,例:1000
* 2.幣別:新台幣
*
* @param int $value
* @return static
*/
public function setAmt($value)
{
return $this->setAmount($value);
}

/**
* @return int
* @throws InvalidRequestException
*/
public function getAmt()
{
return (int) $this->getAmount();
}

/**
* 商品資訊.
* 1.限制長度為 50 字元
Expand Down Expand Up @@ -1026,7 +920,7 @@ public function getData(): array
'MerchantID' => $this->getMerchantID(),
'RespondType' => $this->getRespondType(),
'TimeStamp' => $this->getTimeStamp(),
'Version' => $this->getVersion(),
'Version' => $this->getVersion() ?: '2.0',
'LangType' => $this->getLangType(),
'MerchantOrderNo' => $this->getTransactionId(),
'Amt' => (int) $this->getAmount(),
Expand Down
5 changes: 2 additions & 3 deletions src/Message/PurchaseResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ public function getRedirectMethod()
public function getRedirectData()
{
$encryptor = new Encryptor($this->request->getHashKey(), $this->request->getHashIv());

$tradeInfo = $encryptor->encrypt($this->data);

return [
'MerchantID' => $this->data['MerchantID'],
'TradeInfo' => $tradeInfo,
'TradeSha' => $encryptor->makeHash($tradeInfo, true),
'Version' => $this->request->getVersion(),
'TradeSha' => $encryptor->tradeSha($tradeInfo),
'Version' => $this->data['Version'],
];
}
}
Loading

0 comments on commit ad67782

Please sign in to comment.