diff --git a/README.md b/README.md new file mode 100644 index 0000000..312706f --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# 綠界科技全方位金流 SDK +* 綠界科技對於有收款需求的會員,提供完整的交易介接API,並有多種收款方式(All In One)可選擇使用。 +本套件為PHP版,可使用作建立訂單,接受付款通知,查詢訂單等金流交易的應用。 +* 收款方式清單: + * 信用卡(一次付清、分期付款、定期定額) + * 網路ATM + * ATM櫃員機 + * 超商代碼 + * 超商條碼 + +# 目錄 +* [環境需求](#環境需求) +* [安裝方式](#安裝方式) +* [文件與協助](#文件與協助) + +# 環境需求 +- [PHP](https://www.php.net/) 5.6 以上 + +# 安裝方式 +建議使用 [Composer](https://getcomposer.org/) 安裝 +```bash +composer require ecpay/sdk +``` + +# 文件與協助 +- [串接文件](https://www.ecpay.com.tw/Service/API_Dwnld) +- [綠界技術服務工程師信箱](mailto:techsupport@ecpay.com.tw) +- [範例](example) \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cf8fed0 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "ecpay/sdk", + "description": "ECPay SDK", + "type": "library", + "require": { + "php": ">=5.3" + }, + "license": "MIT", + "require-dev": { + "phpunit/phpunit": "*" + }, + "autoload": { + "psr-4": { + "Ecpay\\Sdk\\": "src/" + } + } +} diff --git a/example/Logistics/CrossBorder/CreateTestData.php b/example/Logistics/CrossBorder/CreateTestData.php new file mode 100644 index 0000000..92bfcaf --- /dev/null +++ b/example/Logistics/CrossBorder/CreateTestData.php @@ -0,0 +1,34 @@ +createWithHash('PostWithAesJsonResponseService', $hashKey, $hashIv); + + $data = [ + 'MerchantID' => '2000132', + 'Country' => 'SG', + 'LogisticsType' => 'CB', + 'LogisticsSubType' => 'UNIMARTCBCVS', + ]; + $input = [ + 'MerchantID' => '2000132', + 'RqHeader' => [ + 'Timestamp' => time(), + 'Revision' => '1.0.0', + ], + 'Data' => $data, + ]; + $url = 'https://logistics-stage.ecpay.com.tw/CrossBorder/CreateTestData'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Logistics/CrossBorder/CreateUnimartCvsOrder.php b/example/Logistics/CrossBorder/CreateUnimartCvsOrder.php new file mode 100644 index 0000000..2729cb9 --- /dev/null +++ b/example/Logistics/CrossBorder/CreateUnimartCvsOrder.php @@ -0,0 +1,51 @@ +createWithHash('PostWithAesJsonResponseService', $hashKey, $hashIv); + + $data = [ + 'MerchantID' => '2000132', + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'MerchantTradeNo' => 'Test' . time(), + 'LogisticsType' => 'CB', + 'LogisticsSubType' => 'UNIMARTCBCVS', + 'GoodsAmount' => 1000, + 'GoodsWeight' => 5.0, + 'GoodsEnglishName' => 'Test goods', + 'ReceiverCountry' => 'SG', + 'ReceiverName' => 'Test Receiver', + 'ReceiverCellPhone' => '65212345678', + 'ReceiverStoreID' => '711_1', + 'ReceiverZipCode' => '419701', + 'ReceiverAddress' => 'address 23424 -fr 13-2', + 'ReceiverEmail' => 'test-receiver@ecpay.com.tw', + 'SenderName' => 'Test Sender', + 'SenderCellPhone' => '886987654321', + 'SenderAddress' => 'address 23424 -fr 13-2, Nangang Dist., Taipei City 115, Taiwan (R.O.C.)', + 'SenderEmail' => 'test-sender@ecpay.com.tw', + 'Remark' => 'Test Remark', + 'ServerReplyURL' => 'https://logistics-stage.ecpay.com.tw/MockMerchant/NoticsTestRtn', + ]; + $input = [ + 'MerchantID' => '2000132', + 'RqHeader' => [ + 'Timestamp' => time(), + 'Revision' => '1.0.0', + ], + 'Data' => $data, + ]; + $url = 'https://logistics-stage.ecpay.com.tw/CrossBorder/Create'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Logistics/CrossBorder/CreateUnimartHomeOrder.php b/example/Logistics/CrossBorder/CreateUnimartHomeOrder.php new file mode 100644 index 0000000..d2478ba --- /dev/null +++ b/example/Logistics/CrossBorder/CreateUnimartHomeOrder.php @@ -0,0 +1,50 @@ +createWithHash('PostWithAesJsonResponseService', $hashKey, $hashIv); + + $data = [ + 'MerchantID' => '2000132', + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'MerchantTradeNo' => 'Test' . time(), + 'LogisticsType' => 'CB', + 'LogisticsSubType' => 'UNIMARTCBHOME', + 'GoodsAmount' => 1000, + 'GoodsWeight' => 5.0, + 'GoodsEnglishName' => 'Test goods', + 'ReceiverCountry' => 'SG', + 'ReceiverName' => 'Test Receiver', + 'ReceiverCellPhone' => '65212345678', + 'ReceiverZipCode' => '419701', + 'ReceiverAddress' => 'address 23424 -fr 13-2', + 'ReceiverEmail' => 'test-receiver@ecpay.com.tw', + 'SenderName' => 'Test Sender', + 'SenderCellPhone' => '886987654321', + 'SenderAddress' => 'address 23424 -fr 13-2, Nangang Dist., Taipei City 115, Taiwan (R.O.C.)', + 'SenderEmail' => 'test-sender@ecpay.com.tw', + 'Remark' => 'Test Remark', + 'ServerReplyURL' => 'https://logistics-stage.ecpay.com.tw/MockMerchant/NoticsTestRtn', + ]; + $input = [ + 'MerchantID' => '2000132', + 'RqHeader' => [ + 'Timestamp' => time(), + 'Revision' => '1.0.0', + ], + 'Data' => $data, + ]; + $url = 'https://logistics-stage.ecpay.com.tw/CrossBorder/Create'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Logistics/CrossBorder/GetMapResponse.php b/example/Logistics/CrossBorder/GetMapResponse.php new file mode 100644 index 0000000..eab8b54 --- /dev/null +++ b/example/Logistics/CrossBorder/GetMapResponse.php @@ -0,0 +1,16 @@ +create(ArrayResponse::class); + + var_dump($arrayResponse->get($_POST)); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Logistics/CrossBorder/GetStatusChangedResponse.php b/example/Logistics/CrossBorder/GetStatusChangedResponse.php new file mode 100644 index 0000000..1b2dd36 --- /dev/null +++ b/example/Logistics/CrossBorder/GetStatusChangedResponse.php @@ -0,0 +1,42 @@ +createWithHash(AesJsonResponse::class, $hashKey, $hashIv); + $aesJsonResponse = $factory->createWithHash(AesRequest::class, $hashKey, $hashIv); + + $json = file_get_contents('php://input'); + + $request = $jsonRequest->get($json); + + /** + * 廠商端變更物流狀態(貨態)流程 + * Ex: 變更物流狀態 > ... + * 以下為變更成功回應參考範例 + */ + $data = [ + 'RtnCode' => 1, + 'RtnMsg' => 'OK', + ]; + $output = [ + 'MerchantID' => '2000132', + 'RqHeader' => [ + 'Timestamp' => time(), + ], + 'TransCode' => 1, + 'TransMsg' => '', + 'Data' => $data, + ]; + echo $aesJsonResponse->get($output); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Logistics/CrossBorder/Map.php b/example/Logistics/CrossBorder/Map.php new file mode 100644 index 0000000..ed74ac0 --- /dev/null +++ b/example/Logistics/CrossBorder/Map.php @@ -0,0 +1,27 @@ +createWithHash('AutoSubmitFormWithAesService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'LogisticsType' => 'CB', + 'LogisticsSubType' => 'UNIMARTCBCVS', + 'Destination' => 'SG', + 'ServerReplyURL' => 'https://logistics-stage.ecpay.com.tw/MockMerchant/NoticsTestRtn', + ]; + $action = 'https://logistics-stage.ecpay.com.tw/CrossBorder/Map'; + + echo $postService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Logistics/CrossBorder/Print.php b/example/Logistics/CrossBorder/Print.php new file mode 100644 index 0000000..20d64ff --- /dev/null +++ b/example/Logistics/CrossBorder/Print.php @@ -0,0 +1,32 @@ +createWithHash('PostWithAesJsonResponseService', $hashKey, $hashIv); + + $data = [ + 'MerchantID' => '2000132', + 'LogisticsID' => '1658389', + ]; + $input = [ + 'MerchantID' => '2000132', + 'RqHeader' => [ + 'Timestamp' => time(), + 'Revision' => '1.0.0', + ], + 'Data' => $data, + ]; + $url = 'https://logistics-stage.ecpay.com.tw/CrossBorder/Print'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Logistics/CrossBorder/QueryLogisticsTradeInfo.php b/example/Logistics/CrossBorder/QueryLogisticsTradeInfo.php new file mode 100644 index 0000000..ebabb72 --- /dev/null +++ b/example/Logistics/CrossBorder/QueryLogisticsTradeInfo.php @@ -0,0 +1,32 @@ +createWithHash('PostWithAesJsonResponseService', $hashKey, $hashIv); + + $data = [ + 'MerchantID' => '2000132', + 'LogisticsID' => '1658389', + ]; + $input = [ + 'MerchantID' => '2000132', + 'RqHeader' => [ + 'Timestamp' => time(), + 'Revision' => '1.0.0', + ], + 'Data' => $data, + ]; + $url = 'https://logistics-stage.ecpay.com.tw/CrossBorder/QueryLogisticsTradeInfo'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/Capture.php b/example/Payment/Capture.php new file mode 100644 index 0000000..70923c4 --- /dev/null +++ b/example/Payment/Capture.php @@ -0,0 +1,27 @@ +createWithHash('PostWithCmvEncodedStrResponseService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => '5fa271cc74e51', + 'TradeNo' => '2011041718071855', + 'Action' => 'C', + 'TotalAmount' => 8685, + ]; + $url = 'https://payment-stage.ecpay.com.tw/CreditDetail/DoAction'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateAtmOrder.php b/example/Payment/CreateAtmOrder.php new file mode 100644 index 0000000..c1b1afe --- /dev/null +++ b/example/Payment/CreateAtmOrder.php @@ -0,0 +1,35 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'ATM', + 'EncryptType' => 1, + + 'ExpireDate' => 7, + 'PaymentInfoURL' => 'https://www.ecpay.com.tw/example/payment-info', + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateBarcodeOrder.php b/example/Payment/CreateBarcodeOrder.php new file mode 100644 index 0000000..7800d0a --- /dev/null +++ b/example/Payment/CreateBarcodeOrder.php @@ -0,0 +1,39 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'BARCODE', + 'EncryptType' => 1, + + 'StoreExpireDate' => 5, + 'Desc_1' => '範例交易描述 1', + 'Desc_2' => '範例交易描述 2', + 'Desc_3' => '範例交易描述 3', + 'Desc_4' => '範例交易描述 4', + 'PaymentInfoURL' => 'https://www.ecpay.com.tw/example/payment-info', + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateCreditOrder.php b/example/Payment/CreateCreditOrder.php new file mode 100644 index 0000000..4fad896 --- /dev/null +++ b/example/Payment/CreateCreditOrder.php @@ -0,0 +1,32 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'Credit', + 'EncryptType' => 1, + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateCvsOrder.php b/example/Payment/CreateCvsOrder.php new file mode 100644 index 0000000..7ed494d --- /dev/null +++ b/example/Payment/CreateCvsOrder.php @@ -0,0 +1,39 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'CVS', + 'EncryptType' => 1, + + 'StoreExpireDate' => 4320, + 'Desc_1' => '範例交易描述 1', + 'Desc_2' => '範例交易描述 2', + 'Desc_3' => '範例交易描述 3', + 'Desc_4' => '範例交易描述 4', + 'PaymentInfoURL' => 'https://www.ecpay.com.tw/example/payment-info', + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateInstallmentOrder.php b/example/Payment/CreateInstallmentOrder.php new file mode 100644 index 0000000..a9f0015 --- /dev/null +++ b/example/Payment/CreateInstallmentOrder.php @@ -0,0 +1,34 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'Credit', + 'EncryptType' => 1, + + 'CreditInstallment' => '3,6,18', + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateOrder.php b/example/Payment/CreateOrder.php new file mode 100644 index 0000000..9dd57e7 --- /dev/null +++ b/example/Payment/CreateOrder.php @@ -0,0 +1,32 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'ALL', + 'EncryptType' => 1, + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateOrderIssueInvoice.php b/example/Payment/CreateOrderIssueInvoice.php new file mode 100644 index 0000000..1225b04 --- /dev/null +++ b/example/Payment/CreateOrderIssueInvoice.php @@ -0,0 +1,46 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $serialNo = time(); + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . $serialNo, + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品1 100 TWD x 1#範例商品2 30 TWD x 3', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'Credit', + 'EncryptType' => 1, + + 'InvoiceMark' => 'Y', + 'RelateNumber' => 'ETest' . $serialNo, + 'CustomerPhone' => '0911222333', + 'TaxType' => 1, + 'CarruerType' => 1, + 'Print' => 0, + 'InvoiceItemName' => UrlService::ecpayUrlEncode('範例商品1|範例商品2'), + 'InvoiceItemCount' => '1|3', + 'InvoiceItemWord' => UrlService::ecpayUrlEncode('顆|盒'), + 'InvoiceItemPrice' => '100|30', + 'DelayDay' => 0, + 'InvType' => '07', + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreatePeriodicOrder.php b/example/Payment/CreatePeriodicOrder.php new file mode 100644 index 0000000..88232e0 --- /dev/null +++ b/example/Payment/CreatePeriodicOrder.php @@ -0,0 +1,38 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'Credit', + 'EncryptType' => 1, + + 'PeriodAmount' => 100, + 'PeriodType' => 'M', + 'Frequency' => 1, + 'ExecTimes' => 6, + 'PeriodReturnURL' => 'https://www.ecpay.com.tw/example/receive', + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/CreateWebAtmOrder.php b/example/Payment/CreateWebAtmOrder.php new file mode 100644 index 0000000..180ee91 --- /dev/null +++ b/example/Payment/CreateWebAtmOrder.php @@ -0,0 +1,33 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'Test' . time(), + 'MerchantTradeDate' => date('Y/m/d H:i:s'), + 'PaymentType' => 'aio', + 'TotalAmount' => 100, + 'TradeDesc' => UrlService::ecpayUrlEncode('交易描述範例'), + 'ItemName' => '範例商品一批 100 TWD x 1', + 'ReturnURL' => 'https://www.ecpay.com.tw/example/receive', + 'ChoosePayment' => 'WebATM', + 'EncryptType' => 1, + 'OrderResultURL' => 'https://dev.ecpay.com.tw/example/Payment/GetCheckoutResponse.php', + ]; + $action = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/DownloadCreditReconcileCsv.php b/example/Payment/DownloadCreditReconcileCsv.php new file mode 100644 index 0000000..f91022a --- /dev/null +++ b/example/Payment/DownloadCreditReconcileCsv.php @@ -0,0 +1,25 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'PayDateType' => 'close', + 'StartDate' => '2015-02-12', + 'EndDate' => '2015-02-12', + ]; + $action = 'https://payment-stage.ecpay.com.tw/CreditDetail/FundingReconDetail'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/DownloadReconcileCsv.php b/example/Payment/DownloadReconcileCsv.php new file mode 100644 index 0000000..60f6da2 --- /dev/null +++ b/example/Payment/DownloadReconcileCsv.php @@ -0,0 +1,26 @@ +createWithHash('AutoSubmitFormWithCmvService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'DateType' => '2', + 'BeginDate' => '2015-02-12', + 'EndDate' => '2015-02-12', + 'MediaFormated' => '0', + ]; + $action = 'https://vendor-stage.ecpay.com.tw/PaymentMedia/TradeNoAio'; + + echo $autoSubmitFormService->generate($input, $action); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/GetCheckoutResponse.php b/example/Payment/GetCheckoutResponse.php new file mode 100644 index 0000000..3f557a6 --- /dev/null +++ b/example/Payment/GetCheckoutResponse.php @@ -0,0 +1,33 @@ +createWithHash(VerifiedArrayResponse::class, $hashKey, $hashIv); + + $_POST = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => 'WPLL4E341E122DB44D62', + 'PaymentDate' => '2019/05/09 00:01:21', + 'PaymentType' => 'Credit_CreditCard', + 'PaymentTypeChargeFee' => '1', + 'RtnCode' => '1', + 'RtnMsg' => '交易成功', + 'SimulatePaid' => '0', + 'TradeAmt' => '500', + 'TradeDate' => '2019/05/09 00:00:18', + 'TradeNo' => '1905090000188278', + 'CheckMacValue' => '59B085BAEC4269DC1182D48DEF106B431055D95622EB285DECD400337144C698', + ]; + + var_dump($checkoutResponse->get($_POST)); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/QueryCreditTrade.php b/example/Payment/QueryCreditTrade.php new file mode 100644 index 0000000..66ecff7 --- /dev/null +++ b/example/Payment/QueryCreditTrade.php @@ -0,0 +1,26 @@ +createWithHash('PostWithCmvJsonResponseService', $hashKey, $hashIv); + + $parameters = [ + 'MerchantID' => '2000132', + 'CreditRefundId' => 11304112, + 'CreditAmount' => 8685, + 'CreditCheckCode' => 91845555, + ]; + $url = 'https://payment-stage.ecPay.com.tw/CreditDetail/QueryTrade/V2'; + + $response = $postService->post($parameters, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/QueryPaymentInfo.php b/example/Payment/QueryPaymentInfo.php new file mode 100644 index 0000000..767375d --- /dev/null +++ b/example/Payment/QueryPaymentInfo.php @@ -0,0 +1,25 @@ +createWithHash('PostWithCmvVerifiedEncodedStrResponseService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => '2019091711192742', + 'TimeStamp' => time(), + ]; + $url = 'https://payment-stage.ecpay.com.tw/Cashier/QueryPaymentInfo'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/QueryPeridicTrade.php b/example/Payment/QueryPeridicTrade.php new file mode 100644 index 0000000..810ebb3 --- /dev/null +++ b/example/Payment/QueryPeridicTrade.php @@ -0,0 +1,25 @@ +createWithHash('PostWithCmvJsonResponseService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => '7fe6e4d229ee43f488f', + 'TimeStamp' => time(), + ]; + $url = 'https://payment-stage.ecpay.com.tw/Cashier/QueryCreditCardPeriodInfo'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/example/Payment/QueryTrade.php b/example/Payment/QueryTrade.php new file mode 100644 index 0000000..2612e7f --- /dev/null +++ b/example/Payment/QueryTrade.php @@ -0,0 +1,25 @@ +createWithHash('PostWithCmvVerifiedEncodedStrResponseService', $hashKey, $hashIv); + + $input = [ + 'MerchantID' => '2000132', + 'MerchantTradeNo' => '2019091711192742', + 'TimeStamp' => time(), + ]; + $url = 'https://payment-stage.ecpay.com.tw/Cashier/QueryTradeInfo/V5'; + + $response = $postService->post($input, $url); + var_dump($response); +} catch (RtnException $e) { + echo '(' . $e->getCode() . ')' . $e->getMessage() . PHP_EOL; +} diff --git a/src/Abstracts/AbstractDecryptedResponse.php b/src/Abstracts/AbstractDecryptedResponse.php new file mode 100644 index 0000000..6b5741c --- /dev/null +++ b/src/Abstracts/AbstractDecryptedResponse.php @@ -0,0 +1,44 @@ +aesService = $aesService; + } + + /** + * 取得 Response + * + * @param mixed $source + * @return array + * + * @throws RtnException + */ + public function get($source) + { + $parsed = $this->toArray($source); + $decrypted = $this->aesService->decryptData($parsed); + $sorted = ArrayService::naturalSort($decrypted); + + return $sorted; + } +} diff --git a/src/Abstracts/AbstractFactory.php b/src/Abstracts/AbstractFactory.php new file mode 100644 index 0000000..ab43468 --- /dev/null +++ b/src/Abstracts/AbstractFactory.php @@ -0,0 +1,41 @@ +isSubclassOf($target)) { + $result = true; + } + } + } + + return $result; + } +} diff --git a/src/Abstracts/AbstractResponse.php b/src/Abstracts/AbstractResponse.php new file mode 100644 index 0000000..b9c9c4b --- /dev/null +++ b/src/Abstracts/AbstractResponse.php @@ -0,0 +1,25 @@ +toArray($source); + $sorted = ArrayService::naturalSort($parsed); + + return $sorted; + } +} diff --git a/src/Abstracts/AbstractVerifiedResponse.php b/src/Abstracts/AbstractVerifiedResponse.php new file mode 100644 index 0000000..696b255 --- /dev/null +++ b/src/Abstracts/AbstractVerifiedResponse.php @@ -0,0 +1,57 @@ +checkMacValueService = $checkMacValueService; + } + + /** + * 取得 Response + * + * @param mixed $source + * @return array + * + * @throws RtnException + */ + public function get($source) + { + $parsed = $this->toArray($source); + if (!$this->verify($parsed)) { + throw new RtnException(106); + } + $sorted = ArrayService::naturalSort($parsed); + + return $sorted; + } + + /** + * 驗證回應 + * + * @param array $source + * @return boolean + */ + public function verify($source) + { + return $this->checkMacValueService->verify($source); + } +} diff --git a/src/Config/RtnException.php b/src/Config/RtnException.php new file mode 100644 index 0000000..7e95486 --- /dev/null +++ b/src/Config/RtnException.php @@ -0,0 +1,12 @@ + 'Generate CheckMacValue failed!', + 104 => 'Class with hash do not exist!', + 105 => 'Get response failed!', + 106 => 'CheckMacValue verify failed', + 107 => 'cURL initialize failed', + 108 => 'Perform cURL session failed', + 109 => 'AES decrypt failed', + 110 => 'AES encrypt failed', +]; diff --git a/src/Exceptions/RtnException.php b/src/Exceptions/RtnException.php new file mode 100644 index 0000000..ffac480 --- /dev/null +++ b/src/Exceptions/RtnException.php @@ -0,0 +1,30 @@ +toRtnMessage($code); + parent::__construct($message, $code); + } + + /** + * 轉換為回應訊息 + * + * @param int $code + * @return string + */ + public function toRtnMessage($code) + { + $messageList = include __DIR__ . '/../Config/RtnException.php'; + $message = ''; + if (isset($messageList[$code])) { + $message = $messageList[$code]; + } + + return $message; + } +} diff --git a/src/Factories/Factory.php b/src/Factories/Factory.php new file mode 100644 index 0000000..a9a2304 --- /dev/null +++ b/src/Factories/Factory.php @@ -0,0 +1,114 @@ +isClassOrAlias($class, CheckMacValueRequest::class): + case $this->isClassOrAlias($class, AbstractVerifiedResponse::class): + $checkMacValueService = $this->createWithHash(CheckMacValueService::class, $key, $iv); + $instance = new $class($checkMacValueService); + break; + + case $this->isClassOrAlias($class, 'PostWithCmvVerifiedEncodedStrResponseService'): + $checkMacValueRequest = $this->createWithHash(CheckMacValueRequest::class, $key, $iv); + $curlService = $this->create(CurlService::class); + $response = $this->createWithHash(VerifiedEncodedStrResponse::class, $key, $iv); + $instance = new PostService($checkMacValueRequest, $curlService, $response); + break; + case $this->isClassOrAlias($class, 'PostWithCmvEncodedStrResponseService'): + $checkMacValueRequest = $this->createWithHash(CheckMacValueRequest::class, $key, $iv); + $curlService = $this->create(CurlService::class); + $response = $this->create(EncodedStrResponse::class); + $instance = new PostService($checkMacValueRequest, $curlService, $response); + break; + case $this->isClassOrAlias($class, 'PostWithCmvJsonResponseService'): + $checkMacValueRequest = $this->createWithHash(CheckMacValueRequest::class, $key, $iv); + $curlService = $this->create(CurlService::class); + $response = $this->create(JsonResponse::class); + $instance = new PostService($checkMacValueRequest, $curlService, $response); + break; + case $this->isClassOrAlias($class, 'AutoSubmitFormWithCmvService'): + $checkMacValueRequest = $this->createWithHash(CheckMacValueRequest::class, $key, $iv); + $htmlService = $this->create(HtmlService::class); + $instance = new AutoSubmitFormService($checkMacValueRequest, $htmlService); + break; + + /** + * AES 應用 + */ + case $this->isClassOrAlias($class, AesRequest::class): + case $this->isClassOrAlias($class, AbstractDecryptedResponse::class): + $aesService = $this->createWithHash(AesService::class, $key, $iv); + $instance = new $class($aesService); + break; + + case $this->isClassOrAlias($class, 'PostWithAesJsonResponseService'): + $aesRequest = $this->createWithHash(AesRequest::class, $key, $iv); + $curlService = $this->create(CurlService::class); + $response = $this->createWithHash(AesJsonResponse::class, $key, $iv); + $instance = new PostService($aesRequest, $curlService, $response); + break; + case $this->isClassOrAlias($class, 'AutoSubmitFormWithAesService'): + $request = $this->create(Request::class); + $htmlService = $this->create(HtmlService::class); + $instance = new AutoSubmitFormService($request, $htmlService); + break; + + default: + $instance = new $class($key, $iv); + } + + return $instance; + } +} diff --git a/src/Interfaces/Request/RequestInterface.php b/src/Interfaces/Request/RequestInterface.php new file mode 100644 index 0000000..1a0c25f --- /dev/null +++ b/src/Interfaces/Request/RequestInterface.php @@ -0,0 +1,23 @@ +aesService = $aesService; + } + + /** + * 取得 Request + * + * @param array $source + * @return mixed + * + * @throws RtnException + */ + public function get($source) + { + $encrypted = $this->aesService->encryptData($source); + $sorted = ArrayService::naturalSort($encrypted); + $encoded = json_encode($sorted); + + return $encoded; + } + + /** + * 轉陣列 + * + * @param mixed $source + * @return array + */ + public function toArray($source) + { + $request = $this->get($source); + $decoded = json_decode($request, true); + + return $decoded; + } +} diff --git a/src/Request/CheckMacValueRequest.php b/src/Request/CheckMacValueRequest.php new file mode 100644 index 0000000..170fcc2 --- /dev/null +++ b/src/Request/CheckMacValueRequest.php @@ -0,0 +1,50 @@ +checkMacValueService = $checkMacValueService; + } + + /** + * 取得 Request + * + * @param array $source + * @return mixed + * + * @throws RtnException + */ + public function get($source) + { + $appened = $this->checkMacValueService->append($source); + $sorted = ArrayService::naturalSort($appened); + + return $sorted; + } + + /** + * 轉陣列 + * + * @param mixed $source + * @return array + */ + public function toArray($source) + { + $request = $this->get($source); + + return $request; + } +} diff --git a/src/Request/Request.php b/src/Request/Request.php new file mode 100644 index 0000000..99dc823 --- /dev/null +++ b/src/Request/Request.php @@ -0,0 +1,36 @@ +get($source); + + return $request; + } +} diff --git a/src/Response/AesJsonResponse.php b/src/Response/AesJsonResponse.php new file mode 100644 index 0000000..576a47b --- /dev/null +++ b/src/Response/AesJsonResponse.php @@ -0,0 +1,20 @@ +setHashKey($key); + $this->setHashIv($iv); + } + + /** + * AES 解密 + * + * @param string $source + * @return array + * + * @throws RtnException + */ + public function decrypt($source) + { + $jsonDecoded = []; + try { + $base64Decoded = base64_decode($source); + $decrypted = openssl_decrypt( + $base64Decoded, + $this->method, + $this->getHashKey(), + $this->options, + $this->getHashIv() + ); + $urlDecoded = urldecode($decrypted); + $jsonDecoded = json_decode($urlDecoded, true); + } catch (Exception $e) { + throw new RtnException(109); + } + + return $jsonDecoded; + } + + /** + * 解密資料 + * + * @param array $source + * @return array + * + * @throws RtnException + */ + public function decryptData($source) + { + $field = $this->getFieldName(); + if (isset($source[$field])) { + $source[$field] = $this->decrypt($source[$field]); + } + + return $source; + } + + /** + * AES 加密 + * + * @param array $source + * @return string + * + * @throws RtnException + */ + public function encrypt($source) + { + $dataBase64Encode = ''; + try { + $jsonEncoded = json_encode($source); + $urlEncoded = urlencode($jsonEncoded); + $encrypted = openssl_encrypt( + $urlEncoded, + $this->method, + $this->getHashKey(), + $this->options, + $this->getHashIv() + ); + $dataBase64Encode = base64_encode($encrypted); + } catch (Exception $e) { + throw new RtnException(110); + } + + return $dataBase64Encode; + } + + /** + * 加密資料 + * + * @param array $source + * @return array + * + * @throws RtnException + */ + public function encryptData($source) + { + $field = $this->getFieldName(); + if (isset($source[$field])) { + $source[$field] = $this->encrypt($source[$field]); + } + + return $source; + } + + /** + * 取得加密欄位名稱 + * + * @return string + */ + public function getFieldName() + { + return 'Data'; + } +} diff --git a/src/Services/ArrayService.php b/src/Services/ArrayService.php new file mode 100644 index 0000000..9b67732 --- /dev/null +++ b/src/Services/ArrayService.php @@ -0,0 +1,23 @@ +request = $request; + $this->htmlService = $htmlService; + } + + /** + * 產生自動送出表單 JS + * + * @param string $formId + * @return string + */ + public function autoSubmitJs($formId) + { + $js =''; + + return $js; + } + + /** + * 產生自動送出表單 + * + * @param array $input + * @param string $action + * @param string $id + * @param string $target + * @param string $submitText + * @return string + */ + public function generate( + $input, + $action, + $target = '_self', + $id = 'auto-submit-form', + $submitText = 'auto-submit-button' + ) { + $request = $this->request->toArray($input); + + $html = $this->htmlService->header(); + $html .= ''; + $html .= $this->htmlService->form($request, $action, $target, $id, $submitText); + $html .= $this->autoSubmitJs($id); + $html .= ''; + $html .= $this->htmlService->footer(); + + return $html; + } +} diff --git a/src/Services/CheckMacValueService.php b/src/Services/CheckMacValueService.php new file mode 100644 index 0000000..ca6db52 --- /dev/null +++ b/src/Services/CheckMacValueService.php @@ -0,0 +1,137 @@ +setHashKey($key); + $this->setHashIv($iv); + } + + /** + * 附加檢查碼 + * + * @param array $source + * @param boolean $isSort + * @return array + */ + public function append($source, $isSort = true) + { + $source[$this->getFieldName()] = $this->generate($source); + if ($isSort) { + $source = $this->sort($source); + } + + return $source; + } + + /** + * 檢查碼參數過濾 + * + * @param array $source + * @return array + */ + public function filter($source) + { + if (isset($source[$this->getFieldName()])) { + unset($source[$this->getFieldName()]); + } + + return $source; + } + + /** + * 產生檢查碼 + * + * @param array $source + * @return string + * + * @throws RtnException + */ + public function generate($source) + { + try { + $filtered = $this->filter($source); + $sorted = $this->sort($filtered); + $combined = $this->toEncodeSourceString($sorted); + $encoded = UrlService::ecpayUrlEncode($combined); + $hash = $this->generateHash($encoded); + $checkMacValue = strtoupper($hash); + } catch (Exception $e) { + throw new RtnException(102); + } + + return $checkMacValue; + } + + /** + * 產生雜湊值 + * + * @param string $source + * @return string + */ + public function generateHash($source) + { + return hash('sha256', $source); + } + + /** + * 取得壓碼欄位名稱 + * + * @return string + */ + public function getFieldName() + { + return 'CheckMacValue'; + } + + /** + * 排序 + * + * @param array $source + * @return array + */ + public function sort($source) + { + return ArrayService::naturalSort($source); + } + + /** + * 轉換為編碼來源字串 + * + * @param array $source + * @return string + */ + public function toEncodeSourceString($source) + { + $combined = 'HashKey=' . $this->getHashKey(); + foreach ($source as $name => $value) { + $combined .= '&' . $name . '=' . $value; + } + $combined .= '&HashIV=' . $this->getHashIv(); + + return $combined; + } + + /** + * 檢核檢查碼 + * + * @param array $source + * @return boolean + */ + public function verify($source) + { + $checkMacValue = $this->generate($source); + + return ($checkMacValue === $source[$this->getFieldName()]); + } +} diff --git a/src/Services/CurlService.php b/src/Services/CurlService.php new file mode 100644 index 0000000..d989119 --- /dev/null +++ b/src/Services/CurlService.php @@ -0,0 +1,39 @@ +'; + } + + /** + * 產生表單 + * + * @param array $input + * @param string $action + * @param string $target + * @param string $id + * @param string $submitText + * @return string + */ + public function form($input, $action, $target, $id, $submitText) + { + $formTarget = $this->escapeHtml($target); + $formAction = $this->escapeHtml($action); + + $form = '
'; + foreach ($input as $name => $value) { + $inputName = $this->escapeHtml($name); + $inputValue = $this->escapeHtml($value); + $form .= ''; + } + $form .= '
'; + + return $form; + } + + /** + * 產生 Header + * + * @return string + */ + public function header() + { + $header = ''; + $header .= ''; + $header .= ''; + $header .= ''; + $header .= ''; + + return $header; + } +} diff --git a/src/Services/PostService.php b/src/Services/PostService.php new file mode 100644 index 0000000..83ed9c5 --- /dev/null +++ b/src/Services/PostService.php @@ -0,0 +1,59 @@ +request = $request; + $this->serverPostService = $httpClientService; + $this->response = $response; + } + + /** + * Server Post + * + * @param array $request + * @param string $url + * @return array + * + * @throws RtnException + */ + public function post($request, $url) + { + $reqeust = $this->request->get($request); + $source = $this->serverPostService->run($reqeust, $url); + $response = $this->response->get($source); + + return $response; + } +} diff --git a/src/Services/UrlService.php b/src/Services/UrlService.php new file mode 100644 index 0000000..03fe75e --- /dev/null +++ b/src/Services/UrlService.php @@ -0,0 +1,51 @@ +factory = new Factory; + } + + /** + * 建立類別 + * + * @param string $class + * @return mixed + */ + protected function create($class) + { + return $this->factory->createWithHash( + $class, + $this->stageOtpHashKey, + $this->stageOtpHashIv + ); + } +} diff --git a/src/TestCase/SingleServiceTestCase.php b/src/TestCase/SingleServiceTestCase.php new file mode 100644 index 0000000..0dbe0fe --- /dev/null +++ b/src/TestCase/SingleServiceTestCase.php @@ -0,0 +1,46 @@ +factory = new Factory; + $this->service = $this->getService(); + } + + /** + * 取得測試 Service + * + * @return mixed + */ + protected function getService() + { + return null; + } +} diff --git a/src/Traits/HashInfo.php b/src/Traits/HashInfo.php new file mode 100644 index 0000000..bed8836 --- /dev/null +++ b/src/Traits/HashInfo.php @@ -0,0 +1,61 @@ +hashIv; + } + + /** + * 取得 Hash Key + * + * @return string + */ + public function getHashKey() + { + return $this->hashKey; + } + + /** + * 設定 Hash IV + * + * @param string $iv + * @return void + */ + public function setHashIv($iv) + { + $this->hashIv = $iv; + } + + /** + * 設定 Hash Key + * + * @param string $key + * @return void + */ + public function setHashKey($key) + { + $this->hashKey = $key; + } +} diff --git a/src/Traits/StageInfo.php b/src/Traits/StageInfo.php new file mode 100644 index 0000000..f048350 --- /dev/null +++ b/src/Traits/StageInfo.php @@ -0,0 +1,19 @@ +