M .rubocop.yml => .rubocop.yml +1 -0
@@ 7,6 7,7 @@ Metrics/LineLength:
Metrics/BlockLength:
ExcludedMethods:
- route
+ - "on"
Layout/Tab:
Enabled: false
M config.ru => config.ru +79 -5
@@ 1,6 1,7 @@
# frozen_string_literal: true
require "braintree"
+require "date"
require "delegate"
require "dhall"
require "pg"
@@ 15,6 16,7 @@ end
require_relative "lib/electrum"
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)
@@ 24,6 26,35 @@ 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.new(@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 activate(customer_id, months)
+ DB.exec_params(
+ "INSERT INTO plan_log VALUES ($1, $2, $3, $4)",
+ [customer_id, @plan[:name], Time.now, Date.today >> months]
+ )
+ end
+end
+
class CreditCardGateway
def initialize(jid, customer_id=nil)
@jid = jid
@@ 81,6 112,19 @@ class CreditCardGateway
)
end
+ def buy_plan(plan_name, months, nonce)
+ plan = Plan.for(plan_name)
+ result = @gateway.transaction.sale(
+ amount: plan.price(months),
+ payment_method_nonce: nonce,
+ merchant_account_id: plan.merchant_account,
+ options: {submit_for_settlement: true}
+ )
+ return false unless result.success?
+ plan.activate(@customer_id, months)
+ true
+ end
+
protected
def redis_key_jid
@@ 154,12 198,42 @@ class JmpPay < Roda
end
r.on :jid do |jid|
- r.on "credit_cards" do
- gateway = CreditCardGateway.new(
- jid,
- request.params["customer_id"]
- )
+ gateway = CreditCardGateway.new(
+ jid,
+ request.params["customer_id"]
+ )
+ r.on "activate" do
+ render = lambda do |l={}|
+ view(
+ "activate",
+ locals: {
+ token: gateway.client_token,
+ customer_id: gateway.customer_id,
+ error: false
+ }.merge(l)
+ )
+ end
+
+ r.get do
+ render.call
+ end
+
+ r.post do
+ result = gateway.buy_plan(
+ request.params["plan_name"],
+ 5,
+ request.params["braintree_nonce"]
+ )
+ if result
+ r.redirect request.params["return_to"], 303
+ else
+ render.call(error: true)
+ end
+ end
+ end
+
+ r.on "credit_cards" do
r.get do
view(
"credit_cards",
A views/activate.slim => views/activate.slim +89 -0
@@ 0,0 1,89 @@
+scss:
+ html, body {
+ font-family: sans-serif;
+ text-align: center;
+ }
+
+ form {
+ margin: auto;
+ max-width: 40em;
+
+ fieldset {
+ max-width: 20em;
+ margin: 2em auto;
+ label {
+ 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"
+
+ 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);
+ instance._mainView.showSheetError();
+ } else {
+ e.target.braintree_nonce.value = payload.nonce;
+ e.target.submit();
+ }
+ });
+ });
+ });