Skip to content

Commit

Permalink
WIP - working on transactions (refund, commit, void)
Browse files Browse the repository at this point in the history
  • Loading branch information
devton committed Jul 2, 2024
1 parent bcbe1cb commit c27d6b1
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 39 deletions.
82 changes: 55 additions & 27 deletions lib/blnk/resourceable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Blnk
# Resoureable module that bring some tweaks for basic REST api integration
class Resourceable < OpenStruct
extend Client
extend Forwardable

def_delegators :'self.class', :put_request, :post_request, :get_request, :with_handler

class SearchResult < OpenStruct; end

Expand All @@ -18,35 +21,30 @@ class << self

attr_accessor :resource_name, :id_field, :create_contract, :search_contract

def find(id) = with_req resp: get_request(path: "/#{resource_name}/#{id}")
def resources_path = "/#{resource_name}"
def resource_path(id) = "/#{resource_name}/#{id}"
def search_path = "/search/#{resource_name}"

def find(id) = with_handler resp: find_request(id)
def find_request(id) = get_request(path: resource_path(id))

def all
check_vars
res = get_request(path: "/#{resource_name}")
return Failure(res.parse&.transform_keys(&:to_sym)) unless res.status.success?

Success(res.parse.map { |r| new(r) })
resp = get_request(path: resources_path)
with_handler(resp:, block: method(:all_handler))
end

def create(**args)
contract = wrap_call(create_contract_new, args)
return contract if contract.failure?

res = post_request(path: "/#{resource_name}", body: contract.to_h)
return Failure(res.parse&.transform_keys(&:to_sym)) unless res.status.success?

Success(new(res.parse))
wrap_call(create_contract_new, args) do |contract|
with_handler(resp: post_request(path: resources_path, body: contract.to_h))
end
end

def search(**args)
contract = wrap_call(search_contract_new, args)
return contract if contract.failure?

res = post_request(path: "/search/#{resource_name}", body: contract.to_h)
return Failure(res.parse&.transform_keys(&:to_sym)) unless res.status.success?

result = SearchResult.new(res.parse.merge(resource_name:))
Success(result)
wrap_call(search_contract_new, args) do |contract|
with_handler(resp: post_request(path: search_path, body: contract.to_h),
block: method(:search_handler))
end
end

def check_vars
Expand All @@ -60,6 +58,8 @@ def wrap_call(contract, args)
ccall = contract.call(args)
return Failure(ccall.errors.to_h) if ccall.failure?

return yield ccall if block_given?

ccall
end

Expand All @@ -71,27 +71,55 @@ def create_contract_new

def search_contract_new = (search_contract || DefaultSearchContract).new

def handler(parsed, status) = (status.success? ? Success(new(parsed)) : Failure(parsed))
def with_req(resp:, self_caller: nil) = using_resp(resp:, self_caller:, &method(:handler))
def search_handler(parsed, status)
success = status.success?
result = SearchResult.new(parsed) if success

def using_resp(resp:, self_caller: nil, &block)
inj_handler(result:, success:, error: parsed)
end

def all_handler(parsed, status)
success = status.success?
result = parsed.map { |r| new(r) } if success
inj_handler(success:, result:, error: parsed)
end

def handler(parsed, status)
inj_handler(
result: new(parsed),
error: parsed,
success: status.success?
)
end

def inj_handler(result:, error:, success:)
(success ? Success(result) : Failure(error))
end

def with_handler(resp:, kself: nil, block: method(:handler))
using_resp(resp:, kself:, &block)
end

def using_resp(resp:, kself: nil, &block)
check_vars
parsed = resp.parse.transform_keys(&:to_sym)
self_caller&.reload
parsed = resp.parse
parsed = parsed.transform_keys(&:to_sym) unless parsed.is_a?(Array)

kself&.reload

block.call(parsed, resp.status)
end
end

def reload
self.class.find(_id) do |res|
self.class.find_request(_id).tap do |res|
next unless res.status.success?

res.parse.each_pair do |k, v|
self[k] = v
end
self
end
self
end

# table[self.class.id_field]
Expand Down
13 changes: 7 additions & 6 deletions lib/blnk/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ class CreateContract < Dry::Validation::Contract
self.id_field = :transaction_id
self.create_contract = CreateContract

def refund = self.class.with_req(req: request_refund, self_caller: self)
def void = raise NotImplementedError
def commit = raise NotImplementedError
def refund = short_hander(resp: req_refund)
def void = short_hander(resp: req_inflight(body: { status: 'void' }))
def commit = short_hander(resp: req_inflight(body: { status: 'commit' }))

private

def short_hander(resp:) = with_handler(resp:, kself: self)
def inflight_path = "/transactions/inflight/#{_id}"
def refund_path = "/refund-transactions/#{_id}"
def request_update_inflight(body:, path: inflight_path) = self.class.put_request(path:, body:)
def request_refund(path: refund_path) = self.class.post_request(path:, body: nil)
def refund_path = "/refund-transaction/#{_id}"
def req_inflight(body:) = put_request(path: inflight_path, body:)
def req_refund = post_request(path: refund_path, body: nil)
end
end
24 changes: 20 additions & 4 deletions test/blnk/test_balance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def stub_find_balance_request_with_error
.to_return_json(body: { error: 'balance with ID \'BALANCE_ID\' not found' }, status: 400)
end

