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 "./proxied_jid"
15require_relative "./tel_selections"
16
17class Registration
18 def self.for(customer, tel_selections)
19 if (reg = customer.registered?)
20 Registered.new(reg.phone)
21 else
22 tel_selections[customer.jid].then(&:choose_tel).then do |tel|
23 BandwidthTnReservationRepo.new.ensure(customer, tel)
24 FinishOrStartActivation.for(customer, tel)
25 end
26 end
27 end
28
29 class Registered
30 def initialize(tel)
31 @tel = tel
32 end
33
34 def write
35 Command.finish("You are already registered with JMP number #{@tel}")
36 end
37 end
38
39 class FinishOrStartActivation
40 def self.for(customer, tel)
41 if customer.active?
42 Finish.new(customer, tel)
43 elsif customer.balance >= CONFIG[:activation_amount_accept]
44 BillPlan.new(customer, tel)
45 else
46 new(customer, tel)
47 end
48 end
49
50 def initialize(customer, tel)
51 @customer = customer
52 @tel = tel
53 end
54
55 def write
56 Command.reply { |reply|
57 reply.allowed_actions = [:next]
58 reply.note_type = :info
59 reply.note_text = File.read("#{__dir__}/../fup.txt")
60 }.then { Activation.for(@customer, @tel).write }
61 end
62 end
63
64 class Activation
65 def self.for(customer, tel)
66 jid = ProxiedJID.new(customer.jid).unproxied
67 if CONFIG[:approved_domains].key?(jid.domain.to_sym)
68 Allow.for(customer, tel, jid)
69 else
70 new(customer, tel)
71 end
72 end
73
74 def initialize(customer, tel)
75 @customer = customer
76 @tel = tel
77 end
78
79 attr_reader :customer, :tel
80
81 def form(center)
82 FormTemplate.render(
83 "registration/activate",
84 tel: tel,
85 rate_center: center
86 )
87 end
88
89 def write
90 rate_center.then { |center|
91 Command.reply do |reply|
92 reply.allowed_actions = [:next]
93 reply.command << form(center)
94 end
95 }.then(&method(:next_step))
96 end
97
98 def next_step(iq)
99 EMPromise.resolve(nil).then {
100 Payment.for(iq, customer, tel)
101 }.then(&:write)
102 end
103
104 protected
105
106 def rate_center
107 EM.promise_fiber {
108 center = BandwidthIris::Tn.get(tel).get_rate_center
109 "#{center[:rate_center]}, #{center[:state]}"
110 }.catch { nil }
111 end
112
113 class Allow < Activation
114 def self.for(customer, tel, jid)
115 credit_to = CONFIG[:approved_domains][jid.domain.to_sym]
116 new(customer, tel, credit_to)
117 end
118
119 def initialize(customer, tel, credit_to)
120 super(customer, tel)
121 @credit_to = credit_to
122 end
123
124 def form(center)
125 FormTemplate.render(
126 "registration/allow",
127 tel: tel,
128 rate_center: center,
129 domain: customer.jid.domain
130 )
131 end
132
133 def next_step(iq)
134 plan_name = iq.form.field("plan_name").value.to_s
135 @customer = customer.with_plan(plan_name)
136 EMPromise.resolve(nil).then { activate }.then do
137 Finish.new(customer, tel).write
138 end
139 end
140
141 protected
142
143 def activate
144 DB.transaction do
145 if @credit_to
146 DB.exec(<<~SQL, [@credit_to, customer.customer_id])
147 INSERT INTO invites (creator_id, used_by_id, used_at)
148 VALUES ($1, $2, LOCALTIMESTAMP)
149 SQL
150 end
151 @customer.activate_plan_starting_now
152 end
153 end
154 end
155 end
156
157 module Payment
158 def self.kinds
159 @kinds ||= {}
160 end
161
162 def self.for(iq, customer, tel, final_message: nil, finish: Finish)
163 plan_name = iq.form.field("plan_name").value.to_s
164 customer = customer.with_plan(plan_name)
165 customer.save_plan!.then do
166 kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) {
167 raise "Invalid activation method"
168 }.call(customer, tel, final_message: final_message, finish: finish)
169 end
170 end
171
172 class Bitcoin
173 Payment.kinds[:bitcoin] = method(:new)
174
175 THIRTY_DAYS = 60 * 60 * 24 * 30
176
177 def initialize(customer, tel, final_message: nil, **)
178 @customer = customer
179 @customer_id = customer.customer_id
180 @tel = tel
181 @final_message = final_message
182 end
183
184 attr_reader :customer_id, :tel
185
186 def save
187 REDIS.setex("pending_tel_for-#{@customer.jid}", THIRTY_DAYS, tel)
188 end
189
190 def note_text(rate, addr)
191 amount = CONFIG[:activation_amount] / rate
192 <<~NOTE
193 Activate your account by sending at least #{'%.6f' % amount} BTC to
194 #{addr}
195
196 You will receive a notification when your payment is complete.
197 NOTE
198 end
199
200 def write
201 EMPromise.all([addr_and_rate, save]).then do |((addr, rate), _)|
202 Command.reply { |reply|
203 reply.allowed_actions = [:prev]
204 reply.status = :canceled
205 reply.note_type = :info
206 reply.note_text = note_text(rate, addr) + @final_message.to_s
207 }.then(&method(:handle_possible_prev))
208 end
209 end
210
211 protected
212
213 def handle_possible_prev(iq)
214 raise "Action not allowed" unless iq.prev?
215
216 Activation.for(@customer, @tel).then(&:write)
217 end
218
219 def addr_and_rate
220 EMPromise.all([
221 @customer.btc_addresses.then { |addrs|
222 addrs.first || @customer.add_btc_address
223 },
224 BTC_SELL_PRICES.public_send(@customer.currency.to_s.downcase)
225 ])
226 end
227 end
228
229 class CreditCard
230 Payment.kinds[:credit_card] = ->(*args, **kw) { self.for(*args, **kw) }
231
232 def self.for(customer, tel, finish: Finish, **)
233 customer.payment_methods.then do |payment_methods|
234 if (method = payment_methods.default_payment_method)
235 Activate.new(customer, method, tel, finish: finish)
236 else
237 new(customer, tel, finish: finish)
238 end
239 end
240 end
241
242 def initialize(customer, tel, finish: Finish)
243 @customer = customer
244 @tel = tel
245 @finish = finish
246 end
247
248 def oob(reply)
249 oob = OOB.find_or_create(reply.command)
250 oob.url = CONFIG[:credit_card_url].call(
251 reply.to.stripped.to_s.gsub("\\", "%5C"),
252 @customer.customer_id
253 )
254 oob.desc = "Add credit card, save, then next here to continue"
255 oob
256 end
257
258 def write
259 Command.reply { |reply|
260 reply.allowed_actions = [:next, :prev]
261 toob = oob(reply)
262 reply.note_type = :info
263 reply.note_text = "#{toob.desc}: #{toob.url}"
264 }.then do |iq|
265 next Activation.for(@customer, @tel).then(&:write) if iq.prev?
266
267 CreditCard.for(@customer, @tel, finish: @finish).then(&:write)
268 end
269 end
270
271 class Activate
272 def initialize(customer, payment_method, tel, finish: Finish)
273 @customer = customer
274 @payment_method = payment_method
275 @tel = tel
276 @finish = finish
277 end
278
279 def write
280 CreditCardSale.create(
281 @customer,
282 amount: CONFIG[:activation_amount],
283 payment_method: @payment_method
284 ).then(
285 ->(_) { sold },
286 ->(_) { declined }
287 )
288 end
289
290 protected
291
292 def sold
293 BillPlan.new(@customer, @tel, finish: @finish).write
294 end
295
296 DECLINE_MESSAGE =
297 "Your bank declined the transaction. " \
298 "Often this happens when a person's credit card " \
299 "is a US card that does not support international " \
300 "transactions, as JMP is not based in the USA, though " \
301 "we do support transactions in USD.\n\n" \
302 "If you were trying a prepaid card, you may wish to use "\
303 "Privacy.com instead, as they do support international " \
304 "transactions.\n\n " \
305 "You may add another card"
306
307 def decline_oob(reply)
308 oob = OOB.find_or_create(reply.command)
309 oob.url = CONFIG[:credit_card_url].call(
310 reply.to.stripped.to_s.gsub("\\", "%5C"),
311 @customer.customer_id
312 )
313 oob.desc = DECLINE_MESSAGE
314 oob
315 end
316
317 def declined
318 Command.reply { |reply|
319 reply_oob = decline_oob(reply)
320 reply.allowed_actions = [:next]
321 reply.note_type = :error
322 reply.note_text = "#{reply_oob.desc}: #{reply_oob.url}"
323 }.then do
324 CreditCard.for(@customer, @tel, finish: @finish).then(&:write)
325 end
326 end
327 end
328 end
329
330 class InviteCode
331 Payment.kinds[:code] = method(:new)
332
333 FIELDS = [{
334 var: "code",
335 type: "text-single",
336 label: "Your referral code",
337 required: true
338 }].freeze
339
340 def initialize(customer, tel, error: nil, **)
341 @customer = customer
342 @tel = tel
343 @error = error
344 end
345
346 def add_form(reply)
347 form = reply.form
348 form.type = :form
349 form.title = "Enter Referral Code"
350 form.instructions = @error if @error
351 form.fields = FIELDS
352 end
353
354 def write
355 Command.reply { |reply|
356 reply.allowed_actions = [:next, :prev]
357 add_form(reply)
358 }.then(&method(:parse))
359 end
360
361 def parse(iq)
362 return Activation.for(@customer, @tel).then(&:write) if iq.prev?
363
364 guard_too_many_tries.then {
365 verify(iq.form.field("code")&.value&.to_s)
366 }.then {
367 Finish.new(@customer, @tel)
368 }.catch_only(InvitesRepo::Invalid, &method(:invalid_code)).then(&:write)
369 end
370
371 protected
372
373 def guard_too_many_tries
374 REDIS.get("jmp_invite_tries-#{customer_id}").then do |t|
375 raise InvitesRepo::Invalid, "Too many wrong attempts" if t.to_i > 10
376 end
377 end
378
379 def invalid_code(e)
380 EMPromise.all([
381 REDIS.incr("jmp_invite_tries-#{customer_id}").then do
382 REDIS.expire("jmp_invite_tries-#{customer_id}", 60 * 60)
383 end,
384 InviteCode.new(@customer, @tel, error: e.message)
385 ]).then(&:last)
386 end
387
388 def customer_id
389 @customer.customer_id
390 end
391
392 def verify(code)
393 InvitesRepo.new(DB).claim_code(customer_id, code) do
394 @customer.activate_plan_starting_now
395 end
396 end
397 end
398
399 class Mail
400 Payment.kinds[:mail] = method(:new)
401
402 def initialize(customer, tel, final_message: nil, **)
403 @customer = customer
404 @tel = tel
405 @final_message = final_message
406 end
407
408 def form
409 FormTemplate.render(
410 "registration/mail",
411 currency: @customer.currency,
412 final_message: @final_message
413 )
414 end
415
416 def write
417 Command.reply { |reply|
418 reply.allowed_actions = [:prev]
419 reply.status = :canceled
420 reply.command << form
421 }.then { |iq|
422 raise "Action not allowed" unless iq.prev?
423
424 Activation.for(@customer, @tel).then(&:write)
425 }
426 end
427 end
428 end
429
430 class BillPlan
431 def initialize(customer, tel, finish: Finish)
432 @customer = customer
433 @tel = tel
434 @finish = finish
435 end
436
437 def write
438 @customer.bill_plan(note: "Bill #{@tel} for first month").then do
439 @finish.new(@customer, @tel).write
440 end
441 end
442 end
443
444 class Finish
445 def initialize(customer, tel)
446 @customer = customer
447 @tel = tel
448 end
449
450 def write
451 BandwidthTnReservationRepo.new.get(@customer, @tel).then do |rid|
452 BandwidthTNOrder.create(
453 @tel,
454 customer_order_id: @customer.customer_id,
455 reservation_id: rid
456 ).then(&:poll).then(
457 ->(_) { customer_active_tel_purchased },
458 method(:number_purchase_error)
459 )
460 end
461 end
462
463 protected
464
465 def number_purchase_error(e)
466 Command.log.error "number_purchase_error", e
467 TEL_SELECTIONS.delete(@customer.jid).then {
468 TelSelections::ChooseTel.new.choose_tel(
469 error: "The JMP number #{@tel} is no longer available."
470 )
471 }.then { |tel| Finish.new(@customer, tel).write }
472 end
473
474 def raise_setup_error(e)
475 Command.log.error "@customer.register! failed", e
476 Command.finish(
477 "There was an error setting up your number, " \
478 "please contact JMP support.",
479 type: :error
480 )
481 end
482
483 def customer_active_tel_purchased
484 @customer.register!(@tel).catch(&method(:raise_setup_error)).then {
485 EMPromise.all([
486 REDIS.del("pending_tel_for-#{@customer.jid}"),
487 Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel, CustomerFwd.for(
488 uri: "xmpp:#{@customer.jid}", voicemail_enabled: true
489 ))
490 ])
491 }.then do
492 Command.finish("Your JMP account has been activated as #{@tel}")
493 end
494 end
495 end
496end