count_ready($mpk);
$neededAddresses = $amount - $readyCount;
for ($i = 0; $i < $neededAddresses; $i++) {
try {
self::force_new_address($cryptoId, $mpk);
}
catch ( \Exception $e ) {
AGC_Util::log(__FILE__, __LINE__, $e->getMessage());
}
}
}
public static function check_all_pending_addresses_for_payment($cryptoId, $mpk, $requiredConfirmations, $percentToVerify) {
global $woocommerce;
$electrumRepo = new AGC_Electrum_Repo($cryptoId, $mpk);
$pendingRecords = $electrumRepo->get_pending();
foreach ($pendingRecords as $record) {
try {
$blockchainTotalReceived = self::get_total_received_for_address($cryptoId, $record['address'], $requiredConfirmations);
}
catch ( \Exception $e ) {
// just go to next record if the endpoint is not responding
continue;
}
$recordTotalReceived = $record['total_received'];
$newPaymentAmount = $blockchainTotalReceived - $recordTotalReceived;
// if we received a new payment
if ($newPaymentAmount > 0.0000001) {
$address = $record['address'];
$orderAmount = $record['order_amount'];
AGC_Util::log(__FILE__, __LINE__, 'Address ' . $address . ' received a new payment of ' . AGC_Cryptocurrencies::get_price_string($cryptoId, $newPaymentAmount) . ' ' . $cryptoId);
// set total in database because we received a payment
$electrumRepo->set_total_received($address, $blockchainTotalReceived);
$amountToVerify = ((float) $orderAmount) * $percentToVerify;
$paymentAmountVerified = $blockchainTotalReceived >= $amountToVerify;
// if new total is enough to process the order
if ($paymentAmountVerified) {
$order_id = $record['order_id'];
$order = new WC_Order( $order_id );
$orderNote = sprintf(
'Order payment of %s %s verified at %s.',
AGC_Cryptocurrencies::get_price_string($cryptoId, $blockchainTotalReceived),
$cryptoId,
date('Y-m-d H:i:s', time()));
//$order->update_status('wc-processing', $orderNote);
$order->payment_complete();
$order->add_order_note($orderNote);
$electrumRepo->set_status($address, 'complete');
}
// we received payment but it was not enough to meet store admin's processing requirement
else {
$order_id = $record['order_id'];
$order = new WC_Order( $order_id );
// handle multiple underpayments, just add a new note
if ($record['status'] === 'underpaid') {
$orderNote = sprintf(
'New payment was received but is still under order total. Received payment of %s %s.
Remaining payment required: %s
Wallet Address: %s',
AGC_Cryptocurrencies::get_price_string($cryptoId, $newPaymentAmount),
$cryptoId,
AGC_Cryptocurrencies::get_price_string($cryptoId, $amountToVerify - $blockchainTotalReceived),
$address);
add_filter('woocommerce_email_subject_customer_note', 'AGC_change_partial_email_note_subject_line', 1, 2);
add_filter('woocommerce_email_heading_customer_note', 'AGC_change_partial_email_heading', 1, 2);
$order->add_order_note($orderNote, true);
}
// handle first underpayment, update status to pending payment (since we use on-hold for orders with no payment yet)
else {
$orderNote = sprintf(
'Payment of %s %s received at %s. This is under the amount required to process this order.
Remaining payment required: %s
Wallet Address: %s',
AGC_Cryptocurrencies::get_price_string($cryptoId, $blockchainTotalReceived),
$cryptoId,
date('m/d/Y g:i a', time() + (60 * 60 * get_option('gmt_offset'))),
AGC_Cryptocurrencies::get_price_string($cryptoId, $amountToVerify - $blockchainTotalReceived),
$address);
add_filter('woocommerce_email_subject_customer_note', 'AGC_change_partial_email_note_subject_line', 1, 2);
add_filter('woocommerce_email_heading_customer_note', 'AGC_change_partial_email_heading', 1, 2);
$order->add_order_note($orderNote, true);
$electrumRepo->set_status($address, 'underpaid');
}
}
}
}
}
private static function get_total_received_for_address($cryptoId, $address, $requiredConfirmations) {
if ($cryptoId === 'BTC') {
return self::get_total_received_for_bitcoin_address($address, $requiredConfirmations);
}
if ($cryptoId === 'LTC') {
return self::get_total_received_for_litecoin_address($address, $requiredConfirmations);
}
if ($cryptoId === 'QTUM') {
return self::get_total_received_for_qtum_address($address, $requiredConfirmations);
}
}
private static function get_total_received_for_bitcoin_address($address, $requiredConfirmations) {
$primaryResult = AGC_Blockchain::get_blockchaininfo_total_received_for_btc_address($address, $requiredConfirmations);
if ($primaryResult['result'] === 'success') {
return $primaryResult['total_received'];
}
$secondaryResult = AGC_Blockchain::get_blockexplorer_total_received_for_btc_address($address);
if ($secondaryResult['result'] === 'success') {
AGC_Util::log(__FILE__, __LINE__, 'Address ' . $address . ' falling back to blockexplorer info.');
return $secondaryResult['total_received'];
}
throw new \Exception("Unable to get btc address information from external sources.");
}
private static function get_total_received_for_litecoin_address($address, $requiredConfirmations) {
$primaryResult = AGC_Blockchain::get_blockcypher_total_received_for_ltc_address($address, $requiredConfirmations);
if ($primaryResult['result'] === 'success') {
return $primaryResult['total_received'];
}
$secondaryResult = AGC_Blockchain::get_chainso_total_received_for_ltc_address($address);
if ($secondaryResult['result'] === 'success') {
return $secondaryResult['total_received'];
}
throw new \Exception("Unable to get ltc address information from external sources.");
}
private static function get_total_received_for_qtum_address($address, $requiredConfirmations) {
$result = AGC_Blockchain::get_qtuminfo_total_received_for_qtum_address($address, $requiredConfirmations);
if ($result['result'] === 'success') {
return $result['total_received'];
}
throw new \Exception("Unable to get ltc address information from external sources.");
}
public static function cancel_expired_addresses($cryptoId, $mpk, $orderCancellationTimeSec) {
global $woocommerce;
$electrumRepo = new AGC_Electrum_Repo($cryptoId, $mpk);
$assignedRecords = $electrumRepo->get_assigned();
foreach ($assignedRecords as $record) {
$assignedAt = $record['assigned_at'];
$totalReceived = $record['total_received'];
$address = $record['address'];
$orderId = $record['order_id'];
$assignedFor = time() - $assignedAt;
AGC_Util::log(__FILE__, __LINE__, 'address ' . $address . ' has been assigned for ' . $assignedFor . '... cancel time: ' . $orderCancellationTimeSec);
if ($assignedFor > $orderCancellationTimeSec && $totalReceived == 0) {
// since order was cancelled we can re-use the address, set status to ready
$electrumRepo->set_status($address, 'ready');
$electrumRepo->set_order_amount($address, 0.0);
$order = new WC_Order($orderId);
$orderNote = sprintf(
'Your order was cancelled because you were unable to pay for %s minute(s). Please do not send any funds to the payment address.',
round($orderCancellationTimeSec/60, 1),
$address);
add_filter('woocommerce_email_subject_customer_note', 'AGC_change_cancelled_email_note_subject_line', 1, 2);
add_filter('woocommerce_email_heading_customer_note', 'AGC_change_cancelled_email_heading', 1, 2);
$order->update_status('wc-cancelled');
$order->add_order_note($orderNote, true);
AGC_Util::log(__FILE__, __LINE__, 'Cancelled order: ' . $orderId . ' which was using address: ' . $address . 'due to non-payment.');
}
}
}
private static function is_dirty_address($cryptoId, $address) {
if ($cryptoId === 'BTC') {
return self::is_dirty_btc_address($address);
}
if ($cryptoId === 'LTC') {
return self::is_dirty_ltc_address($address);
}
if ($cryptoId == 'QTUM') {
return self::is_dirty_qtum_address($address);
}
}
private static function is_dirty_btc_address($address) {
$primaryResult = AGC_Blockchain::get_blockchaininfo_total_received_for_btc_address($address, 0);
if ($primaryResult['result'] === 'success') {
// if we get a non zero balance from first source then address is dirty
if ($primaryResult['total_received'] >= 0.00000001) {
return true;
}
else {
$secondaryResult = AGC_Blockchain::get_blockexplorer_total_received_for_btc_address($address);
// we have a primary resource saying address is clean and backup source failed, so return clean
if ($secondaryResult['result'] === 'error') {
return false;
}
// backup source gave us data
else {
// primary source is clean but if we see a balance we return dirty
if ($secondaryResult['total_received'] >= 0.00000001) {
return true;
}
// both sources return clean
else {
return false;
}
}
}
}
else {
$secondaryResult = AGC_Blockchain::get_blockexplorer_total_received_for_btc_address($address);
if ($secondaryResult['result'] === 'success') {
return $secondaryResult['total_received'] >= 0.00000001;
}
}
throw new \Exception("Unable to get btc address to verify is address is unused.");
}
private static function is_dirty_ltc_address($address) {
$primaryResult = AGC_Blockchain::get_chainso_total_received_for_ltc_address($address);
error_log('primary result: ' . print_r($primaryResult, true));
if ($primaryResult['result'] === 'success') {
// if we get a non zero balance from first source then address is dirty
if ($primaryResult['total_received'] >= 0.00000001) {
return true;
}
else {
$secondaryResult = AGC_Blockchain::get_blockcypher_total_received_for_ltc_address($address, 0);
// we have a primary resource saying address is clean and backup source failed, so return clean
if ($secondaryResult['result'] === 'error') {
return false;
}
// backup source gave us data
else {
// primary source is clean but if we see a balance we return dirty
if ($secondaryResult['total_received'] >= 0.00000001) {
return true;
}
// both sources return clean
else {
return false;
}
}
}
}
else {
$secondaryResult = AGC_Blockchain::get_blockcypher_total_received_for_ltc_address($address, 0);
if ($secondaryResult['result'] === 'success') {
return $secondaryResult['total_received'] >= 0.00000001;
}
}
throw new \Exception("Unable to get ltc address to verify is address is unused.");
}
private static function is_dirty_qtum_address($address) {
$result = AGC_Blockchain::get_qtuminfo_total_received_for_qtum_address($address, 0);
if ($result['result'] === 'success') {
if ($result['total_received'] > 0.00000001) {
return true;
}
else {
return false;
}
}
throw new \Exception("Unable to get ltc address to verify is address is unused.");
}
public static function force_new_address($cryptoId, $mpk) {
$electrumRepo = new AGC_Electrum_Repo($cryptoId, $mpk);
$startIndex = $electrumRepo->get_next_index($mpk);
$address = self::create_electrum_address($cryptoId, $mpk, $startIndex);
try {
while (self::is_dirty_address($cryptoId, $address)) {
$electrumRepo->insert($address, $startIndex, 'dirty');
$startIndex = $startIndex + 1;
$address = self::create_electrum_address($cryptoId, $mpk, $startIndex);
set_time_limit(30);
}
}
catch ( \Exception $e ) {
AGC_Util::log(__FILE__, __LINE__, 'Could not create new addresses: ' . $e->getMessage());
throw new \Exception($e);
}
$electrumRepo->insert($address, $startIndex, 'ready');
}
public static function create_electrum_address($cryptoId, $mpk, $index) {
// 1.5.4 - this is always version 2
$version = self::get_mpk_version($mpk);
if (self::is_valid_mpk($mpk)) {
return ElectrumHelper::mpk_to_bc_address($cryptoId, $mpk, $index, $version);
}
throw new \Exception('Invalid MPK, use Legacy version in Electrum to create your secure addresses via Master Public Key.');
}
public static function is_valid_mpk($mpk) {
$mpkStart = substr($mpk, 0, 5);
$validMpk = strlen($mpk) > 55 && $mpkStart === 'xpub6';
return $validMpk;
}
public static function get_mpk_version($mpk) {
if ($mpk[0] === 'x') {
return 2;
}
else {
return 1;
}
}
}
?>