~singpolyma/sgx-jmp

ref: cc8787a765b4bd29eecefca16702c5c779fd25c2 sgx-jmp/lib/low_balance.rb -rw-r--r-- 3.2 KiB
cc8787a7Stephen Paul Weber Fix linter, integrate patch feedback 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
# 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 |billing_customer|
				for_no_lock(billing_customer, transaction_amount)
			end
		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 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

		"You 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,
				@customer.auto_top_up_amount
			].max
		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