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			cfg = sim_kind.cfg(customer.currency)
  85			log.debug(
  86				"DataOnly#for",
  87				cfg: cfg,
  88				customer_balance: customer.balance,
  89				customer_currency: customer.currency,
  90				cfg_price: cfg[:price]
  91			)
  92			unless cfg && customer.balance >= cfg[:price]
  93				return PayForSim.new(customer, sim_kind)
  94			end
  95
  96			new(customer, sim_kind)
  97		end
  98
  99		def initialize(customer, sim_kind)
 100			@customer = customer
 101			@sim_kind = sim_kind
 102		end
 103
 104		def write
 105			@sim_kind.klass.for(
 106				@customer,
 107				**@sim_kind.cfg(@customer.currency)
 108			).then { |order|
 109				# NOTE: cheogram will swallow any stanza
 110				# with type `completed`
 111				# `can_complete: false` is needed to prevent customer
 112				# from (possibly) permanently losing their eSIM QR code
 113				order.process(can_complete: false)
 114			}
 115		end
 116	end
 117
 118	class RegistrationType
 119		def self.for(customer, google_play_userid, product)
 120			if product.is_a?(SIMKind)
 121				return Registration::DataOnly.for(
 122					customer, product
 123				)
 124			end
 125
 126			Registration::FinishOrStartActivation.for(
 127				customer, google_play_userid, product
 128			)
 129		end
 130	end
 131
 132	def self.for(customer, google_play_userid, tel_selections)
 133		if (reg = customer.registered?)
 134			Registered.for(customer, reg.phone)
 135		else
 136			tel_selections[customer.jid].then(&:choose_tel_or_data).then do |product|
 137				reserve_and_continue(tel_selections, customer, product).then do
 138					RegistrationType.for(customer, google_play_userid, product)
 139				end
 140			end
 141		end
 142	end
 143
 144	def self.reserve_and_continue(tel_selections, customer, tel)
 145		tel.reserve(customer).catch do
 146			tel_selections.delete(customer.jid).then {
 147				tel_selections[customer.jid]
 148			}.then { |choose|
 149				choose.choose_tel_or_data(
 150					error: "The JMP number #{tel} is no longer available."
 151				)
 152			}.then { |n_tel| reserve_and_continue(tel_selections, customer, n_tel) }
 153		end
 154	end
 155
 156	class Registered
 157		def self.for(customer, tel)
 158			if customer.jid.onboarding?
 159				FinishOnboarding.for(customer, tel)
 160			else
 161				new(tel)
 162			end
 163		end
 164
 165		def initialize(tel)
 166			@tel = tel
 167		end
 168
 169		def write
 170			Command.finish("You are already registered with JMP number #{@tel}")
 171		end
 172	end
 173
 174	class FinishOrStartActivation
 175		def self.for(customer, google_play_userid, tel)
 176			if customer.active?
 177				Finish.new(customer, tel)
 178			elsif customer.balance >= CONFIG[:activation_amount_accept]
 179				BillPlan.new(customer, tel)
 180			else
 181				new(customer, google_play_userid, tel)
 182			end
 183		end
 184
 185		def initialize(customer, google_play_userid, tel)
 186			@customer = customer
 187			@tel = tel
 188			@google_play_userid = google_play_userid
 189		end
 190
 191		def write
 192			Command.reply { |reply|
 193				reply.allowed_actions = [:next]
 194				reply.note_type = :info
 195				reply.note_text = File.read("#{__dir__}/../fup.txt")
 196			}.then { Activation.for(@customer, @google_play_userid, @tel).write }
 197		end
 198	end
 199
 200	class Activation
 201		def self.for(customer, google_play_userid, tel)
 202			jid = ProxiedJID.new(customer.jid).unproxied
 203			if CONFIG[:approved_domains].key?(jid.domain.to_sym)
 204				Allow.for(customer, tel, jid)
 205			elsif google_play_userid
 206				GooglePlay.new(customer, google_play_userid, tel)
 207			else
 208				new(customer, tel)
 209			end
 210		end
 211
 212		def initialize(customer, tel)
 213			@customer = customer
 214			@tel = tel
 215		end
 216
 217		attr_reader :customer, :tel
 218
 219		def form
 220			FormTemplate.render("registration/activate", tel: tel)
 221		end
 222
 223		def write
 224			Command.reply { |reply|
 225				reply.allowed_actions = [:next]
 226				reply.command << form
 227			}.then(&method(:next_step))
 228		end
 229
 230		def next_step(iq)
 231			EMPromise.resolve(nil).then do
 232				plan = Plan.for_registration(iq.form.field("plan_name").value.to_s)
 233				@customer = @customer.with_plan(plan.name)
 234				Registration::Payment::InviteCode.new(
 235					@customer, @tel, finish: Finish, db: DB, redis: REDIS
 236				).parse(iq, force_save_plan: true)
 237					.catch_only(InvitesRepo::Invalid) do
 238						Payment.for(iq, @customer, @tel).then(&:write)
 239					end
 240			end
 241		end
 242
 243		class GooglePlay
 244			def initialize(customer, google_play_userid, tel)
 245				@customer = customer
 246				@google_play_userid = google_play_userid
 247				@tel = tel
 248				@invites = InvitesRepo.new(DB, REDIS)
 249				@parent_code_repo = ParentCodeRepo.new(redis: REDIS, db: DB)
 250			end
 251
 252			def used
 253				REDIS.sismember("google_play_userids", @google_play_userid)
 254			end
 255
 256			def form
 257				FormTemplate.render(
 258					"registration/google_play",
 259					tel: @tel
 260				)
 261			end
 262
 263			def write
 264				used.then do |u|
 265					next Activation.for(@customer, nil, @tel).write if u.to_s == "1"
 266
 267					Command.reply { |reply|
 268						reply.allowed_actions = [:next]
 269						reply.command << form
 270					}.then(&method(:activate)).then do
 271						Finish.new(@customer, @tel).write
 272					end
 273				end
 274			end
 275
 276			def activate(iq)
 277				plan = Plan.for_registration(iq.form.field("plan_name").value)
 278				code = iq.form.field("code")&.value
 279				EMPromise.all([
 280					@parent_code_repo.find(code),
 281					REDIS.sadd("google_play_userids", @google_play_userid)
 282				]).then { |(parent, _)|
 283					save_active_plan(plan, parent)
 284				}.then do
 285					use_referral_code(code)
 286				end
 287			end
 288
 289		protected
 290
 291			def save_bogus_transaction
 292				Transaction.new(
 293					customer_id: @customer.customer_id,
 294					transaction_id: "google_play_#{@customer.customer_id}",
 295					amount: 0,
 296					note: "Activated via Google Play",
 297					bonus_eligible?: false
 298				).insert
 299			end
 300
 301			def save_active_plan(plan, parent)
 302				@customer = @customer.with_plan(plan.name, parent_customer_id: parent)
 303				save_bogus_transaction.then do
 304					@customer.activate_plan_starting_now
 305				end
 306			end
 307
 308			def use_referral_code(code)
 309				EMPromise.resolve(nil).then {
 310					@invites.claim_code(@customer.customer_id, code) {
 311						@customer.extend_plan
 312					}
 313				}.catch_only(InvitesRepo::Invalid) do
 314					@invites.stash_code(@customer.customer_id, code)
 315				end
 316			end
 317		end
 318
 319		class Allow < Activation
 320			def self.for(customer, tel, jid)
 321				credit_to = CONFIG[:approved_domains][jid.domain.to_sym]
 322				new(customer, tel, credit_to)
 323			end
 324
 325			def initialize(customer, tel, credit_to)
 326				super(customer, tel)
 327				@credit_to = credit_to
 328			end
 329
 330			def form
 331				FormTemplate.render(
 332					"registration/allow",
 333					tel: tel,
 334					domain: customer.jid.domain
 335				)
 336			end
 337
 338			def next_step(iq)
 339				plan = Plan.for_registration(iq.form.field("plan_name").value.to_s)
 340				@customer = customer.with_plan(plan.name)
 341				EMPromise.resolve(nil).then { activate }.then do
 342					Finish.new(customer, tel).write
 343				end
 344			end
 345
 346		protected
 347
 348			def activate
 349				DB.transaction do
 350					if @credit_to
 351						InvitesRepo.new(DB, REDIS).create_claimed_code(
 352							@credit_to,
 353							customer.customer_id
 354						)
 355					end
 356					@customer.activate_plan_starting_now
 357				end
 358			end
 359		end
 360	end
 361
 362	module Payment
 363		def self.kinds
 364			@kinds ||= {}
 365		end
 366
 367		def self.for(
 368			iq, customer, product,
 369			finish: Finish, maybe_bill: MaybeBill,
 370			price: CONFIG[:activation_amount] + product.price
 371		)
 372			kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) {
 373				raise "Invalid activation method"
 374			}.call(
 375				customer, product, finish: finish, maybe_bill: maybe_bill, price: price
 376			)
 377		end
 378
 379		class CryptoPaymentMethod
 380			def crypto_addrs
 381				raise NotImplementedError, "Subclass must implement"
 382			end
 383
 384			def reg_form_name
 385				raise NotImplementedError, "Subclass must implement"
 386			end
 387
 388			def sell_prices
 389				raise NotImplementedError, "Subclass must implement"
 390			end
 391
 392			def initialize(
 393				customer, product,
 394				price: CONFIG[:activation_amount] + product.price, **
 395			)
 396				@customer = customer
 397				@customer_id = customer.customer_id
 398				@product = product
 399				@price = price
 400			end
 401
 402			def save
 403				return if product.is_a?(SIMKind)
 404
 405				TEL_SELECTIONS.set(@customer.jid, @product)
 406			end
 407
 408			attr_reader :customer_id, :product
 409
 410			def form(rate, addr)
 411				FormTemplate.render(
 412					reg_form_name,
 413					amount: @price / rate,
 414					addr: addr
 415				)
 416			end
 417
 418			def write
 419				EMPromise.all([addr_and_rate, save]).then do |((addr, rate), _)|
 420					Command.reply { |reply|
 421						reply.allowed_actions = [:prev]
 422						reply.status = :canceled
 423						reply.command << form(rate, addr)
 424					}.then(&method(:handle_possible_prev))
 425				end
 426			end
 427
 428		protected
 429
 430			def handle_possible_prev(iq)
 431				raise "Action not allowed" unless iq.prev?
 432
 433				handle_prev(@customer, nil, @product)
 434			end
 435
 436			def addr_and_rate
 437				EMPromise.all([
 438					crypto_addrs.then { |addrs|
 439						addrs.first || add_crypto_addr
 440					},
 441
 442					sell_prices.public_send(@customer.currency.to_s.downcase)
 443				])
 444			end
 445		end
 446
 447		class Bitcoin < CryptoPaymentMethod
 448			Payment.kinds[:bitcoin] = method(:new)
 449
 450			def reg_form_name
 451				"registration/btc"
 452			end
 453
 454			def sell_prices
 455				BTC_SELL_PRICES
 456			end
 457
 458			def crypto_addrs
 459				@customer.btc_addresses
 460			end
 461
 462			def add_crypto_addr
 463				@customer.add_btc_address
 464			end
 465		end
 466
 467		## Like Bitcoin
 468		class BCH < CryptoPaymentMethod
 469			Payment.kinds[:bch] = method(:new)
 470
 471			def reg_form_name
 472				"registration/bch"
 473			end
 474
 475			def sell_prices
 476				BCH_SELL_PRICES
 477			end
 478
 479			def crypto_addrs
 480				@customer.bch_addresses
 481			end
 482
 483			def add_crypto_addr
 484				@customer.add_bch_address
 485			end
 486		end
 487
 488		class MaybeBill
 489			def initialize(customer, tel, finish: Finish)
 490				@customer = customer
 491				@tel = tel
 492				@finish = finish
 493			end
 494
 495			def call
 496				reload_customer(@customer).then do |customer|
 497					if customer.balance >= CONFIG[:activation_amount_accept]
 498						next BillPlan.new(customer, @tel, finish: @finish)
 499					end
 500
 501					yield customer
 502				end
 503			end
 504		end
 505
 506		class JustCharge
 507			def initialize(customer, *, **)
 508				@customer = customer
 509			end
 510
 511			def call; end
 512		end
 513
 514		class CreditCard
 515			Payment.kinds[:credit_card] = method(:new)
 516
 517			def initialize(
 518				customer, product,
 519				finish: Finish, maybe_bill: MaybeBill,
 520				price: CONFIG[:activation_amount] + product.price
 521			)
 522				@customer = customer
 523				@product = product
 524				@finish = finish
 525				@maybe_bill = maybe_bill.new(customer, product, finish: finish)
 526				@price = price
 527			end
 528
 529			def oob(reply)
 530				oob = OOB.find_or_create(reply.command)
 531				oob.url = CONFIG[:credit_card_url].call(
 532					reply.to.stripped.to_s.gsub("\\", "%5C"),
 533					@customer.customer_id
 534				) + "&amount=#{@price.ceil}"
 535				oob.desc = "Pay by credit card, save, then next here to continue"
 536				oob
 537			end
 538
 539			def write
 540				Command.reply { |reply|
 541					reply.allowed_actions = [:next, :prev]
 542					toob = oob(reply)
 543					reply.note_type = :info
 544					reply.note_text = "#{toob.desc}: #{toob.url}"
 545				}.then do |iq|
 546					handle_prev(@customer, nil, @product) if iq.prev?
 547
 548					@maybe_bill.call { self }&.then(&:write)
 549				end
 550			end
 551		end
 552
 553		class InviteCode
 554			Payment.kinds[:code] = method(:new)
 555
 556			FIELDS = [{
 557				var: "code",
 558				type: "text-single",
 559				label: "Your referral code",
 560				required: true
 561			}].freeze
 562
 563			def initialize(
 564				customer, tel,
 565				error: nil, finish: Finish, db: DB, redis: REDIS, **
 566			)
 567				@customer = customer
 568				@tel = tel
 569				@error = error
 570				@finish = finish
 571				@invites_repo = InvitesRepo.new(db, redis)
 572				@parent_code_repo = ParentCodeRepo.new(db: db, redis: redis)
 573			end
 574
 575			def add_form(reply)
 576				form = reply.form
 577				form.type = :form
 578				form.title = "Enter Referral Code"
 579				form.instructions = @error if @error
 580				form.fields = FIELDS
 581			end
 582
 583			def write
 584				Command.reply { |reply|
 585					reply.allowed_actions = [:next, :prev]
 586					add_form(reply)
 587				}.then(&method(:parse)).catch_only(InvitesRepo::Invalid) { |e|
 588					invalid_code(e).write
 589				}
 590			end
 591
 592			def parse(iq, force_save_plan: false)
 593				return Activation.for(@customer, nil, @tel).then(&:write) if iq.prev?
 594
 595				verify(iq.form.field("code")&.value&.to_s, force_save_plan)
 596					.then(&:write)
 597			end
 598
 599		protected
 600
 601			def invalid_code(e)
 602				self.class.new(@customer, @tel, error: e.message, finish: @finish)
 603			end
 604
 605			def customer_id
 606				@customer.customer_id
 607			end
 608
 609			def verify(code, force_save_plan)
 610				@parent_code_repo.claim_code(@customer, code) {
 611					check_parent_balance
 612				}.catch_only(ParentCodeRepo::Invalid) {
 613					(@customer.save_plan! if force_save_plan).then do
 614						@invites_repo.claim_code(customer_id, code) {
 615							@customer.activate_plan_starting_now
 616						}.then { @finish.new(@customer, @tel) }
 617					end
 618				}
 619			end
 620
 621			def check_parent_balance
 622				MaybeBill.new(@customer, @tel, finish: @finish).call do
 623					msg = "Account balance not enough to cover the activation"
 624					invalid_code(RuntimeError.new(msg))
 625				end
 626			end
 627		end
 628
 629		class Mail
 630			Payment.kinds[:mail] = method(:new)
 631
 632			def initialize(
 633				customer, tel,
 634				price: CONFIG[:activation_amount] + tel.price, **
 635			)
 636				@customer = customer
 637				@tel = tel
 638				@price = price
 639			end
 640
 641			def form
 642				FormTemplate.render(
 643					"registration/mail",
 644					currency: @customer.currency,
 645					price: @price,
 646					**onboarding_extras
 647				)
 648			end
 649
 650			def onboarding_extras
 651				jid = ProxiedJID.new(@customer.jid).unproxied
 652				return {} unless jid.domain == CONFIG[:onboarding_domain]
 653
 654				{
 655					customer_id: @customer.customer_id,
 656					in_note: "Customer ID"
 657				}
 658			end
 659
 660			def write
 661				Command.reply { |reply|
 662					reply.allowed_actions = [:prev]
 663					reply.status = :canceled
 664					reply.command << form
 665				}.then { |iq|
 666					raise "Action not allowed" unless iq.prev?
 667
 668					Activation.for(@customer, nil, @tel).then(&:write)
 669				}
 670			end
 671		end
 672	end
 673
 674	class BillPlan
 675		def initialize(customer, tel, finish: Finish)
 676			@customer = customer
 677			@tel = tel
 678			@finish = finish
 679		end
 680
 681		def write
 682			@customer.bill_plan(note: "Bill #{@tel} for first month").then do
 683				updated_customer =
 684					@customer.with_balance(@customer.balance - @customer.monthly_price)
 685				@finish.new(updated_customer, @tel).write
 686			end
 687		end
 688	end
 689
 690	class BuyNumber
 691		def initialize(customer, tel)
 692			@customer = customer
 693			@tel = tel
 694		end
 695
 696		def instructions
 697			<<~I
 698				You've selected #{@tel} as your JMP number.
 699				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.
 700				(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.)
 701			I
 702		end
 703
 704		def write
 705			Command.reply { |reply|
 706				reply.command << FormTemplate.render(
 707					"registration/pay_without_code",
 708					product: @tel,
 709					instructions: instructions,
 710					title: "Purchase Number"
 711				)
 712			}.then(&method(:parse)).then(&:write)
 713		end
 714
 715	protected
 716
 717		def parse(iq)
 718			Payment.for(
 719				iq, @customer, @tel,
 720				maybe_bill: ::Registration::Payment::JustCharge,
 721				price: @tel.price
 722			)
 723		end
 724	end
 725
 726	class Finish
 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				->(_) { 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).then {
 755				TEL_SELECTIONS[@customer.jid]
 756			}.then { |choose|
 757				choose.choose_tel_or_data(
 758					error: "The JMP number #{@tel} is no longer available."
 759				)
 760			}.then { |tel| Finish.new(@customer, tel).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
 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, error: nil, old: nil, db:)
 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