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