M lib/backend_sgx.rb => lib/backend_sgx.rb +12 -7
@@ 1,13 1,14 @@
# frozen_string_literal: true
class BackendSgx
- def initialize(jid=CONFIG[:sgx], creds=CONFIG[:creds])
+ def initialize(customer_id, jid=CONFIG[:sgx], creds=CONFIG[:creds])
+ @customer_id = customer_id
@jid = jid
@creds = creds
end
- def register!(customer_id, tel)
- ibr = mkibr(:set, customer_id)
+ def register!(tel)
+ ibr = mkibr(:set)
ibr.nick = @creds[:account]
ibr.username = @creds[:username]
ibr.password = @creds[:password]
@@ 15,8 16,8 @@ class BackendSgx
IQ_MANAGER.write(ibr)
end
- def registered?(customer_id)
- IQ_MANAGER.write(mkibr(:get, customer_id)).catch { nil }.then do |result|
+ def registered?
+ IQ_MANAGER.write(mkibr(:get)).catch { nil }.then do |result|
if result&.respond_to?(:registered?) && result&.registered?
result
else
@@ 27,9 28,13 @@ class BackendSgx
protected
- def mkibr(type, customer_id)
+ def from_jid
+ "customer_#{@customer_id}@#{CONFIG[:component][:jid]}"
+ end
+
+ def mkibr(type)
ibr = IBR.new(type, @jid)
- ibr.from = "customer_#{customer_id}@#{CONFIG[:component][:jid]}"
+ ibr.from = from_jid
ibr
end
end
M lib/customer.rb => lib/customer.rb +28 -65
@@ 2,6 2,8 @@
require "forwardable"
+require_relative "./customer_plan"
+require_relative "./backend_sgx"
require_relative "./ibr"
require_relative "./payment_methods"
require_relative "./plan"
@@ 25,51 27,52 @@ class Customer
end
end
+ def self.create(jid)
+ BRAINTREE.customer.create.then do |result|
+ raise "Braintree customer create failed" unless result.success?
+ cid = result.customer.id
+ REDIS.msetnx(
+ "jmp_customer_id-#{jid}", cid, "jmp_customer_jid-#{cid}", jid
+ ).then do |redis_result|
+ raise "Saving new customer to redis failed" unless redis_result == 1
+ new(cid)
+ end
+ end
+ end
+
extend Forwardable
attr_reader :customer_id, :balance
- def_delegator :@plan, :name, :plan_name
- def_delegators :@plan, :currency, :merchant_account
+ def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
+ :currency, :merchant_account, :plan_name
+ def_delegators :@sgx, :register!, :registered?
def initialize(
customer_id,
plan_name: nil,
expires_at: Time.now,
- balance: BigDecimal.new(0)
+ balance: BigDecimal.new(0),
+ sgx: BackendSgx.new(customer_id)
)
- @plan = plan_name && Plan.for(plan_name)
- @expires_at = expires_at
+ @plan = CustomerPlan.new(
+ customer_id,
+ plan: plan_name && Plan.for(plan_name),
+ expires_at: expires_at
+ )
@customer_id = customer_id
@balance = balance
+ @sgx = sgx
end
def with_plan(plan_name)
self.class.new(
@customer_id,
balance: @balance,
- expires_at: @expires_at,
+ expires_at: expires_at,
plan_name: plan_name
)
end
- def bill_plan
- EM.promise_fiber do
- DB.transaction do
- charge_for_plan
- add_one_month_to_current_plan unless activate_plan_starting_now
- end
- end
- end
-
- def activate_plan_starting_now
- DB.exec(<<~SQL, [@customer_id, plan_name]).cmd_tuples.positive?
- INSERT INTO plan_log
- (customer_id, plan_name, date_range)
- VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'))
- ON CONFLICT DO NOTHING
- SQL
- end
-
def payment_methods
@payment_methods ||=
BRAINTREE
@@ 78,45 81,5 @@ class Customer
.then(PaymentMethods.method(:for_braintree_customer))
end
- def active?
- @plan && @expires_at > Time.now
- end
-
- def register!(tel)
- BACKEND_SGX.register!(customer_id, tel)
- end
-
- def registered?
- BACKEND_SGX.registered?(customer_id)
- end
-
-protected
-
- def charge_for_plan
- params = [
- @customer_id,
- "#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
- -@plan.monthly_price
- ]
- DB.exec(<<~SQL, params)
- INSERT INTO transactions
- (customer_id, transaction_id, created_at, amount)
- VALUES ($1, $2, LOCALTIMESTAMP, $3)
- SQL
- end
-
- def add_one_month_to_current_plan
- DB.exec(<<~SQL, [@customer_id])
- UPDATE plan_log SET date_range=range_merge(
- date_range,
- tsrange(
- LOCALTIMESTAMP,
- GREATEST(upper(date_range), LOCALTIMESTAMP) + '1 month'
- )
- )
- WHERE
- customer_id=$1 AND
- date_range && tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month')
- SQL
- end
+ protected def_delegator :@plan, :expires_at
end
A lib/customer_plan.rb => lib/customer_plan.rb +69 -0
@@ 0,0 1,69 @@
+# frozen_string_literal: true
+
+require "forwardable"
+
+class CustomerPlan
+ extend Forwardable
+
+ attr_reader :expires_at
+ def_delegator :@plan, :name, :plan_name
+ def_delegators :@plan, :currency, :merchant_account
+
+ def initialize(customer_id, plan: nil, expires_at: Time.now)
+ @customer_id = customer_id
+ @plan = plan
+ @expires_at = expires_at
+ end
+
+ def active?
+ @plan && @expires_at > Time.now
+ end
+
+ def bill_plan
+ EM.promise_fiber do
+ DB.transaction do
+ charge_for_plan
+ add_one_month_to_current_plan unless activate_plan_starting_now
+ end
+ end
+ end
+
+ def activate_plan_starting_now
+ DB.exec(<<~SQL, [@customer_id, plan_name]).cmd_tuples.positive?
+ INSERT INTO plan_log
+ (customer_id, plan_name, date_range)
+ VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'))
+ ON CONFLICT DO NOTHING
+ SQL
+ end
+
+protected
+
+ def charge_for_plan
+ params = [
+ @customer_id,
+ "#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
+ -@plan.monthly_price
+ ]
+ DB.exec(<<~SQL, params)
+ INSERT INTO transactions
+ (customer_id, transaction_id, created_at, amount)
+ VALUES ($1, $2, LOCALTIMESTAMP, $3)
+ SQL
+ end
+
+ def add_one_month_to_current_plan
+ DB.exec(<<~SQL, [@customer_id])
+ UPDATE plan_log SET date_range=range_merge(
+ date_range,
+ tsrange(
+ LOCALTIMESTAMP,
+ GREATEST(upper(date_range), LOCALTIMESTAMP) + '1 month'
+ )
+ )
+ WHERE
+ customer_id=$1 AND
+ date_range && tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month')
+ SQL
+ end
+end
M lib/registration.rb => lib/registration.rb +3 -6
@@ 6,7 6,7 @@ require_relative "./oob"
class Registration
def self.for(iq, customer, web_register_manager)
- EMPromise.resolve(customer&.registered?).then do |registered|
+ customer.registered?.then do |registered|
if registered
Registered.new(iq, registered.phone)
else
@@ 36,13 36,10 @@ class Registration
class Activation
def self.for(iq, customer, tel)
- if customer&.active?
+ if customer.active?
Finish.new(iq, customer, tel)
- elsif customer
- EMPromise.resolve(new(iq, customer, tel))
else
- # Create customer_id
- raise "TODO"
+ EMPromise.resolve(new(iq, customer, tel))
end
end
M sgx_jmp.rb => sgx_jmp.rb +1 -2
@@ 77,7 77,6 @@ class AsyncBraintree
end
BRAINTREE = AsyncBraintree.new(**CONFIG[:braintree])
-BACKEND_SGX = BackendSgx.new
def panic(e)
m = e.respond_to?(:message) ? e.message : e
@@ 178,7 177,7 @@ end
command :execute?, node: "jabber:iq:register", sessionid: nil do |iq|
Customer.for_jid(iq.from.stripped).catch {
- nil
+ Customer.create(iq.from.stripped)
}.then { |customer|
Registration.for(
iq,
M test/test_backend_sgx.rb => test/test_backend_sgx.rb +4 -4
@@ 7,7 7,7 @@ BackendSgx::IQ_MANAGER = Minitest::Mock.new
class BackendSgxTest < Minitest::Test
def setup
- @sgx = BackendSgx.new
+ @sgx = BackendSgx.new("test")
end
def test_registered
@@ 19,7 19,7 @@ class BackendSgxTest < Minitest::Test
assert_equal "customer_test@component", ibr.from.to_s
end]
)
- assert @sgx.registered?("test").sync
+ assert @sgx.registered?.sync
end
em :test_registered
@@ 32,7 32,7 @@ class BackendSgxTest < Minitest::Test
assert_equal "customer_test@component", ibr.from.to_s
end]
)
- refute @sgx.registered?("test").sync
+ refute @sgx.registered?.sync
end
em :test_registered_not_registered
@@ 48,7 48,7 @@ class BackendSgxTest < Minitest::Test
assert_equal "+15555550000", ibr.phone
end]
)
- @sgx.register!("test", "+15555550000")
+ @sgx.register!("+15555550000")
BackendSgx::IQ_MANAGER.verify
end
end
M test/test_customer.rb => test/test_customer.rb +31 -9
@@ 3,8 3,10 @@
require "test_helper"
require "customer"
+Customer::BRAINTREE = Minitest::Mock.new
Customer::REDIS = Minitest::Mock.new
Customer::DB = Minitest::Mock.new
+CustomerPlan::DB = Minitest::Mock.new
class CustomerTest < Minitest::Test
def test_for_jid
@@ 48,12 50,32 @@ class CustomerTest < Minitest::Test
end
em :test_for_customer_id_not_found
+ def test_create
+ braintree_customer = Minitest::Mock.new
+ Customer::BRAINTREE.expect(:customer, braintree_customer)
+ braintree_customer.expect(:create, EMPromise.resolve(
+ OpenStruct.new(success?: true, customer: OpenStruct.new(id: "test"))
+ ))
+ Customer::REDIS.expect(
+ :msetnx,
+ EMPromise.resolve(1),
+ [
+ "jmp_customer_id-test@example.com", "test",
+ "jmp_customer_jid-test", "test@example.com"
+ ]
+ )
+ assert_kind_of Customer, Customer.create("test@example.com").sync
+ braintree_customer.verify
+ Customer::REDIS.verify
+ end
+ em :test_create
+
def test_bill_plan_activate
- Customer::DB.expect(:transaction, nil) do |&block|
+ CustomerPlan::DB.expect(:transaction, nil) do |&block|
block.call
true
end
- Customer::DB.expect(
+ CustomerPlan::DB.expect(
:exec,
nil,
[
@@ 65,22 87,22 @@ class CustomerTest < Minitest::Test
end
]
)
- Customer::DB.expect(
+ CustomerPlan::DB.expect(
:exec,
OpenStruct.new(cmd_tuples: 1),
[String, ["test", "test_usd"]]
)
Customer.new("test", plan_name: "test_usd").bill_plan.sync
- Customer::DB.verify
+ CustomerPlan::DB.verify
end
em :test_bill_plan_activate
def test_bill_plan_update
- Customer::DB.expect(:transaction, nil) do |&block|
+ CustomerPlan::DB.expect(:transaction, nil) do |&block|
block.call
true
end
- Customer::DB.expect(
+ CustomerPlan::DB.expect(
:exec,
nil,
[
@@ 92,14 114,14 @@ class CustomerTest < Minitest::Test
end
]
)
- Customer::DB.expect(
+ CustomerPlan::DB.expect(
:exec,
OpenStruct.new(cmd_tuples: 0),
[String, ["test", "test_usd"]]
)
- Customer::DB.expect(:exec, nil, [String, ["test"]])
+ CustomerPlan::DB.expect(:exec, nil, [String, ["test"]])
Customer.new("test", plan_name: "test_usd").bill_plan.sync
- Customer::DB.verify
+ CustomerPlan::DB.verify
end
em :test_bill_plan_update
end
M test/test_helper.rb => test/test_helper.rb +0 -2
@@ 70,8 70,6 @@ CONFIG = {
credit_card_url: ->(*) { "http://creditcard.example.com" }
}.freeze
-BACKEND_SGX = Minitest::Mock.new(BackendSgx.new)
-
BLATHER = Class.new {
def <<(*); end
}.new.freeze
M test/test_registration.rb => test/test_registration.rb +21 -28
@@ 5,31 5,34 @@ require "registration"
class RegistrationTest < Minitest::Test
def test_for_registered
- BACKEND_SGX.expect(
- :registered?,
- EMPromise.resolve(OpenStruct.new(phone: "+15555550000")),
- ["test"]
+ sgx = OpenStruct.new(
+ registered?: EMPromise.resolve(OpenStruct.new(phone: "+15555550000"))
)
iq = Blather::Stanza::Iq::Command.new
iq.from = "test@example.com"
- result = Registration.for(iq, Customer.new("test"), Minitest::Mock.new).sync
+ result = Registration.for(
+ iq,
+ Customer.new("test", sgx: sgx),
+ Minitest::Mock.new
+ ).sync
assert_kind_of Registration::Registered, result
end
em :test_for_registered
def test_for_activated
- BACKEND_SGX.expect(
- :registered?,
- EMPromise.resolve(nil),
- ["test"]
- )
+ sgx = OpenStruct.new(registered?: EMPromise.resolve(nil))
web_manager = WebRegisterManager.new
web_manager["test@example.com"] = "+15555550000"
iq = Blather::Stanza::Iq::Command.new
iq.from = "test@example.com"
result = Registration.for(
iq,
- Customer.new("test", plan_name: "test_usd", expires_at: Time.now + 999),
+ Customer.new(
+ "test",
+ plan_name: "test_usd",
+ expires_at: Time.now + 999,
+ sgx: sgx
+ ),
web_manager
).sync
assert_kind_of Registration::Finish, result
@@ 37,31 40,20 @@ class RegistrationTest < Minitest::Test
em :test_for_activated
def test_for_not_activated_with_customer_id
- BACKEND_SGX.expect(
- :registered?,
- EMPromise.resolve(nil),
- ["test"]
- )
+ sgx = OpenStruct.new(registered?: EMPromise.resolve(nil))
web_manager = WebRegisterManager.new
web_manager["test@example.com"] = "+15555550000"
iq = Blather::Stanza::Iq::Command.new
iq.from = "test@example.com"
result = Registration.for(
iq,
- Customer.new("test"),
+ Customer.new("test", sgx: sgx),
web_manager
).sync
assert_kind_of Registration::Activation, result
end
em :test_for_not_activated_with_customer_id
- def test_for_not_activated_without_customer_id
- skip "customer_id creation not implemented yet"
- iq = Blather::Stanza::Iq::Command.new
- Registration.for(iq, nil, Minitest::Mock.new).sync
- end
- em :test_for_not_activated_without_customer_id
-
class ActivationTest < Minitest::Test
Registration::Activation::COMMAND_MANAGER = Minitest::Mock.new
def setup
@@ 505,11 497,12 @@ class RegistrationTest < Minitest::Test
Registration::Finish::REDIS = Minitest::Mock.new
def setup
+ @sgx = Minitest::Mock.new(BackendSgx.new("test"))
iq = Blather::Stanza::Iq::Command.new
iq.from = "test\\40example.com@cheogram.com"
@finish = Registration::Finish.new(
iq,
- Customer.new("test"),
+ Customer.new("test", sgx: @sgx),
"+15555550000"
)
end
@@ 548,10 541,10 @@ class RegistrationTest < Minitest::Test
"Content-Type" => "application/json"
}
).to_return(status: 201)
- BACKEND_SGX.expect(
+ @sgx.expect(
:register!,
EMPromise.resolve(OpenStruct.new(error?: false)),
- ["test", "+15555550000"]
+ ["+15555550000"]
)
Registration::Finish::REDIS.expect(
:set,
@@ 580,7 573,7 @@ class RegistrationTest < Minitest::Test
)
@finish.write.sync
assert_requested create_order
- BACKEND_SGX.verify
+ @sgx.verify
Registration::Finish::REDIS.verify
Registration::Finish::BLATHER.verify
end