~singpolyma/sgx-jmp

ref: 1a2640d6f9f1f1ea8cbbfc0c7b4c05e57b3b242e sgx-jmp/lib/low_balance.rb -rw-r--r-- 3.5 KiB
1a2640d6Osakpolor Obaseki Add low balance/auto top up with target amount 2 months 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
# frozen_string_literal: true

require_relative "expiring_lock"
require_relative "transaction"

class LowBalance
	def self.for(customer, transaction_amount=0)
		return Locked.new unless customer.registered?

		ExpiringLock.new(
			"jmp_customer_low_balance-#{customer.billing_customer_id}",
			expiry: 60 * 60 * 24 * 7
		).with(-> { Locked.new }) do
			customer.billing_customer.then do |customer|
				self.for_no_lock(customer, transaction_amount)
			end
		end
	end

	def self.for_no_lock(customer, transaction_amount=0, auto: true)
		if auto && (customer.auto_top_up_amount.positive? || transaction_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 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? && @transaction_amount > @customer.balance)

		"\nYou tried to perform an activity that cost #{@transaction_amount}"\
		"You need an additional #{'%.4f' % (@transaction_amount - @customer.balance)} "\
		"to perform this activity."
	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()
			expected_balance = @customer.balance + @customer.auto_top_up_amount # @cutomer.auto_top_up is the adjusted auto_to_up
			if expected_balance < @target
				deficit = @target - expected_balance
				@customer.auto_top_up_amount + deficit + @margin
			else
				@customer.auto_top_up_amount
			end
		end

		def sale
			Transaction.sale(
				@customer,
				amount: top_up_amount()
			).then do |tx|
				tx.insert.then { tx }
			end
		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 " \
				"$#{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