~singpolyma/sgx-jmp

0ef60a260a81f3554c8d4078efc485b33a6f6673 — Stephen Paul Weber 1 year, 8 months ago d8cb419
auto_top_up_amount and monthly_overage_limit from CustomerRepo

Move load of auto_top_up_amount to the repo, and load monthly_overage_limit in
the same operation.
M lib/customer.rb => lib/customer.rb +2 -1
@@ 22,7 22,8 @@ class Customer
	attr_reader :customer_id, :balance, :jid, :tndetails

	def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
	               :currency, :merchant_account, :plan_name, :auto_top_up_amount
	               :currency, :merchant_account, :plan_name,
	               :auto_top_up_amount, :monthly_overage_limit
	def_delegators :@sgx, :register!, :registered?, :set_ogm_url,
	               :fwd, :transcription_enabled
	def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage

M lib/customer_info.rb => lib/customer_info.rb +7 -13
@@ 1,6 1,7 @@
# frozen_string_literal: true

require "bigdecimal"
require "forwardable"
require "relative_time"
require "value_semantics/monkey_patched"
require_relative "proxied_jid"


@@ 8,17 9,15 @@ require_relative "customer_plan"
require_relative "form_template"

class PlanInfo
	extend Forwardable

	def_delegators :plan, :expires_at, :auto_top_up_amount

	def self.for(plan)
		return EMPromise.resolve(NoPlan.new) unless plan&.plan_name

		EMPromise.all([
			plan.activation_date,
			plan.auto_top_up_amount
		]).then do |adate, atua|
			new(
				plan: plan, start_date: adate,
				auto_top_up_amount: atua
			)
		plan.activation_date.then do |adate|
			new(plan: plan, start_date: adate)
		end
	end



@@ 39,11 38,6 @@ class PlanInfo
	value_semantics do
		plan CustomerPlan
		start_date Time
		auto_top_up_amount Integer
	end

	def expires_at
		plan.expires_at
	end

	def template

M lib/customer_plan.rb => lib/customer_plan.rb +15 -6
@@ 3,19 3,32 @@
require "forwardable"

require_relative "em"
require_relative "plan"

class CustomerPlan
	extend Forwardable

	attr_reader :expires_at
	attr_reader :expires_at, :auto_top_up_amount, :monthly_overage_limit

	def_delegator :@plan, :name, :plan_name
	def_delegators :@plan, :currency, :merchant_account, :monthly_price

	def initialize(customer_id, plan: nil, expires_at: Time.now)
	def self.for(customer_id, plan_name: nil, **kwargs)
		new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
	end

	def initialize(
		customer_id,
		plan: nil,
		expires_at: Time.now,
		auto_top_up_amount: 0,
		monthly_overage_limit: 0
	)
		@customer_id = customer_id
		@plan = plan || OpenStruct.new
		@expires_at = expires_at
		@auto_top_up_amount = auto_top_up_amount
		@monthly_overage_limit = monthly_overage_limit
	end

	def active?


@@ 30,10 43,6 @@ class CustomerPlan
		)
	end

	def auto_top_up_amount
		REDIS.get("jmp_customer_auto_top_up_amount-#{@customer_id}").then(&:to_i)
	end

	def bill_plan
		EM.promise_fiber do
			DB.transaction do

M lib/customer_repo.rb => lib/customer_repo.rb +33 -18
@@ 65,10 65,8 @@ class CustomerRepo
		@bandwidth_tn_repo.put_lidb_name(customer.registered?.phone, lidb_name)
	end

	def put_transcription_enabled(customer, transcription_enabled)
		@sgx_repo.put_transcription_enabled(
			customer.customer_id, transcription_enabled
		)
	def put_transcription_enabled(customer, enabled)
		@sgx_repo.put_transcription_enabled(customer.customer_id, enabled)
	end

	def put_fwd(customer, customer_fwd)


