3 files changed, 0 insertions(+), 328 deletions(-)
M config.ru
D lib/transaction.rb
D views/activate.slim
M config.ru => config.ru +0 -158
@@ 25,7 25,6 @@ end
use Sentry::Rack::CaptureExceptions
REDIS = Redis.new
-PLANS = Dhall.load("env:PLANS").sync
BRAINTREE_CONFIG = Dhall.load("env:BRAINTREE_CONFIG").sync
ELECTRUM = Electrum.new(
**Dhall::Coder.load("env:ELECTRUM_CONFIG", transform_keys: :to_sym)
@@ 35,84 34,6 @@ DB = PG.connect(dbname: "jmp")
DB.type_map_for_results = PG::BasicTypeMapForResults.new(DB)
DB.type_map_for_queries = PG::BasicTypeMapForQueries.new(DB)
-class Plan
- def self.for(plan_name)
- new(PLANS.find { |p| p[:name].to_s == plan_name })
- end
-
- def initialize(plan)
- @plan = plan
- end
-
- def price(months=1)
- (BigDecimal(@plan[:monthly_price].to_i) * months) / 10000
- end
-
- def currency
- @plan[:currency].to_s.to_sym
- end
-
- def merchant_account
- BRAINTREE_CONFIG[:merchant_accounts][currency]
- end
-
- def self.active?(customer_id)
- DB.exec_params(<<~SQL, [customer_id]).first&.[]("count").to_i.positive?
- SELECT count(1) AS count FROM customer_plans
- WHERE customer_id=$1 AND expires_at > NOW()
- SQL
- end
-
- 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
- true
- 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
-
class CreditCardGateway
def initialize(jid, customer_id=nil)
@jid = jid
@@ 175,38 96,6 @@ class CreditCardGateway
)
end
- def decline_guard(ip)
- customer_declines, ip_declines = REDIS.mget(
- "jmp_pay_decline-#{@customer_id}",
- "jmp_pay_decline-#{ip}"
- )
- customer_declines.to_i < 2 && ip_declines.to_i < 4
- end
-
- def sale(ip:, **kwargs)
- return nil unless decline_guard(ip)
-
- tx = Transaction.sale(@gateway, **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)
- nil
- end
-
- def buy_plan(plan_name, nonce, ip)
- plan = Plan.for(plan_name)
- sale(
- ip: ip,
- amount: plan.price(5),
- payment_method_nonce: nonce,
- merchant_account_id: plan.merchant_account,
- options: { submit_for_settlement: true }
- )&.insert && plan.bill_plan(@customer_id)
- end
-
protected
def redis_key_jid
@@ 307,53 196,6 @@ class JmpPay < Roda
gateway = CreditCardGateway.new(jid, params["customer_id"])
topup = "jmp_customer_auto_top_up_amount-#{gateway.customer_id}"
- r.on "activate" do
- Sentry.configure_scope do |scope|
- scope.set_transaction_name("activate")
- scope.set_context(
- "activate",
- plan_name: params["plan_name"]
- )
- end
-
- render = lambda do |l={}|
- view(
- "activate",
- locals: {
- token: gateway.client_token,
- customer_id: gateway.customer_id,
- error: false
- }.merge(l)
- )
- end
-
- r.get do
- if Plan.active?(gateway.customer_id)
- r.redirect params["return_to"], 303
- else
- render.call
- end
- end
-
- r.post do
- result = DB.transaction {
- Plan.active?(gateway.customer_id) || gateway.buy_plan(
- params["plan_name"],
- params["braintree_nonce"],
- request.ip
- )
- }
- if params["auto_top_up_amount"].to_i >= 15
- REDIS.set(topup, params["auto_top_up_amount"].to_i)
- end
- if result
- r.redirect params["return_to"], 303
- else
- render.call(error: true)
- end
- end
- end
-
r.on "credit_cards" do
r.get do
view(
D lib/transaction.rb => lib/transaction.rb +0 -75
@@ 1,75 0,0 @@
-# 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
D views/activate.slim => views/activate.slim +0 -95
@@ 1,95 0,0 @@
-scss:
- html, body {
- font-family: sans-serif;
- text-align: center;
- }
-
- form {
- margin: auto;
- max-width: 40em;
-
- fieldset {
- max-width: 25em;
- margin: 2em auto;
- label { display: block; }
- input[type=number] { max-width: 3em; }
- small { display: block; }
- }
-
- button {
- display: block;
- width: 10em;
- margin: auto;
- }
-
- }
-
- .error {
- color: red;
- max-width: 40em;
- margin: 1em auto;
- }
-
-h1 Activate New Account
-
-- if error
- p.error
- ' Your bank declined the transaction.
- ' Often this happens when a person's credit card is a US card
- ' that does not support international transactions, as JMP is
- ' not based in the USA, though we do support transactions in USD.
- p.error
- ' If you were trying a prepaid card, you may wish to use
- a href="https://privacy.com/" Privacy.com
- | instead, as they do support international transactions.
-
-form method="post" action=""
- #braintree
- | Unfortunately, our credit card processor requires JavaScript.
-
- fieldset
- legend Pay for 5 months of service
- label
- ' $14.95 USD
- input type="radio" name="plan_name" value="usd_beta_unlimited-v20210223" required="required"
- label
- ' $17.95 CAD
- input type="radio" name="plan_name" value="cad_beta_unlimited-v20210223" required="required"
-
- fieldset
- legend Auto top-up when account balance is low?
- label
- | When balance drops below $5, add $
- input type="number" name="auto_top_up_amount" min="15" value="15"
- small Leave blank for no auto top-up.
-
- input type="hidden" name="customer_id" value=customer_id
- input type="hidden" name="braintree_nonce"
-
-script src="https://js.braintreegateway.com/web/dropin/1.26.0/js/dropin.min.js"
-javascript:
- document.querySelector("#braintree").innerHTML = "";
-
- var button = document.createElement("button");
- button.innerHTML = "Pay Now";
- document.querySelector("form").appendChild(button);
- braintree.dropin.create({
- authorization: #{{token.to_json}},
- container: "#braintree",
- vaultManager: false
- }, function (createErr, instance) {
- if(createErr) console.log(createErr);
-
- document.querySelector("form").addEventListener("submit", function(e) {
- e.preventDefault();
-
- instance.requestPaymentMethod(function(err, payload) {
- if(err) {
- console.log(err);
- } else {
- e.target.braintree_nonce.value = payload.nonce;
- e.target.submit();
- }
- });
- });
- });