get_name(); }, $cryptoArray); array_multisort($keys, $cryptoArray); $this->cryptos = $cryptoArray; $this->id = 'agc_gateway'; //$this->icon = AGILE_CASH_PLUGIN_DIR . '/assets/img/dogecoin_logo_small.png'; $this->title = 'Pay using cryptocurrency'; $this->has_fields = true; $this->method_title = 'Pay using cryptocurrency'; $this->method_description = 'Allow customers to pay using cryptocurrency'; $this->init_form_fields(); $this->init_settings(); add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options')); add_action('woocommerce_thankyou_' . $this->id, array( $this, 'thank_you_page') ); } // WooCommerce Admin Payment Method Settings public function init_form_fields() { // general settings $generalSettings = array( 'general_settings' => array( 'title' => 'General settings', 'type' => 'title', ), 'enabled' => array( 'title' => 'Enable/Disable', 'woocommerce', 'type' => 'checkbox', 'label' => 'Enable cryptocurrency payment', 'woocommerce', 'default' => 'yes', ) ); $cryptoSettings = array(); $cryptoSettings['crypto wallets'] = array( 'title' => 'Cryptocurrency Options', 'type' => 'title', 'description' => 'Enable/Disable cryptocurrencies and store your public wallet addresses.', ); foreach ($this->cryptos as $crypto) { $cryptoSettings[$crypto->get_name() . ' Options'] = array( 'title' => $crypto->get_name() . ' (' . $crypto->get_id() . ')', 'type' => 'title', ); $cryptoSettings[$crypto->get_id() . '_enabled'] = array( 'title' => 'Enable/Disable', 'type' => 'checkbox', 'label' => 'Enable ' . $crypto->get_name(), ); $cryptoSettings[$crypto->get_id() . '_address'] = array( 'title' => $crypto->get_name() . ' Wallet Address', 'type' => 'text', ); if ($crypto->electrum_enabled()) { $cryptoSettings[$crypto->get_id() . '_electrum_enabled'] = array( 'title' => 'Electrum enabled', 'type' => 'checkbox', 'default' => 'no' ); $cryptoSettings[$crypto->get_id() . '_electrum_mpk'] = array( 'title' => 'Electrum mpk', 'type' => 'text', 'description' => 'Your electrum master public key.', ); } } $pricingSettings = array( 'pricing options' => array( 'title' => 'Pricing Options', 'type' => 'title', 'description' => 'Price is average of APIs selected.', ), 'use_crypto_compare' => array( 'title' => 'Use crypto compare', 'type' => 'checkbox', 'default' => 'yes', 'description' => 'Use crypto compare in your cryptocurrency pricing.', ), 'use_hitbtc' => array( 'title' => 'Use hitbtc.com ticker price', 'type' => 'checkbox', 'default' => 'no', 'description' => 'Use hitbtc.com in your cryptocurrency pricing.', ), 'use_gateio' => array( 'title' => 'Use gateio.io ticker price', 'type' => 'checkbox', 'default' => 'no', 'description' => 'Use gaitio.io in your cryptocurrency pricing.', ), 'use_bittrex' => array( 'title' => 'Use bittrex.com ticker price', 'type' => 'checkbox', 'default' => 'no', 'description' => 'Use bittrex.com in your cryptocurrency pricing.', ), 'use_poloniex' => array( 'title' => 'Use poloniex.com average trade value', 'type' => 'checkbox', 'default' => 'no', 'description' => 'Use poloniex in your cryptocurrency pricing.', ), ); $this->form_fields = array_merge($generalSettings, $cryptoSettings, $pricingSettings); } // This runs when the user hits the checkout page // We load our crypto select with valid crypto currencies public function payment_fields() { $validCryptos = $this->cryptos_with_valid_settings(); $selectOptions = $this->get_select_options_for_cryptos($validCryptos); woocommerce_form_field( 'agc_currency_id', array( 'type' => 'select', 'label' => 'Choose a cryptocurrency', 'required' => true, 'default' => 'DOGE', 'options' => $selectOptions, ) ); } // This is called whenever the user saves the woocommerce admin settings, for some reason AFTER validate_enabled_field is called public function validate_BTC_electrum_enabled_field($key,$value) { error_log('validating btc electrum enabled'); $post_data = $this->get_post_data(); $gatewaySettings = new AGC_Admin_Gateway_Settings($this->id, $this->cryptos, $post_data); $validMpk = $gatewaySettings->crypto_has_valid_electrum_mpk('BTC'); //$validAddress = $gatewaySettings->crypto_has_valid_wallet('BTC'); if (! $value) { return 'no'; } if (!$validMpk) { WC_Admin_Settings::add_error('Electrum was enabled for Bitcoin but mpk is invalid. Disabling Electrum for Bitcoin'); return 'no'; } return 'yes'; } // This is called whenever the user saves the woocommerce admin settings public function validate_enabled_field( $key, $value ) { error_log('validating enabled'); // if the gateway is not enabled do not do any validation if (! $value) { return 'no'; } $result = 'yes'; $post_data = $this->get_post_data(); $gatewaySettings = new AGC_Admin_Gateway_Settings($this->id, $this->cryptos, $post_data); // fail if no pricing options are selected if (! $gatewaySettings->has_one_enabled_pricing_options()) { WC_Admin_Settings::add_error( 'You must select at least one pricing option' ); $result = 'no'; } // fail if no cryptos are enabled if (! $gatewaySettings->has_one_enabled_crypto()) { WC_Admin_Settings::add_error( 'You must enable at least one cryptocurrency' ); $result = 'no'; } // validation for each crypto foreach ($this->cryptos as $crypto) { $cryptoId = $crypto->get_id(); $cryptoEnabled = $gatewaySettings->is_crypto_enabled($cryptoId); $electrumEnabled = $gatewaySettings->is_electrum_enabled($cryptoId); $validMpk = $gatewaySettings->crypto_has_valid_electrum_mpk($cryptoId); $validAddress = $gatewaySettings->crypto_has_valid_wallet($cryptoId); // fall back to regular address but let user know if ($cryptoEnabled && $electrumEnabled && (!$validMpk) && $validAddress) { // code in validate_BTC_enabled_field handles disabling electrum WC_Admin_Settings::add_error("Invalid MPK for " . $crypto->get_name() . '... We will use your basic wallet address.'); } if ($cryptoEnabled && $electrumEnabled && (!$validMpk) && (!$validAddress)) { // code in validate_BTC_enabled_field handles disabling electrum WC_Admin_Settings::add_error("Invalid wallet address for " . $crypto->get_name() .'... Disabling Plug-in until valid address is entered for each enabled cryptocurrency'); $result = 'no'; } if ($cryptoEnabled && (!$electrumEnabled) && (!$validAddress)) { WC_Admin_Settings::add_error("Invalid wallet address for " . $crypto->get_name() .'... isabling Plug-in until valid address is entered for each enabled cryptocurrency'); $result = 'no'; } } return $result; } // This runs when the user selects Place Order, before process_payment, has nothing to do with the other validation methods public function validate_fields() { // if the currently selected gateway is this gateway we set transients related to conversions and prevent the customer from hitting the thank you page by throwing the WooCommerce Error Notice. if (WC()->session->get('chosen_payment_method') === $this->id) { try { $chosenCrypto = $_POST['agc_currency_id']; $curr = get_woocommerce_currency(); $cryptoPerUsd = $this->get_crypto_value_in_usd($chosenCrypto, $this->cryptos[$chosenCrypto]->get_update_interval()); // this is just a check to make sure we can hit the currency exchange if we need to $usdTotal = AGC_Exchange::get_order_total_in_usd(1.0, $curr); } catch ( \Exception $e) { wc_add_notice($e->getMessage(), 'error'); } } } // This is called when the user clicks Place Order, after validate_fields public function process_payment($order_id) { $order = new WC_Order($order_id); $selectedCryptoId = $_POST['agc_currency_id']; WC()->session->set('chosen_crypto', $selectedCryptoId); return array( 'result' => 'success', 'redirect' => $this->get_return_url( $order ), ); } // This is called when customer hits thank you page public function thank_you_page($order_id) { try { // get the value user selected in gateway select input $chosenCrypto = WC()->session->get('chosen_crypto'); // get some necessary crypto data for the thank you page $cryptoName = $this->cryptos[$chosenCrypto]->get_name(); $cryptoId = $this->cryptos[$chosenCrypto]->get_id(); $order = new WC_Order( $order_id ); // somebody hit refresh on thank you page if ($order->get_status() !== 'pending') { $this->handle_thank_you_refresh( get_post_meta($order_id, 'crypto_type_id', true), get_post_meta($order_id, 'wallet_address', true), get_post_meta($order_id, 'crypto_amount', true)); return; } $electrumEnabled = $cryptoId === 'BTC' && $this->settings['BTC_electrum_enabled'] === 'yes'; if ($electrumEnabled) { $repo = new AGC_MPK_Repo($chosenCrypto, $this->settings['BTC_electrum_mpk']); $walletAddress = $repo->get_oldest_ready_address(); if (!$walletAddress) { AGC_Util::force_new_electrum_address($cryptoId, $this->settings['BTC_electrum_mpk']); $walletAddress = $repo->get_oldest_ready_address(); } WC()->session->set('electrum_wallet_address', $walletAddress); $repo->set_address_status($walletAddress, 'assigned'); $repo->set_address_order_id($walletAddress, $order_id); } else { $walletAddress = $this->settings[$cryptoId . '_address']; } // get order total in chosen crypto $cryptoPerUsd = $this->get_crypto_value_in_usd($cryptoId, $this->cryptos[$chosenCrypto]->get_update_interval() ); $curr = get_woocommerce_currency(); $usdTotal = AGC_Exchange::get_order_total_in_usd($order->get_total(), $curr); $cryptoTotal = round($usdTotal / $cryptoPerUsd, $this->cryptos[$chosenCrypto]->get_round_precision(), PHP_ROUND_HALF_UP); $formattedCryptoTotal = $this->get_price_string($this->cryptos[$chosenCrypto], $cryptoTotal); WC()->session->set($chosenCrypto . '_amount', $cryptoTotal); //TODO: update_post_meta for necessary information if ($electrumEnabled) { $order->add_order_note('Electrum wallet address: ' . $walletAddress . ' is awaiting payment of ฿' . $formattedCryptoTotal); $repo->set_address_order_amount($walletAddress, $formattedCryptoTotal); } update_post_meta($order_id, 'wallet_address', $walletAddress); update_post_meta($order_id, 'crypto_type_id', $chosenCrypto); update_post_meta($order_id, 'crypto_amount', $formattedCryptoTotal); // emails are fired once we update status to on-hold, so hook additional email details here add_action( 'woocommerce_email_order_details', array( $this, 'additional_email_details' ), 10, 4 ); // update the order since everything worked $order->update_status('wc-on-hold', 'Awaiting payment of ' . $formattedCryptoTotal . ' ' . $cryptoName); // output additional thank you page html $this->output_thank_you_html($this->cryptos[$chosenCrypto], $walletAddress, $formattedCryptoTotal); } catch ( \Exception $e ) { $order = new WC_Order($order_id); // cancel order if something went wrong $order->update_status('wc-cancelled', 'Error Message: ' . $e->getMessage()); echo '
'; echo ''; echo '
'; } } public function additional_email_details( $order, $sent_to_admin, $plain_text, $email ) { $chosenCrypto = WC()->session->get('chosen_crypto'); $crypto = $this->cryptos[$chosenCrypto]; $orderCryptoTotal = WC()->session->get($chosenCrypto . '_amount'); $cryptoName = $crypto->get_name(); $cryptoId = $crypto->get_id(); $electrumEnabled = $cryptoId === 'BTC' && $this->settings['BTC_electrum_enabled'] === 'yes'; if ($electrumEnabled) { // electrum wallet that was selected/generated on thank you page $walletAddress = WC()->session->get('electrum_wallet_address'); } else { $walletAddress = $this->get_wallet_address($cryptoId); } $qrCode = $this->get_qr_code($cryptoName, $walletAddress, $orderCryptoTotal); ?>

