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