@@ 82,14 80,17 @@ protected
		TrivialBackendSgxRepo.new.get(customer_id).with(registered?: false)
	end

	def hydrate_plan(customer_id, raw_customer)
		raw_customer.dup.tap do |data|
			data[:plan] = CustomerPlan.new(
				customer_id,
				plan: data.delete(:plan_name)&.then(&Plan.method(:for)),
				expires_at: data.delete(:expires_at)
			)
		end
	def mget(*keys)
		@redis.mget(keys).then { |values| Hash[keys.zip(values)] }
	end

	def fetch_redis(customer_id)
		mget(
			"jmp_customer_auto_top_up_amount-#{customer_id}",
			"jmp_customer_monthly_overage_limit-#{customer_id}"
		).then { |r|
			r.transform_keys { |k| k.match(/^jmp_customer_([^-]+)/)[1].to_sym }
		}
	end

	SQL = <<~SQL


@@ 98,6 99,20 @@ protected
		WHERE customer_id=$1 LIMIT 1
	SQL

	def fetch_sql(customer_id)
		@db.query_defer(SQL, [customer_id]).then do |rows|
			rows.first&.transform_keys(&:to_sym) || { balance: 0 }
		end
	end

	def fetch_all(customer_id)
		EMPromise.all([
			@sgx_repo.get(customer_id),
			fetch_sql(customer_id),
			fetch_redis(customer_id)
		]).then { |sgx, sql, redis| [sgx, sql.merge(redis)] }
	end

	def tndetails(sgx)
		return unless sgx.registered?



@@ 105,14 120,14 @@ protected
	end

	def find_inner(customer_id, jid)
		result = @db.query_defer(SQL, [customer_id])
		EMPromise.all([@sgx_repo.get(customer_id), result]).then do |(sgx, rows)|
			data = hydrate_plan(
				customer_id, rows.first&.transform_keys(&:to_sym) || {}
			)
		fetch_all(customer_id).then do |(sgx, data)|
			Customer.new(
				customer_id, Blather::JID.new(jid),
				sgx: sgx, tndetails: tndetails(sgx), **data
				sgx: sgx, balance: data[:balance], tndetails: tndetails(sgx),
				plan: CustomerPlan.for(
					customer_id,
					**data.delete_if { |(k, _)| k == :balance }
				)
			)
		end
	end

M lib/low_balance.rb => lib/low_balance.rb +10 -10
@@ 11,15 11,13 @@ class LowBalance
			"jmp_customer_low_balance-#{customer.customer_id}",
			expiry: 60 * 60 * 24 * 7
		).with(-> { Locked.new }) do
			customer.auto_top_up_amount.then do |auto_top_up_amount|
				for_auto_top_up_amount(customer, auto_top_up_amount)
			end
			for_auto_top_up_amount(customer)
		end
	end

	def self.for_auto_top_up_amount(customer, auto_top_up_amount)
		if auto_top_up_amount.positive?
			AutoTopUp.new(customer, auto_top_up_amount)
	def self.for_auto_top_up_amount(customer)
		if customer.auto_top_up_amount.positive?
			AutoTopUp.new(customer)
		else
			customer.btc_addresses.then do |btc_addresses|
				new(customer, btc_addresses)


@@ 49,15 47,17 @@ class LowBalance
	end

	class AutoTopUp
		def initialize(customer, auto_top_up_amount)
		def initialize(customer)
			@customer = customer
			@auto_top_up_amount = auto_top_up_amount
			@message = Blather::Stanza::Message.new
			@message.from = CONFIG[:notify_from]
		end

		def sale
			Transaction.sale(@customer, amount: @auto_top_up_amount).then do |tx|
			Transaction.sale(
				@customer,
				amount: @customer.auto_top_up_amount
			).then do |tx|
				tx.insert.then { tx }
			end
		end


@@ 70,7 70,7 @@ class LowBalance
			}.catch { |e|
				@message.body =
					"Automatic top-up transaction for " \
					"$#{@auto_top_up_amount} failed: #{e.message}"
					"$#{customer.auto_top_up_amount} failed: #{e.message}"
			}.then { @customer.stanza_to(@message) }
		end
	end

