WordPress plugin to accept HUSH payments in WooCommerce.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

548 lines
22 KiB

<?php
/**
* Plugin Name: HUSH Payment Gateway
* Plugin URI: https://hush.is
* Description: Allows your WooCommerce store to accept HUSH for payment
* Version: 1.0.0
* Author: Hush Developers
* Text Domain: hush-wc-gateway
* Domain Path: /i18n/languages/
*
* Copyright: (c) 2022 Hush Developers and WooCommerce
*
* License: GNU General Public License v3.0
* License URI: http://www.gnu.org/licenses/gpl-3.0.html
*
* @package WC-Hush-Gateway
* @author Hush Developers
* @category Admin
* @copyright Copyright (c) 2022 Hush Developers and WooCommerce
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
*
*/
defined( 'ABSPATH' ) or exit;
// Include phpqrcode lib
if(!class_exists('QRtools')){
include_once('phpqrcode.php');
}
// Make sure WooCommerce is active
if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))){
return;
}
/**
* Add the gateway to WC Available Gateways
*
* @since 1.0.0
* @param array $gateways all available WC gateways
* @return array $gateways all WC gateways + hush gateway
*/
add_filter('woocommerce_payment_gateways', 'hush_add_to_gateways');
function hush_add_to_gateways( $gateways ) {
$gateways[] = 'Hush_WC_Gateway';
return $gateways;
}
/**
* Adds plugin page links
*
* @since 1.0.0
* @param array $links all plugin links
* @return array $links all plugin links + our custom links (i.e., "Settings")
*/
add_filter('plugin_action_links_' . plugin_basename( __FILE__ ), 'hush_gateway_plugin_links');
function hush_gateway_plugin_links( $links ) {
$plugin_links = array(
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=checkout&section=hush_gateway' ) . '">' . __( 'Configure', 'hush-wc-gateway' ) . '</a>'
);
return array_merge( $plugin_links, $links );
}
/**
* Gets current HUSH price from CoinGecko API
*/
function get_hush_price() {
// Get HUSH price from CoinGecko and convert amount to send
$request = wp_remote_get('https://api.coingecko.com/api/v3/simple/price?ids=hush&vs_currencies=btc%2Cusd%2Ceur%2Ceth%2Cgbp%2Ccny%2Cjpy%2Cidr%2Crub%2Ccad%2Csgd%2Cchf%2Cinr%2Caud%2Cinr%2Ckrw%2Cthb%2Cnzd%2Czar%2Cvef%2Cxau%2Cxag%2Cvnd%2Csar%2Ctwd%2Caed%2Cars%2Cbdt%2Cbhd%2Cbmd%2Cbrl%2Cclp%2Cczk%2Cdkk%2Chuf%2Cils%2Ckwd%2Clkr%2Cpkr%2Cnok%2Ctry%2Csek%2Cmxn%2Cuah%2Chkd&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true');
if(is_wp_error($request)){
return get_option('hushPriceCached');
}
$body = wp_remote_retrieve_body($request);
$data = json_decode($body, true);
if(!empty($data)){
$hushPrice = $data['hush']['usd'];
if(is_numeric($hushPrice)){
update_option('hushPriceCached',sanitize_text_field($hushPrice));
return $hushPrice;
}
else{
return get_option('hushPriceCached');
}
}
else{
return get_option('hushPriceCached');
}
}
/**
* Gets amount of HUSH to send based on USD total, HUSH price, and markup
*/
function get_hush_amount($orderAmt, $hushPrice, $markup) {
if(is_numeric($orderAmt) && is_numeric($hushPrice) && is_numeric($markup)){
$hushAmt = $orderAmt / $hushPrice;
$hushAmt = $hushAmt + ($hushAmt * $markup);
$hushAmt = number_format((float)$hushAmt, 8, '.', '');
return $hushAmt;
}
else{
return false;
}
}
/**
* Set HUSH payment gateway description on checkout page
*/
add_filter('woocommerce_gateway_description', 'hush_payment_gateway_description', 25, 2);
function hush_payment_gateway_description($description, $gateway_id) {
if('hush_gateway' === $gateway_id) {
//Get cart object
$cart = WC()->cart;
// Get plugin options
$options = get_option('woocommerce_hush_gateway_settings', 'default text');
// Get HUSH price
$hushPrice = get_hush_price();
// Get volatility markup option
$markup = $options['volatilityMarkup'];
$markupPercent = $markup * 100;
//Get HUSH amount to send
$hushAmt = get_hush_amount($cart->total, $hushPrice, $markup);
//Set Description
$description .= "<p><strong>HUSH Price:</strong> $".esc_html(get_hush_price())."<br/> <strong>HUSH Amount Estimate: </strong>".esc_html($hushAmt)." <br/>(Including ".esc_html($markupPercent)."% volatility markup)</p>";
$description .= "<p><br/><br/>Speak & Transact Freely! Your order will not be processed and shipped until HUSH payment has been received and confirmed. Exact amount of HUSH to send is specified after placing order.</p>";
}
return $description;
}
/**
* Set and save expected Hush payment details when order created
*/
add_action('woocommerce_checkout_create_order', 'set_hush_payment_details', 20, 2);
function set_hush_payment_details($order, $data) {
// Get order amount
$orderAmt = $order->get_total();
// Get plugin options
$options = get_option('woocommerce_hush_gateway_settings', 'default text');
// Get HUSH price
$hushPrice = get_hush_price();
// Convert addresses to an array
$hushAddressArray = explode(",", $options['hushAddresses']);
// Shuffle for random address
shuffle($hushAddressArray);
$hushAddress = trim($hushAddressArray[0]);
// Get volatility markup option
$markup = $options['volatilityMarkup'];
// Get HUSH amount to send
$hushAmt = get_hush_amount($orderAmt, $hushPrice, $markup);
// Save payment instruction related meta for verifying orders later
$order->update_meta_data( '_hush_price', sanitize_text_field($hushPrice));
$order->update_meta_data( '_hush_receive_address', sanitize_text_field($hushAddress));
$order->update_meta_data( '_hush_expected', sanitize_text_field($hushAmt));
$order->save();
}
/**
* Display expected HUSH payment details on the admin order edit page
*/
add_action('woocommerce_admin_order_data_after_billing_address', 'hush_display_admin_order_meta', 10, 1);
function hush_display_admin_order_meta($order){
echo("<p><strong>Receive Address:</strong> ". esc_html(get_post_meta($order->get_id(), '_hush_receive_address', true)) . "</p>");
echo("<p><strong>Expected HUSH:</strong> ". esc_html(get_post_meta($order->get_id(), '_hush_expected', true)) . "</p>");
echo("<p><strong>Exchange Rate:</strong> $". esc_html(get_post_meta($order->get_id(), '_hush_price', true)) . " USD </p>");
}
/**
* Change default order notes field to include memo info
*/
add_filter('woocommerce_checkout_fields', 'hush_woocommerce_checkout_fields');
function hush_woocommerce_checkout_fields( $fields )
{
$fields['order']['order_comments']['label'] = 'Memo And/Or Special Instructions';
$fields['order']['order_comments']['placeholder'] = 'Include any special memo or notes about your order. For HUSH payments, this will be used in the memo field of a transaction';
return $fields;
}
/**
* Display HUSH amount on frontend for products
*/
add_filter('woocommerce_get_price_html', 'hush_amount_after_price');
function hush_amount_after_price($price){
// Get plugin options
$options = get_option('woocommerce_hush_gateway_settings', 'default text');
// Get display options
$displayOnFrontend = $options['displayOnFrontend'];
if($displayOnFrontend == "Yes"){
// Get HUSH price
$hushPrice = get_hush_price();
// Get volatility markup option
$markup = $options['volatilityMarkup'];
// Remove all formatting and get starting price only
$cleanPrice = strip_tags($price);
$cleanPrice = html_entity_decode($cleanPrice);
$cleanPriceArray = explode("$", $cleanPrice);
$cleanPrice = preg_replace("/[^0-9.]/", "", $cleanPriceArray[1]);
$hushAmt = get_hush_amount($cleanPrice, $hushPrice, $markup);
// Return price and HUSH amount
return $price . " (~".number_format(esc_html($hushAmt), 2)." HUSH)";
}
else{
return $price;
}
}
/**
* Display HUSH amount with total on frontend
*/
add_filter('woocommerce_cart_total', 'hush_total_message', 10, 1);
function hush_total_message( $price ) {
// Get plugin options
$options = get_option('woocommerce_hush_gateway_settings', 'default text');
// Get display options
$displayOnFrontend = $options['displayOnFrontend'];
if($displayOnFrontend == "Yes"){
// Get HUSH price
$hushPrice = get_hush_price();
// Get volatility markup option
$markup = $options['volatilityMarkup'];
// Remove all formatting and get starting price only
$cleanPrice = strip_tags($price);
$cleanPrice = html_entity_decode($cleanPrice);
$cleanPrice = preg_replace("/[^0-9.]/", "", $cleanPrice);
$hushAmt = get_hush_amount($cleanPrice, $hushPrice, $markup);
// Return price and HUSH amount
return $price . " (~".number_format(esc_html($hushAmt), 2)." HUSH)";
}
else{
return $price;
}
}
/**
* Delete Hush related meta data when order status changes to completed
*/
add_action('woocommerce_order_status_changed', 'hush_order_status_completed', 10, 3);
function hush_order_status_completed($order_id, $old_status, $new_status)
{
// Get plugin options
$options = get_option('woocommerce_hush_gateway_settings', 'default text');
// Get meta option
$deleteMeta = $options['deleteMeta'];
if($deleteMeta == "Yes"){
if(($old_status == 'processing' OR $old_status == 'on-hold') && $new_status == 'completed'){
delete_post_meta($order_id, '_hush_price');
delete_post_meta($order_id, '_hush_receive_address');
delete_post_meta($order_id, '_hush_expected');
}
}
}
/**
* Hush Payment Gateway
* Provides simple instructions for customers to pay with Hush
* We load it later to ensure WC is loaded first since we're extending it.
*
* @class Hush_WC_Gateway
* @extends WC_Payment_Gateway
* @version 1.0.0
* @package WooCommerce/Classes/Payment
* @author Hush Developers
*/
add_action('plugins_loaded', 'hush_gateway_init', 11);
function hush_gateway_init() {
class Hush_WC_Gateway extends WC_Payment_Gateway {
/**
* Constructor for the gateway.
*/
public function __construct() {
$this->id = 'hush_gateway';
$this->icon = apply_filters('woocommerce_hush_icon', '');
$this->has_fields = false;
$this->method_title = __( 'HUSH', 'hush-wc-gateway' );
$this->method_description = __( 'Provides simple instructions for customers to pay with HUSH', 'hush-wc-gateway' );
// Load the settings.
$this->init_form_fields();
$this->init_settings();
// Define user set variables
$this->title = $this->get_option( 'title' );
$this->volatilityMarkup = $this->get_option( 'volatilityMarkup' );
$this->checkoutInstructions = $this->get_option( 'checkoutInstructions' );
$this->hushAddresses = $this->get_option( 'hushAddresses' );
$this->displayOnFrontend = $this->get_option( 'displayOnFrontend' );
$this->deleteMeta = $this->get_option( 'deleteMeta' );
// Actions
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_action( 'woocommerce_thankyou_' . $this->id, array( $this, 'thankyou_page' ) );
}
/**
* Initialize Gateway Settings Form Fields
* This is where the Store Owners will enter all of their Hush Settings.
*/
public function init_form_fields() {
$this->form_fields = apply_filters( 'hush_form_fields', array(
'enabled' => array(
'title' => __( 'Enable/Disable:', 'hush-wc-gateway' ),
'type' => 'checkbox',
'label' => __( 'Enable HUSH', 'hush-wc-gateway' ),
'default' => 'yes'
),
'checkoutInstructions' => array(
'title' => __( 'Checkout Payment Instructions:', 'hush-wc-gateway' ),
'type' => 'textarea',
'description' => __( 'This text will be displayed on the order confirmation/thank you page, letting the customer know where to send payment.' ),
'default' => __( 'Thank you for supporting the Hush network. Manual payment instructions are below. Your order will remain on hold until payment is manually verified as being received. Please send payment as soon as possible. We reserve the right to cancel any orders due to extreme market volatility conditions.', 'hush-wc-gateway' ),
'desc_tip' => true,
),
'title' => array(
'title' => __( 'Title:', 'hush-wc-gateway' ),
'type' => 'text',
'description' => __( 'This controls the title for the payment method the customer sees during checkout.', 'hush-wc-gateway' ),
'default' => __( 'HUSH - Private zk-SNARKS cryptocurrency. ', 'hush-wc-gateway' ),
'desc_tip' => true,
),
'hushAddresses' => array(
'title' => __( 'HUSH Addresses:', 'hush-wc-gateway' ),
'type' => 'textarea',
'description' => __( 'Enter a comma separated list of Hush addresses to be used for receiving payments. You can enter as many as you want and update this setting as often as you like for added privacy. During checkout, 1 address from this list will be chosen randomly for the customer to send payment to. It is recommended to submit at least 10 addresses and change once a month for privacy.' ),
'default' => __( '', 'hush-wc-gateway' ),
'desc_tip' => true,
),
'volatilityMarkup' => array(
'title' => __( 'Volatility markup:', 'hush-wc-gateway' ),
'type' => 'text',
'description' => __( 'This allows setting a percentage markup to apply to current HUSH price to help with any market volatility. Enter as decimal (IE: 2% markup enter as 0.02)', 'hush-wc-gateway' ),
'default' => __( '0.02', 'hush-wc-gateway' ),
'desc_tip' => true,
),
'displayOnFrontend' => array(
'title' => __( 'Display HUSH Prices on frontend?:', 'hush-wc-gateway' ),
'type' => 'select',
'description' => __( 'This will display HUSH price/amount on frontend when viewing products, cart, checkout etc. Ideal for shops that only offer HUSH as crypto payment. Not recommended for shops that offer multiple crypto currency payment methods.', 'hush-wc-gateway' ),
'default' => __( 'Yes', 'hush-wc-gateway' ),
'desc_tip' => true,
'options' => array(
'Yes' => 'Yes',
'No' => 'No'
)
),
'deleteMeta' => array(
'title' => __( 'Delete HUSH related meta on order completion?:', 'hush-wc-gateway' ),
'type' => 'select',
'description' => __( 'This will delete HUSH related meta keys (price, amount, and receive address) on an order when status changes from processing to completed.', 'hush-wc-gateway' ),
'default' => __( 'Yes', 'hush-wc-gateway' ),
'desc_tip' => true,
'options' => array(
'Yes' => 'Yes',
'No' => 'No'
)
),
) );
}
/**
* Output for the order received page.
*/
public function thankyou_page( $order_id ) {
// Get Hush price at time of order
$hushPrice = get_post_meta($order_id, '_hush_price', true);
// Get address to send to
$hushAddress = get_post_meta($order_id, '_hush_receive_address', true);
// Get expected amount to be received
$hushAmt = get_post_meta($order_id, '_hush_expected', true);
// Get notes/memo
$hasMemo = FALSE;
$order = wc_get_order( $order_id );
$hushMemo = rawurlencode($order->get_customer_note());
if(strlen($hushMemo) >= 1){
$hasMemo = TRUE;
}
else{
$hasMemo = FALSE;
}
// Set HUSH URIs
// TODO: Remove hushQRURI if no longer needed. It previously used hush:// and does not validate in app
if($hasMemo){
$hushURI = "hush:".$hushAddress."?amt=".$hushAmt."&memo=".$hushMemo;
$hushQRURI = "hush:".$hushAddress."?amt=".$hushAmt."&memo=".$hushMemo;
}
else{
$hushURI = "hush:".$hushAddress."?amt=".$hushAmt;
$hushQRURI = "hush:".$hushAddress."?amt=".$hushAmt;
}
// Set timestamp for filename uniqueness
$timestamp = time();
// Set and create uploads directory for plugin (wp-content/uploads/hush)
$upload_dir = wp_upload_dir();
$upload_dir = $upload_dir['basedir'] . '/' . "hush";
if(!file_exists($upload_dir)) wp_mkdir_p($upload_dir);
// Set logo path for adding to QR code
$logo_path = plugins_url('hush-icon-dark.jpg' , __FILE__);
// Set QR code path
$qr_path = $upload_dir.'/qr_code-'.$timestamp.'.png';
// Delete any previously created QR codes to store as little data as possible
$old_files = glob($upload_dir."/*");
foreach ($old_files as $file) {
if (is_file($file)) {
unlink($file);
}
}
// Generate QR code in uploads directory
QRcode::png($hushQRURI, $qr_path, QR_ECLEVEL_H, 8);
// Start adding logo to QR code
$qr = imagecreatefrompng($qr_path);
$logo = imagecreatefromjpeg($logo_path);
$qr_width = imagesx($qr);
$qr_height = imagesy($qr);
$logo_width = imagesx($logo);
$logo_height = imagesy($logo);
// Scale logo to fit in the QR code
$logo_qr_width = $qr_width/3;
$scale = $logo_width/$logo_qr_width;
$logo_qr_height = $logo_height/$scale;
// Create image with QR code and logo
imagecopyresampled($qr, $logo, $qr_width/3, $qr_height/3.5, 0, 0, $logo_qr_width, $logo_qr_height, $logo_width, $logo_height);
// Save QR code with logo
imagepng($qr,$qr_path);
// Display instructions for customer to send payment to
$htmlOutput .= "<h2>HUSH Payment Instructions</h2>";
$htmlOutput .= esc_html($this->checkoutInstructions)."<br/><br/>";
$htmlOutput .= "<strong>Address to send HUSH to: </strong><input type='text' value='".esc_html($hushAddress)."' id='hushAddress' style='width:100%' readonly='readonly'/><br/><br/>";
$htmlOutput .= "<strong>Amount to send: </strong>(Exchange Rate is </strong>".esc_html($hushPrice)." USD + ". esc_html($this->volatilityMarkup * 100) ."% volatility markup)<br/><input type='text' value='".esc_html($hushAmt)."' id='hushAmt' style='width:100%' readonly='readonly'/><br/><br/>";
$htmlOutput .= "<strong>Memo: </strong>(Optional and uses submitted order notes)<br/><input type='text' value='".esc_html($hushMemo)."' id='hushMemo' style='width:100%' readonly='readonly'/><br/><br/>";
$htmlOutput .= "An auto-generated HUSH URI may be used instead for easier/quicker payment. In lite or full wallet, go to <strong>File > Pay HUSH URI</strong> and copy paste the URI below. Do not modify this URI. Orders will only be fulfilled if expected amount of HUSH is received.<br/><br/>";
$htmlOutput .= "<strong>Pay HUSH URI: </strong><input type='text' value='".esc_html($hushURI)."' id='hushURI' style='width:100%' readonly='readonly'/><br/><br/>";
$htmlOutput .= "<strong>Silent Dragon Android QR Code: </strong><br/><img src='/wp-content/uploads/hush/qr_code-".esc_html($timestamp).".png' /><br/><br/>";
$allowedHTML = array('br'=>array(), 'p'=>array(), 'strong'=>array(), 'input'=>array('value' => array(), 'type' => array(), 'id' => array(), 'style' => array(), 'readonly' => array()), 'img'=>array('src' => array()));
echo wp_kses($htmlOutput, $allowedHTML);
}
/**
* Process the payment and return the result
* This will put the order into on-hold status, reduce inventory levels, and empty customer shopping cart.
*
* @param int $order_id
* @return array
*/
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
// Mark as on-hold (we're awaiting the payment)
$order->update_status( 'on-hold', __( 'Awaiting HUSH payment', 'hush-wc-gateway' ) );
// Reduce stock levels
wc_reduce_stock_levels($order_id);
// Remove cart
WC()->cart->empty_cart();
// Return thankyou redirect
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
}
} // end \Hush_WC_Gateway class
}