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 Transaction.sale(
279 @customer,
280 amount: CONFIG[:activation_amount],
281 payment_method: @payment_method
282 ).then(
283 method(:sold),
284 ->(_) { declined }
285 )
286 end
287
288 protected
289
290 def sold(tx)
291 tx.insert.then do
292 BillPlan.new(@customer, @tel, finish: @finish).write
293 end
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 invite 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 Invite 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 BandwidthTNOrder.create(
452 @tel,
453 customer_order_id: @customer.customer_id
454 ).then(&:poll).then(
455 ->(_) { customer_active_tel_purchased },
456 ->(_) { number_purchase_error }
457 )
458 end
459
460 protected
461
462 def number_purchase_error
463 TEL_SELECTIONS.delete(@customer.jid).then {
464 TelSelections::ChooseTel.new.choose_tel(
465 error: "The JMP number #{@tel} is no longer available."
466 )
467 }.then { |tel| Finish.new(@customer, tel).write }
468 end
469
470 def raise_setup_error(e)
471 Command.log.error "@customer.register! failed", e
472 Command.finish(
473 "There was an error setting up your number, " \
474 "please contact JMP support.",
475 type: :error
476 )
477 end
478
479 def customer_active_tel_purchased
480 @customer.register!(@tel).catch(&method(:raise_setup_error)).then {
481 EMPromise.all([
482 REDIS.del("pending_tel_for-#{@customer.jid}"),
483 Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel, CustomerFwd.for(
484 uri: "xmpp:#{@customer.jid}", voicemail_enabled: true
485 ))
486 ])
487 }.then do
488 Command.finish("Your JMP account has been activated as #{@tel}")
489 end
490 end
491 end
492end