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(customer, tel)
 722			@customer = customer
 723			@tel = tel
 724			@invites = InvitesRepo.new(DB, REDIS)
 725		end
 726
 727		def write
 728			return buy_number if @customer.balance < @tel.price
 729
 730			@tel.order(DB, @customer).then(
 731				method(:customer_active_tel_purchased),
 732				method(:number_purchase_error)
 733			)
 734		end
 735
 736	protected
 737
 738		def buy_number
 739			BuyNumber.new(@customer, @tel).write.then do
 740				reload_customer(@customer).then { |customer|
 741					Finish.new(customer, @tel)
 742				}.then(&:write)
 743			end
 744		end
 745
 746		def number_purchase_error(e)
 747			Command.log.error "number_purchase_error", e
 748			TEL_SELECTIONS.delete(@customer.jid)
 749				.then { TEL_SELECTIONS[@customer] }
 750				.then { |c|
 751					c.choose_tel_or_data(
 752						error: TN_UNAVAILABLE % @tel,
 753						allow_data_only: false
 754					)
 755				}
 756				.then { |p| Finish.new(@customer, p) }
 757				.then(&:write)
 758		end
 759
 760		def raise_setup_error(e)
 761			Command.log.error "@customer.register! failed", e
 762			Command.finish(
 763				"There was an error setting up your number, " \
 764				"please contact JMP support.",
 765				type: :error
 766			)
 767		end
 768
 769		def put_default_fwd
 770			Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel.tel, CustomerFwd.for(
 771				uri: "xmpp:#{@customer.jid}",
 772				voicemail_enabled: true
 773			))
 774		end
 775
 776		def use_referral_code
 777			@invites.use_pending_group_code(@customer.customer_id).then do |credit_to|
 778				next unless credit_to
 779
 780				Transaction.new(
 781					customer_id: @customer.customer_id,
 782					transaction_id: "referral_#{@customer.customer_id}_#{credit_to}",
 783					amount: @customer.monthly_price,
 784					note: "Referral Bonus",
 785					bonus_eligible?: false
 786				).insert
 787			end
 788		end
 789
 790		def customer_active_tel_purchased(customer)
 791			customer.register!(@tel.tel).catch(&method(:raise_setup_error)).then {
 792				EMPromise.all([
 793					TEL_SELECTIONS.delete(customer.jid),
 794					put_default_fwd,
 795					use_referral_code,
 796					@tel.charge(customer)
 797				])
 798			}.then do
 799				FinishOnboarding.for(customer, @tel).then(&:write)
 800			end
 801		end
 802	end
 803
 804	module FinishOnboarding
 805		def self.for(customer, tel, db: LazyObject.new { DB })
 806			jid = ProxiedJID.new(customer.jid).unproxied
 807			if jid.domain == CONFIG[:onboarding_domain]
 808				Snikket.for(customer, tel, db: db)
 809			else
 810				NotOnboarding.new(customer, tel)
 811			end
 812		end
 813
 814		class Snikket
 815			def self.for(customer, tel, db:)
 816				::Snikket::Repo.new(db: db).find_by_customer(customer).then do |is|
 817					if is.empty?
 818						new(customer, tel, db: db)
 819					elsif is[0].bootstrap_token.empty?
 820						# This is a need_dns one, try the launch again
 821						new(customer, tel, db: db).launch(is[0].domain)
 822					else
 823						GetInvite.for(customer, is[0], tel, db: db)
 824					end
 825				end
 826			end
 827
 828			def initialize(customer, tel, db:, error: nil, old: nil)
 829				@customer = customer
 830				@tel = tel
 831				@error = error
 832				@db = db
 833				@old = old
 834			end
 835
 836			ACTION_VAR = "http://jabber.org/protocol/commands#actions"
 837
 838			def form
 839				FormTemplate.render(
 840					"registration/snikket",
 841					tel: @tel,
 842					error: @error
 843				)
 844			end
 845
 846			def write
 847				Command.reply { |reply|
 848					reply.allowed_actions = [:next]
 849					reply.command << form
 850				}.then(&method(:next_step))
 851			end
 852
 853			def next_step(iq)
 854				subdomain = empty_nil(iq.form.field("subdomain")&.value)
 855				domain = "#{subdomain}.snikket.chat"
 856				if iq.form.field(ACTION_VAR)&.value == "custom_domain"
 857					CustomDomain.new(@customer, @tel, old: @old).write
 858				elsif @old && (!subdomain || domain == @old.domain)
 859					GetInvite.for(@customer, @old, @tel, db: @db).then(&:write)
 860				else
 861					launch(domain)
 862				end
 863			end
 864
 865			def launch(domain)
 866				IQ_MANAGER.write(::Snikket::Launch.new(
 867					nil, CONFIG[:snikket_hosting_api], domain: domain
 868				)).then { |launched|
 869					save_instance_and_wait(domain, launched)
 870				}.catch { |e|
 871					next EMPromise.reject(e) unless e.respond_to?(:text)
 872
 873					Snikket.new(@customer, @tel, old: @old, error: e.text, db: @db).write
 874				}
 875			end
 876
 877			def save_instance_and_wait(domain, launched)
 878				instance = ::Snikket::CustomerInstance.for(@customer, domain, launched)
 879				repo = ::Snikket::Repo.new(db: @db)
 880				(@old&.domain == domain ? EMPromise.resolve(nil) : repo.del(@old))
 881					.then { repo.put(instance) }.then do
 882						if launched.status == :needs_dns
 883							NeedsDNS.new(@customer, instance, @tel, launched.records).write
 884						else
 885							GetInvite.for(@customer, instance, @tel, db: @db).then(&:write)
 886						end
 887					end
 888			end
 889
 890			def empty_nil(s)
 891				s.nil? || s.empty? ? nil : s
 892			end
 893
 894			class NeedsDNS < Snikket
 895				def initialize(customer, instance, tel, records, db: DB)
 896					@customer = customer
 897					@instance = instance
 898					@tel = tel
 899					@records = records
 900					@db = db
 901				end
 902
 903				def form
 904					FormTemplate.render(
 905						"registration/snikket_needs_dns",
 906						records: @records
 907					)
 908				end
 909
 910				def write
 911					Command.reply { |reply|
 912						reply.allowed_actions = [:prev, :next]
 913						reply.command << form
 914					}.then do |iq|
 915						if iq.prev?
 916							CustomDomain.new(@customer, @tel, old: @instance).write
 917						else
 918							launch(@instance.domain)
 919						end
 920					end
 921				end
 922			end
 923
 924			class GetInvite
 925				def self.for(customer, instance, tel, db: DB)
 926					instance.fetch_invite.then do |xmpp_uri|
 927						if xmpp_uri
 928							GoToInvite.new(xmpp_uri)
 929						else
 930							new(customer, instance, tel, db: db)
 931						end
 932					end
 933				end
 934
 935				def initialize(customer, instance, tel, db: DB)
 936					@customer = customer
 937					@instance = instance
 938					@tel = tel
 939					@db = db
 940				end
 941
 942				def form
 943					FormTemplate.render(
 944						"registration/snikket_wait",
 945						domain: @instance.domain
 946					)
 947				end
 948
 949				def write
 950					Command.reply { |reply|
 951						reply.allowed_actions = [:prev, :next]
 952						reply.command << form
 953					}.then do |iq|
 954						if iq.prev?
 955							Snikket.new(@customer, @tel, old: @instance, db: @db).write
 956						else
 957							GetInvite.for(@customer, @instance, @tel, db: @db).then(&:write)
 958						end
 959					end
 960				end
 961			end
 962
 963			class GoToInvite
 964				def initialize(xmpp_uri)
 965					@xmpp_uri = xmpp_uri
 966				end
 967
 968				def write
 969					Command.finish do |reply|
 970						oob = OOB.find_or_create(reply.command)
 971						oob.url = @xmpp_uri
 972					end
 973				end
 974			end
 975		end
 976
 977		class CustomDomain < Snikket
 978			def initialize(customer, tel, old: nil, error: nil, db: DB)
 979				@customer = customer
 980				@tel = tel
 981				@error = error
 982				@old = old
 983				@db = db
 984			end
 985
 986			def form
 987				FormTemplate.render(
 988					"registration/snikket_custom",
 989					tel: @tel,
 990					error: @error
 991				)
 992			end
 993
 994			def write
 995				Command.reply { |reply|
 996					reply.allowed_actions = [:prev, :next]
 997					reply.command << form
 998				}.then do |iq|
 999					if iq.prev?
1000						Snikket.new(@customer, @tel, db: @db, old: @old).write
1001					else
1002						launch(empty_nil(iq.form.field("domain")&.value) || @old&.domain)
1003					end
1004				end
1005			end
1006		end
1007
1008		class NotOnboarding
1009			def initialize(customer, tel)
1010				@customer = customer
1011				@tel = tel
1012			end
1013
1014			def write
1015				WelcomeMessage.new(@customer, @tel).welcome
1016				Command.finish("Your JMP account has been activated as #{@tel}")
1017			end
1018		end
1019	end
1020
1021	def self.public_onboarding_invite
1022		Command.finish { |reply|
1023			reply.allowed_actions = [:prev]
1024			oob = OOB.find_or_create(reply.command)
1025			oob.url = CONFIG[:public_onboarding_url]
1026			oob.desc = "Get your XMPP account"
1027		}
1028	end
1029end