├── TODO.md ├── .gitignore ├── Info ├── InfoDecorator.php ├── Period │ ├── Decorator │ │ ├── BackUrl.php │ │ ├── NotifyUrl.php │ │ ├── ReturnUrl.php │ │ ├── Webhook.php │ │ └── Memo.php │ ├── BasicInfo.php │ └── Info.php ├── Decorator │ ├── EnableWebAtm.php │ ├── EnableLinePay.php │ ├── EnableUnionPay.php │ ├── EnableGooglePay.php │ ├── EnableCreditBonus.php │ ├── EnableSamsungPay.php │ ├── EnableAmericanExpress.php │ ├── EnableAliPay.php │ ├── EnableEzPay.php │ ├── EnableVacc.php │ ├── EnableCvs.php │ ├── EnableBarcode.php │ ├── Enable.php │ ├── PayerEmailEditable.php │ ├── Language.php │ ├── PayInInstallments.php │ ├── PayComplete.php │ ├── PayCancel.php │ ├── EnableCredit.php │ ├── Comment.php │ ├── TradeLimit.php │ ├── EnableCvsCom.php │ ├── AliPayProduct.php │ └── OfflinePay.php ├── AliPayInfo.php ├── BasicInfo.php ├── AliPayBasicInfo.php └── Info.php ├── Constants ├── Cipher.php ├── LanguageType.php └── Period │ ├── VersionType.php │ ├── AlterType.php │ └── PeriodStartType.php ├── Contracts ├── InfoInterface.php ├── PayerInterface.php ├── AliPayPayerInterface.php ├── QuickCreditInterface.php ├── OrderInterface.php ├── AliPayProductInterface.php └── Period │ ├── ContactInterface.php │ └── OrderInterface.php ├── Exceptions └── TradeInfoException.php ├── composer.json ├── phpunit.xml.dist ├── Tests ├── MerchantTest.php ├── InfoTest.php ├── NewebPayTest.php └── AliPayInfoTest.php ├── LICENSE ├── Merchant.php ├── Cryption.php ├── Response.php ├── README.md └── NewebPay.php /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | phpunit.xml -------------------------------------------------------------------------------- /Info/InfoDecorator.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'BackURL' => $this->url, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Decorator/EnableWebAtm.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'WEBATM' => $this->isEnable? 1: 0, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Period/Decorator/NotifyUrl.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'NotifyURL' => $this->url, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Period/Decorator/ReturnUrl.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'ReturnURL' => $this->url, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Decorator/EnableLinePay.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'LINEPAY' => $this->isEnable? 1: 0, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Decorator/EnableUnionPay.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'UNIONPAY' => $this->isEnable? 1: 0, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Decorator/EnableGooglePay.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'ANDROIDPAY' => $this->isEnable? 1: 0, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Decorator/EnableCreditBonus.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'CreditRed' => $this->isEnable? 1: 0, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Decorator/EnableSamsungPay.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'SAMSUNGPAY' => $this->isEnable? 1: 0, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Info/Decorator/EnableAmericanExpress.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 10 | [ 11 | 'CREDITAE' => $this->isEnable? 1: 0, 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Contracts/PayerInterface.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 14 | [ 15 | 'ALIPAY' => $this->isEnable? 1: 0, 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Info/Decorator/EnableEzPay.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 14 | [ 15 | 'P2G' => $this->isEnable? 1: 0, 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Info/Decorator/EnableVacc.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 15 | [ 16 | 'VACC' => $this->isEnable? 1: 0, 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Constants/Period/AlterType.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 15 | [ 16 | 'CVS' => $this->isEnable? 1: 0, 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Info/Decorator/EnableBarcode.php: -------------------------------------------------------------------------------- 1 | info->getInfo() + 15 | [ 16 | 'BARCODE' => $this->isEnable? 1: 0, 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Contracts/AliPayPayerInterface.php: -------------------------------------------------------------------------------- 1 | info = $info; 22 | 23 | $this->url = $url; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Contracts/OrderInterface.php: -------------------------------------------------------------------------------- 1 | info = $info; 19 | 20 | $this->isEnable = $isEnable; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Info/AliPayInfo.php: -------------------------------------------------------------------------------- 1 | count = $count; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Contracts/AliPayProductInterface.php: -------------------------------------------------------------------------------- 1 | info = $info; 19 | 20 | $this->canEditEmail = $canEditEmail; 21 | } 22 | 23 | public function getInfo() 24 | { 25 | return $this->info->getInfo() + 26 | [ 27 | 'EmailModify' => $this->canEditEmail? 1: 0, 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Info/BasicInfo.php: -------------------------------------------------------------------------------- 1 | $this->merchantId, 13 | 'RespondType' => 'JSON', 14 | 'TimeStamp' => time(), 15 | 'Version' => NewebPay::VERSION, 16 | 'NotifyURL' => $this->notifyUrl, 17 | 'Amt' => $this->order->getAmt(), 18 | 'ItemDesc' => $this->order->getItemDesc(), 19 | 'MerchantOrderNo' => $this->order->getMerchantOrderNo(), 20 | 'Email' => $this->payer->getEmail(), 21 | 'LoginType' => $this->payer->getLoginType()? 1: 0, 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Info/Decorator/Language.php: -------------------------------------------------------------------------------- 1 | info = $info; 20 | 21 | $this->language = $language; 22 | } 23 | 24 | public function getInfo() 25 | { 26 | return $this->info->getInfo() + 27 | [ 28 | 'LangType' => $this->language ?? LanguageType::ZH_TW, 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Constants/Period/PeriodStartType.php: -------------------------------------------------------------------------------- 1 | info = $info; 25 | 26 | $this->instFlag = $instFlag; 27 | } 28 | 29 | public function getInfo() 30 | { 31 | return $this->info->getInfo() + 32 | [ 33 | 'InstFlag' => $this->instFlag, 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Info/Decorator/PayComplete.php: -------------------------------------------------------------------------------- 1 | info = $info; 23 | 24 | $this->setReturnUrl($returnUrl); 25 | } 26 | 27 | public function getInfo() 28 | { 29 | return $this->info->getInfo() + 30 | [ 31 | 'ReturnURL' => $this->returnUrl, 32 | ]; 33 | } 34 | 35 | protected function setReturnUrl(string $returnUrl = null) 36 | { 37 | $this->returnUrl = $returnUrl; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ./Tests/ 18 | 19 | 20 | 21 | 22 | 23 | ./ 24 | 25 | ./Tests 26 | ./vendor 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Info/Period/Decorator/Memo.php: -------------------------------------------------------------------------------- 1 | info = $info; 22 | 23 | $this->setMemo($memo); 24 | } 25 | 26 | public function getInfo() 27 | { 28 | return $this->info->getInfo() + 29 | [ 30 | 'PeriodMemo' => $this->memo, 31 | ]; 32 | } 33 | 34 | protected function setMemo(string $memo) 35 | { 36 | if (mb_strlen($memo) > 255) { 37 | throw new \LogicException("unsupported length of this memo $memo"); 38 | } 39 | 40 | $this->memo = $memo; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Info/Decorator/PayCancel.php: -------------------------------------------------------------------------------- 1 | info = $info; 23 | 24 | $this->setClientBackUrl($clientBackUrl); 25 | } 26 | 27 | public function getInfo() 28 | { 29 | return $this->info->getInfo() + 30 | [ 31 | 'ClientBackURL' => $this->clientBackUrl, 32 | ]; 33 | } 34 | 35 | protected function setClientBackUrl(string $clientBackUrl = null) 36 | { 37 | $this->clientBackUrl = $clientBackUrl; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/MerchantTest.php: -------------------------------------------------------------------------------- 1 | decryptTradeInfo($encryptedStr); 21 | 22 | //assert 23 | $this->assertEquals($expected, $result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Info/Decorator/EnableCredit.php: -------------------------------------------------------------------------------- 1 | quickCredit = $quickCredit; 18 | } 19 | 20 | public function getInfo() 21 | { 22 | $result = $this->info->getInfo() + 23 | [ 24 | 'CREDIT' => $this->isEnable? 1: 0, 25 | ]; 26 | 27 | if ($this->quickCredit) { 28 | $result += [ 29 | 'TokenTerm' => $this->quickCredit->getTokenTerm(), 30 | 'TokenTermDemand' => $this->quickCredit->getTokenTermDemand(), 31 | ]; 32 | } 33 | 34 | return $result; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Info/Period/BasicInfo.php: -------------------------------------------------------------------------------- 1 | 'JSON', 11 | 'TimeStamp' => time(), 12 | 'Version' => $this->version, 13 | 'MerOrderNo' => $this->order->getMerOrderNo(), 14 | 'ProdDesc' => $this->order->getProdDesc(), 15 | 'PeriodAmt' => $this->order->getAmount(), 16 | 'PeriodType' => $this->order->getPeriodType(), 17 | 'PeriodPoint' => $this->order->getPeriodPoint(), 18 | 'PeriodStartType' => $this->periodStartType, 19 | 'PeriodTimes' => $this->order->getPeriodTimes(), 20 | 'PayerEmail' => $this->contact->getPayerEmail(), 21 | 'EmailModify' => $this->contact->getPayerEmailModify(), 22 | 'PaymentInfo' => $this->contact->getPaymentInfo(), 23 | 'OrderInfo' => $this->contact->getOrderInfo(), 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Info/AliPayBasicInfo.php: -------------------------------------------------------------------------------- 1 | $this->merchantId, 14 | 'RespondType' => 'JSON', 15 | 'TimeStamp' => time(), 16 | 'Version' => NewebPay::VERSION, 17 | 'NotifyURL' => $this->notifyUrl, 18 | 'Amt' => $this->order->getAmt(), 19 | 'ItemDesc' => $this->order->getItemDesc(), 20 | 'MerchantOrderNo' => $this->order->getMerchantOrderNo(), 21 | 'Email' => $this->payer->getEmail(), 22 | 'LoginType' => $this->payer->getLoginType()? 1: 0, 23 | // AliPay 必填 24 | 'Receiver' => $this->payer->getReceiver(), 25 | 'Tel1' => $this->payer->getTel1(), 26 | 'Tel2' => $this->payer->getTel2(), 27 | 'Count' => $this->count, 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Info/Decorator/Comment.php: -------------------------------------------------------------------------------- 1 | info = $info; 23 | 24 | $this->setComment($comment); 25 | } 26 | 27 | public function getInfo() 28 | { 29 | return $this->info->getInfo() + 30 | [ 31 | 'OrderComment' => $this->comment, 32 | ]; 33 | } 34 | 35 | protected function setComment(string $comment) 36 | { 37 | if (mb_strlen($comment) > 300) { 38 | throw new \LogicException('comment must less or equal than 300'); 39 | } 40 | 41 | $this->comment = $comment; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020 Lin Jinghong 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Info/Decorator/TradeLimit.php: -------------------------------------------------------------------------------- 1 | info = $info; 25 | 26 | $this->setLimit($limit); 27 | } 28 | 29 | public function getInfo() 30 | { 31 | return $this->info->getInfo() + 32 | [ 33 | 'TradeLimit' => $this->limit, 34 | ]; 35 | } 36 | 37 | protected function setLimit(int $limit) 38 | { 39 | if (1 <= $limit && $limit <= 60) { 40 | $limit = 60; 41 | } 42 | 43 | if ($limit > 900) { 44 | $limit = 900; 45 | } 46 | 47 | $this->limit = $limit; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Info/Decorator/EnableCvsCom.php: -------------------------------------------------------------------------------- 1 | info = $info; 26 | 27 | $this->setType($type); 28 | } 29 | 30 | /** 31 | * 物流啟用 32 | * 33 | * @return array 34 | */ 35 | public function getInfo() 36 | { 37 | return $this->info->getInfo() + 38 | [ 39 | 'CVSCOM' => $this->type, 40 | ]; 41 | } 42 | 43 | /** 44 | * @param int $type 45 | */ 46 | protected function setType(int $type) 47 | { 48 | if ($type < 0 || $type > 3) { 49 | throw new \LogicException('Newebpay does not support this type'); 50 | } 51 | 52 | $this->type = $type; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Info/Decorator/AliPayProduct.php: -------------------------------------------------------------------------------- 1 | info = $info; 25 | 26 | $this->index = $index; 27 | 28 | $this->product = $product; 29 | } 30 | 31 | public function getInfo() 32 | { 33 | return $this->info->getInfo() + 34 | [ 35 | "Pid{$this->index}" => $this->product->getProductId(), 36 | "Title{$this->index}" => $this->product->getTitle(), 37 | "Desc{$this->index}" => $this->product->getDescription(), 38 | "Price{$this->index}" => $this->product->getPrice(), 39 | "Qty{$this->index}" => $this->product->getQuantity(), 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Contracts/Period/OrderInterface.php: -------------------------------------------------------------------------------- 1 | order = $order; 46 | 47 | $this->payer = $payer; 48 | 49 | $this->merchantId = $merchantId; 50 | 51 | $this->notifyUrl = $notifyUrl; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function getMerchantId() 58 | { 59 | return $this->merchantId; 60 | } 61 | 62 | /** 63 | * @return OrderInterface 64 | */ 65 | public function getOrder() 66 | { 67 | return $this->order; 68 | } 69 | 70 | /** 71 | * @return PayerInterface 72 | */ 73 | public function getPayer() 74 | { 75 | return $this->payer; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Info/Decorator/OfflinePay.php: -------------------------------------------------------------------------------- 1 | info = $info; 32 | 33 | $this->customerUrl = $customerUrl; 34 | 35 | $this->setTtl($ttl); 36 | } 37 | 38 | public function getInfo() 39 | { 40 | return $this->info->getInfo() + 41 | [ 42 | 'ExpireDate' => $this->countExpireDate(), 43 | 'CustomerURL' => $this->customerUrl, 44 | ]; 45 | } 46 | 47 | protected function setTtl(int $ttl = null) 48 | { 49 | if ($ttl <= 0) { 50 | throw new \LogicException('ttl must be large than 1'); 51 | } 52 | 53 | if ($ttl > 180) { 54 | throw new \LogicException('ttl must be less than or equal to 180'); 55 | } 56 | 57 | $this->ttl = $ttl; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | protected function countExpireDate() 64 | { 65 | if (! $this->ttl) { 66 | return ''; 67 | } 68 | 69 | return date('Ymd', strtotime("+$this->ttl days")); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Info/Period/Info.php: -------------------------------------------------------------------------------- 1 | order = $order; 53 | 54 | $this->contact = $contact; 55 | 56 | $this->setPeriodStartType($periodStartType); 57 | 58 | $this->setVersion($version); 59 | } 60 | 61 | protected function setPeriodStartType(string $periodStartType) 62 | { 63 | // if (! PeriodStartType::isValid($periodStartType)) { 64 | // throw new \LogicException("unsupported version $periodStartType"); 65 | // } 66 | 67 | $this->periodStartType = $periodStartType; 68 | } 69 | 70 | protected function setVersion(string $version) 71 | { 72 | // if (! VersionType::isValid($version)) { 73 | // throw new \LogicException("unsupported version $version"); 74 | // } 75 | 76 | $this->version = $version; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Merchant.php: -------------------------------------------------------------------------------- 1 | id = $id; 38 | 39 | $this->hashKey = $hashKey; 40 | 41 | $this->hashIv = $hashIv; 42 | } 43 | 44 | /** 45 | * 因為商城代號, hashKey, hashIv 是一整組的, 要就一次修改 46 | * response 是屬於個別商城的, 改商城順手清空response 47 | * @param string $id 48 | * @param string $hashKey 49 | * @param string $hashIv 50 | * @return $this 51 | */ 52 | public function reset(string $id, string $hashKey, string $hashIv) 53 | { 54 | $this->id = $id; 55 | $this->hashKey = $hashKey; 56 | $this->hashIv = $hashIv; 57 | 58 | $this->response = null; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * 注入來自藍新通知的交易資訊 65 | * @param array $rawData 66 | * @return $this 67 | * @throws TradeInfoException 68 | */ 69 | public function setRawData(array $rawData) 70 | { 71 | if (! isset($rawData['TradeInfo']) || ! isset($rawData['TradeSha'])) { 72 | throw new TradeInfoException('invalid data'); 73 | } 74 | 75 | $this->response = new Response( 76 | json_decode($this->decryptTradeInfo($rawData['TradeInfo']), true), 77 | $rawData['TradeInfo'], 78 | $rawData['TradeSha'] 79 | ); 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * 用來確認藍新通知的資料是否真的屬於此商城 86 | * @return bool 87 | */ 88 | public function validateResponse() 89 | { 90 | if (! $this->response) { 91 | throw new \LogicException('set rawData first'); 92 | } 93 | 94 | return $this->response->getTradeSha() === $this->countTradeSha($this->response->getTradeInfo()); 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function getId() 101 | { 102 | return $this->id; 103 | } 104 | 105 | /** 106 | * @return string 107 | */ 108 | public function getHashKey(): string 109 | { 110 | return $this->hashKey; 111 | } 112 | 113 | /** 114 | * @return string 115 | */ 116 | public function getHashIv() 117 | { 118 | return $this->hashIv; 119 | } 120 | 121 | /** 122 | * @return Response 123 | */ 124 | public function getResponse() 125 | { 126 | return $this->response; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Cryption.php: -------------------------------------------------------------------------------- 1 | getInfo(); 22 | 23 | return $this->createEncryptedStr($infoPayload); 24 | } 25 | 26 | /** 27 | * @param string $tradeInfo 28 | * @return string 29 | */ 30 | public function countTradeSha(string $tradeInfo) 31 | { 32 | if (! $tradeInfo) { 33 | throw new \LogicException('empty trade info'); 34 | } 35 | 36 | return strtoupper( 37 | hash( 38 | "sha256", 39 | "HashKey={$this->hashKey}&{$tradeInfo}&HashIV={$this->hashIv}" 40 | ) 41 | ); 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getHashKey() 48 | { 49 | return $this->hashKey; 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function getHashIv() 56 | { 57 | return $this->hashIv; 58 | } 59 | 60 | public function createEncryptedStr(array $infoPayload = []) 61 | { 62 | return trim( 63 | bin2hex( 64 | openssl_encrypt( 65 | $this->addPadding(http_build_query($infoPayload)), 66 | Cipher::METHOD, 67 | $this->hashKey, 68 | OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, 69 | $this->hashIv 70 | ) 71 | ) 72 | ); 73 | } 74 | 75 | /** 76 | * 從藍新回傳的加密交易資訊解成可讀的字串 77 | * @param string $tradeInfo 78 | * @return string 79 | * @throws TradeInfoException 80 | */ 81 | public function decryptTradeInfo(string $tradeInfo) 82 | { 83 | if (! $tradeInfo) { 84 | throw new \LogicException('empty trade info'); 85 | } 86 | 87 | return $this->stripPadding( 88 | openssl_decrypt( 89 | hex2bin($tradeInfo), 90 | Cipher::METHOD, 91 | $this->hashKey, 92 | OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, 93 | $this->hashIv 94 | ) 95 | ); 96 | } 97 | 98 | protected function addPadding(string $string, int $blockSize = 32) 99 | { 100 | $len = strlen($string); 101 | $pad = $blockSize - ($len % $blockSize); 102 | $string .= str_repeat(chr($pad), $pad); 103 | return $string; 104 | } 105 | 106 | /** 107 | * @param $string 108 | * @return string 109 | * @throws TradeInfoException 110 | */ 111 | protected function stripPadding($string) 112 | { 113 | $slast = ord(substr($string, -1)); 114 | $slastc = chr($slast); 115 | $pcheck = substr($string, -$slast); 116 | if (preg_match("/$slastc{" . $slast . "}/", $string)) { 117 | $string = substr($string, 0, strlen($string) - $slast); 118 | return $string; 119 | } 120 | 121 | throw new TradeInfoException(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Response.php: -------------------------------------------------------------------------------- 1 | data = $data; 25 | 26 | $this->tradeInfo = $tradeInfo; 27 | 28 | $this->tradeSha = $tradeSha; 29 | } 30 | 31 | /** 32 | * 回傳狀態 33 | * 成功: SUCCESS 34 | * 失敗: 錯誤代碼 35 | * @return string 36 | */ 37 | public function getStatus() 38 | { 39 | return $this->data['Status']; 40 | } 41 | 42 | /** 43 | * 回傳訊息 44 | * @return string 45 | */ 46 | public function getMessage() 47 | { 48 | return $this->data['Message']; 49 | } 50 | 51 | /** 52 | * 回傳參數細節 53 | * @return array 54 | */ 55 | public function getResult() 56 | { 57 | return $this->data['Result']; 58 | } 59 | 60 | /** 61 | * 商城代號 62 | * @return string|null 63 | */ 64 | public function getMerchantId() 65 | { 66 | return $this->data['Result']['MerchantID'] ?? null; 67 | } 68 | 69 | /** 70 | * 交易金額 71 | * @return int|null 72 | */ 73 | public function getAmt() 74 | { 75 | return ((int) $this->data['Result']['Amt']) ?? null; 76 | } 77 | 78 | /** 79 | * 交易序號 80 | * @return string|null 81 | */ 82 | public function getTradeNo() 83 | { 84 | return $this->data['Result']['TradeNo'] ?? null; 85 | } 86 | 87 | /** 88 | * 用來跟藍新溝通的訂單編號, 也就是OrderInterface 提供的MerchantOrderNo 89 | * @return string|null 90 | */ 91 | public function getMerchantOrderNo() 92 | { 93 | return $this->data['Result']['MerchantOrderNo'] ?? null; 94 | } 95 | 96 | /** 97 | * 付款方式 98 | * @return string|null 99 | */ 100 | public function getPaymentType() 101 | { 102 | return $this->data['Result']['PaymentType'] ?? null; 103 | } 104 | 105 | public function getResponseType() 106 | { 107 | return $this->data['Result']['ResponseType'] ?? null; 108 | } 109 | 110 | /** 111 | * 付款完成時間 112 | * @return string|null 113 | */ 114 | public function getPayTime() 115 | { 116 | return $this->data['Result']['PayTime'] ?? null; 117 | } 118 | 119 | /** 120 | * 付款人取號或交易時的ip 121 | * @return string|null 122 | */ 123 | public function getIp() 124 | { 125 | return $this->data['Result']['IP'] ?? null; 126 | } 127 | 128 | /** 129 | * 款項保管銀行 130 | * @return string|null 131 | */ 132 | public function getEscrowBank() 133 | { 134 | return $this->data['Result']['EscrowBank'] ?? null; 135 | } 136 | 137 | /** 138 | * 離線金流會帶繳費到期日回來, 格式: yyyy-mm-dd 139 | * @return string|null 140 | */ 141 | public function getExpireDate() 142 | { 143 | return $this->data['Result']['ExpireDate'] ?? null; 144 | } 145 | 146 | /** 147 | * @return string 148 | */ 149 | public function getTradeInfo() 150 | { 151 | return $this->tradeInfo; 152 | } 153 | 154 | /** 155 | * @return string 156 | */ 157 | public function getTradeSha() 158 | { 159 | return $this->tradeSha; 160 | } 161 | 162 | /** 163 | * @return array 164 | */ 165 | public function getData() 166 | { 167 | return $this->data; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Tests/InfoTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(OrderInterface::class) 32 | ->getMock(); 33 | 34 | $order->expects($this->once()) 35 | ->method('getMerchantOrderNo') 36 | ->willReturn($orderMerchantNo = (string) time()); 37 | 38 | $order->expects($this->once()) 39 | ->method('getItemDesc') 40 | ->willReturn($itemDesc = 'This is an apple'); 41 | 42 | $order->expects($this->once()) 43 | ->method('getAmt') 44 | ->willReturn($amt = 100); 45 | 46 | $payer = $this->getMockBuilder(PayerInterface::class) 47 | ->getMock(); 48 | 49 | $payer->expects($this->once()) 50 | ->method('getEmail') 51 | ->willReturn($email = 'foobar@gg.cc'); 52 | 53 | $payer->expects($this->once()) 54 | ->method('getLoginType') 55 | ->willReturn($loginType = false); 56 | 57 | $ttl = 3; 58 | 59 | $customerUrl = 'customer.url'; 60 | 61 | //act 62 | $info = 63 | new PayComplete( 64 | new OfflinePay( 65 | new PayCancel( 66 | new Language( 67 | new PayerEmailEditable( 68 | new BasicInfo($merchant->getId(), $notifyUrl, $order, $payer), 69 | $email 70 | ), 71 | $lang = LanguageType::EN 72 | ), 73 | $clientBackUrl 74 | ), 75 | $customerUrl, 76 | $ttl 77 | ), 78 | $returnUrl 79 | ) 80 | ; 81 | $result = $info->getInfo(); 82 | 83 | //assert 84 | $this->assertArrayHasKey('MerchantID', $result); 85 | $this->assertEquals($merchant->getId(), $result['MerchantID']); 86 | 87 | $this->assertArrayHasKey('RespondType', $result); 88 | $this->assertEquals('JSON', $result['RespondType']); 89 | 90 | $this->assertArrayHasKey('TimeStamp', $result); 91 | 92 | $this->assertArrayHasKey('Version', $result); 93 | $this->assertEquals(NewebPay::VERSION, $result['Version']); 94 | 95 | $this->assertArrayHasKey('NotifyURL', $result); 96 | $this->assertEquals($notifyUrl, $result['NotifyURL']); 97 | 98 | $this->assertArrayHasKey('Amt', $result); 99 | $this->assertEquals($amt, $result['Amt']); 100 | 101 | $this->assertArrayHasKey('ItemDesc', $result); 102 | $this->assertEquals($itemDesc, $result['ItemDesc']); 103 | 104 | $this->assertArrayHasKey('MerchantOrderNo', $result); 105 | $this->assertEquals($orderMerchantNo, $result['MerchantOrderNo']); 106 | 107 | $this->assertArrayHasKey('Email', $result); 108 | $this->assertEquals($email, $result['Email']); 109 | 110 | $this->assertArrayHasKey('LoginType', $result); 111 | $this->assertEquals($loginType, $result['LoginType']); 112 | 113 | $this->assertArrayHasKey('EmailModify', $result); 114 | $this->assertEquals(1, $result['EmailModify']); 115 | 116 | $this->assertArrayHasKey('LangType', $result); 117 | $this->assertEquals($lang, $result['LangType']); 118 | 119 | $this->assertArrayHasKey('ClientBackURL', $result); 120 | $this->assertEquals($clientBackUrl, $result['ClientBackURL']); 121 | 122 | $this->assertArrayHasKey('ExpireDate', $result); 123 | 124 | $this->assertArrayHasKey('CustomerURL', $result); 125 | $this->assertEquals($customerUrl, $result['CustomerURL']); 126 | 127 | $this->assertArrayHasKey('ReturnURL', $result); 128 | $this->assertEquals($returnUrl, $result['ReturnURL']); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Tests/NewebPayTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(Merchant::class) 34 | ->disableOriginalConstructor() 35 | ->getMock(); 36 | 37 | $merchant->expects($this->atLeastOnce()) 38 | ->method('getId') 39 | ->willReturn($merchantId); 40 | 41 | $merchant->expects($this->once()) 42 | ->method('countTradeInfo') 43 | ->willReturn($tradeInfo = '12345'); 44 | 45 | $merchant->expects($this->once()) 46 | ->method('countTradeSha') 47 | ->willReturn($tradeSha = '22345'); 48 | 49 | $email = 'test@gg.cc'; 50 | 51 | $returnUrl= 'return.url'; 52 | 53 | $notifyUrl = 'notify.url'; 54 | 55 | $clientBackUrl = 'client.back.url'; 56 | 57 | $order = $this->getMockBuilder(OrderInterface::class) 58 | ->getMock(); 59 | 60 | $payer = $this->getMockBuilder(PayerInterface::class) 61 | ->getMock(); 62 | 63 | $ttl = 3; 64 | 65 | $customerUrl = 'customer.url'; 66 | 67 | $info = new BasicInfo($merchant->getId(), $notifyUrl, $order, $payer); 68 | $info = new PayerEmailEditable($info, $email); 69 | $info = new Language($info, LanguageType::EN); 70 | $info = new PayCancel($info, $clientBackUrl); 71 | $info = new OfflinePay($info, $customerUrl, $ttl); 72 | $info = new PayComplete($info, $returnUrl); 73 | 74 | $expected = <<