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