M test/test_customer_info.rb => test/test_customer_info.rb +0 -12
@@ 19,12 19,6 @@ class CustomerInfoTest < Minitest::Test
		sgx = Minitest::Mock.new
		sgx.expect(:registered?, false)

		CustomerPlan::REDIS.expect(
			:get,
			EMPromise.resolve(nil),
			["jmp_customer_auto_top_up_amount-test"]
		)

		CustomerPlan::DB.expect(
			:query_defer,
			EMPromise.resolve([{ "start_date" => Time.now }]),


@@ 44,12 38,6 @@ class CustomerInfoTest < Minitest::Test
		fwd = CustomerFwd.for(uri: "tel:+12223334444", timeout: 15)
		sgx.expect(:fwd, fwd)

		CustomerPlan::REDIS.expect(
			:get,
			EMPromise.resolve(nil),
			["jmp_customer_auto_top_up_amount-test"]
		)

		CustomerPlan::DB.expect(
			:query_defer,
			EMPromise.resolve([{ "start_date" => Time.now }]),

M test/test_helper.rb => test/test_helper.rb +15 -13
@@ 39,19 39,21 @@ require "tel_selections"
$VERBOSE = nil
Sentry.init

def customer(customer_id="test", plan_name: nil, **kwargs)
	jid = kwargs.delete(:jid) || Blather::JID.new("#{customer_id}@example.net")
	if plan_name
		expires_at = kwargs.delete(:expires_at) || Time.now
		plan = CustomerPlan.new(
			customer_id,
			plan: Plan.for(plan_name),
			expires_at: expires_at
		)
		Customer.new(customer_id, jid, plan: plan, **kwargs)
	else
		Customer.new(customer_id, jid, **kwargs)
	end
def customer(
	customer_id="test",
	plan_name: nil,
	jid: Blather::JID.new("#{customer_id}@example.net"),
	expires_at: Time.now,
	auto_top_up_amount: 0,
	**kwargs
)
	plan = CustomerPlan.for(
		customer_id,
		plan_name: plan_name,
		expires_at: expires_at,
		auto_top_up_amount: auto_top_up_amount
	)
	Customer.new(customer_id, jid, plan: plan, **kwargs)
end

CONFIG = {

M test/test_low_balance.rb => test/test_low_balance.rb +3 -13
@@ 24,11 24,6 @@ class LowBalanceTest < Minitest::Test
			EMPromise.resolve(0),
			["jmp_customer_low_balance-test"]
		)
		CustomerPlan::REDIS.expect(
			:get,
			EMPromise.resolve(nil),
			["jmp_customer_auto_top_up_amount-test"]
		)
		Customer::REDIS.expect(
			:smembers,
			EMPromise.resolve([]),


@@ 53,11 48,6 @@ class LowBalanceTest < Minitest::Test
			EMPromise.resolve(0),
			["jmp_customer_low_balance-test"]
		)
		CustomerPlan::REDIS.expect(
			:get,
			EMPromise.resolve("15"),
			["jmp_customer_auto_top_up_amount-test"]
		)
		ExpiringLock::REDIS.expect(
			:setex,
			EMPromise.resolve(nil),


@@ 65,7 55,7 @@ class LowBalanceTest < Minitest::Test
		)
		assert_kind_of(
			LowBalance::AutoTopUp,
			LowBalance.for(customer).sync
			LowBalance.for(customer(auto_top_up_amount: 15)).sync
		)
		assert_mock ExpiringLock::REDIS
	end


@@ 75,8 65,8 @@ class LowBalanceTest < Minitest::Test
		LowBalance::AutoTopUp::Transaction = Minitest::Mock.new

		def setup
			@customer = customer
			@auto_top_up = LowBalance::AutoTopUp.new(@customer, 100)
			@customer = customer(auto_top_up_amount: 100)
			@auto_top_up = LowBalance::AutoTopUp.new(@customer)
		end

		def test_notify!