registration.rb

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