From cbbbe757f7ea15fbde1a63a12c775044e55d009e Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 2 Aug 2021 13:42:09 -0500 Subject: [PATCH] Copy in account activation logic from sgx-jmp This is largely duplicated code, but the whole web-activation path should go away soon. This fixes web activation to produce the data we actually expect instead of the hack previously produced. Instead of an account activation for 5 months, we insert 5 months of balance and then bill for only one month as is reasonable. --- config.ru | 65 +++++++++++++++++++++++++++++++++--------- lib/transaction.rb | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 lib/transaction.rb diff --git a/config.ru b/config.ru index 1c1fcfcfbdcd674ec363096445ad65410ff69465..2c7c4a165f9a0179938bc907aff7dc18b1fe8139 100644 --- a/config.ru +++ b/config.ru @@ -61,12 +61,52 @@ class Plan SQL end - def activate(customer_id, months) - DB.exec_params( - "INSERT INTO plan_log VALUES ($1, $2, tsrange($3, $4))", - [customer_id, @plan[:name], Time.now, Date.today >> months] - ) - true + def bill_plan(customer_id) + DB.transaction do + charge_for_plan(customer_id) + unless activate_plan_starting_now(customer_id) + add_one_month_to_current_plan(customer_id) + end + end + end + + def activate_plan_starting_now(customer_id) + 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(customer_id) + params = [ + customer_id, + "#{customer_id}-bill-#{@plan[:name]}-at-#{Time.now.to_i}", + -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(customer_id) + 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 @@ -140,18 +180,18 @@ class CreditCardGateway end def sale(ip:, **kwargs) - return false unless decline_guard(ip) - result = @gateway.transaction.sale(**kwargs) - return true if result.success? + return nil unless decline_guard(ip) + tx = Transaction.sale(**kwargs) + return tx if tx REDIS.incr("jmp_pay_decline-#{@customer_id}") REDIS.expire("jmp_pay_decline-#{@customer_id}", 60 * 60 * 24) REDIS.incr("jmp_pay_decline-#{ip}") REDIS.expire("jmp_pay_decline-#{ip}", 60 * 60 * 24) - false + nil end - def buy_plan(plan_name, months, nonce, ip) + def buy_plan(plan_name, nonce, ip) plan = Plan.for(plan_name) sale( ip: ip, @@ -159,7 +199,7 @@ class CreditCardGateway payment_method_nonce: nonce, merchant_account_id: plan.merchant_account, options: {submit_for_settlement: true} - ) && plan.activate(@customer_id, months) + )&.insert && plan.bill_plan(@customer_id) end protected @@ -292,7 +332,6 @@ class JmpPay < Roda result = DB.transaction do Plan.active?(gateway.customer_id) || gateway.buy_plan( request.params["plan_name"], - 5, request.params["braintree_nonce"], request.ip ) diff --git a/lib/transaction.rb b/lib/transaction.rb new file mode 100644 index 0000000000000000000000000000000000000000..1c4eb29a20c23f3b75781d57d3fc403e60477bda --- /dev/null +++ b/lib/transaction.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "bigdecimal" + +# Largely copied from sgx-jmp to support web activation more properly +# Goes away when web activation goes away +class Transaction + def self.sale(gateway, **kwargs) + response = gateway.transaction.sale(**kwargs) + response.success? ? new(response.transaction) : nil + end + + attr_reader :amount + + def initialize(braintree_transaction) + @customer_id = braintree_transaction.customer_details.id + @transaction_id = braintree_transaction.id + @created_at = braintree_transaction.created_at + @amount = BigDecimal(braintree_transaction.amount, 4) + end + + def insert + DB.transaction do + insert_tx + insert_bonus + end + true + end + + def bonus + return BigDecimal(0) if amount <= 15 + amount * + case amount + when (15..29.99) + 0.01 + when (30..139.99) + 0.03 + else + 0.05 + end + end + + def to_s + plus = " + #{'%.4f' % bonus} bonus" + "$#{'%.2f' % amount}#{plus if bonus.positive?}" + end + +protected + + def insert_tx + params = [@customer_id, @transaction_id, @created_at, @amount] + DB.exec(<<~SQL, params) + INSERT INTO transactions + (customer_id, transaction_id, created_at, amount, note) + VALUES + ($1, $2, $3, $4, 'Credit card payment') + SQL + end + + def insert_bonus + return if bonus <= 0 + params = [@customer_id, "bonus_for_#{@transaction_id}", @created_at, bonus] + DB.exec(<<~SQL, params) + INSERT INTO transactions + (customer_id, transaction_id, created_at, amount, note) + VALUES + ($1, $2, $3, $4, 'Credit card payment bonus') + SQL + end +end