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 "./parent_code_repo"
 15require_relative "./proxied_jid"
 16require_relative "./tel_selections"
 17require_relative "./welcome_message"
 18
 19def reload_customer(customer)
 20	EMPromise.resolve(nil).then do
 21		Command.execution.customer_repo.find(customer.customer_id)
 22	end
 23end
 24
 25class Registration
 26	def self.for(customer, google_play_userid, tel_selections)
 27		if (reg = customer.registered?)
 28			Registered.for(customer, reg.phone)
 29		else
 30			tel_selections[customer.jid].then(&:choose_tel).then do |tel|
 31				reserve_and_continue(tel_selections, customer, tel).then do
 32					FinishOrStartActivation.for(customer, google_play_userid, tel)
 33				end
 34			end
 35		end
 36	end
 37
 38	def self.reserve_and_continue(tel_selections, customer, tel)
 39		tel.reserve(customer).catch do
 40			tel_selections.delete(customer.jid).then {
 41				tel_selections[customer.jid]
 42			}.then { |choose|
 43				choose.choose_tel(
 44					error: "The JMP number #{tel} is no longer available."
 45				)
 46			}.then { |n_tel| reserve_and_continue(tel_selections, customer, n_tel) }
 47		end
 48	end
 49
 50	class Registered
 51		def self.for(customer, tel)
 52			jid = ProxiedJID.new(customer.jid).unproxied
 53			if jid.domain == CONFIG[:onboarding_domain]
 54				FinishOnboarding.for(customer, tel)
 55			else
 56				new(tel)
 57			end
 58		end
 59
 60		def initialize(tel)
 61			@tel = tel
 62		end
 63
 64		def write
 65			Command.finish("You are already registered with JMP number #{@tel}")
 66		end
 67	end
 68
 69	class FinishOrStartActivation
 70		def self.for(customer, google_play_userid, tel)
 71			if customer.active?
 72				Finish.new(customer, tel)
 73			elsif customer.balance >= CONFIG[:activation_amount_accept]
 74				BillPlan.new(customer, tel)
 75			else
 76				new(customer, google_play_userid, tel)
 77			end
 78		end
 79
 80		def initialize(customer, google_play_userid, tel)
 81			@customer = customer
 82			@tel = tel
 83			@google_play_userid = google_play_userid
 84		end
 85
 86		def write
 87			Command.reply { |reply|
 88				reply.allowed_actions = [:next]
 89				reply.note_type = :info
 90				reply.note_text = File.read("#{__dir__}/../fup.txt")
 91			}.then { Activation.for(@customer, @google_play_userid, @tel).write }
 92		end
 93	end
 94
 95	class Activation
 96		def self.for(customer, google_play_userid, tel)
 97			jid = ProxiedJID.new(customer.jid).unproxied
 98			if CONFIG[:approved_domains].key?(jid.domain.to_sym)
 99				Allow.for(customer, tel, jid)
