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 {
124 type: "text-single",
125 var: "nickname",
126 label: "Nickname",
127 required: false
128 }
129 ]
130 end
131
132 # @param [Blather::Stanza::Iq] iq the stanza
133 # containing a filled out `order_sim/with_balance`
134 def complete(iq)
135 EMPromise.resolve(nil).then {
136 commit(
137 iq.form.field("nickname")&.value.presence || self.class.label
138 )
139 }.then do |sim|
140 ActivationCode.new(sim).complete
141 end
142 end
143 end
144
145 class ActivationCode
146 # @param [Sim] sim the sim which the customer
147 # just ordered
148 def initialize(sim)
149 @sim = sim
150 end
151
152 def complete
153 Command.finish do |reply|
154 oob = OOB.find_or_create(reply.command)
155 oob.url = @sim.lpa_code
156 oob.desc = "LPA Activation Code"
157 reply.command << FormTemplate.render(
158 "order_sim/esim_complete", sim: @sim
159 )
160 end
161 end
162 end
163
164 class WithTopUp
165 def initialize(customer, continue, price:, plan:, top_up:)
166 @customer = customer
167 @price = price
168 @plan = plan
169 @top_up = top_up
170 @continue = continue
171 end
172
173 def form
174 FormTemplate.render(
175 "order_sim/with_top_up",
176 price: @price,
177 plan: @plan,
178 top_up_amount: @top_up.top_up_amount,
179 label: @continue.label,
180 fillable_fields: @continue.fillable_fields
181 )
182 end
183
184 def complete(iq)
185 @top_up.notify!.then do |amount|
186 if amount.positive?
187 @continue.new(@customer, price: @price, plan: @plan).complete(iq)
188 else
189 Command.finish("Could not top up", type: :error)
190 end
191 end
192 end
193 end
194
195 class PleaseTopUp
196 def initialize(price:, plan:)
197 @price = price
198 @plan = plan
199 end
200
201 def form
202 FormTemplate.render(
203 "order_sim/please_top_up",
204 price: @price,
205 plan: @plan
206 )
207 end
208
209 def complete(_)
210 Command.finish
211 end
212 end
213end