~singpolyma/sgx-jmp

sgx-jmp/lib/low_balance.rb -rw-r--r-- 3.6 KiB
9558f369SavagePeanut show subaccounts in admin_info if any exist 16 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# frozen_string_literal: true

require_relative "expiring_lock"
require_relative "transaction"
require_relative "credit_card_sale"

class LowBalance
	def self.for(customer, transaction_amount=0)
		locked_if_no_services(customer).then do |locked|
			locked || ExpiringLock.new(
				"jmp_customer_low_balance-#{customer.billing_customer_id}",
				expiry: 60 * 60 * 24 * 7
			).with(-> { Locked.new }) do
				customer.billing_customer.then do |billing_customer|
					for_no_lock(billing_customer, transaction_amount)
				end
			end
		end
	end

	def self.locked_if_no_services(customer)
		return if customer.registered?

		DB.query_defer(
			"SELECT COUNT(*) AS c FROM sims WHERE customer_id=$1",
			[customer.customer_id]
		).then do |result|
			next if result.first["c"].to_i.positive?

			Locked.new
		end
	end

	def self.for_no_lock(customer, transaction_amount, auto: true)
		if auto && customer.auto_top_up_amount.positive?
			AutoTopUp.for(customer, transaction_amount)
		else
			customer.btc_addresses.then do |btc_addresses|
				new(customer, btc_addresses, transaction_amount)
			end
		end
	end

	def initialize(customer, btc_addresses, transaction_amount=0)
		@customer = customer
		@btc_addresses = btc_addresses
		@transaction_amount = transaction_amount
	end

	def can_top_up?
		false
	end

	def notify!
		m = Blather::Stanza::Message.new
		m.from = CONFIG[:notify_from]
		m.body =
			"Your balance of $#{'%.4f' % @customer.balance} is low." \
			"#{pending_cost_for_notification}" \
			"#{btc_addresses_for_notification}"
		@customer.stanza_to(m)
		EMPromise.resolve(0)
	end

	def pending_cost_for_notification
		return unless @transaction_amount&.positive?
		return unless @transaction_amount > @customer.balance

		"\nYou need an additional " \
		"$#{'%.2f' % (@transaction_amount - @customer.balance)} "\
		"to complete this transaction."
	end

	def btc_addresses_for_notification
		return if @btc_addresses.empty?

		"\nYou can buy credit by sending any amount of Bitcoin to one of " \
		"these addresses:\n#{@btc_addresses.join("\n")}"
	end

	class AutoTopUp
		def self.for(customer, target=0)
			customer.payment_methods.then(&:default_payment_method).then do |method|
				blocked?(method).then do |block|
					next AutoTopUp.new(customer, method, target) if block.zero?

					log.info("#{customer.customer_id} auto top up blocked")
					LowBalance.for_no_lock(customer, target, auto: false)
				end
			end
		end

		def self.blocked?(method)
			return EMPromise.resolve(1) if method.nil?

			REDIS.exists(
				"jmp_auto_top_up_block-#{method&.unique_number_identifier}"
			)
		end

		def initialize(customer, method=nil, target=0, margin: 10)
			@customer = customer
			@method = method
			@target = target
			@margin = margin
			@message = Blather::Stanza::Message.new
			@message.from = CONFIG[:notify_from]
		end

		def top_up_amount
			[
				((@target + @margin) - @customer.balance).round(2),
				@customer.auto_top_up_amount
			].max
		end

		def can_top_up?
			true
		end

		def sale
			CreditCardSale.create(@customer, amount: top_up_amount)
		end

		def failed(e)
			@method && REDIS.setex(
				"jmp_auto_top_up_block-#{@method.unique_number_identifier}",
				60 * 60 * 24 * 30,
				Time.now
			)
			@message.body =
				"Automatic top-up transaction for " \
				"$#{'%.2f' % top_up_amount} failed: #{e.message}"
			0
		end

		def notify!
			sale.then { |tx|
				@message.body =
					"Automatic top-up has charged your default " \
					"payment method and added #{tx} to your balance."
				tx.total
			}.catch(&method(:failed)).then { |amount|
				@customer.stanza_to(@message)
				amount
			}
		end
	end

	class Locked
		def notify!
			EMPromise.resolve(0)
		end
	end
end