Additional Details

QR Code Payment:

/>

Wallet Address:

Currency: get_logo_file_path() . '" />' . $crypto->get_name(); ?>

Total: get_symbol() === '') { echo $this->get_price_string($crypto, $orderCryptoTotal) . ' ' . $crypto->get_id(); } else { echo $crypto->get_symbol() . $this->get_price_string($crypto, $orderCryptoTotal); } ?>

cryptos as $crypto) { if ( $this->crypto_has_valid_settings($crypto) ) { $cryptosWithValidSettings[] = $crypto; } } return $cryptosWithValidSettings; } // check if crypto has valid settings private function crypto_has_valid_settings($crypto) { if (! $this->crypto_is_enabled($crypto)) { return false; } $electrumValid = $this->crypto_has_electrum_enabled($crypto) && $this->crypto_has_electrum_mpk($crypto); if ($electrumValid || $this->crypto_has_wallet_address($crypto)) { return true; } return false; } // check admin settings to see if crypto is enabled private function crypto_is_enabled($crypto) { $enabledSetting = $crypto->get_id() . '_enabled'; return $this->settings[$enabledSetting] === 'yes'; } // check admin settings to see if crypto has a wallet address private function crypto_has_wallet_address($crypto) { $walletSetting = $crypto->get_id() . '_address'; return ! empty( $this->settings[$walletSetting] ); } private function crypto_has_electrum_enabled($crypto) { $electrumEnabledSetting = $crypto->get_id() . '_electrum_enabled'; if (! array_key_exists($electrumEnabledSetting, $this->settings)) { return false; } return $this->settings[$electrumEnabledSetting] === 'yes'; } private function crypto_has_electrum_mpk($crypto) { $electrumMpkSetting = $crypto->get_id() . '_electrum_mpk'; if ( !array_key_exists($electrumMpkSetting, $this->settings)) { return false; } return ! empty($this->settings[$electrumMpkSetting]); } // convert array of cryptos to option array private function get_select_options_for_cryptos($cryptos) { $selectOptionArray = array(); foreach ($cryptos as $crypto) { $selectOptionArray[$crypto->get_id()] = $crypto->get_name(); } return $selectOptionArray; } private function get_qr_code($cryptoName, $walletAddress, $cryptoTotal) { $endpoint = 'https://api.qrserver.com/v1/create-qr-code/?data='; $formattedName = strtolower(str_replace(' ', '', $cryptoName)); $qrData = $formattedName . ':' . $walletAddress . '?amount=' . $cryptoTotal; return $endpoint . $qrData; } private function output_thank_you_html($crypto, $walletAddress, $cryptoTotal) { $formattedPrice = $this->get_price_string($crypto, $cryptoTotal); $qrCode = $this->get_qr_code($crypto->get_name(), $walletAddress, $formattedPrice); ?>

