· 6 years ago · Feb 01, 2019, 08:56 PM
1<?php
2/*
3Bitcoin Payments for WooCommerce
4http://www.bitcoinway.com/
5*/
6
7
8//---------------------------------------------------------------------------
9add_action('plugins_loaded', 'BWWC__plugins_loaded__load_bitcoin_gateway', 0);
10//---------------------------------------------------------------------------
11
12//###########################################################################
13// Hook payment gateway into WooCommerce
14
15function BWWC__plugins_loaded__load_bitcoin_gateway()
16{
17 if (!class_exists('WC_Payment_Gateway'))
18 // Nothing happens here is WooCommerce is not loaded
19 return;
20
21 //=======================================================================
22 /**
23 * Bitcoin Payment Gateway
24 *
25 * Provides a Bitcoin Payment Gateway
26 *
27 * @class BWWC_Bitcoin
28 * @extends WC_Payment_Gateway
29 * @version
30 * @package
31 * @author BitcoinWay
32 */
33
34 class BWWC_Bitcoin extends WC_Payment_Gateway
35 {
36
37 //-------------------------------------------------------------------
38 /**
39 * Constructor for the gateway.
40 *
41 * @access public
42 * @return void
43 */
44
45 public function __construct()
46 {
47 $this->id = 'bitcoin';
48 $this->icon = plugins_url('/images/btc_buyitnow_32x.png', __FILE__); // 32 pixels high
49 $this->has_fields = false;
50 $this->method_title = __('Bitcoin', 'woocommerce');
51 // Load the settings.
52
53 $bwwc_settings = BWWC__get_settings();
54 $this->confs_num = $bwwc_settings['confs_num']; //$this->settings['confirmations'];
55 $this->service_provider = $bwwc_settings['service_provider'];
56 $this->init_form_fields();
57
58 $this->init_settings();
59 // Define user set variables
60 $this->title = $this->get_option('title');
61 ///$this->title = $this->settings['title']; // The title which the user is shown on the checkout – retrieved from the settings which init_settings loads.
62 //$this->bitcoin_addr_merchant = $this->settings['bitcoin_addr_merchant']; // Forwarding address where all product payments will aggregate.
63
64 $this->bitcoin_addr_merchant = $this->get_option('bitcoin_addr_merchant');
65
66 //$this->description = $this->settings['description']; // Short description about the gateway which is shown on checkout.
67 $this->description = $this->get_option('description');
68 $this->instructions = $this->settings['instructions']; // Detailed payment instructions for the buyer.
69 $this->instructions_multi_payment_str = __('You may send payments from multiple accounts to reach the total required.', 'woocommerce');
70 $this->instructions_single_payment_str = __('You must pay in a single payment in full.', 'woocommerce');
71
72 // Load the form fields.
73 // $this->init_form_fields();
74
75 // Actions
76 if (version_compare(WOOCOMMERCE_VERSION, '2.0.0', '>='))
77 add_action('woocommerce_update_options_payment_gateways_' . $this->id, array(
78 $this,
79 'process_admin_options'
80 ));
81 else
82 add_action('woocommerce_update_options_payment_gateways', array(
83 &$this,
84 'process_admin_options'
85 )); // hook into this action to save options in the backend
86 add_action('woocommerce_thankyou_' . $this->id, array(
87 &$this,
88 'BWWC__thankyou_page'
89 )); // hooks into the thank you page after payment
90 // Customer Emails
91 add_action('woocommerce_email_before_order_table', array(
92 &$this,
93 'BWWC__email_instructions'
94 ), 10, 2); // hooks into the email template to show additional details
95
96 // Hook IPN callback logic
97 if (version_compare(WOOCOMMERCE_VERSION, '2.0', '<'))
98 add_action('init', array(
99 &$this,
100 'BWWC__maybe_bitcoin_ipn_callback'
101 ));
102 else
103 add_action('woocommerce_api_' . strtolower(get_class($this)), array(
104 $this,
105 'BWWC__maybe_bitcoin_ipn_callback'
106 ));
107
108 // Validate currently set currency for the store. Must be among supported ones.
109 if (!$this->is_gateway_valid_for_use())
110 $this->enabled = false;
111 }
112 //-------------------------------------------------------------------
113
114 //-------------------------------------------------------------------
115 /**
116 * Check if this gateway is enabled and available for the store's default currency
117 *
118 * @access public
119 * @return bool
120 */
121 function is_gateway_valid_for_use(&$ret_reason_message = NULL)
122 {
123 $valid = true;
124
125 //----------------------------------
126 // Validate settings
127 if (!$this->service_provider) {
128 $reason_message = __("Bitcoin Service Provider is not selected", 'woocommerce');
129 $valid = false;
130 } else if ($this->service_provider == 'blockchain_info') {
131 if ($this->bitcoin_addr_merchant == '') {
132 $reason_message = __("Your personal bitcoin address is not selected", 'woocommerce');
133 $valid = false;
134 } else if ($this->bitcoin_addr_merchant == '1H9uAP3x439YvQDoKNGgSYCg3FmrYRzpD2') {
135 $reason_message = __("Your personal bitcoin address is invalid. The address specified is Bitcoinway.com's donation address :)", 'woocommerce');
136 $valid = false;
137 }
138 } else if ($this->service_provider == 'electrum_wallet') {
139 $mpk = BWWC__get_next_available_mpk();
140 if (!$mpk) {
141 $reason_message = __("Please specify Electrum Master Public Key (MPK) in Bitcoinway plugin settings. <br />To retrieve MPK: launch your electrum wallet, select: Wallet->Master Public Keys, OR: <br />Preferences->Import/Export->Master Public Key->Show", 'woocommerce');
142 $valid = false;
143 } else if (!preg_match('/^[a-f0-9]{128}$/', $mpk) && !preg_match('/^xpub661[a-zA-Z0-9]{104}$/', $mpk)) {
144 $reason_message = __("Electrum Master Public Key is invalid. Must be 128 or 111 characters long, consisting of digits and letters.", 'woocommerce');
145 $valid = false;
146 } else if (!extension_loaded('gmp') && !extension_loaded('bcmath')) {
147 $reason_message = __("ERROR: neither 'bcmath' nor 'gmp' math extensions are loaded For Electrum wallet options to function. Contact your hosting company and ask them to enable either 'bcmath' or 'gmp' extensions. 'gmp' is preferred (much faster)! \nAlternatively you may choose another 'Bitcoin Service Provider' option.", 'woocommerce');
148 $valid = false;
149 }
150 }
151
152 if (!$valid) {
153 if ($ret_reason_message !== NULL)
154 $ret_reason_message = $reason_message;
155 return false;
156 }
157 //----------------------------------
158
159 //----------------------------------
160 // Validate connection to exchange rate services
161
162 $store_currency_code = get_woocommerce_currency();
163 if ($store_currency_code != 'BTC') {
164 $currency_rate = BWWC__get_exchange_rate_per_bitcoin($store_currency_code, 'getfirst', false);
165 if (!$currency_rate) {
166 $valid = false;
167
168 // Assemble error message.
169 $error_msg = "ERROR: Cannot determine exchange rates (for '$store_currency_code')! {{{ERROR_MESSAGE}}} Make sure your PHP settings are configured properly and your server can (is allowed to) connect to external WEB services via PHP.";
170 $extra_error_message = "";
171 $fns = array(
172 'file_get_contents',
173 'curl_init',
174 'curl_setopt',
175 'curl_setopt_array',
176 'curl_exec'
177 );
178 $fns = array_filter($fns, 'BWWC__function_not_exists');
179 $extra_error_message = "";
180 if (count($fns))
181 $extra_error_message = "The following PHP functions are disabled on your server: " . implode(", ", $fns) . ".";
182
183 $reason_message = str_replace('{{{ERROR_MESSAGE}}}', $extra_error_message, $error_msg);
184
185 if ($ret_reason_message !== NULL)
186 $ret_reason_message = $reason_message;
187 return false;
188 }
189 }
190 //----------------------------------
191
192 //----------------------------------
193 // NOTE: currenly this check is not performed.
194 // Do not limit support with present list of currencies. This was originally created because exchange rate APIs did not support many, but today
195 // they do support many more currencies, hence this check is removed for now.
196
197 // Validate currency
198 // $currency_code = get_woocommerce_currency();
199 // $supported_currencies_arr = BWWC__get_settings ('supported_currencies_arr');
200
201 // if ($currency_code != 'BTC' && !@in_array($currency_code, $supported_currencies_arr))
202 // {
203 // $reason_message = __("Store currency is set to unsupported value", 'woocommerce') . "('{$currency_code}'). " . __("Valid currencies: ", 'woocommerce') . implode ($supported_currencies_arr, ", ");
204 // if ($ret_reason_message !== NULL)
205 // $ret_reason_message = $reason_message;
206 // return false;
207 // }
208
209 return true;
210 //----------------------------------
211 }
212 //-------------------------------------------------------------------
213
214 //-------------------------------------------------------------------
215 /**
216 * Initialise Gateway Settings Form Fields
217 *
218 * @access public
219 * @return void
220 */
221 function init_form_fields()
222 {
223 // This defines the settings we want to show in the admin area.
224 // This allows user to customize payment gateway.
225 // Add as many as you see fit.
226 // See this for more form elements: http://wcdocs.woothemes.com/codex/extending/settings-api/
227
228 //-----------------------------------
229 // Assemble currency ticker.
230 $store_currency_code = get_woocommerce_currency();
231 if ($store_currency_code == 'BTC')
232 $currency_code = 'USD';
233 else
234 $currency_code = $store_currency_code;
235
236 $currency_ticker = BWWC__get_exchange_rate_per_bitcoin($currency_code, 'getfirst', true);
237 //-----------------------------------
238
239 //-----------------------------------
240 // Payment instructions
241 $payment_instructions = '
242<table class="bwwc-payment-instructions-table" id="bwwc-payment-instructions-table">
243<tr class="bpit-table-row">
244<td colspan="2">' . __('Please send your bitcoin payment as follows:', 'woocommerce') . '</td>
245</tr>
246<tr class="bpit-table-row">
247<td style="vertical-align:middle;" class="bpit-td-name bpit-td-name-amount">
248' . __('Amount', 'woocommerce') . ' (<strong>BTC</strong>):
249</td>
250<td class="bpit-td-value bpit-td-value-amount">
251<div style="border:1px solid #FCCA09;padding:2px 6px;margin:2px;background-color:#FCF8E3;border-radius:4px;color:#CC0000;font-weight: bold;font-size: 120%;">
252{{{BITCOINS_AMOUNT}}}
253</div>
254</td>
255</tr>
256<tr class="bpit-table-row">
257<td style="vertical-align:middle;" class="bpit-td-name bpit-td-name-btcaddr">
258Address:
259</td>
260<td class="bpit-td-value bpit-td-value-btcaddr">
261<div style="border:1px solid #FCCA09;padding:2px 6px;margin:2px;background-color:#FCF8E3;border-radius:4px;color:#555;font-weight: bold;font-size: 120%;">
262{{{BITCOINS_ADDRESS}}}
263</div>
264</td>
265</tr>
266<tr class="bpit-table-row">
267<td style="vertical-align:middle;" class="bpit-td-name bpit-td-name-qr">
268QR Code:
269</td>
270<td class="bpit-td-value bpit-td-value-qr">
271<div style="border:1px solid #FCCA09;padding:5px;margin:2px;background-color:#FCF8E3;border-radius:4px;">
272<a href="bitcoin://{{{BITCOINS_ADDRESS}}}?amount={{{BITCOINS_AMOUNT}}}"><img src="https://blockchain.info/qr?data=bitcoin://{{{BITCOINS_ADDRESS}}}?amount={{{BITCOINS_AMOUNT}}}&size=180" style="vertical-align:middle;border:1px solid #888;" /></a>
273</div>
274</td>
275</tr>
276</table>
277
278' . __('Please note:', 'woocommerce') . '
279<ol class="bpit-instructions">
280<li>' . __('You must make a payment within 1 hour, or your order will be cancelled', 'woocommerce') . '</li>
281<li>' . __('As soon as your payment is received in full you will receive email confirmation with order delivery details.', 'woocommerce') . '</li>
282<li>{{{EXTRA_INSTRUCTIONS}}}</li>
283</ol>
284';
285 $payment_instructions = trim($payment_instructions);
286
287 $payment_instructions_description = '
288<p class="description" style="width:50%;float:left;width:49%;">
289' . __('Specific instructions given to the customer to complete Bitcoins payment.<br />You may change it, but make sure these tags will be present: <b>{{{BITCOINS_AMOUNT}}}</b>, <b>{{{BITCOINS_ADDRESS}}}</b> and <b>{{{EXTRA_INSTRUCTIONS}}}</b> as these tags will be replaced with customer - specific payment details.', 'woocommerce') . '
290</p>
291<p class="description" style="width:50%;float:left;width:49%;">
292Payment Instructions, original template (for reference):<br />
293<textarea rows="2" onclick="this.focus();this.select()" readonly="readonly" style="width:100%;background-color:#f1f1f1;height:4em">' . $payment_instructions . '</textarea>
294</p>
295';
296 $payment_instructions_description = trim($payment_instructions_description);
297 //-----------------------------------
298
299 $this->form_fields = array(
300 'enabled' => array(
301 'title' => __('Enable/Disable', 'woocommerce'),
302 'type' => 'checkbox',
303 'label' => __('Enable Bitcoin Payments', 'woocommerce'),
304 'default' => 'yes'
305 ),
306 'title' => array(
307 'title' => __('Title', 'woocommerce'),
308 'type' => 'text',
309 'description' => __('This controls the title which the user sees during checkout.', 'woocommerce'),
310 'default' => __('Bitcoin Payment', 'woocommerce')
311 ),
312
313 'bitcoin_addr_merchant' => array(
314 'title' => __('Your personal bitcoin address', 'woocommerce'),
315 'type' => 'text',
316 'css' => $this->service_provider != 'blockchain_info' ? 'display:none;' : '',
317 'disabled' => $this->service_provider != 'blockchain_info' ? true : false,
318 'description' => $this->service_provider != 'blockchain_info' ? __('Available when Bitcoin service provider is set to: <b>Blockchain.info</b> (at BitcoinWay plugin settings page)', 'woocommerce') : __('Your own bitcoin address (such as: 1H9uAP3x439YvQDoKNGgSYCg3FmrYRzpD2) - where you would like the payment to be sent. When customer sends you payment for the product - it will be automatically forwarded to this address by blockchain.info APIs.', 'woocommerce'),
319 'default' => ''
320 ),
321
322
323 'description' => array(
324 'title' => __('Customer Message', 'woocommerce'),
325 'type' => 'text',
326 'description' => __('Initial instructions for the customer at checkout screen', 'woocommerce'),
327 'default' => __('Please proceed to the next screen to see necessary payment details.', 'woocommerce')
328 ),
329 'instructions' => array(
330 'title' => __('Payment Instructions (HTML)', 'woocommerce'),
331 'type' => 'textarea',
332 'description' => $payment_instructions_description,
333 'default' => $payment_instructions
334 )
335 );
336 }
337 //-------------------------------------------------------------------
338 /*
339 ///!!!
340 '<table>' .
341 ' <tr><td colspan="2">' . __('Please send your bitcoin payment as follows:', 'woocommerce' ) . '</td></tr>' .
342 ' <tr><td>Amount (฿): </td><td><div style="border:1px solid #CCC;padding:2px 6px;margin:2px;background-color:#FEFEF0;border-radius:4px;color:#CC0000;">{{{BITCOINS_AMOUNT}}}</div></td></tr>' .
343 ' <tr><td>Address: </td><td><div style="border:1px solid #CCC;padding:2px 6px;margin:2px;background-color:#FEFEF0;border-radius:4px;color:blue;">{{{BITCOINS_ADDRESS}}}</div></td></tr>' .
344 '</table>' .
345 __('Please note:', 'woocommerce' ) .
346 '<ol>' .
347 ' <li>' . __('You must make a payment within 8 hours, or your order will be cancelled', 'woocommerce' ) . '</li>' .
348 ' <li>' . __('As soon as your payment is received in full you will receive email confirmation with order delivery details.', 'woocommerce' ) . '</li>' .
349 ' <li>{{{EXTRA_INSTRUCTIONS}}}</li>' .
350 '</ol>'
351
352 */
353
354 //-------------------------------------------------------------------
355 /**
356 * Admin Panel Options
357 * - Options for bits like 'title' and availability on a country-by-country basis
358 *
359 * @access public
360 * @return void
361 */
362 public function admin_options()
363 {
364 $validation_msg = "";
365 $store_valid = $this->is_gateway_valid_for_use($validation_msg);
366
367 // After defining the options, we need to display them too; thats where this next function comes into play:
368?>
369<h3><?php
370 _e('Bitcoin Payment', 'woocommerce');
371?></h3>
372<p>
373<?php
374 _e('Allows to accept payments in bitcoin. <a href="https://en.bitcoin.it/wiki/Main_Page" target="_blank">Bitcoins</a> are peer-to-peer, decentralized digital currency that enables instant payments from anyone to anyone, anywhere in the world
375<p style="border:1px solid #890e4e;padding:5px 10px;color:#004400;background-color:#FFF;"><u>Please donate BTC to</u>: <span style="color:#d21577;font-size:110%;font-weight:bold;">12fFTMkeu3mcunCtGHtWb7o5BcWA9eFx7R</span> <u>or via Paypal to</u>: <span style="color:#d21577;font-size:110%;font-weight:bold;">donate@bitcoinway.com</span> <span style="font-size:95%;">(All supporters will be acknowledged and listed within plugin repository)</span></p>
376', 'woocommerce');
377?>
378</p>
379<?php
380 echo $store_valid ? ('<p style="border:1px solid #DDD;padding:5px 10px;font-weight:bold;color:#004400;background-color:#CCFFCC;">' . __('Bitcoin payment gateway is operational', 'woocommerce') . '</p>') : ('<p style="border:1px solid #DDD;padding:5px 10px;font-weight:bold;color:#EE0000;background-color:#FFFFAA;">' . __('Bitcoin payment gateway is not operational: ', 'woocommerce') . $validation_msg . '</p>');
381?>
382<table class="form-table">
383<?php
384 // Generate the HTML For the settings form.
385 $this->generate_settings_html();
386?>
387</table><!--/.form-table-->
388<?php
389 }
390 //-------------------------------------------------------------------
391
392 //-------------------------------------------------------------------
393 // Hook into admin options saving.
394 public function process_admin_options()
395 {
396 return; // Not needed as all bitcoinway's settings are now inside BWWC plugin.
397
398 // // Call parent
399 // parent::process_admin_options();
400
401 // if (isset($_POST) && is_array($_POST))
402 // {
403 // $bwwc_settings = BWWC__get_settings ();
404 // if (!isset($bwwc_settings['gateway_settings']) || !is_array($bwwc_settings['gateway_settings']))
405 // $bwwc_settings['gateway_settings'] = array();
406
407 // // Born from __(..., 'woocommerce') + '$this->id'
408 // $prefix = 'woocommerce_bitcoin_';
409 // $prefix_length = strlen($prefix);
410
411 // foreach ($_POST as $varname => $varvalue)
412 // {
413 // if (strpos($varname, $prefix) === 0)
414 // {
415 // $trimmed_varname = substr($varname, $prefix_length);
416 // if ($trimmed_varname != 'description' && $trimmed_varname != 'instructions')
417 // $bwwc_settings['gateway_settings'][$trimmed_varname] = $varvalue;
418 // }
419 // }
420
421 // // Update gateway settings within BWWC own settings for easier access.
422 // BWWC__update_settings ($bwwc_settings);
423 // }
424 }
425 //-------------------------------------------------------------------
426
427 //-------------------------------------------------------------------
428 /**
429 * Process the payment and return the result
430 *
431 * @access public
432 * @param int $order_id
433 * @return array
434 */
435 function process_payment($order_id)
436 {
437 $bwwc_settings = BWWC__get_settings();
438 $order = new WC_Order($order_id);
439
440 // TODO: Implement CRM features within store admin dashboard
441 $order_meta = array();
442 $order_meta['bw_order'] = $order;
443 $order_meta['bw_items'] = $order->get_items();
444 $order_meta['bw_b_addr'] = $order->get_formatted_billing_address();
445 $order_meta['bw_s_addr'] = $order->get_formatted_shipping_address();
446 $order_meta['bw_b_email'] = $order->billing_email;
447 $order_meta['bw_currency'] = $order->order_currency;
448 $order_meta['bw_store'] = plugins_url('', __FILE__);
449
450
451 //-----------------------------------
452 // Save bitcoin payment info together with the order.
453 // Note: this code must be on top here, as other filters will be called from here and will use these values ...
454 //
455 // Calculate realtime bitcoin price (if exchange is necessary)
456
457 $exchange_rate = BWWC__get_exchange_rate_per_bitcoin(get_woocommerce_currency(), 'getfirst');
458 /// $exchange_rate = BWWC__get_exchange_rate_per_bitcoin (get_woocommerce_currency(), $this->exchange_rate_retrieval_method, $this->exchange_rate_type);
459 if (!$exchange_rate) {
460 $msg = 'ERROR: Cannot determine Bitcoin exchange rate. Possible issues: store server does not allow outgoing connections, exchange rate servers are blocking incoming connections or down. ' . 'You may avoid that by setting store currency directly to Bitcoin(BTC)';
461 BWWC__log_event(__FILE__, __LINE__, $msg);
462 exit('<h2 style="color:red;">' . $msg . '</h2>');
463 }
464
465 $order_total_in_btc = ($order->get_total() / $exchange_rate);
466 if (get_woocommerce_currency() != 'BTC')
467 // Apply exchange rate multiplier only for stores with non-bitcoin default currency.
468 $order_total_in_btc = $order_total_in_btc;
469
470 $order_total_in_btc = sprintf("%.8f", $order_total_in_btc);
471
472 $bitcoins_address = false;
473
474 $order_info = array(
475 'order_meta' => $order_meta,
476 'order_id' => $order_id,
477 'order_total' => $order_total_in_btc, // Order total in BTC
478 'order_datetime' => date('Y-m-d H:i:s T'),
479 'requested_by_ip' => @$_SERVER['REMOTE_ADDR']
480 );
481
482 $ret_info_array = array();
483
484 if ($this->service_provider == 'blockchain_info') {
485 $bitcoin_addr_merchant = $this->bitcoin_addr_merchant;
486 $secret_key = substr(md5(microtime()), 0, 16); # Generate secret key to be validate upon receiving IPN callback to prevent spoofing.
487 $callback_url = trailingslashit(home_url()) . "?wc-api=BWWC_Bitcoin&secret_key={$secret_key}&bitcoinway=1&src=bcinfo&order_id={$order_id}"; // http://www.example.com/?bitcoinway=1&order_id=74&src=bcinfo
488 BWWC__log_event(__FILE__, __LINE__, "Calling BWWC__generate_temporary_bitcoin_address__blockchain_info(). Payments to be forwarded to: '{$bitcoin_addr_merchant}' with callback URL: '{$callback_url}' ...");
489
490 // This function generates temporary bitcoin address and schedules IPN callback at the same
491 $ret_info_array = BWWC__generate_temporary_bitcoin_address__blockchain_info($bitcoin_addr_merchant, $callback_url);
492
493 /*
494 $ret_info_array = array (
495 'result' => 'success', // OR 'error'
496 'message' => '...',
497 'host_reply_raw' => '......',
498 'generated_bitcoin_address' => '1H9uAP3x439YvQDoKNGgSYCg3FmrYRzpD2', // or false
499 );
500 */
501 $bitcoins_address = @$ret_info_array['generated_bitcoin_address'];
502 } else if ($this->service_provider == 'electrum_wallet') {
503 // Generate bitcoin address for electrum wallet provider.
504 /*
505 $ret_info_array = array (
506 'result' => 'success', // OR 'error'
507 'message' => '...',
508 'host_reply_raw' => '......',
509 'generated_bitcoin_address' => '1H9uAP3x439YvQDoKNGgSYCg3FmrYRzpD2', // or false
510 );
511 */
512 $ret_info_array = BWWC__get_bitcoin_address_for_payment__electrum(BWWC__get_next_available_mpk(), $order_info);
513 $bitcoins_address = @$ret_info_array['generated_bitcoin_address'];
514 }
515
516 if (!$bitcoins_address) {
517 $msg = "ERROR: cannot generate bitcoin address for the order: '" . @$ret_info_array['message'] . "'";
518 BWWC__log_event(__FILE__, __LINE__, $msg);
519 exit('<h2 style="color:red;">' . $msg . '</h2>');
520 }
521
522 BWWC__log_event(__FILE__, __LINE__, " Generated unique bitcoin address: '{$bitcoins_address}' for order_id " . $order_id);
523
524 if ($this->service_provider == 'blockchain_info') {
525 update_post_meta($order_id, // post id ($order_id)
526 'secret_key', // meta key
527 $secret_key // meta value. If array - will be auto-serialized
528 );
529 }
530
531 update_post_meta($order_id, // post id ($order_id)
532 'order_total_in_btc', // meta key
533 $order_total_in_btc // meta value. If array - will be auto-serialized
534 );
535 update_post_meta($order_id, // post id ($order_id)
536 'bitcoins_address', // meta key
537 $bitcoins_address // meta value. If array - will be auto-serialized
538 );
539 update_post_meta($order_id, // post id ($order_id)
540 'bitcoins_paid_total', // meta key
541 "0" // meta value. If array - will be auto-serialized
542 );
543 update_post_meta($order_id, // post id ($order_id)
544 'bitcoins_refunded', // meta key
545 "0" // meta value. If array - will be auto-serialized
546 );
547 update_post_meta($order_id, // post id ($order_id)
548 '_incoming_payments', // meta key. Starts with '_' - hidden from UI.
549 array() // array (array('datetime'=>'', 'from_addr'=>'', 'amount'=>''),)
550 );
551 update_post_meta($order_id, // post id ($order_id)
552 '_payment_completed', // meta key. Starts with '_' - hidden from UI.
553 0 // array (array('datetime'=>'', 'from_addr'=>'', 'amount'=>''),)
554 );
555 //-----------------------------------
556
557
558 // The bitcoin gateway does not take payment immediately, but it does need to change the orders status to on-hold
559 // (so the store owner knows that bitcoin payment is pending).
560 // We also need to tell WooCommerce that it needs to redirect to the thankyou page – this is done with the returned array
561 // and the result being a success.
562 //
563 global $woocommerce;
564
565 // Updating the order status:
566
567 // Mark as on-hold (we're awaiting for bitcoins payment to arrive)
568 $order->update_status('on-hold', __('Awaiting bitcoin payment to arrive', 'woocommerce'));
569
570 /*
571 ///////////////////////////////////////
572 // timbowhite's suggestion:
573 // -----------------------
574 // Mark as pending (we're awaiting for bitcoins payment to arrive), not 'on-hold' since
575 // woocommerce does not automatically cancel expired on-hold orders. Woocommerce handles holding the stock
576 // for pending orders until order payment is complete.
577 $order->update_status('pending', __('Awaiting bitcoin payment to arrive', 'woocommerce'));
578
579 // Me: 'pending' does not trigger "Thank you" page and neither email sending. Not sure why.
580 // Also - I think cancellation of unpaid orders needs to be initiated from cron job, as only we know when order needs to be cancelled,
581 // by scanning "on-hold" orders through 'assigned_address_expires_in_mins' timeout check.
582 ///////////////////////////////////////
583 */
584 // Remove cart
585 $woocommerce->cart->empty_cart();
586
587 // Empty awaiting payment session
588 unset($_SESSION['order_awaiting_payment']);
589
590 // Return thankyou redirect
591 if (version_compare(WOOCOMMERCE_VERSION, '2.1', '<')) {
592 return array(
593 'result' => 'success',
594 'redirect' => add_query_arg('key', $order->order_key, add_query_arg('order', $order_id, get_permalink(woocommerce_get_page_id('thanks'))))
595 );
596 } else {
597 return array(
598 'result' => 'success',
599 'redirect' => add_query_arg('key', $order->order_key, add_query_arg('order', $order_id, $this->get_return_url($order)))
600 );
601 }
602 }
603 //-------------------------------------------------------------------
604
605 //-------------------------------------------------------------------
606 /**
607 * Output for the order received page.
608 *
609 * @access public
610 * @return void
611 */
612 function BWWC__thankyou_page($order_id)
613 {
614 // BWWC__thankyou_page is hooked into the "thank you" page and in the simplest case can just echo’s the description.
615
616 // Get order object.
617 // http://wcdocs.woothemes.com/apidocs/class-WC_Order.html
618 $order = new WC_Order($order_id);
619
620 // Assemble detailed instructions.
621 $order_total_in_btc = get_post_meta($order->id, 'order_total_in_btc', true); // set single to true to receive properly unserialized array
622 $bitcoins_address = get_post_meta($order->id, 'bitcoins_address', true); // set single to true to receive properly unserialized array
623
624
625 $instructions = $this->instructions;
626 $instructions = str_replace('{{{BITCOINS_AMOUNT}}}', $order_total_in_btc, $instructions);
627 $instructions = str_replace('{{{BITCOINS_ADDRESS}}}', $bitcoins_address, $instructions);
628 $instructions = str_replace('{{{EXTRA_INSTRUCTIONS}}}', $this->instructions_multi_payment_str, $instructions);
629 $order->add_order_note(__("Order instructions: price=฿{$order_total_in_btc}, incoming account:{$bitcoins_address}", 'woocommerce'));
630
631 echo wpautop(wptexturize($instructions));
632 }
633 //-------------------------------------------------------------------
634
635 //-------------------------------------------------------------------
636 /**
637 * Add content to the WC emails.
638 *
639 * @access public
640 * @param WC_Order $order
641 * @param bool $sent_to_admin
642 * @return void
643 */
644 function BWWC__email_instructions($order, $sent_to_admin)
645 {
646 if ($sent_to_admin)
647 return;
648 if (!in_array($order->status, array(
649 'pending',
650 'on-hold'
651 ), true))
652 return;
653 if ($order->payment_method !== 'bitcoin')
654 return;
655
656 // Assemble payment instructions for email
657 $order_total_in_btc = get_post_meta($order->id, 'order_total_in_btc', true); // set single to true to receive properly unserialized array
658 $bitcoins_address = get_post_meta($order->id, 'bitcoins_address', true); // set single to true to receive properly unserialized array
659
660
661 $instructions = $this->instructions;
662 $instructions = str_replace('{{{BITCOINS_AMOUNT}}}', $order_total_in_btc, $instructions);
663 $instructions = str_replace('{{{BITCOINS_ADDRESS}}}', $bitcoins_address, $instructions);
664 $instructions = str_replace('{{{EXTRA_INSTRUCTIONS}}}', $this->instructions_multi_payment_str, $instructions);
665
666 echo wpautop(wptexturize($instructions));
667 }
668 //-------------------------------------------------------------------
669
670 //-------------------------------------------------------------------
671 /**
672 * Check for Bitcoin-related IPN callabck
673 *
674 * @access public
675 * @return void
676 */
677 function BWWC__maybe_bitcoin_ipn_callback()
678 {
679 // If example.com/?bitcoinway=1 is present - it is callback URL.
680 if (isset($_REQUEST['bitcoinway']) && $_REQUEST['bitcoinway'] == '1') {
681 BWWC__log_event(__FILE__, __LINE__, "BWWC__maybe_bitcoin_ipn_callback () called and 'bitcoinway=1' detected. REQUEST = " . serialize(@$_REQUEST));
682
683 if (@$_GET['src'] != 'bcinfo') {
684 $src = $_GET['src'];
685 BWWC__log_event(__FILE__, __LINE__, "Warning: received IPN notification with 'src'= '{$src}', which is not matching expected: 'bcinfo'. Ignoring ...");
686 exit();
687 }
688
689 // Processing IPN callback from blockchain.info ('bcinfo')
690
691
692 $order_id = @$_GET['order_id'];
693
694 $secret_key = get_post_meta($order_id, 'secret_key', true);
695 $secret_key_sent = @$_GET['secret_key'];
696 // Check the Request secret_key matches the original one (blockchain.info sends all params back)
697 if ($secret_key_sent != $secret_key) {
698 BWWC__log_event(__FILE__, __LINE__, "Warning: secret_key does not match! secret_key sent: '{$secret_key_sent}'. Expected: '{$secret_key}'. Processing aborted.");
699 exit('Invalid secret_key');
700 }
701
702 $confirmations = @$_GET['confirmations'];
703
704
705 if ($confirmations >= $this->confs_num) {
706
707 // The value of the payment received in satoshi (not including fees). Divide by 100000000 to get the value in BTC.
708 $value_in_btc = @$_GET['value'] / 100000000;
709 $txn_hash = @$_GET['transaction_hash'];
710 $txn_confirmations = @$_GET['confirmations'];
711
712 //---------------------------
713 // Update incoming payments array stats
714 $incoming_payments = get_post_meta($order_id, '_incoming_payments', true);
715 $incoming_payments[$txn_hash] = array(
716 'txn_value' => $value_in_btc,
717 'dest_address' => @$_GET['address'],
718 'confirmations' => $txn_confirmations,
719 'datetime' => date("Y-m-d, G:i:s T")
720 );
721
722 update_post_meta($order_id, '_incoming_payments', $incoming_payments);
723 //---------------------------
724
725 //---------------------------
726 // Recalc total amount received for this order by adding totals from uniquely hashed txn's ...
727 $paid_total_so_far = 0;
728 foreach ($incoming_payments as $k => $txn_data)
729 $paid_total_so_far += $txn_data['txn_value'];
730
731 update_post_meta($order_id, 'bitcoins_paid_total', $paid_total_so_far);
732 //---------------------------
733
734 $order_total_in_btc = get_post_meta($order_id, 'order_total_in_btc', true);
735 if ($paid_total_so_far >= $order_total_in_btc) {
736 BWWC__process_payment_completed_for_order($order_id, false);
737 } else {
738 BWWC__log_event(__FILE__, __LINE__, "NOTE: Payment received (for BTC {$value_in_btc}), but not enough yet to cover the required total. Will be waiting for more. Bitcoins: now/total received/needed = {$value_in_btc}/{$paid_total_so_far}/{$order_total_in_btc}");
739 }
740
741 // Reply '*ok*' so no more notifications are sent
742 exit('*ok*');
743 } else {
744 // Number of confirmations are not there yet... Skip it this time ...
745 // Don't print *ok* so the notification resent again on next confirmation
746 BWWC__log_event(__FILE__, __LINE__, "NOTE: Payment notification received (for BTC {$value_in_btc}), but number of confirmations is not enough yet. Confirmations received/required: {$confirmations}/{$this->confs_num}");
747 exit();
748 }
749 }
750 }
751 //-------------------------------------------------------------------
752 }
753 //=======================================================================
754
755
756 //-----------------------------------------------------------------------
757 // Hook into WooCommerce - add necessary hooks and filters
758 add_filter('woocommerce_payment_gateways', 'BWWC__add_bitcoin_gateway');
759
760 // Disable unnecessary billing fields.
761 /// Note: it affects whole store.
762 /// add_filter ('woocommerce_checkout_fields' , 'BWWC__woocommerce_checkout_fields' );
763
764 add_filter('woocommerce_currencies', 'BWWC__add_btc_currency');
765 add_filter('woocommerce_currency_symbol', 'BWWC__add_btc_currency_symbol', 10, 2);
766
767 // Change [Order] button text on checkout screen.
768 /// Note: this will affect all payment methods.
769 /// add_filter ('woocommerce_order_button_text', 'BWWC__order_button_text');
770 //-----------------------------------------------------------------------
771
772 //=======================================================================
773 /**
774 * Add the gateway to WooCommerce
775 *
776 * @access public
777 * @param array $methods
778 * @package
779 * @return array
780 */
781 function BWWC__add_bitcoin_gateway($methods)
782 {
783 $methods[] = 'BWWC_Bitcoin';
784 return $methods;
785 }
786 //=======================================================================
787
788 //=======================================================================
789 // Our hooked in function - $fields is passed via the filter!
790 function BWWC__woocommerce_checkout_fields($fields)
791 {
792 unset($fields['order']['order_comments']);
793 unset($fields['billing']['billing_first_name']);
794 unset($fields['billing']['billing_last_name']);
795 unset($fields['billing']['billing_company']);
796 unset($fields['billing']['billing_address_1']);
797 unset($fields['billing']['billing_address_2']);
798 unset($fields['billing']['billing_city']);
799 unset($fields['billing']['billing_postcode']);
800 unset($fields['billing']['billing_country']);
801 unset($fields['billing']['billing_state']);
802 unset($fields['billing']['billing_phone']);
803 return $fields;
804 }
805 //=======================================================================
806
807 //=======================================================================
808 function BWWC__add_btc_currency($currencies)
809 {
810 $currencies['BTC'] = __('Bitcoin (฿)', 'woocommerce');
811 return $currencies;
812 }
813 //=======================================================================
814
815 //=======================================================================
816 function BWWC__add_btc_currency_symbol($currency_symbol, $currency)
817 {
818 switch ($currency) {
819 case 'BTC':
820 $currency_symbol = '฿';
821 break;
822 }
823
824 return $currency_symbol;
825 }
826 //=======================================================================
827
828 //=======================================================================
829 function BWWC__order_button_text()
830 {
831 return 'Continue';
832 }
833 //=======================================================================
834}
835//###########################################################################
836
837//===========================================================================
838function BWWC__process_payment_completed_for_order($order_id, $bitcoins_paid = false)
839{
840
841 if ($bitcoins_paid)
842 update_post_meta($order_id, 'bitcoins_paid_total', $bitcoins_paid);
843
844 // Payment completed
845 // Make sure this logic is done only once, in case customer keep sending payments :)
846 if (!get_post_meta($order_id, '_payment_completed', true)) {
847 update_post_meta($order_id, '_payment_completed', '1');
848
849 BWWC__log_event(__FILE__, __LINE__, "Success: order '{$order_id}' paid in full. Processing and notifying customer ...");
850
851 // Instantiate order object.
852 $order = new WC_Order($order_id);
853 $order->add_order_note(__('Order paid in full', 'woocommerce'));
854
855 $order->payment_complete();
856 }
857}
858//===========================================================================