From ba54f8313c1327fb15de3e2a6e29e53248af8910 Mon Sep 17 00:00:00 2001 From: Sergey Yarmolich Date: Thu, 23 Jan 2025 18:19:58 +0800 Subject: [PATCH] add cryptomus pyament gateway --- gateways/cryptomus/icons/cryptomus.svg | 1 + gateways/cryptomus/icons/cryptomus_front.svg | 40 +++ .../leyka-class-cryptomus-gateway.php | 332 ++++++++++++++++++ gateways/cryptomus/lib/Payment.php | 117 ++++++ gateways/cryptomus/lib/RequestBuilder.php | 90 +++++ .../cryptomus/lib/RequestBuilderException.php | 44 +++ 6 files changed, 624 insertions(+) create mode 100644 gateways/cryptomus/icons/cryptomus.svg create mode 100644 gateways/cryptomus/icons/cryptomus_front.svg create mode 100644 gateways/cryptomus/leyka-class-cryptomus-gateway.php create mode 100644 gateways/cryptomus/lib/Payment.php create mode 100644 gateways/cryptomus/lib/RequestBuilder.php create mode 100644 gateways/cryptomus/lib/RequestBuilderException.php diff --git a/gateways/cryptomus/icons/cryptomus.svg b/gateways/cryptomus/icons/cryptomus.svg new file mode 100644 index 000000000..8ff77ece9 --- /dev/null +++ b/gateways/cryptomus/icons/cryptomus.svg @@ -0,0 +1 @@ + diff --git a/gateways/cryptomus/icons/cryptomus_front.svg b/gateways/cryptomus/icons/cryptomus_front.svg new file mode 100644 index 000000000..dbe05b3dd --- /dev/null +++ b/gateways/cryptomus/icons/cryptomus_front.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gateways/cryptomus/leyka-class-cryptomus-gateway.php b/gateways/cryptomus/leyka-class-cryptomus-gateway.php new file mode 100644 index 000000000..83b96fdef --- /dev/null +++ b/gateways/cryptomus/leyka-class-cryptomus-gateway.php @@ -0,0 +1,332 @@ +_id = 'cryptomus'; + $this->_title = __('Cryptomus', 'leyka'); + + $this->_description = apply_filters( + 'leyka_gateway_description', + __('https://cryptomus.com/', 'leyka'), + $this->_id + ); + + $this->_docs_link = 'https://doc.cryptomus.com/personal'; + $this->_registration_link = 'https://app.cryptomus.com/signup'; + $this->_has_wizard = false; + + $this->_min_commission = '1'; + $this->_receiver_types = ['legal']; + $this->_may_support_recurring = false; + + } + + protected function _set_options_defaults() + { + + if ($this->_options) { + return; + } + + $this->_options = [ + $this->_id . '_payment_key' => [ + 'type' => 'text', + 'title' => __('Payment API-key', 'leyka'), + /* translators: %s: Label. */ + 'comment' => __('You can find the API key in the settings of your personal account. Read more https://cryptomus.com/plugins', 'leyka'), + 'required' => true, + ], + $this->_id . '_merchant_uuid' => [ + 'type' => 'text', + 'title' => __('Merchant UUID', 'leyka'), + /* translators: 1: Label, 2: Title. */ + 'comment' => __('You can find the UUID in the settings of your personal account. Read more https://cryptomus.com/plugins', 'leyka'), + 'required' => true, + /* translators: %s: Placeholder. */ + 'is_password' => true, + ], + $this->_id . '_lifetime' => [ + 'type' => 'number', + 'title' => __('Invoice Lifetime (hours)', 'leyka'), + /* translators: %s: Label. */ + 'required' => false, + 'default' => 1 + ] + ]; + + } + + public function is_setup_complete($pm_id = false) + { + return leyka_options()->opt($this->_id . '_payment_key') + && leyka_options()->opt($this->_id . '_merchant_uuid'); + } + + protected function _initialize_pm_list() + { + if (empty($this->_payment_methods['cryptomus'])) { + $this->_payment_methods['cryptomus'] = Leyka_Cryptomus::get_instance(); + } + } + + public function process_form($gateway_id, $pm_id, $donation_id, $form_data) + { + $currency_id = !empty($_POST['leyka_donation_currency']) ? + $_POST['leyka_donation_currency'] : leyka_get_main_currency(); + $donation = Leyka_Donations::get_instance()->get_donation($donation_id); + + $apiKey = str_replace(' ', '', leyka_options()->opt($this->_id . '_payment_key')); + $merchantId = str_replace(' ', '', leyka_options()->opt($this->_id . '_merchant_uuid')); + + $paymentData = [ + 'amount' => $form_data['leyka_donation_amount'], + 'currency' => $currency_id, + 'merchant' => $merchantId, + 'order_id' => 'leyka_' . $donation_id, + 'url_return' => get_site_url() . '/leyka/service/' . $this->_id . '/', + 'url_callback' => get_site_url() . '/leyka/service/' . $this->_id . '/', + 'lifetime' => (int)leyka_options()->opt($this->_id . '_lifetime') * 3600, + 'plugin_name' => 'leyka', + ]; + + try { + $paymentService = new \Payment($apiKey, $merchantId); + $response = $paymentService->create($paymentData); + $donation->add_gateway_response($response); + $this->api_redirect_url = $response['url']; + } catch (\RequestBuilderException $exception) { + $donation->add_gateway_response($exception->getMessage()); + + leyka()->add_payment_form_error(new WP_Error( + 'leyka_donation_error', + /* translators: 1: Message, 2: Email. */ + sprintf( + /* translators: 1: Message, 2: Support email. */ + __('Error while processing the payment: %1$s. Your money will remain intact. Please report to the website tech support.', 'leyka'), + $exception->getMessage(), + leyka_get_website_tech_support_email() + ))); + } + + return $paymentData; + } + + public function submission_redirect_url($current_url, $pm_id) + { + return $this->api_redirect_url; + } + + + public function submission_redirect_type($redirect_type, $pm_id, $donation_id) + { + return 'redirect'; + } + + public function submission_form_data($form_data, $pm_id, $donation_id) + { + $donation = Leyka_Donations::get_instance()->get_donation($donation_id); + $currency_id = !empty($_POST['leyka_donation_currency']) ? + $_POST['leyka_donation_currency'] : leyka_get_main_currency(); + + $data = [ + 'amount' => $form_data['leyka_donation_amount'], + 'currency' => $currency_id, + 'order_id' => 'layka_' . $donation_id, + 'url_return' => site_url(), + 'url_callback' => get_site_url() . '/leyka/service/' . $this->_id . '/', + 'lifetime' => (int)leyka_options()->opt($this->_id . '_lifetime') * 3600, + 'plugin_name' => 'leyka', + ]; + + return apply_filters('leyka_cryptomus_custom_submission_data', $data, $pm_id, $donation); + } + + public function get_gateway_response_formatted(Leyka_Donation_Base $donation) + { + if (!$donation->gateway_response) { + return []; + } + + $vars = $donation->gateway_response; + + if (!$vars || !is_array($vars)) { + return []; + } + + $vars_final[__('UUID:', 'leyka')] = $vars['uuid']; + $vars_final[__('Order:', 'leyka')] = $vars['order_id']; + $vars_final[__('Status:', 'leyka')] = $vars['status']; + + return $vars_final; + } + + protected function _is_callback_hash_correct($data) + { + $apiKey = str_replace(' ', '', leyka_options()->opt($this->_id . '_payment_key')); + + if (!$apiKey) { + return false; + } + + $signature = $data['sign']; + if (!$signature) { + return false; + } + + unset($data['sign']); + + $hash = md5(base64_encode(json_encode($data, JSON_UNESCAPED_UNICODE)) . $apiKey); + if (!hash_equals($hash, $signature)) { + return false; + } + + return true; + + } + + public function _handle_service_calls($call_type = '') + { + $notification = json_decode(file_get_contents('php://input'), true); + + if (empty($notification['uuid'])) { + exit(500); + } + + if (!$this->_is_callback_hash_correct($notification)) { + exit(500); + } + + if (preg_match('/^leyka_(\d+)$/', $notification['order_id'], $matches)) { + $orderId = $matches[1]; + } else { + exit(500); + } + + $donation = Leyka_Donations::get_instance()->get($orderId); + + if( !$donation ) { + /** @todo Process the error somehow */ + exit(500); + } + $donation->add_gateway_response($notification); + + $cryptomusOrderStatus = $notification['status']; + + $orderStatus = null; + if (in_array($cryptomusOrderStatus, self::CRYPTOMUS_ERROR_STATUSES)) { + Leyka_Donation_Management::send_error_notifications($donation); + } elseif (in_array($cryptomusOrderStatus, self::CRYPTOMUS_PENDING_STATUSES)) { + // nothing to do + } elseif (in_array($cryptomusOrderStatus, self::PAID_STATUSES)) { + $donation->status = 'funded'; + Leyka_Donation_Management::send_all_emails($donation->id); + + if( // GUA direct integration - "purchase" event: + leyka_options()->opt('use_gtm_ua_integration') === 'enchanced_ua_only' + && leyka_options()->opt('gtm_ua_tracking_id') + && in_array('purchase', leyka_options()->opt('gtm_ua_enchanced_events')) + // We should send data to GA only for single or init recurring donations: + && ($donation->type === 'single' || $donation->is_init_recurring_donation) + ) { + require_once LEYKA_PLUGIN_DIR.'vendor/autoload.php'; + + $analytics = new TheIconic\Tracking\GoogleAnalytics\Analytics(true); + $analytics // Main params: + ->setProtocolVersion('1') + ->setTrackingId(leyka_options()->opt('gtm_ua_tracking_id')) + ->setClientId($donation->ga_client_id ? $donation->ga_client_id : leyka_gua_get_client_id()) + // Transaction params: + ->setTransactionId($donation->id) + ->setAffiliation(get_bloginfo('name')) + ->setRevenue($donation->amount) + ->addProduct([ // Donation params + 'name' => $donation->payment_title, + 'price' => $donation->amount, + 'brand' => get_bloginfo('name'), // Mb, it won't work with it + 'category' => $donation->type_label, // Mb, it won't work with it + 'quantity' => 1, + ]) + ->setProductActionToPurchase() + ->setEventCategory('Checkout') + ->setEventAction('Purchase') + ->sendEvent(); + } + // GUA direct integration - "purchase" event END + } + exit('ok'); + } + +} + +class Leyka_Cryptomus extends Leyka_Payment_Method +{ + + protected static $_instance = null; + + public function _set_attributes() + { + + $this->_id = 'cryptomus'; + $this->_gateway_id = 'cryptomus'; + $this->_category = 'digital_currencies'; + + $this->_description = apply_filters( + 'leyka_pm_description', + '', + $this->_id, + $this->_gateway_id, + $this->_category + ); + + $this->_label_backend = __('Cryptocurrency', 'leyka'); + $this->_label = __('Cryptomus', 'leyka'); + $this->_icons = apply_filters('leyka_icons_'.$this->_gateway_id.'_'.$this->_id, [ + LEYKA_PLUGIN_BASE_URL.'gateways/'.$this->_id.'/icons/cryptomus_front.svg', + ]); + + $this->_supported_currencies = ['eur', 'usd', 'rub', 'uah', 'kgs', 'byn', 'gbp', 'jpy', 'aud', 'cad', 'chf', 'nzd', 'cny', 'hkd', 'sgd', 'inr']; + $this->_default_currency = 'usd'; + + } + +} + +function leyka_add_gateway_cryptomus() +{ // Use named function to leave a possibility to remove/replace it on the hook + leyka_add_gateway(Leyka_Cryptomus_Gateway::get_instance()); +} + +add_action('leyka_init_actions', 'leyka_add_gateway_cryptomus'); diff --git a/gateways/cryptomus/lib/Payment.php b/gateways/cryptomus/lib/Payment.php new file mode 100644 index 000000000..2e67d7466 --- /dev/null +++ b/gateways/cryptomus/lib/Payment.php @@ -0,0 +1,117 @@ +requestBuilder = new RequestBuilder($paymentKey, $merchantUuid); + } + + + /** + * @param array $parameters Additional parameters + * @return bool|mixed + * @throws RequestBuilderException + */ + public function services(array $parameters = []) + { + return $this->requestBuilder->sendRequest($this->version . '/payment/services', $parameters); + } + + /** + * @param array $data + * - @var string amount: Amount to pay + * - @var string currency: Payment currency + * - @var string network: Payment network + * - @var string order_id: Order ID in your system + * - @var string url_return: Redirect link + * - @var string url_callback: Callback link + * - @var boolean is_payment_multiple: Allow surcharges on payment * + * - @var string lifetime: Payment lifetime in seconds + * - @var string to_currency: Currency to convert amount to + * @return bool|mixed + * @throws RequestBuilderException + */ + public function create(array $data) + { + return $this->requestBuilder->sendRequest($this->version . '/payment', $data); + } + + /** + * uuid or order_id + * @param array $data + * - @var string uuid + * - @var string order_id + * @return bool|mixed + * @throws RequestBuilderException + */ + public function info($data = []) + { + return $this->requestBuilder->sendRequest($this->version . '/payment/info', $data); + } + + /** + * @param string|int $page Pagination cursor + * @param array $parameters Additional parameters + * @return bool|mixed + * @throws RequestBuilderException + */ + public function history($page = 1, array $parameters = []) + { + $data = array_merge($parameters, ['cursor' => strval($page)]); + return $this->requestBuilder->sendRequest($this->version . '/payment/list', $data); + } + + /** + * @param array $parameters Additional parameters + * @return bool|mixed + * @throws RequestBuilderException + */ + public function balance(array $parameters = []) + { + return $this->requestBuilder->sendRequest($this->version . '/balance', $parameters); + } + + /** + * uuid or order_id + * @param array $data + * - @var string uuid: Payment's UUID + * - @var string order_id: Order ID in your system + * @return bool|mixed + * @throws RequestBuilderException + */ + public function reSendNotifications(array $data) + { + return $this->requestBuilder->sendRequest($this->version . '/payment/resend', $data); + } + + /** + * @param array $data + * - @var string network: Network + * - @var string currency: Payment currency + * - @var string order_id: Order ID in your system + * - @var string url_callback: Callback url + * @return bool|mixed + * @throws RequestBuilderException + */ + public function createWallet(array $data) + { + return $this->requestBuilder->sendRequest($this->version . '/wallet', $data); + } +} \ No newline at end of file diff --git a/gateways/cryptomus/lib/RequestBuilder.php b/gateways/cryptomus/lib/RequestBuilder.php new file mode 100644 index 000000000..ab878fa38 --- /dev/null +++ b/gateways/cryptomus/lib/RequestBuilder.php @@ -0,0 +1,90 @@ +secretKey = $secretKey; + $this->merchantUuid = $merchantUuid; + } + + /** + * @param $uri + * @param array $data + * @return bool|mixed + * @throws RequestBuilderException + */ + public function sendRequest($uri, array $data = []) + { + $curl = curl_init(); + $url = self::API_URL . $uri; + $body = json_encode($data, JSON_UNESCAPED_UNICODE); + + $headers = [ + 'Accept: application/json', + 'Content-Type: application/json;charset=UTF-8', + 'Content-Length: ' . strlen($body), + 'merchant: ' . $this->merchantUuid, + 'sign: ' . md5(base64_encode($body) . $this->secretKey) + ]; + + curl_setopt_array( + $curl, + [ + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $body, + CURLOPT_RETURNTRANSFER => 1, + ] + ); + + $response = curl_exec($curl); + $responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + + if ($response === false) { + throw new RequestBuilderException(curl_error($curl), $responseCode, $uri); + } + + if (false === empty($response)) { + $json = json_decode($response, true); + if (is_null($json)) { + throw new RequestBuilderException(json_last_error_msg(), $responseCode, $uri); + } + + if ($responseCode !== 200 || (!is_null($json['state']) && $json['state'] != 0)) { + if (!empty($json['message'])) { + throw new RequestBuilderException($json['message'], $responseCode, $uri); + } + + if (!empty($json['errors'])) { + throw new RequestBuilderException('Validation error', $responseCode, $uri, $json['errors']); + } + } + + if (!empty($json['result']) && !is_null($json['state']) && $json['state'] == 0) { + return $json['result']; + } + } + + return true; + } +} \ No newline at end of file diff --git a/gateways/cryptomus/lib/RequestBuilderException.php b/gateways/cryptomus/lib/RequestBuilderException.php new file mode 100644 index 000000000..b1c880942 --- /dev/null +++ b/gateways/cryptomus/lib/RequestBuilderException.php @@ -0,0 +1,44 @@ +method = $uri; + $this->errors = $errors; + + parent::__construct($message, $responseCode, $previous); + } + + /** + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * @return array + */ + public function getErrors() + { + return $this->errors; + } +} \ No newline at end of file