def balance_response_body # rubocop:disable Metrics/MethodLength
def balance_response_body(opts: {}) # rubocop:disable Metrics/MethodLength
{ balance: 0,
version: 1,
inflight_balance: 0,
Expand All @@ -23,12 +23,12 @@ def balance_response_body # rubocop:disable Metrics/MethodLength
currency: 'USD',
created_at: '2024-06-26T01:19:35.122774Z',
inflight_expires_at: '0001-01-01T00:00:00Z',
meta_data: nil }
meta_data: nil }.merge(opts)
end

def stub_find_balance_request_with_success
def stub_find_balance_request_with_success(opts: {})
stub_request(:get, %r{/balances/(.*)})
.to_return_json(body: balance_response_body, status: 200)
.to_return_json(body: balance_response_body(opts:), status: 200)
end

def stub_create_balance_request_with_success
Expand Down Expand Up @@ -60,6 +60,22 @@ def test_that_balance_not_found
assert find.failure?
end

def test_that_balance_reload # rubocop:disable Metrics/AbcSize
stub_find_balance_request_with_success(opts: { credit_balance: 20 })
find = Blnk::Balance.find 'BALANCE_ID'

assert find.success?
assert find.value!.is_a?(Blnk::Balance)
assert find.value!.credit_balance.eql?(20)
assert find.value!.balance_id.eql?(balance_response_body[:balance_id])

stub_find_balance_request_with_success(opts: { credit_balance: 100 })

find.value!.reload

assert find.value!.credit_balance.eql?(100)
end

def test_that_balance_find_success
stub_find_balance_request_with_success
find = Blnk::Balance.find 'BALANCE_ID'
Expand Down
87 changes: 85 additions & 2 deletions test/blnk/test_transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,37 @@ def stub_find_transaction_request_with_error
.to_return_json(body: { error: 'balance with ID \'transaction_id\' not found' }, status: 400)
end

def transaction_response_body # rubocop:disable Metrics/MethodLength
def refunded_txn_body # rubocop:disable Metrics/MethodLength
{
transaction_id: 'txn_043d4ac3-4caf-4149-9c0d-927ae637d83f',
tag: 'Refund',
reference: 'ref_70ffd90f-7bcf-43b8-90ba-70505c113b52',
amount: 300,
currency: 'NGN',
payment_method: '',
description: '',
drcr: 'Debit',
status: 'APPLIED',
ledger_id: 'ldg_073f7ffe-9dfd-42ce-aa50-d1dca1788adc',
balance_id: 'bln_0be360ca-86fe-457d-be43-daa3f966d8f0',
credit_balance_before: 600,
debit_balance_before: 0,
credit_balance_after: 600,
debit_balance_after: 300,
balance_before: 600,
balance_after: 300,
created_at: '2024-02-20T05:40:52.630481718Z',
scheduled_for: '0001-01-01T00:00:00Z',
risk_tolerance_threshold: 0,
risk_score: 0.03108,
meta_data: {
refunded_transaction_id: 'txn_5bbbe4d3-2d82-4da1-8191-aaa491d025de'
},
group_ids: nil
}
end

def transaction_response_body(opts: {}) # rubocop:disable Metrics/MethodLength
{
precise_amount: 7500,
amount: 75,
Expand All @@ -27,7 +57,17 @@ def transaction_response_body # rubocop:disable Metrics/MethodLength
created_at: '2024-06-27T20:23:17.737289826Z',
scheduled_for: '0001-01-01T00:00:00Z',
inflight_expiry_date: '0001-01-01T00:00:00Z'
}
}.merge(opts)
end

def stub_refund_transaction_request_with_success
stub_request(:post, %r{/refund-transaction/(.*)})
.to_return_json(body: refunded_txn_body, status: 200)
end

def stub_refund_transaction_request_with_error
stub_request(:post, %r{/refund-transaction/(.*)})
.to_return_json(body: { error: 'failed_refund_transaction' }, status: 400)
end

def stub_find_transaction_request_with_success
Expand Down Expand Up @@ -119,4 +159,47 @@ def test_that_transaction_create_success # rubocop:disable Metric/AbcSize, Metri
assert create.value!.transaction_id.eql?(transaction_response_body[:transaction_id])
assert create.value!.name.eql?(transaction_response_body[:name])
end

def test_that_transaction_refund_error # rubocop:disable Metric/Metrics/MethodLength
stub_find_transaction_request_with_success
stub_create_transaction_request_with_success
stub_refund_transaction_request_with_error

txn = Blnk::Transaction.create(
amount: 75,
reference: 'ref_005',
currency: 'BRLX',
precision: 100,
source: '@world',
destination: 'bln_469f93bc-40e9-4e0e-b6ab-d11c3638c15d',
description: 'For fees',
allow_overdraft: true
)

refund_txn = txn.value!.refund

assert refund_txn.failure?
end

def test_that_transaction_refund_success # rubocop:disable Metric/Metrics/MethodLength
stub_find_transaction_request_with_success
stub_create_transaction_request_with_success
stub_refund_transaction_request_with_success

txn = Blnk::Transaction.create(
amount: 75,
reference: 'ref_005',
currency: 'BRLX',
precision: 100,
source: '@world',
destination: 'bln_469f93bc-40e9-4e0e-b6ab-d11c3638c15d',
description: 'For fees',
allow_overdraft: true
)

refund_txn = txn.value!.refund

assert refund_txn.success?
assert txn.value!.transaction_id != refund_txn.value!.transaction_id
end
end

0 comments on commit c27d6b1

Please sign in to comment.