sim_order.rb

  1# frozen_string_literal: true
  2
  3require "bigdecimal/util"
  4
  5require_relative "low_balance"
  6require_relative "transaction"
  7
  8class SIMOrder
  9	def self.for(customer, price:, **kwargs)
 10		price = price.to_i / 100.to_d
 11		return new(customer, price: price, **kwargs) if customer.balance >= price
 12
 13		LowBalance::AutoTopUp.for(customer, price).then do |top_up|
 14			if top_up.can_top_up?
 15				WithTopUp.new(customer, self, price: price, top_up: top_up, **kwargs)
 16			else
 17				PleaseTopUp.new(price: price, **kwargs)
 18			end
 19		end
 20	end
 21
 22	def self.label
 23		"SIM"
 24	end
 25
 26	def self.fillable_fields
 27		[
 28			{
 29				type: "text-multi",
 30				label: "Shipping Address",
 31				var: "addr",
 32				required: true
 33			},
 34			{
 35				type: "text-single",
 36				var: "nickname",
 37				label: "Nickname",
 38				required: false
 39			}
 40		]
 41	end
 42
 43	def initialize(customer, price:, plan:)
 44		@customer = customer
 45		@price = price
 46		@plan = plan
 47		@sim_repo = SIMRepo.new(db: DB)
 48	end
 49
 50	def form
 51		FormTemplate.render(
 52			"order_sim/with_balance",
 53			price: @price,
 54			plan: @plan,
 55			label: self.class.label,
 56			fillable_fields: self.class.fillable_fields
 57		)
 58	end
 59
 60	def complete(iq)
 61		form = iq.form
 62		EMPromise.resolve(nil).then {
 63			commit(form.field("nickname")&.value.presence || self.class.label)
 64		}.then do |sim|
 65			Ack.new(
 66				@customer,
 67				sim,
 68				Array(form.field("addr").value).join("\n")
 69			).complete
 70		end
 71	end
 72
 73	class Ack
 74		def initialize(customer, sim, addr)
 75			@customer = customer
 76			@sim = sim
 77			@addr = addr
 78		end
 79
 80		def complete
 81			@customer.stanza_from(Blather::Stanza::Message.new(
 82				Blather::JID.new(""), # Doesn't matter, sgx is set to direct target
 83				"SIM ORDER: #{@sim.iccid}\n#{@addr}"
 84			))
 85			Command.finish(
 86				"You will receive an notice from support when your SIM ships."
 87			)
 88		end
 89	end
 90
 91protected
 92
 93	# @param [String, nil] nickname the nickname, if any, assigned to
 94	# 					   the sim by the customer
 95	def commit(nickname)
 96		DB.transaction do
 97			sim = @sim_repo.available.sync
 98			@sim_repo.put_owner(sim, @customer, nickname)
 99			keepgo_tx = @sim_repo.refill(sim, amount_mb: 1024).sync
100			raise "SIM activation failed" unless keepgo_tx["ack"] == "success"
101
102			transaction(sim, keepgo_tx).insert_tx
103			sim
104		end
105	end
106
107	def transaction(sim, keepgo_tx)
108		Transaction.new(
109			customer_id: @customer.customer_id,
110			transaction_id: keepgo_tx["transaction_id"],
111			amount: -@price,
112			note: "#{self.class.label} Activation #{sim.iccid}"
113		)
114	end
115
116	class ESIM < SIMOrder
117		def self.label
118			"eSIM"
119		end
120
121		def self.fillable_fields
122			[]
123		end
124
125		# @param [Blather::Stanza::Iq] iq the stanza
126		# 		  containing a filled out `order_sim/with_balance`
127		def complete(iq)
128			EMPromise.resolve(nil).then {
129				commit(
130					nickname: iq.form.field("nickname").value.presence || self.class.label
131				)
132			}.then do |sim|
133				ActivationCode.new(sim).complete
134			end
135		end
136	end
137
138	class ActivationCode
139		# @param [Sim] sim the sim which the customer
140		# 			   just ordered
141		def initialize(sim)
142			@sim = sim
143		end
144
145		def complete
146			Command.finish do |reply|
147				oob = OOB.find_or_create(reply.command)
148				oob.url = @sim.lpa_code
149				oob.desc = "LPA Activation Code"
150				reply.command << FormTemplate.render(
151					"order_sim/esim_complete", sim: @sim
152				)
153			end
154		end
155	end
156
157	class WithTopUp
158		def initialize(customer, continue, price:, plan:, top_up:)
159			@customer = customer
160			@price = price
161			@plan = plan
162			@top_up = top_up
163			@continue = continue
164		end
165
166		def form
167			FormTemplate.render(
168				"order_sim/with_top_up",
169				price: @price,
170				plan: @plan,
171				top_up_amount: @top_up.top_up_amount,
172				label: @continue.label,
173				fillable_fields: @continue.fillable_fields
174			)
175		end
176
177		def complete(iq)
178			@top_up.notify!.then do |amount|
179				if amount.positive?
180					@continue.new(@customer, price: @price, plan: @plan).complete(iq)
181				else
182					Command.finish("Could not top up", type: :error)
183				end
184			end
185		end
186	end
187
188	class PleaseTopUp
189		def initialize(price:, plan:)
190			@price = price
191			@plan = plan
192		end
193
194		def form
195			FormTemplate.render(
196				"order_sim/please_top_up",
197				price: @price,
198				plan: @plan
199			)
200		end
201
202		def complete(_)
203			Command.finish
204		end
205	end
206end