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', ) ); $electrumSettings = array( 'electrum_settings' => array( 'title' => 'Electrum Settings', 'type' => 'title', ), 'electrum_percent_to_process' => array( 'title' => 'Order Fulfillment Percent', 'type' => 'number', 'default' => '0.97', 'custom_attributes' => array( 'min' => 0.01, 'max' => 1.00, 'step' => 0.01, ), ), 'electrum_required_confirmations' => array( 'title' => 'Required Confirmations', 'type' => 'number', 'default' => '2', 'custom_attributes' => array( 'min' => 0, 'max' => 10, 'step' => 1, ), ), 'electrum_order_cancellation_time_hr' => array( 'title' => 'Cancel orders after (hr)', 'type' => 'number', 'default' => 24, 'custom_attributes' => array( 'min' => 0.01, 'max' => 24 * 7, // 7 days 'step' => 0.01, ), ), ); $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', 'class' => 'wc-settings-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->has_electrum()) { $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, $electrumSettings, $cryptoSettings, $pricingSettings); } // 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) { $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 ) { // 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 $errorMessage = sprintf( 'Invalid Master Public Key for %s. Falling back to regular wallet address.', $crypto->get_name()); // EVEN THOUGH WE THROW AN ERROR WE DO NOT DISABLE THE PLUGIN WC_Admin_Settings::add_error($errorMessage); } if ($cryptoEnabled && $electrumEnabled && (!$validMpk) && (!$validAddress)) { $errorMessage = sprintf( 'Invalid wallet address for %s... Plug-in will be disabled until each enabled cryptocurrency has a valid wallet address', $crypto->get_name()); // code in validate_BTC_enabled_field handles disabling electrum WC_Admin_Settings::add_error($errorMessage); $result = 'no'; } if ($cryptoEnabled && (!$electrumEnabled) && (!$validAddress)) { $errorMessage = sprintf( 'Invalid wallet address for %s... Plug-in will be disabled until each enabled cryptocurrency has a valid wallet address', $crypto->get_name()); WC_Admin_Settings::add_error($errorMessage); $result = 'no'; } } return $result; } // 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(); foreach ($validCryptos as $crypto) { if ($crypto->has_electrum()) { $mpk = $this->get_crypto_electrum_mpk($crypto); $repo = new AGC_MPK_Repo($crypto->get_id(), $mpk); $count = $repo->count_ready_addresses(); if ($count < 1) { try { AGC_Util::force_new_electrum_address($crypto->get_id(), $mpk); } catch ( \Exception $e) { AGC_Util::log(__FILE__, __LINE__, 'UNABLE TO GENERATE ELECTRUM ADDRESS FOR ' . $crypto->get_name() . ' ADMIN MUST BE NOTIFIED. REMOVING CRYPTO FROM PAYMENT OPTIONS'); unset($validCryptos[$crypto->get_id()]); } } } } $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, ) ); } // return list of cryptocurrencies that have valid settings private function cryptos_with_valid_settings() { $cryptosWithValidSettings = array(); foreach ($this->cryptos as $crypto) { if ( $this->crypto_has_valid_settings($crypto) ) { $cryptosWithValidSettings[$crypto->get_id()] = $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; } // 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 if something goes wrong we 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 { $chosenCryptoId = $_POST['agc_currency_id']; $crypto = $this->cryptos[$chosenCryptoId]; $curr = get_woocommerce_currency(); $cryptoPerUsd = $this->get_crypto_value_in_usd($crypto->get_id(), $crypto->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) { AGC_Util::log(__FILE__, __LINE__, $e->getMessage()); 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_id', $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 { $chosenCryptoId = WC()->session->get('chosen_crypto_id'); $order = new WC_Order($order_id); $crypto = $this->cryptos[$chosenCryptoId]; // get current price of crypto $cryptoPerUsd = $this->get_crypto_value_in_usd($crypto->get_id(), $crypto->get_update_interval()); // handle different woocommerce currencies and get the order total in USD $curr = get_woocommerce_currency(); $usdTotal = AGC_Exchange::get_order_total_in_usd($order->get_total(), $curr); // order total in cryptocurrency $cryptoTotal = round($usdTotal / $cryptoPerUsd, $crypto->get_round_precision(), PHP_ROUND_HALF_UP); // format the crypto amount based on crypto $formattedCryptoTotal = AGC_Cryptocurrencies::get_price_string($crypto->get_id(), $cryptoTotal); // set the amount in the session so we can send an email WC()->session->set($crypto->get_id() . '_amount', $cryptoTotal); $walletCheck = get_post_meta($order_id, 'wallet_address'); // if we already set this then we are on a page refresh, so handle refresh if (count($walletCheck) > 0) { $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 = $crypto->has_electrum() && $this->crypto_has_electrum_enabled($crypto); // if electrum is enabled we have stuff to do if ($electrumEnabled) { $mpk = $this->get_crypto_electrum_mpk($crypto); $repo = new AGC_MPK_Repo($crypto->get_id(), $mpk); // get fresh electrum wallet $walletAddress = $repo->get_oldest_ready_address(); // if we couldnt find a fresh one, force a new one if (!$walletAddress) { try { AGC_Util::force_new_electrum_address($crypto->get_id(), $mpk); $walletAddress = $repo->get_oldest_ready_address(); } catch ( \Exception $e) { throw new \Exception('Unable to get payment address for order. This order has been cancelled. Please try again or contact the site administrator... Inner Exception: ' . $e->getMessage()); } } // set electrum wallet address to get later WC()->session->set('electrum_wallet_address', $walletAddress); // update the database $repo->set_address_status($walletAddress, 'assigned'); $repo->set_address_order_id($walletAddress, $order_id); $repo->set_address_order_amount($walletAddress, $formattedCryptoTotal); $orderNote = sprintf( 'Electrum wallet address %s is awaiting payment of %s', $walletAddress, $formattedCryptoTotal); } else { $walletAddress = $this->get_crypto_wallet_address($crypto); $orderNote = sprintf( 'Awaiting payment of %s %s to %s.', $formattedCryptoTotal, $crypto->get_name(), $walletAddress); } update_post_meta($order_id, 'wallet_address', $walletAddress); update_post_meta($order_id, 'crypto_type_id', $crypto->get_id()); 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); $order->update_status('wc-on-hold', $orderNote); // output additional thank you page html $this->output_thank_you_html($crypto, $walletAddress, $formattedCryptoTotal); } catch ( \Exception $e ) { $order = new WC_Order($order_id); // cancel order if something went wrong $order->update_status('wc-failed', 'Error Message: ' . $e->getMessage()); AGC_Util::log(__FILE__, __LINE__, 'Something went wrong during checkout: ' . $e->getMessage()); echo '
'; echo ''; echo '
'; } } public function additional_email_details( $order, $sent_to_admin, $plain_text, $email ) { $chosenCrypto = WC()->session->get('chosen_crypto_id'); $crypto = $this->cryptos[$chosenCrypto]; $orderCryptoTotal = WC()->session->get($chosenCrypto . '_amount'); $cryptoName = $crypto->get_name(); $cryptoId = $crypto->get_id(); $electrumEnabled = $crypto->has_electrum() && $this->settings[$cryptoId . '_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->settings[$cryptoId . '_address']; } $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 AGC_Cryptocurrencies::get_price_string($crypto->get_id(), $orderCryptoTotal) . ' ' . $crypto->get_id(); } else { echo $crypto->get_symbol() . AGC_Cryptocurrencies::get_price_string($crypto->get_id(), $orderCryptoTotal); } ?>

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 get_crypto_wallet_address($crypto) { $walletSetting = $crypto->get_id() . '_address'; return $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]); } private function get_crypto_electrum_mpk($crypto) { $electrumMpkSetting = $crypto->get_id() . '_electrum_mpk'; return $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 = AGC_Cryptocurrencies::get_price_string($crypto->get_id(), $cryptoTotal); $qrCode = $this->get_qr_code($crypto->get_name(), $walletAddress, $formattedPrice); ?>

Here are your cryptocurrency payment details.

output_thank_you_html($this->cryptos[$chosenCrypto], $walletAddress, $cryptoTotal); } // 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; } } ?>