Merge branch 'rubocop'

Stephen Paul Weber created

* rubocop:
  Update rubocop

Change summary

.rubocop.yml                         | 146 +++++++++++++++++------------
bin/active_tels_on_catapult          |   1 
bin/billing_monthly_cronjob          |  29 ++++-
bin/correct_duplicate_addrs          |  10 +-
bin/detect_duplicate_addrs           |   3 
bin/process_pending_btc_transactions |  29 +++--
config.ru                            |  85 ++++++++---------
lib/blather_notify.rb                |   4 
lib/electrum.rb                      |  15 ++
lib/redis_addresses.rb               |  13 +-
lib/transaction.rb                   |   7 +
test/test_electrum.rb                |  18 +-
12 files changed, 206 insertions(+), 154 deletions(-)

Detailed changes

.rubocop.yml 🔗

@@ -1,113 +1,133 @@
 AllCops:
-  TargetRubyVersion: 2.3
+  TargetRubyVersion: 2.5
+  NewCops: enable
 
-Metrics/LineLength:
-  Max: 80
+Metrics/ClassLength:
+  Exclude:
+    - test/*
+
+Metrics/MethodLength:
+  Exclude:
+    - test/*
 
 Metrics/BlockLength:
   ExcludedMethods:
     - route
     - "on"
+  Exclude:
+    - test/*
+
+Metrics/AbcSize:
+  Exclude:
+    - test/*
+
+Naming/MethodParameterName:
+  AllowNamesEndingInNumbers: false
+  AllowedNames:
+    - m
+    - e
+    - r
+    - q
+    - s
+    - k
+    - v
+    - ex
+    - tx
+    - id
+    - iq
+    - ip
+    - db
+
+Layout/CaseIndentation:
+  EnforcedStyle: end
 
-Layout/Tab:
+Layout/IndentationStyle:
   Enabled: false
+  EnforcedStyle: tabs
+  IndentationWidth: 4
 
 Layout/IndentationWidth:
   Width: 1 # one tab
 
-Lint/EndAlignment:
-  EnforcedStyleAlignWith: variable
+Layout/LineLength:
+  Max: 80
+  Exclude:
+    - Gemfile
 
-Lint/RescueException:
-  Enabled: false
+Layout/SpaceAroundEqualsInParameterDefault:
+  EnforcedStyle: no_space
 
-Style/AndOr:
-  Enabled: false
+Layout/AccessModifierIndentation:
+  EnforcedStyle: outdent
 
-Layout/AlignParameters:
-  Enabled: false
+Layout/FirstParameterIndentation:
+  EnforcedStyle: consistent
 
-Style/BlockDelimiters:
+Style/AccessModifierDeclarations:
   Enabled: false
 
-Layout/CaseIndentation:
-  EnforcedStyle: end
+Style/StringLiterals:
+  EnforcedStyle: double_quotes
 
-Style/Documentation:
+Style/NumericLiterals:
   Enabled: false
 
-Style/FormatString:
-  EnforcedStyle: percent
-
-Layout/LeadingCommentSpace:
-  Enabled: false
+Style/SymbolArray:
+  EnforcedStyle: brackets
 
-Layout/MultilineMethodCallBraceLayout:
-  Enabled: false
+Style/WordArray:
+  EnforcedStyle: brackets
 
-Layout/MultilineOperationIndentation:
+Style/Documentation:
   Enabled: false
 
-Style/MultilineTernaryOperator:
+Style/DoubleNegation:
+  EnforcedStyle: allowed_in_returns
   Enabled: false
 
-Style/Next:
+Style/PerlBackrefs:
   Enabled: false
 
-Style/Not:
-  Enabled: false
+Style/SpecialGlobalVars:
+  EnforcedStyle: use_perl_names
 
-Style/NumericLiterals:
-  MinDigits: 20
-  Strict: true
+Style/RegexpLiteral:
+  EnforcedStyle: slashes
+  AllowInnerSlashes: true
 
-Style/NumericPredicate:
-  Enabled: false
+Lint/EndAlignment:
+  EnforcedStyleAlignWith: variable
 
-Layout/SpaceAroundOperators:
+Lint/OutOfRangeRegexpRef:
   Enabled: false
 
-Layout/SpaceInsideHashLiteralBraces:
-  EnforcedStyle: no_space
-
-Style/StringLiterals:
-  EnforcedStyle: double_quotes
-
-Style/NegatedIf:
+Lint/MissingSuper:
   Enabled: false
 
-Style/RedundantReturn:
-  Enabled: false
+Style/BlockDelimiters:
+  EnforcedStyle: semantic
+  AllowBracesOnProceduralOneLiners: true
+  ProceduralMethods:
+    - execute_command
 
 Style/MultilineBlockChain:
   Enabled: false
 
-Layout/SpaceAroundEqualsInParameterDefault:
-  EnforcedStyle: no_space
-
-Layout/IndentArray:
+Layout/FirstArgumentIndentation:
   EnforcedStyle: consistent
 
-Style/SymbolArray:
-  EnforcedStyle: brackets
-
-Layout/FirstParameterIndentation:
+Layout/FirstArrayElementIndentation:
   EnforcedStyle: consistent
 
-Style/Lambda:
-  EnforcedStyle: lambda
-
-Layout/AccessModifierIndentation:
-  EnforcedStyle: outdent
+Style/FormatString:
+  EnforcedStyle: percent
 
 Style/FormatStringToken:
-  Enabled: false
+  EnforcedStyle: unannotated
 
-Style/WordArray:
-  EnforcedStyle: brackets
-
-Lint/UriEscapeUnescape:
-  Enabled: false
+Style/FrozenStringLiteralComment:
+  Exclude:
+    - forms/*
 
-Style/RescueModifier:
+Naming/AccessorMethodName:
   Enabled: false

bin/active_tels_on_catapult 🔗

@@ -37,6 +37,7 @@ DB.exec(
 ).each do |row|
 	cid = row["customer_id"]
 	next if REDIS.exists?("catapult_cred-customer_#{cid}@jmp.chat")
+
 	jid = REDIS.get("jmp_customer_jid-#{cid}")
 	tel = REDIS.lindex("catapult_cred-#{jid}", 3)
 	location = get_location(tel)

bin/billing_monthly_cronjob 🔗

@@ -71,7 +71,7 @@ end
 stats = Stats.new(
 	not_renewed: 0,
 	renewed: 0,
-	revenue: BigDecimal.new(0)
+	revenue: BigDecimal(0)
 )
 
 class Plan
@@ -85,7 +85,7 @@ class Plan
 	end
 
 	def price
-		BigDecimal.new(@plan["monthly_price"].to_i) * 0.0001
+		BigDecimal(@plan["monthly_price"].to_i) * 0.0001
 	end
 
 	def bill_customer(db, customer_id)
@@ -147,18 +147,29 @@ class ExpiredCustomer
 
 		def try_renew(_, stats)
 			stats.add(:not_renewed, 1)
-			if REDIS.exists?("jmp_customer_auto_top_up_amount-#{customer_id}") && \
-			   @row["expires_at"] > LAST_WEEK
-				@db.exec_params("SELECT pg_notify('low_balance', $1)", [customer_id])
+			topup = "jmp_customer_auto_top_up_amount-#{customer_id}"
+			if REDIS.exists?(topup) && @row["expires_at"] > LAST_WEEK
+				@db.exec_params(
+					"SELECT pg_notify('low_balance', $1)",
+					[customer_id]
+				)
 			else
-				return if REDIS.exists?("jmp_customer_low_balance-#{customer_id}")
-				REDIS.set("jmp_customer_low_balance-#{customer_id}", Time.now, ex: ONE_WEEK)
-				send_notification
+				notify_if_needed
 			end
 		end
 
 	protected
 
+		def notify_if_needed
+			return if REDIS.exists?("jmp_customer_low_balance-#{customer_id}")
+
+			REDIS.set(
+				"jmp_customer_low_balance-#{customer_id}",
+				Time.now, ex: ONE_WEEK
+			)
+			send_notification
+		end
+
 		def jid
 			REDIS.get("jmp_customer_jid-#{customer_id}")
 		end
@@ -175,12 +186,14 @@ class ExpiredCustomer
 
 		def btc_addresses_for_notification
 			return if btc_addresses.empty?
+
 			"\nYou can buy credit by sending any amount of Bitcoin to one of "\
 			"these addresses:\n#{btc_addresses.join("\n")}"
 		end
 
 		def send_notification
 			raise "No JID for #{customer_id}, cannot notify" unless jid
+
 			BlatherNotify.say(
 				CONFIG[:notify_using][:target].call(jid),
 				CONFIG[:notify_using][:body].call(

bin/correct_duplicate_addrs 🔗

@@ -12,14 +12,14 @@ redis = Redis.new
 
 customer_id = ENV["DEFAULT_CUSTOMER_ID"]
 unless customer_id
-	puts "The env-var DEFAULT_CUSTOMER_ID must be set to the ID of the customer "\
-		"who will receive the duplicated addrs, preferably a support customer or "\
-		"something linked to notifications when stray money is sent to these "\
-		"addresses"
+	puts "The env-var DEFAULT_CUSTOMER_ID must be set to the ID " \
+		"of the customer who will receive the duplicated addrs, preferably " \
+		"a support customer or something linked to notifications when " \
+		"stray money is sent to these addresses"
 	exit 1
 end
 
-STDIN.each_line do |line|
+$stdin.each_line do |line|
 	match = line.match(/^(\w+) is used by the following \d+ keys: (.*)/)
 	unless match
 		puts "The following line can't be understood and is being ignored"

bin/detect_duplicate_addrs 🔗

@@ -8,6 +8,7 @@ redis = Redis.new
 
 get_addresses_with_users(redis).each do |addr, keys|
 	if keys.length > 1
-		puts "#{addr} is used by the following #{keys.length} keys: #{keys.join(' ')}"
+		puts "#{addr} is used by the following " \
+		     "#{keys.length} keys: #{keys.join(' ')}"
 	end
 end

bin/process_pending_btc_transactions 🔗

@@ -48,7 +48,7 @@ unless (cad_to_usd = REDIS.get("cad_to_usd")&.to_f)
 	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)
+	REDIS.set("cad_to_usd", cad_to_usd, ex: 60 * 60)
 end
 
 canadianbitcoins = Nokogiri::HTML.parse(
@@ -59,7 +59,7 @@ 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(
+btc_sell_price[:CAD] = BigDecimal(
 	bitcoin_row.at("td:nth-of-type(3)").text.match(/^\$(\d+\.\d+)/)[1]
 )
 btc_sell_price[:USD] = btc_sell_price[:CAD] * cad_to_usd
@@ -82,6 +82,7 @@ class Plan
 
 	def self.from_name(customer, plan_name, klass: Plan)
 		return unless plan_name
+
 		plan = CONFIG[:plans].find { |p| p[:name] == plan_name }
 		klass.new(customer, plan) if plan
 	end
@@ -100,7 +101,8 @@ class Plan
 	end
 
 	def bonus_for(fiat_amount)
-		return BigDecimal.new(0) if fiat_amount <= 15
+		return BigDecimal(0) if fiat_amount <= 15
+
 		fiat_amount * case fiat_amount
 		when (15..29.99)
 			0.01
@@ -112,7 +114,7 @@ class Plan
 	end
 
 	def price
-		BigDecimal.new(@plan[:monthly_price].to_i) * 0.0001
+		BigDecimal(@plan[:monthly_price].to_i) * 0.0001
 	end
 
 	def insert(start:, expire:)
@@ -134,7 +136,7 @@ class Plan
 		end
 
 		def activation_amount
-			camnt = BigDecimal.new(CONFIG[:activation_amount].to_i) * 0.0001
+			camnt = BigDecimal(CONFIG[:activation_amount].to_i) * 0.0001
 			[camnt, price].max
 		end
 
@@ -159,6 +161,7 @@ class Plan
 				-price,
 				"Activate pending plan"
 			)
+
 			insert(start: Date.today, expire: @go_until)
 			REDIS.del("pending_plan_for-#{@customer.id}")
 			notify_approved
@@ -186,6 +189,7 @@ class Customer
 	def notify(body)
 		jid = REDIS.get("jmp_customer_jid-#{@customer_id}")
 		raise "No JID for #{customer_id}" unless jid
+
 		BlatherNotify.say(
 			CONFIG[:notify_using][:target].call(jid),
 			CONFIG[:notify_using][:body].call(jid, body)
@@ -204,12 +208,13 @@ class Customer
 		result = DB.exec_params(<<-SQL, [@customer_id]).first&.[]("balance")
 			SELECT balance FROM balances WHERE customer_id=$1
 		SQL
-		result || BigDecimal.new(0)
+		result || BigDecimal(0)
 	end
 
 	def add_btc_credit(txid, btc_amount, fiat_amount)
 		return unless add_transaction(txid, fiat_amount, "Bitcoin payment")
-		if (bonus = plan.bonus_for(fiat_amount)) > 0
+
+		if (bonus = plan.bonus_for(fiat_amount)).positive?
 			add_transaction("bonus_for_#{txid}", bonus, "Bitcoin payment bonus")
 		end
 		notify_btc_credit(txid, btc_amount, fiat_amount, bonus)
@@ -220,13 +225,14 @@ class Customer
 		notify([
 			"Your Bitcoin transaction of #{btc_amount.to_s('F')} BTC ",
 			"has been added as $#{'%.4f' % fiat_amount} (#{plan.currency}) ",
-			("+ $#{'%.4f' % bonus} bonus " if bonus > 0),
+			("+ $#{'%.4f' % bonus} bonus " if bonus.positive?),
 			"to your account.\n(txhash: #{tx_hash})"
 		].compact.join)
 	end
 
 	def add_transaction(id, amount, note)
-		DB.exec_params(<<-SQL, [@customer_id, id, amount, note]).cmd_tuples > 0
+		args = [@customer_id, id, amount, note]
+		DB.exec_params(<<-SQL, args).cmd_tuples.positive?
 			INSERT INTO transactions
 				(customer_id, transaction_id, amount, note)
 			VALUES
@@ -236,10 +242,11 @@ class Customer
 	end
 end
 
-done = REDIS.hgetall("pending_btc_transactions").map do |(txid, customer_id)|
+done = REDIS.hgetall("pending_btc_transactions").map { |(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
 		# This is a send, not a receive, do not record it
@@ -258,6 +265,6 @@ done = REDIS.hgetall("pending_btc_transactions").map do |(txid, customer_id)|
 			warn "No plan for #{customer_id} cannot save #{txid}"
 		end
 	end
-end
+}
 
 puts done.compact.join("\n")

config.ru 🔗

@@ -4,6 +4,7 @@ require "braintree"
 require "date"
 require "delegate"
 require "dhall"
+require "forwardable"
 require "pg"
 require "redis"
 require "roda"
@@ -44,7 +45,7 @@ class Plan
 	end
 
 	def price(months=1)
-		(BigDecimal.new(@plan[:monthly_price].to_i) * months) / 10000
+		(BigDecimal(@plan[:monthly_price].to_i) * months) / 10000
 	end
 
 	def currency
@@ -56,7 +57,7 @@ class Plan
 	end
 
 	def self.active?(customer_id)
-		DB.exec_params(<<~SQL, [customer_id]).first&.[]("count").to_i > 0
+		DB.exec_params(<<~SQL, [customer_id]).first&.[]("count").to_i.positive?
 			SELECT count(1) AS count FROM customer_plans
 			WHERE customer_id=$1 AND expires_at > NOW()
 		SQL
@@ -139,6 +140,7 @@ class CreditCardGateway
 
 		result = @gateway.customer.create
 		raise "Braintree customer create failed" unless result.success?
+
 		@customer_id = result.customer.id
 		save_customer_id!
 	end
@@ -183,6 +185,7 @@ class CreditCardGateway
 
 	def sale(ip:, **kwargs)
 		return nil unless decline_guard(ip)
+
 		tx = Transaction.sale(@gateway, **kwargs)
 		return tx if tx
 
@@ -200,7 +203,7 @@ class CreditCardGateway
 			amount: plan.price(5),
 			payment_method_nonce: nonce,
 			merchant_account_id: plan.merchant_account,
-			options: {submit_for_settlement: true}
+			options: { submit_for_settlement: true }
 		)&.insert && plan.bill_plan(@customer_id)
 	end
 
@@ -219,15 +222,18 @@ class UnknownTransactions
 	def self.from(customer_id, address, tx_hashes)
 		self.for(
 			customer_id,
-			fetch_rows_for(address, tx_hashes).map { |row| row["transaction_id"] }
+			fetch_rows_for(address, tx_hashes).map { |row|
+				row["transaction_id"]
+			}
 		)
 	end
 
 	def self.fetch_rows_for(address, tx_hashes)
-		values = tx_hashes.map do |tx_hash|
+		values = tx_hashes.map { |tx_hash|
 			"('#{DB.escape_string(tx_hash)}/#{DB.escape_string(address)}')"
-		end
+		}
 		return [] if values.empty?
+
 		DB.exec_params(<<-SQL)
 			SELECT transaction_id FROM
 				(VALUES #{values.join(',')}) AS t(transaction_id)
@@ -257,24 +263,25 @@ class UnknownTransactions
 	end
 end
 
-# This class must contain all of the routes because of how the DSL works
-# rubocop:disable Metrics/ClassLength
 class JmpPay < Roda
 	SENTRY_DSN = ENV["SENTRY_DSN"] && URI(ENV["SENTRY_DSN"])
 	plugin :render, engine: "slim"
 	plugin :common_logger, $stdout
 
+	extend Forwardable
+	def_delegators :request, :params
+
 	def redis_key_btc_addresses
-		"jmp_customer_btc_addresses-#{request.params['customer_id']}"
+		"jmp_customer_btc_addresses-#{params['customer_id']}"
 	end
 
 	def verify_address_customer_id(r)
-		return if REDIS.sismember(redis_key_btc_addresses, request.params["address"])
+		return if REDIS.sismember(redis_key_btc_addresses, params["address"])
 
 		warn "Address and customer_id do not match"
 		r.halt([
 			403,
-			{"Content-Type" => "text/plain"},
+			{ "Content-Type" => "text/plain" },
 			"Address and customer_id do not match"
 		])
 	end
@@ -284,10 +291,10 @@ class JmpPay < Roda
 			verify_address_customer_id(r)
 
 			UnknownTransactions.from(
-				request.params["customer_id"],
-				request.params["address"],
+				params["customer_id"],
+				params["address"],
 				ELECTRUM
-					.getaddresshistory(request.params["address"])
+					.getaddresshistory(params["address"])
 					.map { |item| item["tx_hash"] }
 			).enqueue!
 
@@ -295,19 +302,17 @@ class JmpPay < Roda
 		end
 
 		r.on :jid do |jid|
-			Sentry.set_user(id: request.params["customer_id"], jid: jid)
+			Sentry.set_user(id: params["customer_id"], jid: jid)
 
-			gateway = CreditCardGateway.new(
-				jid,
-				request.params["customer_id"]
-			)
+			gateway = CreditCardGateway.new(jid, params["customer_id"])
+			topup = "jmp_customer_auto_top_up_amount-#{gateway.customer_id}"
 
 			r.on "activate" do
 				Sentry.configure_scope do |scope|
 					scope.set_transaction_name("activate")
 					scope.set_context(
 						"activate",
-						plan_name: request.params["plan_name"]
+						plan_name: params["plan_name"]
 					)
 				end
 
@@ -324,28 +329,25 @@ class JmpPay < Roda
 
 				r.get do
 					if Plan.active?(gateway.customer_id)
-						r.redirect request.params["return_to"], 303
+						r.redirect params["return_to"], 303
 					else
 						render.call
 					end
 				end
 
 				r.post do
-					result = DB.transaction do
+					result = DB.transaction {
 						Plan.active?(gateway.customer_id) || gateway.buy_plan(
-							request.params["plan_name"],
-							request.params["braintree_nonce"],
+							params["plan_name"],
+							params["braintree_nonce"],
 							request.ip
 						)
-					end
-					if request.params["auto_top_up_amount"].to_i >= 15
-						REDIS.set(
-							"jmp_customer_auto_top_up_amount-#{gateway.customer_id}",
-							request.params["auto_top_up_amount"].to_i
-						)
+					}
+					if params["auto_top_up_amount"].to_i >= 15
+						REDIS.set(topup, params["auto_top_up_amount"].to_i)
 					end
 					if result
-						r.redirect request.params["return_to"], 303
+						r.redirect params["return_to"], 303
 					else
 						render.call(error: true)
 					end
@@ -359,24 +361,18 @@ class JmpPay < Roda
 						locals: {
 							token: gateway.client_token,
 							customer_id: gateway.customer_id,
-							auto_top_up: REDIS.get(
-								"jmp_customer_auto_top_up_amount-#{gateway.customer_id}"
-							) || (gateway.payment_methods? ? "" : "15")
+							auto_top_up: REDIS.get(topup) ||
+							             (gateway.payment_methods? ? "" : "15")
 						}
 					)
 				end
 
 				r.post do
-					gateway.default_payment_method = request.params["braintree_nonce"]
-					if request.params["auto_top_up_amount"].to_i >= 15
-						REDIS.set(
-							"jmp_customer_auto_top_up_amount-#{gateway.customer_id}",
-							request.params["auto_top_up_amount"].to_i
-						)
-					elsif request.params["auto_top_up_amount"].to_i == 0
-						REDIS.del(
-							"jmp_customer_auto_top_up_amount-#{gateway.customer_id}"
-						)
+					gateway.default_payment_method = params["braintree_nonce"]
+					if params["auto_top_up_amount"].to_i >= 15
+						REDIS.set(topup, params["auto_top_up_amount"].to_i)
+					elsif params["auto_top_up_amount"].to_i.zero?
+						REDIS.del(topup)
 					end
 					"OK"
 				end
@@ -384,6 +380,5 @@ class JmpPay < Roda
 		end
 	end
 end
-# rubocop:enable Metrics/ClassLength
 
 run JmpPay.freeze.app

lib/blather_notify.rb 🔗

@@ -16,11 +16,11 @@ module BlatherNotify
 
 		EM.error_handler(&method(:panic))
 
-		@thread = Thread.new do
+		@thread = Thread.new {
 			EM.run do
 				client.run
 			end
-		end
+		}
 
 		Timeout.timeout(30) { @ready.pop }
 		at_exit { wait_then_exit }

lib/electrum.rb 🔗

@@ -47,7 +47,7 @@ class Electrum
 		end
 
 		def amount_for(*addresses)
-			BigDecimal.new(
+			BigDecimal(
 				@tx["outputs"]
 					.select { |o| addresses.include?(o["address"]) }
 					.map { |o| o["value_sats"] }
@@ -67,16 +67,23 @@ protected
 		).body)
 	end
 
-	def post_json(data)
-		req = Net::HTTP::Post.new(@rpc_uri, "Content-Type" => "application/json")
+	def post_json_req(data)
+		req = Net::HTTP::Post.new(
+			@rpc_uri,
+			"Content-Type" => "application/json"
+		)
 		req.basic_auth(@rpc_username, @rpc_password)
 		req.body = data.to_json
+		req
+	end
+
+	def post_json(data)
 		Net::HTTP.start(
 			@rpc_uri.hostname,
 			@rpc_uri.port,
 			use_ssl: @rpc_uri.scheme == "https"
 		) do |http|
-			http.request(req)
+			http.request(post_json_req(data))
 		end
 	end
 end

lib/redis_addresses.rb 🔗

@@ -25,12 +25,15 @@ end
 
 module RedisBtcAddresses
 	def self.each_user(redis)
-		# I picked 1000 because it made a relatively trivial case take 15 seconds
-		#   instead of forever.
+		# I picked 1000 because it made a relatively trivial case take
+		# 15 seconds instead of forever.
 		# Basically it's "how long does each command take"
-		# The lower it is (default is 10), it will go back and forth to the client a
-		#   ton
-		redis.scan_each(match: "jmp_customer_btc_addresses-*", count: 1000) do |key|
+		# The lower it is (default is 10), it will go back and forth
+		# to the client a ton
+		redis.scan_each(
+			match: "jmp_customer_btc_addresses-*",
+			count: 1000
+		) do |key|
 			yield key, redis.smembers(key)
 		end
 	end

lib/transaction.rb 🔗

@@ -29,6 +29,7 @@ class Transaction
 
 	def bonus
 		return BigDecimal(0) if amount <= 15
+
 		amount *
 			case amount
 			when (15..29.99)
@@ -59,7 +60,11 @@ protected
 
 	def insert_bonus
 		return if bonus <= 0
-		params = [@customer_id, "bonus_for_#{@transaction_id}", @created_at, bonus]
+
+		params = [
+			@customer_id, "bonus_for_#{@transaction_id}",
+			@created_at, bonus
+		]
 		DB.exec(<<~SQL, params)
 			INSERT INTO transactions
 				(customer_id, transaction_id, created_at, amount, note)

test/test_electrum.rb 🔗

@@ -16,7 +16,7 @@ class ElectrumTest < Minitest::Test
 
 	def stub_rpc(method, params)
 		stub_request(:post, RPC_URI).with(
-			headers: {"Content-Type" => "application/json"},
+			headers: { "Content-Type" => "application/json" },
 			basic_auth: ["username", "password"],
 			body: hash_including(
 				method: method,
@@ -29,7 +29,7 @@ class ElectrumTest < Minitest::Test
 	def getaddresshistory(address)
 		req =
 			stub_rpc("getaddresshistory", address: address)
-			.to_return(body: {result: "result"}.to_json)
+			.to_return(body: { result: "result" }.to_json)
 		assert_equal "result", @electrum.getaddresshistory(address)
 		assert_requested(req)
 	end
@@ -38,7 +38,7 @@ class ElectrumTest < Minitest::Test
 	def get_tx_status(tx_hash)
 		req =
 			stub_rpc("get_tx_status", txid: tx_hash)
-			.to_return(body: {result: "result"}.to_json)
+			.to_return(body: { result: "result" }.to_json)
 		assert_equal "result", @electrum.get_tx_status(tx_hash)
 		assert_requested(req)
 	end
@@ -47,10 +47,10 @@ class ElectrumTest < Minitest::Test
 	def gettransaction(tx_hash, dummy_tx)
 		req1 =
 			stub_rpc("gettransaction", txid: tx_hash)
-			.to_return(body: {result: dummy_tx}.to_json)
+			.to_return(body: { result: dummy_tx }.to_json)
 		req2 =
 			stub_rpc("deserialize", [dummy_tx])
-			.to_return(body: {result: {outputs: []}}.to_json)
+			.to_return(body: { result: { outputs: [] } }.to_json)
 		assert_kind_of Electrum::Transaction, @electrum.gettransaction(tx_hash)
 		assert_requested(req1)
 		assert_requested(req2)
@@ -73,7 +73,7 @@ class ElectrumTest < Minitest::Test
 			electrum_mock, tx = transaction
 			electrum_mock.expect(
 				:get_tx_status,
-				{"confirmations" => 1234},
+				{ "confirmations" => 1234 },
 				["txhash"]
 			)
 			assert_equal 1234, tx.confirmations
@@ -85,17 +85,17 @@ class ElectrumTest < Minitest::Test
 		end
 
 		def test_amount_for_address_not_present
-			_, tx = transaction([{"address" => "address", "value_sats" => 1}])
+			_, tx = transaction([{ "address" => "address", "value_sats" => 1 }])
 			assert_equal 0, tx.amount_for("other_address")
 		end
 
 		def test_amount_for_address_present
-			_, tx = transaction([{"address" => "address", "value_sats" => 1}])
+			_, tx = transaction([{ "address" => "address", "value_sats" => 1 }])
 			assert_equal 0.00000001, tx.amount_for("address")
 		end
 
 		def test_amount_for_one_of_address_present
-			_, tx = transaction([{"address" => "address", "value_sats" => 1}])
+			_, tx = transaction([{ "address" => "address", "value_sats" => 1 }])
 			assert_equal 0.00000001, tx.amount_for("boop", "address", "lol")
 		end
 	end