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, google_play_userid, tel_selections)
20 if (reg = customer.registered?)
21 Registered.for(customer, 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, google_play_userid, tel)
26 end
27 end
28 end
29
30 class Registered
31 def self.for(customer, tel)
32 jid = ProxiedJID.new(customer.jid).unproxied
33 if jid.domain == CONFIG[:onboarding_domain]
34 FinishOnboarding.for(customer, tel)
35 else
36 new(tel)
37 end
38 end
39
40 def initialize(tel)
41 @tel = tel
42 end
43
44 def write
45 Command.finish("You are already registered with JMP number #{@tel}")
46 end
47 end
48
49 class FinishOrStartActivation
50 def self.for(customer, google_play_userid, tel)
51 if customer.active?
52 Finish.new(customer, tel)
53 elsif customer.balance >= CONFIG[:activation_amount_accept]
54 BillPlan.new(customer, tel)
55 else
56 new(customer, google_play_userid, tel)
57 end
58 end
59
60 def initialize(customer, google_play_userid, tel)
61 @customer = customer
62 @tel = tel
63 @google_play_userid = google_play_userid
64 end
65
66 def write
67 Command.reply { |reply|
68 reply.allowed_actions = [:next]
69 reply.note_type = :info
70 reply.note_text = File.read("#{__dir__}/../fup.txt")
71 }.then { Activation.for(@customer, @google_play_userid, @tel).write }
72 end
73 end
74
75 class Activation
76 def self.for(customer, google_play_userid, tel)
77 jid = ProxiedJID.new(customer.jid).unproxied
78 if CONFIG[:approved_domains].key?(jid.domain.to_sym)
79 Allow.for(customer, tel, jid)
80 elsif google_play_userid
81 GooglePlay.new(customer, google_play_userid, tel)
82 else
83 new(customer, tel)
84 end
85 end
86
87 def initialize(customer, tel)
88 @customer = customer
89 @tel = tel
90 @invites = InvitesRepo.new(DB, REDIS)
91 end
92
93 attr_reader :customer, :tel
94
95 def form(center)
96 FormTemplate.render(
97 "registration/activate",
98 tel: tel,
99 rate_center: center
100 )
101 end
102
103 def write
104 rate_center.then { |center|
105 Command.reply do |reply|
106 reply.allowed_actions = [:next]
107 reply.command << form(center)
108 end
109 }.then(&method(:next_step))
110 end
111
112 def next_step(iq)
113 code = iq.form.field("code")&.value&.to_s
114 save_customer_plan(iq).then {
115 finish_if_valid_invite(code)
116 }.catch_only(InvitesRepo::Invalid) do
117 @invites.stash_code(customer.customer_id, code).then do
118 Payment.for(iq, @customer, @tel).then(&:write)
119 end
120 end
121 end
122
123 protected
124
125 def finish_if_valid_invite(code)
126 @invites.claim_code(@customer.customer_id, code) {
127 @customer.activate_plan_starting_now
128 }.then do
129 Finish.new(@customer, @tel).write
130 end
131 end
132
133 def save_customer_plan(iq)
134 plan_name = iq.form.field("plan_name").value.to_s
135 @customer = @customer.with_plan(plan_name)
136 @customer.save_plan!
137 end
138
139 def rate_center
140 EM.promise_fiber {
141 center = BandwidthIris::Tn.get(tel).get_rate_center
142 "#{center[:rate_center]}, #{center[:state]}"
143 }.catch { nil }
144 end
145
146 class GooglePlay
147 def initialize(customer, google_play_userid, tel)
148 @customer = customer
149 @google_play_userid = google_play_userid
150 @tel = tel
151 @invites = InvitesRepo.new(DB, REDIS)
152 end
153
154 def used
155 REDIS.sismember("google_play_userids", @google_play_userid)
156 end
157
158 def form
159 FormTemplate.render(
160 "registration/google_play",
161 tel: @tel
162 )
163 end
164
165 def write
166 used.then do |u|
167 next Activation.for(@customer, nil, @tel).write if u.to_s == "1"
168
169 Command.reply { |reply|
170 reply.allowed_actions = [:next]
171 reply.command << form
172 }.then(&method(:activate)).then do
173 Finish.new(@customer, @tel).write
174 end
175 end
176 end
177
178 def activate(iq)
179 REDIS.sadd("google_play_userids", @google_play_userid).then {
180 plan_name = iq.form.field("plan_name").value.to_s
181 @customer = @customer.with_plan(plan_name)
182 @customer.activate_plan_starting_now
183 }.then do
184 use_referral_code(iq.form.field("code")&.value&.to_s)
185 end
186 end
187
188 protected
189
190 def use_referral_code(code)
191 @invites.claim_code(@customer.customer_id, code) {
192 @customer.extend_plan
193 }.catch_only(InvitesRepo::Invalid) do
194 @invites.stash_code(customer.customer_id, code)
195 end
196 end
197 end
198
199 class Allow < Activation
200 def self.for(customer, tel, jid)
201 credit_to = CONFIG[:approved_domains][jid.domain.to_sym]
202 new(customer, tel, credit_to)
203 end
204
205 def initialize(customer, tel, credit_to)
206 super(customer, tel)
207 @credit_to = credit_to
208 end
209
210 def form(center)
211 FormTemplate.render(
212 "registration/allow",
213 tel: tel,
214 rate_center: center,
215 domain: customer.jid.domain
216 )
217 end
218
219 def next_step(iq)
220 plan_name = iq.form.field("plan_name").value.to_s
221 @customer = customer.with_plan(plan_name)
222 EMPromise.resolve(nil).then { activate }.then do
223 Finish.new(customer, tel).write
224 end
225 end
226
227 protected
228
229 def activate
230 DB.transaction do
231 if @credit_to
232 DB.exec(<<~SQL, [@credit_to, customer.customer_id])
233 INSERT INTO invites (creator_id, used_by_id, used_at)
234 VALUES ($1, $2, LOCALTIMESTAMP)
235 SQL
236 end
237 @customer.activate_plan_starting_now
238 end
239 end
240 end
241 end
242
243 module Payment
244 def self.kinds
245 @kinds ||= {}
246 end
247
248 def self.for(iq, customer, tel, final_message: nil, finish: Finish)
249 kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) {
250 raise "Invalid activation method"
251 }.call(customer, tel, final_message: final_message, finish: finish)
252 end
253
254 class Bitcoin
255 Payment.kinds[:bitcoin] = method(:new)
256
257 THIRTY_DAYS = 60 * 60 * 24 * 30
258
259 def initialize(customer, tel, final_message: nil, **)
260 @customer = customer
261 @customer_id = customer.customer_id
262 @tel = tel
263 @final_message = final_message
264 end
265
266 attr_reader :customer_id, :tel
267
268 def save
269 REDIS.setex("pending_tel_for-#{@customer.jid}", THIRTY_DAYS, tel)
270 end
271
272 def form(rate, addr)
273 amount = CONFIG[:activation_amount] / rate
274
275 FormTemplate.render(
276 "registration/btc",
277 amount: amount,
278 addr: addr,
279 final_message: @final_message
280 )
281 end
282
283 def write
284 EMPromise.all([addr_and_rate, save]).then do |((addr, rate), _)|
285 Command.reply { |reply|
286 reply.allowed_actions = [:prev]
287 reply.status = :canceled
288 reply.command << form(rate, addr)
289 }.then(&method(:handle_possible_prev))
290 end
291 end
292
293 protected
294
295 def handle_possible_prev(iq)
296 raise "Action not allowed" unless iq.prev?
297
298 Activation.for(@customer, nil, @tel).then(&:write)
299 end
300
301 def addr_and_rate
302 EMPromise.all([
303 @customer.btc_addresses.then { |addrs|
304 addrs.first || @customer.add_btc_address
305 },
306 BTC_SELL_PRICES.public_send(@customer.currency.to_s.downcase)
307 ])
308 end
309 end
310
311 class CreditCard
312 Payment.kinds[:credit_card] = ->(*args, **kw) { self.for(*args, **kw) }
313
314 def self.for(in_customer, tel, finish: Finish, **)
315 reload_customer(in_customer).then do |(customer, payment_methods)|
316 if customer.balance >= CONFIG[:activation_amount_accept]
317 next BillPlan.new(customer, tel, finish: finish)
318 end
319
320 if (method = payment_methods.default_payment_method)
321 next Activate.new(customer, method, tel, finish: finish)
322 end
323
324 new(customer, tel, finish: finish)
325 end
326 end
327
328 def self.reload_customer(customer)
329 EMPromise.all([
330 Command.execution.customer_repo.find(customer.customer_id),
331 customer.payment_methods
332 ])
333 end
334
335 def initialize(customer, tel, finish: Finish)
336 @customer = customer
337 @tel = tel
338 @finish = finish
339 end
340
341 def oob(reply)
342 oob = OOB.find_or_create(reply.command)
343 oob.url = CONFIG[:credit_card_url].call(
344 reply.to.stripped.to_s.gsub("\\", "%5C"),
345 @customer.customer_id
346 ) + "&amount=#{CONFIG[:activation_amount]}"
347 oob.desc = "Add credit card, save, then next here to continue"
348 oob
349 end
350
351 def write
352 Command.reply { |reply|
353 reply.allowed_actions = [:next, :prev]
354 toob = oob(reply)
355 reply.note_type = :info
356 reply.note_text = "#{toob.desc}: #{toob.url}"
357 }.then do |iq|
358 next Activation.for(@customer, nil, @tel).then(&:write) if iq.prev?
359
360 CreditCard.for(@customer, @tel, finish: @finish).then(&:write)
361 end
362 end
363
364 class Activate
365 def initialize(customer, payment_method, tel, finish: Finish)
366 @customer = customer
367 @payment_method = payment_method
368 @tel = tel
369 @finish = finish
370 end
371
372 def write
373 CreditCardSale.create(
374 @customer,
375 amount: CONFIG[:activation_amount],
376 payment_method: @payment_method
377 ).then(
378 ->(_) { sold },
379 ->(_) { declined }
380 )
381 end
382
383 protected
384
385 def sold
386 BillPlan.new(@customer, @tel, finish: @finish).write
387 end
388
389 DECLINE_MESSAGE =
390 "Your bank declined the transaction. " \
391 "Often this happens when a person's credit card " \
392 "is a US card that does not support international " \
393 "transactions, as JMP is not based in the USA, though " \
394 "we do support transactions in USD.\n\n" \
395 "You may add another card"
396
397 def decline_oob(reply)
398 oob = OOB.find_or_create(reply.command)
399 oob.url = CONFIG[:credit_card_url].call(
400 reply.to.stripped.to_s.gsub("\\", "%5C"),
401 @customer.customer_id
402 ) + "&amount=#{CONFIG[:activation_amount]}"
403 oob.desc = DECLINE_MESSAGE
404 oob
405 end
406
407 def declined
408 Command.reply { |reply|
409 reply_oob = decline_oob(reply)
410 reply.allowed_actions = [:next]
411 reply.note_type = :error
412 reply.note_text = "#{reply_oob.desc}: #{reply_oob.url}"
413 }.then do
414 CreditCard.for(@customer, @tel, finish: @finish).then(&:write)
415 end
416 end
417 end
418 end
419
420 class InviteCode
421 Payment.kinds[:code] = method(:new)
422
423 FIELDS = [{
424 var: "code",
425 type: "text-single",
426 label: "Your referral code",
427 required: true
428 }].freeze
429
430 def initialize(customer, tel, error: nil, **)
431 @customer = customer
432 @tel = tel
433 @error = error
434 end
435
436 def add_form(reply)
437 form = reply.form
438 form.type = :form
439 form.title = "Enter Referral Code"
440 form.instructions = @error if @error
441 form.fields = FIELDS
442 end
443
444 def write
445 Command.reply { |reply|
446 reply.allowed_actions = [:next, :prev]
447 add_form(reply)
448 }.then(&method(:parse))
449 end
450
451 def parse(iq)
452 return Activation.for(@customer, nil, @tel).then(&:write) if iq.prev?
453
454 verify(iq.form.field("code")&.value&.to_s).then {
455 Finish.new(@customer, @tel)
456 }.catch_only(InvitesRepo::Invalid, &method(:invalid_code)).then(&:write)
457 end
458
459 protected
460
461 def invalid_code(e)
462 InviteCode.new(@customer, @tel, error: e.message)
463 end
464
465 def customer_id
466 @customer.customer_id
467 end
468
469 def verify(code)
470 InvitesRepo.new(DB, REDIS).claim_code(customer_id, code) do
471 @customer.activate_plan_starting_now
472 end
473 end
474 end
475
476 class Mail
477 Payment.kinds[:mail] = method(:new)
478
479 def initialize(customer, tel, final_message: nil, **)
480 @customer = customer
481 @tel = tel
482 @final_message = final_message
483 end
484
485 def form
486 FormTemplate.render(
487 "registration/mail",
488 currency: @customer.currency,
489 final_message: @final_message,
490 **onboarding_extras
491 )
492 end
493
494 def onboarding_extras
495 jid = ProxiedJID.new(@customer.jid).unproxied
496 return {} unless jid.domain == CONFIG[:onboarding_domain]
497
498 {
499 customer_id: @customer.customer_id,
500 in_note: "Customer ID"
501 }
502 end
503
504 def write
505 Command.reply { |reply|
506 reply.allowed_actions = [:prev]
507 reply.status = :canceled
508 reply.command << form
509 }.then { |iq|
510 raise "Action not allowed" unless iq.prev?
511
512 Activation.for(@customer, nil, @tel).then(&:write)
513 }
514 end
515 end
516 end
517
518 class BillPlan
519 def initialize(customer, tel, finish: Finish)
520 @customer = customer
521 @tel = tel
522 @finish = finish
523 end
524
525 def write
526 @customer.bill_plan(note: "Bill #{@tel} for first month").then do
527 @finish.new(@customer, @tel).write
528 end
529 end
530 end
531
532 class Finish
533 def initialize(customer, tel)
534 @customer = customer
535 @tel = tel
536 end
537
538 def write
539 BandwidthTnReservationRepo.new.get(@customer, @tel).then do |rid|
540 BandwidthTNOrder.create(
541 @tel,
542 customer_order_id: @customer.customer_id,
543 reservation_id: rid
544 ).then(&:poll).then(
545 ->(_) { customer_active_tel_purchased },
546 method(:number_purchase_error)
547 )
548 end
549 end
550
551 protected
552
553 def number_purchase_error(e)
554 Command.log.error "number_purchase_error", e
555 TEL_SELECTIONS.delete(@customer.jid).then {
556 TelSelections::ChooseTel.new.choose_tel(
557 error: "The JMP number #{@tel} is no longer available."
558 )
559 }.then { |tel| Finish.new(@customer, tel).write }
560 end
561
562 def raise_setup_error(e)
563 Command.log.error "@customer.register! failed", e
564 Command.finish(
565 "There was an error setting up your number, " \
566 "please contact JMP support.",
567 type: :error
568 )
569 end
570
571 def put_default_fwd
572 Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel, CustomerFwd.for(
573 uri: "xmpp:#{@customer.jid}",
574 voicemail_enabled: true
575 ))
576 end
577
578 def customer_active_tel_purchased
579 @customer.register!(@tel).catch(&method(:raise_setup_error)).then {
580 EMPromise.all([
581 REDIS.del("pending_tel_for-#{@customer.jid}"),
582 put_default_fwd
583 ])
584 }.then do
585 FinishOnboarding.for(@customer, @tel).then(&:write)
586 end
587 end
588 end
589
590 module FinishOnboarding
591 def self.for(customer, tel, db: LazyObject.new { DB })
592 jid = ProxiedJID.new(customer.jid).unproxied
593 if jid.domain == CONFIG[:onboarding_domain]
594 Snikket.for(customer, tel, db: db)
595 else
596 NotOnboarding.new(customer, tel)
597 end
598 end
599
600 class Snikket
601 def self.for(customer, tel, db:)
602 ::Snikket::Repo.new(db: db).find_by_customer(customer).then do |is|
603 if is.empty?
604 new(customer, tel, db: db)
605 else
606 GetInvite.for(is[0])
607 end
608 end
609 end
610
611 def initialize(customer, tel, error: nil, db:)
612 @customer = customer
613 @tel = tel
614 @error = error
615 @db = db
616 end
617
618 ACTION_VAR = "http://jabber.org/protocol/commands#actions"
619
620 def write
621 Command.reply { |reply|
622 reply.allowed_actions = [:next]
623 reply.command << form
624 }.then do |iq|
625 if iq.form.field(ACTION_VAR)&.value == "custom_domain"
626 CustomDomain.new(@tel).write
627 else
628 launch("#{iq.form.field('subdomain')&.value}.snikket.chat")
629 end
630 end
631 end
632
633 def form
634 FormTemplate.render(
635 "registration/snikket",
636 tel: @tel,
637 error: @error
638 )
639 end
640
641 def launch(domain)
642 IQ_MANAGER.write(::Snikket::Launch.new(
643 nil, CONFIG[:snikket_hosting_api], domain: domain
644 )).then { |launched|
645 save_instance_and_wait(domain, launched)
646 }.catch { |e|
647 next EMPromise.reject(e) unless e.respond_to?(:text)
648
649 Snikket.new(@customer, @tel, error: e.text, db: @db).write
650 }
651 end
652
653 def save_instance_and_wait(domain, launched)
654 instance = ::Snikket::CustomerInstance.for(@customer, domain, launched)
655 ::Snikket::Repo.new(db: @db).put(instance).then do
656 GetInvite.for(instance).then(&:write)
657 end
658 end
659
660 class GetInvite
661 def self.for(instance)
662 instance.fetch_invite.then do |xmpp_uri|
663 if xmpp_uri
664 GoToInvite.new(xmpp_uri)
665 else
666 new(instance)
667 end
668 end
669 end
670
671 def initialize(instance)
672 @instance = instance
673 end
674
675 def write
676 Command.reply { |reply|
677 reply.allowed_actions = [:next]
678 reply.command << FormTemplate.render(
679 "registration/snikket_wait",
680 domain: @instance.domain
681 )
682 }.then { GetInvite.for(@instance).then(&:write) }
683 end
684 end
685
686 class GoToInvite
687 def initialize(xmpp_uri)
688 @xmpp_uri = xmpp_uri
689 end
690
691 def write
692 Command.finish do |reply|
693 oob = OOB.find_or_create(reply.command)
694 oob.url = @xmpp_uri
695 end
696 end
697 end
698 end
699
700 class CustomDomain
701 def initialize(tel)
702 @tel = tel
703 end
704
705 CONTACT_SUPPORT =
706 "Please contact JMP support to set up " \
707 "an instance on an existing domain."
708
709 def write
710 Command.reply { |reply|
711 reply.allowed_actions = [:prev]
712 reply.status = :canceled
713 reply.note_type = :info
714 reply.note_text = CONTACT_SUPPORT
715 }.then do |iq|
716 raise "Action not allowed" unless iq.prev?
717
718 Snikket.new(@tel).write
719 end
720 end
721 end
722
723 class NotOnboarding
724 def initialize(customer, tel)
725 @customer = customer
726 @tel = tel
727 end
728
729 def write
730 WelcomeMessage.new(@customer, @tel).welcome
731 Command.finish("Your JMP account has been activated as #{@tel}")
732 end
733 end
734 end
735end