config.dhall.sample 🔗
@@ -17,6 +17,11 @@
environment = "sandbox",
merchant_id = "",
public_key = "",
- private_key = ""
- }
+ private_key = "",
+ merchant_accounts = {
+ USD = "",
+ CAD = ""
+ }
+ },
+ plans = ./plans.dhall
}
Stephen Paul Weber created
Look up the user's plan to find out what currency to charge them in.
config.dhall.sample | 9 ++++++-
schemas | 2
sgx_jmp.rb | 53 +++++++++++++++++++++++++++++-----------------
3 files changed, 41 insertions(+), 23 deletions(-)
@@ -17,6 +17,11 @@
environment = "sandbox",
merchant_id = "",
public_key = "",
- private_key = ""
- }
+ private_key = "",
+ merchant_accounts = {
+ USD = "",
+ CAD = ""
+ }
+ },
+ plans = ./plans.dhall
}
@@ -1 +1 @@
-Subproject commit b0729aba768a943ed9f695d1468f1c62f2076727
+Subproject commit e005a4d6b09636d21614be0c513ce9360cef2ccb
@@ -9,11 +9,14 @@ require "em-hiredis"
require "em_promise"
require "time-hash"
-CONFIG = Dhall::Coder.load(ARGV[0])
+CONFIG =
+ Dhall::Coder
+ .new(safe: Dhall::Coder::JSON_LIKE + [Symbol])
+ .load(ARGV[0], transform_keys: ->(k) { k&.to_sym })
# Braintree is not async, so wrap in EM.defer for now
class AsyncBraintree
- def initialize(environment:, merchant_id:, public_key:, private_key:)
+ def initialize(environment:, merchant_id:, public_key:, private_key:, **)
@gateway = Braintree::Gateway.new(
environment: environment,
merchant_id: merchant_id,
@@ -50,7 +53,7 @@ class AsyncBraintree
end
end
-BRAINTREE = AsyncBraintree.new(**CONFIG["braintree"].transform_keys(&:to_sym))
+BRAINTREE = AsyncBraintree.new(**CONFIG[:braintree])
def node(name, parent, ns: nil)
Niceogiri::XML::Node.new(
@@ -94,7 +97,7 @@ end
def proxy_jid(jid)
Blather::JID.new(
escape_jid(jid.stripped),
- CONFIG["component"]["jid"],
+ CONFIG[:component][:jid],
jid.resource
)
end
@@ -162,18 +165,18 @@ when_ready do
DB.type_map_for_queries = PG::BasicTypeMapForQueries.new(DB)
EM.add_periodic_timer(3600) do
- ping = Blather::Stanza::Iq::Ping.new(:get, CONFIG["server"]["host"])
- ping.from = CONFIG["component"]["jid"]
+ ping = Blather::Stanza::Iq::Ping.new(:get, CONFIG[:server][:host])
+ ping.from = CONFIG[:component][:jid]
self << ping
end
end
# workqueue_count MUST be 0 or else Blather uses threads!
setup(
- CONFIG["component"]["jid"],
- CONFIG["component"]["secret"],
- CONFIG["server"]["host"],
- CONFIG["server"]["port"],
+ CONFIG[:component][:jid],
+ CONFIG[:component][:secret],
+ CONFIG[:server][:host],
+ CONFIG[:server][:port],
nil,
nil,
workqueue_count: 0
@@ -186,7 +189,7 @@ end
ibr :get? do |iq|
fwd = iq.dup
fwd.from = proxy_jid(iq.from)
- fwd.to = Blather::JID.new(nil, CONFIG["sgx"], iq.to.resource)
+ fwd.to = Blather::JID.new(nil, CONFIG[:sgx], iq.to.resource)
fwd.id = "JMPGET%#{iq.id}"
self << fwd
end
@@ -205,7 +208,7 @@ ibr :result? do |iq|
reply.id = iq.id.sub(/JMP[GS]ET%/, "")
reply.from = Blather::JID.new(
nil,
- CONFIG["component"]["jid"],
+ CONFIG[:component][:jid],
iq.from.resource
)
reply.to = unproxy_jid(iq.to)
@@ -217,7 +220,7 @@ ibr :error? do |iq|
reply.id = iq.id.sub(/JMP[GS]ET%/, "")
reply.from = Blather::JID.new(
nil,
- CONFIG["component"]["jid"],
+ CONFIG[:component][:jid],
iq.from.resource
)
reply.to = unproxy_jid(iq.to)
@@ -226,11 +229,11 @@ end
ibr :set? do |iq|
fwd = iq.dup
- CONFIG["creds"].each do |k, v|
+ CONFIG[:creds].each do |k, v|
fwd.public_send("#{k}=", v)
end
fwd.from = proxy_jid(iq.from)
- fwd.to = Blather::JID.new(nil, CONFIG["sgx"], iq.to.resource)
+ fwd.to = Blather::JID.new(nil, CONFIG[:sgx], iq.to.resource)
fwd.id = "JMPSET%#{iq.id}"
self << fwd
end
@@ -289,6 +292,7 @@ disco_items node: "http://jabber.org/protocol/commands" do |iq|
reply = iq.reply
reply.items = [
# TODO: don't show this item if no braintree methods available
+ # TODO: don't show this item if no plan for this customer
Blather::Stanza::DiscoItems::Item.new(
iq.to,
"buy-credit",
@@ -310,16 +314,23 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq|
EMPromise.all([
DB.query_defer(
- "SELECT balance FROM balances WHERE customer_id=$1 LIMIT 1",
+ "SELECT COALESCE(balance,0) AS balance, plan_name FROM " \
+ "balances LEFT JOIN customer_plans USING (customer_id) " \
+ "WHERE customer_id=$1 LIMIT 1",
[customer_id]
).then do |rows|
- rows.first&.dig("balance") || BigDecimal.new(0)
+ rows.first || { "balance" => BigDecimal.new(0) }
end,
BRAINTREE.customer.find(customer_id).payment_methods
])
- }.then { |(balance, payment_methods)|
+ }.then { |(row, payment_methods)|
raise "No payment methods available" if payment_methods.empty?
+ plan = CONFIG[:plans].find { |p| p[:name] == row["plan_name"] }
+ raise "No plan for this customer" unless plan
+ merchant_account = CONFIG[:braintree][:merchant_accounts][plan[:currency]]
+ raise "No merchant account for this currency" unless merchant_account
+
default_payment_method = payment_methods.index(&:default?)
form = reply.form
@@ -328,7 +339,7 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq|
form.fields = [
{
type: "fixed",
- value: "Current balance: $#{'%.2f' % balance}"
+ value: "Current balance: $#{'%.2f' % row['balance']}"
},
if payment_methods.length > 1
{
@@ -356,9 +367,10 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq|
EMPromise.all([
payment_methods,
+ merchant_account,
command_reply_and_promise(reply)
])
- }.then { |(payment_methods, iq2)|
+ }.then { |(payment_methods, merchant_account, iq2)|
iq = iq2 # This allows the catch to use it also
payment_method = payment_methods.fetch(
iq.form.field("payment_method")&.value.to_i
@@ -366,6 +378,7 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq|
BRAINTREE.transaction.sale(
amount: iq.form.field("amount").value.to_s,
payment_method_token: payment_method.token,
+ merchant_account_id: merchant_account,
options: { submit_for_settlement: true }
)
}.then { |braintree_response|