100			elsif false && google_play_userid
101				GooglePlay.new(customer, google_play_userid, tel)
102			else
103				new(customer, tel)
104			end
105		end
106
107		def initialize(customer, tel)
108			@customer = customer
109			@tel = tel
110		end
111
112		attr_reader :customer, :tel
113
114		def form
115			FormTemplate.render("registration/activate", tel: tel)
116		end
117
118		def write
119			Command.reply { |reply|
120				reply.allowed_actions = [:next]
121				reply.command << form
122			}.then(&method(:next_step))
123		end
124
125		def next_step(iq)
126			EMPromise.resolve(nil).then do
127				plan = Plan.for_registration(iq.form.field("plan_name").value.to_s)
128				@customer = @customer.with_plan(plan.name)
129				Registration::Payment::InviteCode.new(
130					@customer, @tel, finish: Finish, db: DB, redis: REDIS
131				).parse(iq, force_save_plan: true)
132					.catch_only(InvitesRepo::Invalid) do
133						Payment.for(iq, @customer, @tel).then(&:write)
134					end
135			end
136		end
137
138		class GooglePlay
139			def initialize(customer, google_play_userid, tel)
140				@customer = customer
141				@google_play_userid = google_play_userid
142				@tel = tel
143				@invites = InvitesRepo.new(DB, REDIS)
144				@parent_code_repo = ParentCodeRepo.new(redis: REDIS, db: DB)
145			end
146
147			def used
148				REDIS.sismember("google_play_userids", @google_play_userid)
149			end
150
151			def form
152				FormTemplate.render(
153					"registration/google_play",
154					tel: @tel
155				)
156			end
157
158			def write
159				used.then do |u|
160					next Activation.for(@customer, nil, @tel).write if u.to_s == "1"
161
162					Command.reply { |reply|
163						reply.allowed_actions = [:next]
164						reply.command << form
165					}.then(&method(:activate)).then do
166						Finish.new(@customer, @tel).write
167					end
168				end
169			end
170
171			def activate(iq)
172				plan = Plan.for_registration(iq.form.field("plan_name").value)
173				code = iq.form.field("code")&.value
174				EMPromise.all([
175					@parent_code_repo.find(code),
176					REDIS.sadd("google_play_userids", @google_play_userid)
177				]).then { |(parent, _)|
178					save_active_plan(plan, parent)
179				}.then do
180					use_referral_code(code)
181				end
182			end
183
184		protected
185
186			def save_bogus_transaction
187				Transaction.new(
188					customer_id: @customer.customer_id,
189					transaction_id: "google_play_#{@customer.customer_id}",
190					amount: 0,
191					note: "Activated via Google Play",
192					bonus_eligible?: false
193				).insert
194			end
195
196			def save_active_plan(plan, parent)
197				@customer = @customer.with_plan(plan.name, parent_customer_id: parent)
198				save_bogus_transaction.then do
199					@customer.activate_plan_starting_now
200				end
201			end
202
203			def use_referral_code(code)
204				EMPromise.resolve(nil).then {
205					@invites.claim_code(@customer.customer_id, code) {
206						@customer.extend_plan
207					}
208				}.catch_only(InvitesRepo::Invalid) do
209					@invites.stash_code(@customer.customer_id, code)
210				end
211			end
212		end
213
214		class Allow < Activation
215			def self.for(customer, tel, jid)
216				credit_to = CONFIG[:approved_domains][jid.domain.to_sym]
217				new(customer, tel, credit_to)
218			end
219
220			def initialize(customer, tel, credit_to)
221				super(customer, tel)
222				@credit_to = credit_to
223			end
224
225			def form
226				FormTemplate.render(
227					"registration/allow",
228					tel: tel,
229					domain: customer.jid.domain
230				)
231			end
232
233			def next_step(iq)
234				plan = Plan.for_registration(iq.form.field("plan_name").value.to_s)
235				@customer = customer.with_plan(plan.name)
236				EMPromise.resolve(nil).then { activate }.then do
237					Finish.new(customer, tel).write
238				end
239			end
240
241		protected
242
243			def activate
244				DB.transaction do
245					if @credit_to
246						InvitesRepo.new(DB, REDIS).create_claimed_code(
247							@credit_to,
248							customer.customer_id
249						)
250					end
251					@customer.activate_plan_starting_now
252				end
253			end
254		end
255	end
256
257	module Payment
258		def self.kinds
259			@kinds ||= {}
260		end
261
262		def self.for(
263			iq, customer, tel,
264			finish: Finish, maybe_bill: MaybeBill,
265			price: CONFIG[:activation_amount] + tel.price
266		)
267			kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) {
268				raise "Invalid activation method"
269			}.call(
270				customer, tel, finish: finish, maybe_bill: maybe_bill, price: price
271			)
272		end
273
274		class CryptoPaymentMethod
275			def crypto_addrs
276				raise NotImplementedError, "Subclass must implement"
277			end
278
279			def reg_form_name
280				raise NotImplementedError, "Subclass must implement"
281			end
282
283			def sell_prices
284				raise NotImplementedError, "Subclass must implement"
285			end
286
287			def initialize(
288				customer, tel,
289				price: CONFIG[:activation_amount] + tel.price, **
290			)
291				@customer = customer
292				@customer_id = customer.customer_id
293				@tel = tel
294				@price = price
295			end
296
297			def save
298				TEL_SELECTIONS.set(@customer.jid, @tel)
299			end
300
301			attr_reader :customer_id, :tel
302
303			def form(rate, addr)
304				FormTemplate.render(
305					reg_form_name,
306					amount: @price / rate,
307					addr: addr
308				)
309			end
310
311			def write
312				EMPromise.all([addr_and_rate, save]).then do |((addr, rate), _)|
313					Command.reply { |reply|
314						reply.allowed_actions = [:prev]
315						reply.status = :canceled
316						reply.command << form(rate, addr)
317					}.then(&method(:handle_possible_prev))
318				end
319			end
320
321		protected
322
323			def handle_possible_prev(iq)
324				raise "Action not allowed" unless iq.prev?
325
326				Activation.for(@customer, nil, @tel).then(&:write)
327			end
328
329			def addr_and_rate
330				EMPromise.all([
331					crypto_addrs.then { |addrs|
332						addrs.first || add_crypto_addr
333					},
334
335					sell_prices.public_send(@customer.currency.to_s.downcase)
336				])
337			end
338		end
339
340		class Bitcoin < CryptoPaymentMethod
341			Payment.kinds[:bitcoin] = method(:new)
342
343			def reg_form_name
344				"registration/btc"
345			end
346
347			def sell_prices
348				BTC_SELL_PRICES
349			end
350
351			def crypto_addrs
352				@customer.btc_addresses
353			end
354
355			def add_crypto_addr
356				@customer.add_btc_address
357			end
358		end
359
360		## Like Bitcoin
361		class BCH < CryptoPaymentMethod
362			Payment.kinds[:bch] = method(:new)
363
364			def reg_form_name
365				"registration/bch"
366			end
367
368			def sell_prices
369				BCH_SELL_PRICES
370			end
371
372			def crypto_addrs
373				@customer.bch_addresses
374			end
375
376			def add_crypto_addr
377				@customer.add_bch_address
378			end
379		end
380
381		class MaybeBill
382			def initialize(customer, tel, finish: Finish)
383				@customer = customer
384				@tel = tel
385				@finish = finish
386			end
387
388			def call
389				reload_customer(@customer).then do |customer|
390					if customer.balance >= CONFIG[:activation_amount_accept]
391						next BillPlan.new(customer, @tel, finish: @finish)
392					end
393
394					yield customer
395				end
396			end
397		end
398
399		class JustCharge
400			def initialize(customer, *, **)
401				@customer = customer
402			end
403
404			def call; end
405		end
406
407		class CreditCard
408			Payment.kinds[:credit_card] = method(:new)
409
410			def initialize(
411				customer, tel,
412				finish: Finish, maybe_bill: MaybeBill,
413				price: CONFIG[:activation_amount] + tel.price
414			)
415				@customer = customer
416				@tel = tel
417				@finish = finish
418				@maybe_bill = maybe_bill.new(customer, tel, finish: finish)
419				@price = price
420			end
421
422			def oob(reply)
423				oob = OOB.find_or_create(reply.command)
424				oob.url = CONFIG[:credit_card_url].call(
425					reply.to.stripped.to_s.gsub("\\", "%5C"),
426					@customer.customer_id
427				) + "&amount=#{@price.ceil}"
428				oob.desc = "Pay by credit card, save, then next here to continue"
429				oob
430			end
431
432			def write
433				Command.reply { |reply|
434					reply.allowed_actions = [:next, :prev]
435					toob = oob(reply)
436					reply.note_type = :info
437					reply.note_text = "#{toob.desc}: #{toob.url}"
438				}.then do |iq|
439					next Activation.for(@customer, nil, @tel).then(&:write) if iq.prev?
440
441					@maybe_bill.call { self }&.then(&:write)
442				end
443			end
444		end
445
446		class InviteCode
447			Payment.kinds[:code] = method(:new)
448
449			FIELDS = [{
450				var: "code",
451				type: "text-single",
452				label: "Your referral code",
453				required: true
454			}].freeze
455
456			def initialize(
457				customer, tel,
458				error: nil, finish: Finish, db: DB, redis: REDIS, **
459			)
460				@customer = customer
461				@tel = tel
462				@error = error
463				@finish = finish
464				@invites_repo = InvitesRepo.new(db, redis)
465				@parent_code_repo = ParentCodeRepo.new(db: db, redis: redis)
466			end
467
468			def add_form(reply)
469				form = reply.form
470				form.type = :form
471				form.title = "Enter Referral Code"
472				form.instructions = @error if @error
473				form.fields = FIELDS
474			end
475
476			def write
477				Command.reply { |reply|
478					reply.allowed_actions = [:next, :prev]
479					add_form(reply)
480				}.then(&method(:parse)).catch_only(InvitesRepo::Invalid) { |e|
481					invalid_code(e).write
482				}
483			end
484
485			def parse(iq, force_save_plan: false)
486				return Activation.for(@customer, nil, @tel).then(&:write) if iq.prev?
487
488				verify(iq.form.field("code")&.value&.to_s, force_save_plan)
489					.then(&:write)
490			end
491
492		protected
493
494			def invalid_code(e)
495				self.class.new(@customer, @tel, error: e.message, finish: @finish)
496			end
497
498			def customer_id
499				@customer.customer_id
500			end
501
502			def verify(code, force_save_plan)
503				@parent_code_repo.claim_code(@customer, code) {
504					check_parent_balance
505				}.catch_only(ParentCodeRepo::Invalid) {
506					(@customer.save_plan! if force_save_plan).then do
507						@invites_repo.claim_code(customer_id, code) {
508							@customer.activate_plan_starting_now
509						}.then { @finish.new(@customer, @tel) }
510					end
511				}
512			end
513
514			def check_parent_balance
515				MaybeBill.new(@customer, @tel, finish: @finish).call do
516					msg = "Account balance not enough to cover the activation"
517					invalid_code(RuntimeError.new(msg))
518				end
519			end
520		end
521
522		class Mail
523			Payment.kinds[:mail] = method(:new)
524
525			def initialize(
526				customer, tel,
527				price: CONFIG[:activation_amount] + tel.price, **
528			)
529				@customer = customer
530				@tel = tel
531				@price = price
532			end
533
534			def form
535				FormTemplate.render(
536					"registration/mail",
537					currency: @customer.currency,
538					price: @price,
539					**onboarding_extras
540				)
541			end
542
543			def onboarding_extras
544				jid = ProxiedJID.new(@customer.jid).unproxied
545				return {} unless jid.domain == CONFIG[:onboarding_domain]
546
547				{
548					customer_id: @customer.customer_id,
549					in_note: "Customer ID"
550				}
551			end
552
553			def write
554				Command.reply { |reply|
555					reply.allowed_actions = [:prev]
556					reply.status = :canceled
557					reply.command << form
558				}.then { |iq|
559					raise "Action not allowed" unless iq.prev?
560
561					Activation.for(@customer, nil, @tel).then(&:write)
562				}
563			end
564		end
565	end
566
567	class BillPlan
568		def initialize(customer, tel, finish: Finish)
569			@customer = customer
570			@tel = tel
571			@finish = finish
572		end
573
574		def write
575			@customer.bill_plan(note: "Bill #{@tel} for first month").then do
576				updated_customer =
577					@customer.with_balance(@customer.balance - @customer.monthly_price)
578				@finish.new(updated_customer, @tel).write
579			end
580		end
581	end
582
583	class BuyNumber
584		def initialize(customer, tel)
585			@customer = customer
586			@tel = tel
587		end
588
589		def write
590			Command.reply { |reply|
591				reply.command << FormTemplate.render(
592					"registration/buy_number",
593					tel: @tel
594				)
595			}.then(&method(:parse)).then(&:write)
596		end
597
598	protected
599
600		def parse(iq)
601			Payment.for(
602				iq, @customer, @tel,
603				maybe_bill: ::Registration::Payment::JustCharge,
604				price: @tel.price
605			)
606		end
607	end
608
609	class Finish
610		def initialize(customer, tel)
611			@customer = customer
612			@tel = tel
613			@invites = InvitesRepo.new(DB, REDIS)
614		end
615
616		def write
617			return buy_number if @customer.balance < @tel.price
618
619			@tel.order(DB, @customer).then(
620				->(_) { customer_active_tel_purchased },
621				method(:number_purchase_error)
622			)
623		end
624
625	protected
626
627		def buy_number
628			BuyNumber.new(@customer, @tel).write.then do
629				reload_customer(@customer).then { |customer|
630					Finish.new(customer, @tel)
631				}.then(&:write)
632			end
633		end
634
635		def number_purchase_error(e)
636			Command.log.error "number_purchase_error", e
637			TEL_SELECTIONS.delete(@customer.jid).then {
638				TEL_SELECTIONS[@customer.jid]
639			}.then { |choose|
640				choose.choose_tel(
641					error: "The JMP number #{@tel} is no longer available."
642				)
643			}.then { |tel| Finish.new(@customer, tel).write }
644		end
645
646		def raise_setup_error(e)
647			Command.log.error "@customer.register! failed", e
648			Command.finish(
649				"There was an error setting up your number, " \
650				"please contact JMP support.",
651				type: :error
652			)
653		end
654
655		def put_default_fwd
656			Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel.tel, CustomerFwd.for(
657				uri: "xmpp:#{@customer.jid}",
658				voicemail_enabled: true
659			))
660		end
661
662		def use_referral_code
663			@invites.use_pending_group_code(@customer.customer_id).then do |credit_to|
664				next unless credit_to
665
666				Transaction.new(
667					customer_id: @customer.customer_id,
668					transaction_id: "referral_#{@customer.customer_id}_#{credit_to}",
669					amount: @customer.monthly_price,
670					note: "Referral Bonus",
671					bonus_eligible?: false
672				).insert
673			end
674		end
675
676		def customer_active_tel_purchased
677			@customer.register!(@tel.tel).catch(&method(:raise_setup_error)).then {
678				EMPromise.all([
679					TEL_SELECTIONS.delete(@customer.jid),
680					put_default_fwd,
681					use_referral_code,
682					@tel.charge(@customer)
683				])
684			}.then do
685				FinishOnboarding.for(@customer, @tel).then(&:write)
686			end
687		end
688	end
689
690	module FinishOnboarding
691		def self.for(customer, tel, db: LazyObject.new { DB })
692			jid = ProxiedJID.new(customer.jid).unproxied
693			if jid.domain == CONFIG[:onboarding_domain]
694				Snikket.for(customer, tel, db: db)
695			else
696				NotOnboarding.new(customer, tel)
697			end
698		end
699
700		class Snikket
701			def self.for(customer, tel, db:)
702				::Snikket::Repo.new(db: db).find_by_customer(customer).then do |is|
703					if is.empty?
704						new(customer, tel, db: db)
705					elsif is[0].bootstrap_token.empty?
706						# This is a need_dns one, try the launch again
707						new(customer, tel, db: db).launch(is[0].domain)
708					else
709						GetInvite.for(customer, is[0], tel, db: db)
710					end
711				end
712			end
713
714			def initialize(customer, tel, error: nil, old: nil, db:)
715				@customer = customer
716				@tel = tel
717				@error = error
718				@db = db
719				@old = old
720			end
721
722			ACTION_VAR = "http://jabber.org/protocol/commands#actions"
723
724			def form
725				FormTemplate.render(
726					"registration/snikket",
727					tel: @tel,
728					error: @error
729				)
730			end
731
732			def write
733				Command.reply { |reply|
734					reply.allowed_actions = [:next]
735					reply.command << form
736				}.then(&method(:next_step))
737			end
738
739			def next_step(iq)
740				subdomain = empty_nil(iq.form.field("subdomain")&.value)
741				domain = "#{subdomain}.snikket.chat"
742				if iq.form.field(ACTION_VAR)&.value == "custom_domain"
743					CustomDomain.new(@customer, @tel, old: @old).write
744				elsif @old && (!subdomain || domain == @old.domain)
745					GetInvite.for(@customer, @old, @tel, db: @db).then(&:write)
746				else
747					launch(domain)
748				end
749			end
750
751			def launch(domain)
752				IQ_MANAGER.write(::Snikket::Launch.new(
753					nil, CONFIG[:snikket_hosting_api], domain: domain
754				)).then { |launched|
755					save_instance_and_wait(domain, launched)
756				}.catch { |e|
757					next EMPromise.reject(e) unless e.respond_to?(:text)
758
759					Snikket.new(@customer, @tel, old: @old, error: e.text, db: @db).write
760				}
761			end
762
763			def save_instance_and_wait(domain, launched)
764				instance = ::Snikket::CustomerInstance.for(@customer, domain, launched)
765				repo = ::Snikket::Repo.new(db: @db)
766				(@old&.domain == domain ? EMPromise.resolve(nil) : repo.del(@old))
767					.then { repo.put(instance) }.then do
768						if launched.status == :needs_dns
769							NeedsDNS.new(@customer, instance, @tel, launched.records).write
770						else
771							GetInvite.for(@customer, instance, @tel, db: @db).then(&:write)
772						end
773					end
774			end
775
776			def empty_nil(s)
777				s.nil? || s.empty? ? nil : s
778			end
779
780			class NeedsDNS < Snikket
781				def initialize(customer, instance, tel, records, db: DB)
782					@customer = customer
783					@instance = instance
784					@tel = tel
785					@records = records
786					@db = db
787				end
788
789				def form
790					FormTemplate.render(
791						"registration/snikket_needs_dns",
792						records: @records
793					)
794				end
795
796				def write
797					Command.reply { |reply|
798						reply.allowed_actions = [:prev, :next]
799						reply.command << form
800					}.then do |iq|
801						if iq.prev?
802							CustomDomain.new(@customer, @tel, old: @instance).write
803						else
804							launch(@instance.domain)
805						end
806					end
807				end
808			end
809
810			class GetInvite
811				def self.for(customer, instance, tel, db: DB)
812					instance.fetch_invite.then do |xmpp_uri|
813						if xmpp_uri
814							GoToInvite.new(xmpp_uri)
815						else
816							new(customer, instance, tel, db: db)
817						end
818					end
819				end
820
821				def initialize(customer, instance, tel, db: DB)
822					@customer = customer
823					@instance = instance
824					@tel = tel
825					@db = db
826				end
827
828				def form
829					FormTemplate.render(
830						"registration/snikket_wait",
831						domain: @instance.domain
832					)
833				end
834
835				def write
836					Command.reply { |reply|
837						reply.allowed_actions = [:prev, :next]
838						reply.command << form
839					}.then do |iq|
840						if iq.prev?
841							Snikket.new(@customer, @tel, old: @instance, db: @db).write
842						else
843							GetInvite.for(@customer, @instance, @tel, db: @db).then(&:write)
844						end
845					end
846				end
847			end
848
849			class GoToInvite
850				def initialize(xmpp_uri)
851					@xmpp_uri = xmpp_uri
852				end
853
854				def write
855					Command.finish do |reply|
856						oob = OOB.find_or_create(reply.command)
857						oob.url = @xmpp_uri
858					end
859				end
860			end
861		end
862
863		class CustomDomain < Snikket
864			def initialize(customer, tel, old: nil, error: nil, db: DB)
865				@customer = customer
866				@tel = tel
867				@error = error
868				@old = old
869				@db = db
870			end
871
872			def form
873				FormTemplate.render(
874					"registration/snikket_custom",
875					tel: @tel,
876					error: @error
877				)
878			end
879
880			def write
881				Command.reply { |reply|
882					reply.allowed_actions = [:prev, :next]
883					reply.command << form
884				}.then do |iq|
885					if iq.prev?
886						Snikket.new(@customer, @tel, db: @db, old: @old).write
887					else
888						launch(empty_nil(iq.form.field("domain")&.value) || @old&.domain)
889					end
890				end
891			end
892		end
893
894		class NotOnboarding
895			def initialize(customer, tel)
896				@customer = customer
897				@tel = tel
898			end
899
900			def write
901				WelcomeMessage.new(@customer, @tel).welcome
902				Command.finish("Your JMP account has been activated as #{@tel}")
903			end
904		end
905	end
906end