From 612a1eb476532806515bd5e3e44514a80bc59263 Mon Sep 17 00:00:00 2001 From: Hoang Tran Date: Thu, 29 Sep 2022 16:27:50 +0700 Subject: [PATCH] add plugin payment gpay atm --- .../gpay_domestic/gpay_domestic.php | 430 ++++++++++++++++++ .../gpay_domestic/gpay_domestic.xml | 100 ++++ ...n-GB.plg_redshop_payment_gpay_domestic.ini | 16 + ....plg_redshop_payment_gpay_domestic.sys.ini | 2 + .../redshop_payment/gpay_domestic/logo.png | Bin 0 -> 4832 bytes 5 files changed, 548 insertions(+) create mode 100644 plugins/redshop_payment/gpay_domestic/gpay_domestic.php create mode 100644 plugins/redshop_payment/gpay_domestic/gpay_domestic.xml create mode 100644 plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.ini create mode 100644 plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.sys.ini create mode 100644 plugins/redshop_payment/gpay_domestic/logo.png diff --git a/plugins/redshop_payment/gpay_domestic/gpay_domestic.php b/plugins/redshop_payment/gpay_domestic/gpay_domestic.php new file mode 100644 index 00000000000..16b5887979a --- /dev/null +++ b/plugins/redshop_payment/gpay_domestic/gpay_domestic.php @@ -0,0 +1,430 @@ + 0 && $data['carttotal'] <= 0) { + $amount = $data['shipping']; + } + + $this->getToken(); + + $jsonResult = $this->createTransaction($amount, $data['order_id']); + + JFactory::getApplication()->redirect($jsonResult['response']['order_url']); + } + + protected function preparePaymentInput($orderInfo) + { + } + + private function getToken() + { + $merchantCode = $this->params->get('merchantCode'); + $password = $this->params->get('password'); + + $data = array( + 'merchant_code' => $merchantCode, + 'password' => $password + ); + + $response = $this->execPostRequest("/authentication/token", json_encode($data)); + $result = json_decode($response); + $this->accessToken = $result->response->token; + } + + private function createTransaction($orderTotal, $orderId) + { + $user = JFactory::getUser(); + $merchantCode = $this->params->get('merchantCode'); + $amount = number_format($orderTotal, 0, "", ""); + $orderTime = time(); + $enbed = ""; + + $rawHash = "merchant_code=" . $merchantCode . "&order_id=" . $orderId . "&order_amt=" . $amount . "&embed_data=Customer Data&order_currency=VND&language=vi"; + $signature = $this->getSignature($rawHash); + $callbackUrl = $this->getNotifyUrl($orderId); + $webhookUrl = $this->getWebhookUrl($orderId); + + $description = "Order: " . $orderId; + $customerId = $this->params->get('customerId'); + + $tokenId = RedshopEntityField_Data::getInstance()->loadItemByArray( + array( + 'fieldid' => RedshopHelperExtrafields::getField('rs_token_gpay')->id, + 'itemid' => $user->id, + 'section' => RedshopHelperExtrafields::SECTION_PRIVATE_BILLING_ADDRESS + ) + )->getItem()->data_txt; + + $data = array( + 'merchant_code' => $merchantCode, + 'order_id' => $orderId, + 'order_amt' => (int) $amount, + 'order_currency' => 'VND', + 'description' => $description, + 'order_time' => $orderTime, + 'customer_id' => $customerId, + 'callback_url' => $callbackUrl, + 'webhook_url' => $webhookUrl, + 'token_id' => $tokenId, + 'language' => 'vi', + 'service_code' => 'PAYMENTGATEWAY', + 'payment_method' => 'BANK_ATM', + 'payment_type' => 'IMMEDIATE', + 'signature' => $signature, + 'embed_data' => 'Customer Data' + ); + + //Create order in API Gpay + $result = $this->execPostRequest("/order/init", json_encode($data)); + + $response = json_decode($result, true); + + if ($response['meta']['code'] != 200 || $response == NULL) + { + $app = JFactory::getApplication(); + $Itemid = RedshopHelperRouter::getCheckoutItemId(); + $language = $app->input->get('lang'); + + switch ($language) { + case 'vi-VN': $lang = 'vi';break; + case 'en-GB': $lang = 'en';break; + default: $lang = 'vi'; + } + + if ($lang = 'vi') + { + $messageError = 'Lỗi thanh toán'; + } + else + { + $messageError = 'Error with payment'; + } + + $msg = $response['return_message'] ?? $messageError; + $msgType = 'error'; + + // Write order log + \RedshopHelperOrder::writeOrderLog($orderId, 0, 'P', 'Unpaid', $msg); + + $app->redirect(JRoute::_('index.php?option=com_redshop&view=checkout&Itemid=' . $Itemid . '&lang=' .$lang, false), $msg, $msgType); + } + + return $response; // decode jsonson + } + + private function execPostRequest($url, $data, $method = "POST") + { + $endpoint = $this->params->get('production'); + + if ($this->params->get('isTest')) { + $endpoint = $this->params->get('sandbox'); + } + + $ch = curl_init($endpoint . $url); + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + + if ($method === "POST") { + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt( + $ch, + CURLOPT_HTTPHEADER, + array( + 'Signature ' . $this->getSignature($data), + 'Accept: application/json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $this->accessToken + ) + ); + + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); + $result = curl_exec($ch); + curl_close($ch); + + //Write log + $this->writeLog( + "Request: ".json_encode($data,JSON_UNESCAPED_UNICODE), + 'payment_api' + ); + $this->writeLog( + "Response: ".json_encode($result,JSON_UNESCAPED_UNICODE), + 'payment_api' + ); + + return $result; + } + + + /** + * Notify payment + * + * @param string $element Name of plugin + * @param array $request HTTP request data + * + * @return object Contains the information of order success of falier in object + * @since 1.0 + */ + public function onNotifyPaymentGpay_domestic($element, $request) + { + if ($element != 'gpay_domestic') { + return false; + } + + $app = JFactory::getApplication(); + $user = JFactory::getUser(); + + $merchantCode = $this->params->get('merchantCode'); + $gpayTransId = $request['gpay_trans_id']; + $orderId = $request['order_id']; + $token = $request['token_info']; + + $this->getToken(); + + $rawHash = "merchant_code=" . $merchantCode . "&gpay_trans_id=" . $gpayTransId; + $signature = $this->getSignature($rawHash); + + $data = array( + 'merchant_code' => $merchantCode, + 'gpay_trans_id' => $gpayTransId, + 'signature' => $signature + ); + + $response = $this->execPostRequest("/order/detail", json_encode($data)); + + $tokerUserExits = RedshopEntityField_Data::getInstance()->loadItemByArray( + array( + 'fieldid' => RedshopHelperExtrafields::getField('rs_token_gpay')->id, + 'itemid' => $user->id, + 'section' => RedshopHelperExtrafields::SECTION_PRIVATE_BILLING_ADDRESS + ) + )->getItem()->data_txt; + + if (!$tokerUserExits) + { + $this->saveTokenUser($user, $token); + } + + $result = json_decode($response, true); + $responseOrderIds = $result['response']['order_id']; + + //Write Log in custom field + $this->saveDataJson($responseOrderIds , json_encode(json_encode($result), JSON_UNESCAPED_UNICODE)); + + if ($result['response']['order_status'] == "ORDER_SUCCESS") + { + $values = $this->setStatus( + $responseOrderIds, + $result['response']['gpay_trans_id'], + $this->params->get('verify_status', ''), + 'Paid', + JText::_('PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_SUCCESS'), + JText::_('PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_SUCCESS') + ); + + if (isset($request['ipn']) && $request['ipn']) + { + $values->log = $values->log . ' IPN'; + //Change order status + RedshopHelperOrder::changeOrderStatus($values); + } + + return $values; + } + + + if (isset($request['ipn']) && $request['ipn']) + { + $values = $this->setStatus( + $responseOrderIds, + $result['response']['gpay_trans_id'], + $this->params->get('verify_status', ''), + 'Paid', + JText::_('PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_SUCCESS'), + JText::_('PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_SUCCESS') + ); + + $values->log = $values->log . ' IPN'; + + //Change order status + RedshopHelperOrder::changeOrderStatus($values); + } + // Redirect "checkout" page when order unpaid. + $app = JFactory::getApplication(); + $itemId = $app->input->get('Itemid'); + + $app->redirect(JRoute::_( + JUri::base() . "index.php?option=com_redshop&Itemid=". $itemId."&view=checkout", + false + )); + } + + public function saveDataJson($orderId = '', $dataJson = '') + { + try + { + $db = JFactory::getDbo(); + $customField = RedshopEntityField::getInstanceByField('name', + 'rs_gpay_order_ref'); + + if (!$customField || !$dataJson || !$orderId) + { + return; + } + + $columns = array('fieldid', 'data_txt', 'itemid', 'section'); + + $values = array( + $db->q((int) $customField->get('id')), + $db->q($dataJson), + $db->q((int) $orderId), + $db->q((int) RedshopHelperExtrafields::SECTION_ORDER) + ); + + $query = $db->getQuery(true) + ->insert($db->qn('#__redshop_fields_data')) + ->columns($db->qn($columns)) + ->values(implode(',', $values)); + + $db->setQuery($query)->execute(); + } + catch (Exception $e) + { + return; + } + } + + public function saveTokenUser($user, $token) + { + $db = JFactory::getDbo(true); + + $values = array( + RedshopHelperExtrafields::getField('rs_token_gpay')->id, + $token, + $user->id, + (int) RedshopHelperExtrafields::SECTION_PRIVATE_BILLING_ADDRESS + ); + + $query = $db->getQuery(true); + $query->insert($db->quoteName('#__redshop_fields_data')) + ->columns($db->quoteName(array('fieldid', 'data_txt', 'itemid', 'section'))) + ->values(implode(',', $db->q($values))); + + $db->setQuery($query)->execute(); + } + + /** + * + * Get webhook information + * + * @since YI-1066 + * @return void + */ + public function getInformationByWebhook($data, $payload) { + + if($data['payment_method'] != 'gpay_domestic') return; + + $status = $payload['status']; + $gpayTransId = $payload['gpay_trans_id']; + $gOrderId = $payload['order_id']; + + if ($status === "ORDER_SUCCESS") + { + //Write Log in custom field + $this->saveDataJson($gOrderId , json_encode($payload, JSON_UNESCAPED_UNICODE)); + + if ($status === "ORDER_SUCCESS") + { + $values = $this->setStatus( + $gOrderId, + $gpayTransId, + $this->params->get('verify_status', ''), + 'Paid', + JText::_('PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_SUCCESS'), + 'Success' + ); + + //Change order status + RedshopHelperOrder::changeOrderStatus($values); + + echo json_encode( array( + 'code' => 200, + 'message' => "Order $gOrderId payment successfully!" + ) ); + } + } + + exit; + } + + /** + * + * Reprocess the encoded base-64 data from webhook and return an array. + * + * @param $webhookDataStr string this string should be encoded base-64, get from the webhook + * @since YI-1066 + * @return array|false + */ + private function webhookDataReprocess(string $webhookDataStr) { + $dataDecoded = base64_decode($webhookDataStr); + + if(!$dataDecoded) return false; + + return json_decode($dataDecoded, true); + } + + private function writeLog($comment, $name, $logType = Log::NOTICE) + { + Log::addLogger( + array('text_file' => $name . '.log'), + Log::ALL, + 'com_redshop' + ); + + Log::add($comment, $logType, 'com_redshop'); + } + + private function getSignature($data) + { + $private_key_pem = openssl_pkey_get_private($this->params->get('privateKey') + ); + + openssl_sign($data, $binary_signature, $private_key_pem, OPENSSL_ALGO_SHA256); + + return base64_encode($binary_signature); + } +} diff --git a/plugins/redshop_payment/gpay_domestic/gpay_domestic.xml b/plugins/redshop_payment/gpay_domestic/gpay_domestic.xml new file mode 100644 index 00000000000..1c55cb82653 --- /dev/null +++ b/plugins/redshop_payment/gpay_domestic/gpay_domestic.xml @@ -0,0 +1,100 @@ + + + PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC + 1.0.0 + 2.0 + Mar 2022 + redCOMPONENT.com + support@redweb.vn + https://redweb.vn/ + redWEB Vietnam + GNU General Public License v2 + PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_DESC + + + gpay_domestic.php + logo.png + + + + language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.ini + language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.sys.ini + + + + +
+ + + + + + + + + + + + + +
+
+ + + +
+
+
+
diff --git a/plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.ini b/plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.ini new file mode 100644 index 00000000000..d30fe1b05ba --- /dev/null +++ b/plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.ini @@ -0,0 +1,16 @@ +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC="ATM Card (GPay)" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_DESC="GPAY domestic" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_MERCHANT_CODE="MerchantCode" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PASSWORD="Password" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PRIVATE_KEY="Private Key" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_ORDER_STATUS="Order Status for successful transactions" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_SUCCESS_FAILED_STATUS="Order Status for failed transactions" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_ENVIRONMENT="Environment" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_LOGO="Logo" +PLG_GPAY_DOMESTIC="ATM Card (GPay)" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_SUCCESS="Payment success" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_SUCCESS_LOG="Payment Logged successfully with GPAY domestic." +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_REJECTED="Payment Rejected" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_PAYMENT_REJECTED_LOG="Payment rejected on GPAY domestic." +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_URL_API_PRODUCTION="Url Api Production" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_URL_API_SANDBOX="Url Api Sandbox" \ No newline at end of file diff --git a/plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.sys.ini b/plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.sys.ini new file mode 100644 index 00000000000..a772408dcb7 --- /dev/null +++ b/plugins/redshop_payment/gpay_domestic/language/en-GB/en-GB.plg_redshop_payment_gpay_domestic.sys.ini @@ -0,0 +1,2 @@ +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC="ATM Card (GPay)" +PLG_REDSHOP_PAYMENT_GPAY_DOMESTIC_DESC="GPAY domestic" \ No newline at end of file diff --git a/plugins/redshop_payment/gpay_domestic/logo.png b/plugins/redshop_payment/gpay_domestic/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2a2331f566e1eac8e2987487317b4c4c4a265f GIT binary patch literal 4832 zcmaJ@c{r49-@a|xmq@lSjU}=SV~MfvqpV{avNXon$7l?L${LC6Th^>0Lz@`87F$G! zgd%HVtRaOyJ@3==et&$=cOS=n-`Dax&);?)*KyspFf(Fh;%5Q?fYlhTk2wCT9KYun zP8@$7P9#_W0F$1Vo}PuVo*u{_k8}6(#Q;F?M1d6%nZ9>gb9xWMZmT=K@i^Nz5&(;k z7XE#FE zDU_e@f3_^;FC7eo46;X@rH7UFgQC>59ib1pGcr67PFS?iutKo2kQ$m*#Z z6OaJ_z9H9j!N3DnVD+1^CLJ(dl|ID-jF+6LVgM4UfE)=UQEKgG;EGKoLYlhc1yDrN z2FucR)KW#**F%k{BU|zB1c!c9{{OP1A0PSZ)iX!G(~d5?K4#_{xM`Y z70}U_`6m0)u)-;F=39Q^*2c{2+1VGqk9AQzk{s@g^WgmKJP|JV5{=0K3jpx%4?RCp z(Mnlbod3M&zT~_9t+V9FX9EY;Ji9r4)N+TK7T_=&T%T}RSQw-6@1}Yb*zQwx8JMsI zJg4`a6ebyq9GKpQ?k9w(9B2y_7F+bF3!Y(OW~PX_7PrdHC20Oq_>7xL2-`j0J(>*N z0Iue0=hSqRMju{9`32P?l9u{tb>;o`mPC%aW5N%UB!^}dA3I8O$8REUlP$oe-64&L zRQB$!_gr3ub!mpvF>=M9=foA>!WX

Eu9y~423n3gWeM)=!Ix22>3XL+!9})Y@TXK0 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