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