#!/usr/bin/ruby # frozen_string_literal: true # Usage: bin/process_pending-btc_transactions '{ # oxr_app_id = "", # required_confirmations = 3, # electrum = env:ELECTRUM_CONFIG, # plans = ./plans.dhall # }' require "bigdecimal" require "dhall" require "money/bank/open_exchange_rates_bank" require "net/http" require "nokogiri" require "pg" require "redis" require_relative "../lib/electrum" CONFIG = Dhall::Coder .new(safe: Dhall::Coder::JSON_LIKE + [Symbol]) .load(ARGV[0], transform_keys: :to_sym) REDIS = Redis.new ELECTRUM = Electrum.new(**CONFIG[:electrum]) DB = PG.connect(dbname: "jmp") DB.type_map_for_results = PG::BasicTypeMapForResults.new(DB) DB.type_map_for_queries = PG::BasicTypeMapForQueries.new(DB) unless (cad_to_usd = REDIS.get("cad_to_usd")&.to_f) oxr = Money::Bank::OpenExchangeRatesBank.new(Money::RatesStore::Memory.new) oxr.app_id = CONFIG.fetch(:oxr_app_id) oxr.update_rates cad_to_usd = oxr.get_rate("CAD", "USD") REDIS.set("cad_to_usd", cad_to_usd, ex: 60*60) end canadianbitcoins = Nokogiri::HTML.parse( Net::HTTP.get(URI("https://www.canadianbitcoins.com")) ) bitcoin_row = canadianbitcoins.at("#ticker > table > tbody > tr") raise "Bitcoin row has moved" unless bitcoin_row.at("td").text == "Bitcoin" btc_sell_price = {} btc_sell_price[:CAD] = BigDecimal.new( bitcoin_row.at("td:nth-of-type(3)").text.match(/^\$(\d+\.\d+)/)[1] ) btc_sell_price[:USD] = btc_sell_price[:CAD] * cad_to_usd class Plan def self.for_customer(customer_id) row = DB.exec_params(<<-SQL, [customer_id]).first SELECT plan_name FROM customer_plans WHERE customer_id=$1 LIMIT 1 SQL return unless row plan = CONFIG[:plans].find { |p| p["plan_name"] = row["plan_name"] } new(plan) if plan end def initialize(plan) @plan = plan end def currency @plan[:currency] end end REDIS.hgetall("pending_btc_transactions").each do |(txid, customer_id)| tx_hash, address = txid.split("/", 2) transaction = ELECTRUM.gettransaction(tx_hash) next unless transaction.confirmations >= CONFIG[:required_confirmations] btc = transaction.amount_for(address) if btc <= 0 warn "Transaction shows as #{btc}, skipping #{txid}" next end DB.transaction do plan = Plan.for_customer(customer_id) if plan amount = btc * btc_sell_price.fetch(plan.currency).round(4, :floor) DB.exec_params(<<-SQL, [customer_id, txid, amount]) INSERT INTO transactions (customer_id, transaction_id, amount, note) VALUES ($1, $2, $3, 'Bitcoin payment') ON CONFLICT (transaction_id) DO NOTHING SQL else warn "No plan for #{customer_id} cannot save #{txid}" end end REDIS.hdel("pending_btc_transactions", txid) end