1# frozen_string_literal: true
2
3require "erb"
4require "ruby-bandwidth-iris"
5require "securerandom"
6
7require_relative "./alt_top_up_form"
8require_relative "./bandwidth_tn_order"
9require_relative "./bandwidth_tn_reservation_repo"
10require_relative "./command"
11require_relative "./em"
12require_relative "./invites_repo"
13require_relative "./oob"
14require_relative "./proxied_jid"
15require_relative "./tel_selections"
16require_relative "./welcome_message"
17
18class Registration
19 def self.for(customer, tel_selections)
20 if (reg = customer.registered?)
21 Registered.new(reg.phone)
22 else
23 tel_selections[customer.jid].then(&:choose_tel).then do |tel|
24 BandwidthTnReservationRepo.new.ensure(customer, tel)
25 FinishOrStartActivation.for(customer, tel)
26 end
27 end
28 end
29
30 class Registered
31 def initialize(tel)
32 @tel = tel
33 end
34
35 def write
36 Command.finish("You are already registered with JMP number #{@tel}")
37 end
38 end
39
40 class FinishOrStartActivation
41 def self.for(customer, tel)
42 if customer.active?
43 Finish.new(customer, tel)
44 elsif customer.balance >= CONFIG[:activation_amount_accept]
45 BillPlan.new(customer, tel)
46 else
47 new(customer, tel)
48 end
49 end
50
51 def initialize(customer, tel)
52 @customer = customer
53 @tel = tel
54 end
55
56 def write
57 Command.reply { |reply|
58 reply.allowed_actions = [:next]
59 reply.note_type = :info
60 reply.note_text = File.read("#{__dir__}/../fup.txt")
61 }.then { Activation.for(@customer, @tel).write }
62 end
63 end
64
65 class Activation
66 def self.for(customer, tel)
67 jid = ProxiedJID.new(customer.jid).unproxied
68 if CONFIG[:approved_domains].key?(jid.domain.to_sym)
69 Allow.for(customer, tel, jid)
70 else
71 new(customer, tel)
72 end
73 end
74
75 def initialize(customer, tel)
76 @customer = customer
77 @tel = tel
78 end
79
80 attr_reader :customer, :tel
81
82 def form(center)
83 FormTemplate.render(
84 "registration/activate",
85 tel: tel,
86 rate_center: center
87 )
88 end
89
90 def write
91 rate_center.then { |center|
92 Command.reply do |reply|
93 reply.allowed_actions = [:next]
94 reply.command << form(center)
95 end
96 }.then(&method(:next_step))
97 end
98
99 def next_step(iq)
100 EMPromise.resolve(nil).then {
101 Payment.for(iq, customer, tel)
102 }.then(&:write)
103 end
104
105 protected
106
107 def rate_center
108 EM.promise_fiber {
109 center = BandwidthIris::Tn.get(tel).get_rate_center
110 "#{center[:rate_center]}, #{center[:state]}"
111 }.catch { nil }
112 end
113
114 class Allow < Activation
115 def self.for(customer, tel, jid)
116 credit_to = CONFIG[:approved_domains][jid.domain.to_sym]
117 new(customer, tel, credit_to)
118 end
119
120 def initialize(customer, tel, credit_to)
121 super(customer, tel)
122 @credit_to = credit_to
123 end
124
125 def form(center)
126 FormTemplate.render(
127 "registration/allow",
128 tel: tel,
129 rate_center: center,
130 domain: customer.jid.domain
131 )
132 end
133
134 def next_step(iq)
135 plan_name = iq.form.field("plan_name").value.to_s
136 @customer = customer.with_plan(plan_name)
137 EMPromise.resolve(nil).then { activate }.then do
138 Finish.new(customer, tel).write
139 end
140 end
141
142 protected
143
144 def activate
145 DB.transaction do
146 if @credit_to
147 DB.exec(<<~SQL, [@credit_to, customer.customer_id])
148 INSERT INTO invites (creator_id, used_by_id, used_at)
149 VALUES ($1, $2, LOCALTIMESTAMP)
150 SQL
151 end
152 @customer.activate_plan_starting_now
153 end
154 end
155 end
156 end
157
158 module Payment
159 def self.kinds
160 @kinds ||= {}
161 end
162
163 def self.for(iq, customer, tel, final_message: nil, finish: Finish)
164 plan_name = iq.form.field("plan_name").value.to_s
165 customer = customer.with_plan(plan_name)
166 customer.save_plan!.then do
167 kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) {
168 raise "Invalid activation method"
169 }.call(customer, tel, final_message: final_message, finish: finish)
170 end
171 end
172
173 class Bitcoin
174 Payment.kinds[:bitcoin] = method(:new)
175
176 THIRTY_DAYS = 60 * 60 * 24 * 30
177
178 def initialize(customer, tel, final_message: nil, **)
179 @customer = customer
180 @customer_id = customer.customer_id
181 @tel = tel
182 @final_message = final_message
183 end
184
185 attr_reader :customer_id, :tel
186
187 def save
188 REDIS.setex("pending_tel_for-#{@customer.jid}", THIRTY_DAYS, tel)
189 end
190
191 def note_text(rate, addr)
192 amount = CONFIG[:activation_amount] / rate
193 <<~NOTE
194 Activate your account by sending at least #{'%.6f' % amount} BTC to
195 #{addr}
196
197 You will receive a notification when your payment is complete.
198 NOTE
199 end
200
201 def write
202 EMPromise.all([addr_and_rate, save]).then do |((addr, rate), _)|
203 Command.reply { |reply|
204 reply.allowed_actions = [:prev]
205 reply.status = :canceled
206 reply.note_type = :info
207 reply.note_text = note_text(rate, addr) + @final_message.to_s
208 }.then(&method(:handle_possible_prev))
209 end
210 end
211
212 protected
213
214 def handle_possible_prev(iq)
215 raise "Action not allowed" unless iq.prev?
216
217 Activation.for(@customer, @tel).then(&:write)
218 end
219
220 def addr_and_rate
221 EMPromise.all([
222 @customer.btc_addresses.then { |addrs|
223 addrs.first || @customer.add_btc_address
224 },
225 BTC_SELL_PRICES.public_send(@customer.currency.to_s.downcase)
226 ])
227 end
228 end
229
230 class CreditCard
231 Payment.kinds[:credit_card] = ->(*args, **kw) { self.for(*args, **kw) }
232
233 def self.for(in_customer, tel, finish: Finish, **)
234 reload_customer(in_customer).then do |(customer, payment_methods)|
235 if customer.balance >= CONFIG[:activation_amount_accept]
236 next BillPlan.new(customer, tel, finish: finish)
237 end
238
239 if (method = payment_methods.default_payment_method)
240 next Activate.new(customer, method, tel, finish: finish)
241 end
242
243 new(customer, tel, finish: finish)
244 end
245 end
246
247 def self.reload_customer(customer)
248 EMPromise.all([
249 Command.execution.customer_repo.find(customer.customer_id),
250 customer.payment_methods
251 ])
252 end
253
254 def initialize(customer, tel, finish: Finish)
255 @customer = customer
256 @tel = tel
257 @finish = finish
258 end
259
260 def oob(reply)
261 oob = OOB.find_or_create(reply.command)
262 oob.url = CONFIG[:credit_card_url].call(
263 reply.to.stripped.to_s.gsub("\\", "%5C"),
264 @customer.customer_id
265 ) + "&amount=#{CONFIG[:activation_amount]}"
266 oob.desc = "Add credit card, save, then next here to continue"
267 oob
268 end
269
270 def write
271 Command.reply { |reply|
272 reply.allowed_actions = [:next, :prev]
273 toob = oob(reply)
274 reply.note_type = :info
275 reply.note_text = "#{toob.desc}: #{toob.url}"
276 }.then do |iq|
277 next Activation.for(@customer, @tel).then(&:write) if iq.prev?
278
279 CreditCard.for(@customer, @tel, finish: @finish).then(&:write)
280 end
281 end
282
283 class Activate
284 def initialize(customer, payment_method, tel, finish: Finish)
285 @customer = customer
286 @payment_method = payment_method
287 @tel = tel
288 @finish = finish
289 end
290
291 def write
292 CreditCardSale.create(
293 @customer,
294 amount: CONFIG[:activation_amount],
295 payment_method: @payment_method
296 ).then(
297 ->(_) { sold },
298 ->(_) { declined }
299 )
300 end
301
302 protected
303
304 def sold
305 BillPlan.new(@customer, @tel, finish: @finish).write
306 end
307
308 DECLINE_MESSAGE =
309 "Your bank declined the transaction. " \
310 "Often this happens when a person's credit card " \
311 "is a US card that does not support international " \
312 "transactions, as JMP is not based in the USA, though " \
313 "we do support transactions in USD.\n\n" \
314 "You may add another card"
315
316 def decline_oob(reply)
317 oob = OOB.find_or_create(reply.command)
318 oob.url = CONFIG[:credit_card_url].call(
319 reply.to.stripped.to_s.gsub("\\", "%5C"),
320 @customer.customer_id
321 ) + "&amount=#{CONFIG[:activation_amount]}"
322 oob.desc = DECLINE_MESSAGE
323 oob
324 end
325
326 def declined
327 Command.reply { |reply|
328 reply_oob = decline_oob(reply)
329 reply.allowed_actions = [:next]
330 reply.note_type = :error
331 reply.note_text = "#{reply_oob.desc}: #{reply_oob.url}"
332 }.then do
333 CreditCard.for(@customer, @tel, finish: @finish).then(&:write)
334 end
335 end
336 end
337 end
338
339 class InviteCode
340 Payment.kinds[:code] = method(:new)
341
342 FIELDS = [{
343 var: "code",
344 type: "text-single",
345 label: "Your referral code",
346 required: true
347 }].freeze
348
349 def initialize(customer, tel, error: nil, **)
350 @customer = customer
351 @tel = tel
352 @error = error
353 end
354
355 def add_form(reply)
356 form = reply.form
357 form.type = :form
358 form.title = "Enter Referral Code"
359 form.instructions = @error if @error
360 form.fields = FIELDS
361 end
362
363 def write
364 Command.reply { |reply|
365 reply.allowed_actions = [:next, :prev]
366 add_form(reply)
367 }.then(&method(:parse))
368 end
369
370 def parse(iq)
371 return Activation.for(@customer, @tel).then(&:write) if iq.prev?
372
373 guard_too_many_tries.then {
374 verify(iq.form.field("code")&.value&.to_s)
375 }.then {
376 Finish.new(@customer, @tel)
377 }.catch_only(InvitesRepo::Invalid, &method(:invalid_code)).then(&:write)
378 end
379
380 protected
381
382 def guard_too_many_tries
383 REDIS.get("jmp_invite_tries-#{customer_id}").then do |t|
384 raise InvitesRepo::Invalid, "Too many wrong attempts" if t.to_i > 10
385 end
386 end
387
388 def invalid_code(e)
389 EMPromise.all([
390 REDIS.incr("jmp_invite_tries-#{customer_id}").then do
391 REDIS.expire("jmp_invite_tries-#{customer_id}", 60 * 60)
392 end,
393 InviteCode.new(@customer, @tel, error: e.message)
394 ]).then(&:last)
395 end
396
397 def customer_id
398 @customer.customer_id
399 end
400
401 def verify(code)
402 InvitesRepo.new(DB).claim_code(customer_id, code) do
403 @customer.activate_plan_starting_now
404 end
405 end
406 end
407
408 class Mail
409 Payment.kinds[:mail] = method(:new)
410
411 def initialize(customer, tel, final_message: nil, **)
412 @customer = customer
413 @tel = tel
414 @final_message = final_message
415 end
416
417 def form
418 FormTemplate.render(
419 "registration/mail",
420 currency: @customer.currency,
421 final_message: @final_message
422 )
423 end
424
425 def write
426 Command.reply { |reply|
427 reply.allowed_actions = [:prev]
428 reply.status = :canceled
429 reply.command << form
430 }.then { |iq|
431 raise "Action not allowed" unless iq.prev?
432
433 Activation.for(@customer, @tel).then(&:write)
434 }
435 end
436 end
437 end
438
439 class BillPlan
440 def initialize(customer, tel, finish: Finish)
441 @customer = customer
442 @tel = tel
443 @finish = finish
444 end
445
446 def write
447 @customer.bill_plan(note: "Bill #{@tel} for first month").then do
448 @finish.new(@customer, @tel).write
449 end
450 end
451 end
452
453 class Finish
454 def initialize(customer, tel)
455 @customer = customer
456 @tel = tel
457 end
458
459 def write
460 BandwidthTnReservationRepo.new.get(@customer, @tel).then do |rid|
461 BandwidthTNOrder.create(
462 @tel,
463 customer_order_id: @customer.customer_id,
464 reservation_id: rid
465 ).then(&:poll).then(
466 ->(_) { customer_active_tel_purchased },
467 method(:number_purchase_error)
468 )
469 end
470 end
471
472 protected
473
474 def number_purchase_error(e)
475 Command.log.error "number_purchase_error", e
476 TEL_SELECTIONS.delete(@customer.jid).then {
477 TelSelections::ChooseTel.new.choose_tel(
478 error: "The JMP number #{@tel} is no longer available."
479 )
480 }.then { |tel| Finish.new(@customer, tel).write }
481 end
482
483 def raise_setup_error(e)
484 Command.log.error "@customer.register! failed", e
485 Command.finish(
486 "There was an error setting up your number, " \
487 "please contact JMP support.",
488 type: :error
489 )
490 end
491
492 def put_default_fwd
493 Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel, CustomerFwd.for(
494 uri: "xmpp:#{@customer.jid}",
495 voicemail_enabled: true
496 ))
497 end
498
499 def customer_active_tel_purchased
500 @customer.register!(@tel).catch(&method(:raise_setup_error)).then {
501 EMPromise.all([
502 REDIS.del("pending_tel_for-#{@customer.jid}"),
503 put_default_fwd
504 ])
505 }.then do
506 WelcomeMessage.new(@customer, @tel).welcome
507 Command.finish("Your JMP account has been activated as #{@tel}")
508 end
509 end
510 end
511end