fix: display correct price for data-only

Phillip Davis created

to that end: make `SIMKind` easier to hold by making internals, as well
as the raw CONFIG[:sims] entry, protected

Change summary

lib/registration.rb       | 17 +++++++----------
lib/sim_kind.rb           | 36 ++++++++++++++++++++++++++++++------
test/test_registration.rb |  6 +++---
test/test_sim_kind.rb     | 24 +++++++++++++-----------
4 files changed, 53 insertions(+), 30 deletions(-)

Detailed changes

lib/registration.rb 🔗

@@ -46,17 +46,17 @@ class Registration
 					product: @sim_kind,
 					instructions: instructions,
 					currency_required: !@customer.currency,
-					title: "Pay for #{@sim_kind.klass.label}"
+					title: "Pay for #{@sim_kind.label}"
 				)
 			}.then(&method(:parse)).then(&:write)
 		end
 
 		def instructions
-			cfg = @sim_kind.cfg(@customer.currency)
-			return nil unless cfg
+			price = @sim_kind.price(@customer)
+			return nil unless price
 
 			<<~I
-				To activate your data SIM, you need to deposit $#{'%.2f' % (cfg[:price] / 100.0)} to your balance.
+				To activate your data SIM, you need to deposit $#{'%.2f' % price} to your balance.
 				(If you'd like to pay in another cryptocurrency, currently we recommend using a service like simpleswap.io, morphtoken.com, changenow.io, or godex.io.)
 			I
 		end
@@ -71,7 +71,7 @@ class Registration
 		def process_payment(iq)
 			Payment.for(
 				iq, @customer, @sim_kind,
-				price: @sim_kind.cfg(@customer.currency)[:price],
+				price: @sim_kind.price(@customer),
 				maybe_bill: Registration::Payment::JustCharge
 			).write.then { reload_customer(@customer) }.then { |customer|
 				DataOnly.for(customer, @sim_kind)
@@ -81,7 +81,7 @@ class Registration
 
 	class DataOnly
 		def self.for(customer, sim_kind)
-			price = sim_kind.price_dollars(customer.currency)
+			price = sim_kind.price(customer)
 			unless price && customer.balance >= price
 				return PayForSim.new(customer, sim_kind)
 			end
@@ -95,10 +95,7 @@ class Registration
 		end
 
 		def write
-			@sim_kind.klass.for(
-				@customer,
-				**@sim_kind.cfg(@customer.currency)
-			).then { |order|
+			@sim_kind.get_processor(@customer).then { |order|
 				# NOTE: cheogram will swallow any stanza
 				# with type `completed`
 				# `can_complete: false` is needed to prevent customer

lib/sim_kind.rb 🔗

@@ -1,8 +1,6 @@
 # frozen_string_literal: true
 
 class SIMKind
-	attr_reader :klass
-
 	def initialize(variant)
 		@klass =
 			case variant
@@ -18,14 +16,40 @@ class SIMKind
 		new(form.field("sim_kind")&.value)
 	end
 
-	def cfg(currency)
-		CONFIG.dig(:sims, @variant.to_sym, currency)
+	# @param [Customer] customer
+	# @raise [ArgumentError] If matching `CONFIG[:sims]`
+	# 	key not found for `@variant` and customer
+	def get_processor(customer)
+		cfg = cfg(customer.currency)
+		raise ArgumentError, "Invalid config" unless cfg
+
+		@klass.new(
+			customer,
+			**cfg
+		)
+	end
+
+	# @return [String] The result of either
+	# 	SIMOrder.label or SIMOrder::ESIM.label
+	def label
+		@klass.label
 	end
 
-	def price_dollars(currency)
-		cfg = cfg(currency)
+	# @param [Customer] customer
+	# @return [Float, NilClass] Returns nil if `customer.currency`
+	# 	is nil, or if @variant is malformed.
+	def price(customer)
+		cfg = cfg(customer.currency)
 		return nil unless cfg
 
 		cfg[:price] / 100.to_d
 	end
+
+protected
+
+	# @param [Symbol] currency One of the currencies from
+	# 	CONFIG[:plans] entries
+	def cfg(currency)
+		CONFIG.dig(:sims, @variant.to_sym, currency)
+	end
 end

test/test_registration.rb 🔗

@@ -2540,7 +2540,7 @@ class RegistrationTest < Minitest::Test
 					:for,
 					ejector_mock
 				) do |*, price:, maybe_bill:|
-					assert_equal 500, price
+					assert_in_epsilon 5, price, 0.001
 					assert_equal(
 						Registration::Payment::JustCharge,
 						maybe_bill
@@ -2589,7 +2589,7 @@ class RegistrationTest < Minitest::Test
 					:for,
 					ejector_mock
 				) do |*, price:, maybe_bill:|
-					assert_equal 500, price
+					assert_in_epsilon 5, price, 0.001
 					assert_equal(
 						Registration::Payment::JustCharge,
 						maybe_bill
@@ -2626,7 +2626,7 @@ class RegistrationTest < Minitest::Test
 					assert_equal @customer, customer
 					assert_equal @sim_kind, sim_kind
 					assert_equal Registration::Payment::JustCharge, maybe_bill
-					assert_equal 500, price
+					assert_in_epsilon 5, price, 0.001
 				end
 				exe.customer_repo.expect(
 					:find,

test/test_sim_kind.rb 🔗

@@ -25,53 +25,55 @@ class SIMKindTest < Minitest::Test
 		@missing_form = Blather::Stanza::Iq::Command.new.tap { |iq|
 			iq.form.fields = []
 		}
+
+		@cust = customer(plan_name: "test_usd")
 	end
 
 	def test_initialize_with_sim
 		kind = SIMKind.new("sim")
-		assert_equal SIMOrder, kind.klass
+		assert_instance_of SIMOrder, kind.get_processor(@cust)
 	end
 
 	def test_initialize_with_esim
 		kind = SIMKind.new("esim")
-		assert_equal SIMOrder::ESIM, kind.klass
+		assert_instance_of SIMOrder::ESIM, kind.get_processor(@cust)
 	end
 
 	def test_initialize_with_invalid_variant
 		kind = SIMKind.new("invalid")
-		assert_nil kind.klass
+		assert_raises(ArgumentError) { kind.get_processor(@cust) }
 	end
 
 	def test_initialize_with_nil
 		kind = SIMKind.new(nil)
-		assert_nil kind.klass
+		assert_raises(NoMethodError) { kind.get_processor(@cust) }
 	end
 
 	def test_from_form_with_sim
 		kind = SIMKind.from_form(@sim_form.form)
-		assert_equal SIMOrder, kind.klass
+		assert_instance_of SIMOrder, kind.get_processor(@cust)
 	end
 
 	def test_from_form_with_esim
 		kind = SIMKind.from_form(@esim_form.form)
-		assert_equal SIMOrder::ESIM, kind.klass
+		assert_instance_of SIMOrder::ESIM, kind.get_processor(@cust)
 	end
 
-	def test_cfg_returns_nil_when_customer_currency_nil
+	def test_price_returns_nil_when_customer_currency_nil
 		kind = SIMKind.new("sim")
 		cust = customer(currency: nil)
-		assert_nil kind.cfg(cust.currency)
+		assert_nil kind.price(cust)
 	end
 
-	def test_cfg_returns_nil_when_config_missing_variant
+	def test_price_returns_nil_when_config_missing_variant
 		kind = SIMKind.new("invalid")
 		cust = customer(currency: :USD)
-		assert_nil kind.cfg(cust.currency)
+		assert_nil kind.price(cust)
 	end
 
 	def test_cfg_returns_nil_when_config_missing_currency
 		kind = SIMKind.new("sim")
 		cust = customer(currency: :EUR)
-		assert_nil kind.cfg(cust.currency)
+		assert_nil kind.price(cust)
 	end
 end