Here are your cryptocurrency payment details.

get_round_precision(), PHP_ROUND_HALF_UP); $formattedAmount = number_format($roundedAmount, $crypto->get_round_precision(), '.', ''); $amountWithoutZeroes = rtrim($formattedAmount, '0'); $amountWithoutTrailingDecimal = rtrim($amountWithoutZeroes, '.'); return $amountWithoutTrailingDecimal; } private function handle_thank_you_refresh($chosenCrypto, $walletAddress, $cryptoTotal) { $this->output_thank_you_html($this->cryptos[$chosenCrypto], $walletAddress, $cryptoTotal); } private function get_wallet_address($cryptoId) { return $this->settings[$cryptoId . '_address']; } // this function hits all the crypto exchange APIs that the user selected, then averages them and returns a conversion rate for USD // if the user has selected no exchanges to fetch data from it instead takes the average from all of them private function get_crypto_value_in_usd($cryptoId, $updateInterval) { $prices = array(); if ($this->settings['use_crypto_compare'] === 'yes') { $ccPrice = AGC_Exchange::get_cryptocompare_price($cryptoId, $updateInterval); if ($ccPrice > 0) { $prices[] = $ccPrice; } } if ($this->settings['use_hitbtc'] === 'yes') { $hitbtcPrice = AGC_Exchange::get_hitbtc_price($cryptoId, $updateInterval); if ($hitbtcPrice > 0) { $prices[] = $hitbtcPrice; } } if ($this->settings['use_gateio'] === 'yes') { $gateioPrice = AGC_Exchange::get_gateio_price($cryptoId, $updateInterval); if ($gateioPrice > 0) { $prices[] = $gateioPrice; } } if ($this->settings['use_bittrex'] === 'yes') { $bittrexPrice = AGC_Exchange::get_bittrex_price($cryptoId, $updateInterval); if ($bittrexPrice > 0) { $prices[] = $bittrexPrice; } } if ($this->settings['use_poloniex'] === 'yes') { $poloniexPrice = AGC_Exchange::get_poloniex_price($cryptoId, $updateInterval); // if there were no trades do not use this pricing method if ($poloniexPrice > 0) { $prices[] = $poloniexPrice; } } $sum = 0; $count = count($prices); if ($count === 0) { throw new \Exception( 'No cryptocurrency exchanges could be reached, please try again.' ); } foreach ($prices as $price) { $sum += $price; } $average_price = $sum / $count; return $average_price; } } ?>