afterpay-sdk 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.sample +2 -0
- data/.github/workflows/gempush.yml +30 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +33 -0
- data/.travis.yml +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +89 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/Rakefile +6 -0
- data/afterpay-sdk.gemspec +45 -0
- data/bin/bundle +105 -0
- data/bin/console +20 -0
- data/bin/setup +8 -0
- data/configure.rb +19 -0
- data/lib/afterpay-sdk.rb +6 -0
- data/lib/afterpay.rb +48 -0
- data/lib/afterpay/address.rb +49 -0
- data/lib/afterpay/client.rb +72 -0
- data/lib/afterpay/config.rb +23 -0
- data/lib/afterpay/consumer.rb +35 -0
- data/lib/afterpay/discount.rb +28 -0
- data/lib/afterpay/error.rb +16 -0
- data/lib/afterpay/item.rb +54 -0
- data/lib/afterpay/order.rb +117 -0
- data/lib/afterpay/payment.rb +164 -0
- data/lib/afterpay/payment_event.rb +16 -0
- data/lib/afterpay/refund.rb +31 -0
- data/lib/afterpay/shipping_courier.rb +14 -0
- data/lib/afterpay/utils/money.rb +26 -0
- data/lib/afterpay/version.rb +5 -0
- metadata +215 -0
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "afterpay/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "afterpay-sdk"
|
7
|
+
spec.version = Afterpay::VERSION
|
8
|
+
spec.authors = ["Sachin Saxena"]
|
9
|
+
spec.email = ["dev@yourmechanic.com"]
|
10
|
+
|
11
|
+
spec.summary = "Afterpay ruby wrapper"
|
12
|
+
spec.homepage = "https://github.com/YourMechanic/afterpay-sdk"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
16
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
17
|
+
if spec.respond_to?(:metadata)
|
18
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
19
|
+
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
21
|
+
spec.metadata["source_code_uri"] = "https://github.com/YourMechanic/afterpay-sdk"
|
22
|
+
else
|
23
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
24
|
+
"public gem pushes."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
28
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
29
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
30
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
31
|
+
end
|
32
|
+
spec.bindir = "exe"
|
33
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ["lib"]
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
37
|
+
spec.add_development_dependency "dotenv", "~> 2.2", ">= 2.2.1"
|
38
|
+
spec.add_development_dependency "pry"
|
39
|
+
spec.add_development_dependency "rake", "~> 12.3", ">= 12.3.3"
|
40
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
41
|
+
|
42
|
+
spec.add_dependency "faraday", ">= 0.8", "< 1.0"
|
43
|
+
spec.add_dependency "faraday_middleware", "~> 0.13.1"
|
44
|
+
spec.add_dependency "money", ">= 6.7.1", "< 7.0.0"
|
45
|
+
end
|
data/bin/bundle
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'bundle' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "rubygems"
|
12
|
+
|
13
|
+
m = Module.new do
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def invoked_as_script?
|
17
|
+
File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def env_var_version
|
21
|
+
ENV["BUNDLER_VERSION"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_arg_version
|
25
|
+
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
+
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
27
|
+
|
28
|
+
bundler_version = nil
|
29
|
+
update_index = nil
|
30
|
+
ARGV.each_with_index do |a, i|
|
31
|
+
bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
32
|
+
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
33
|
+
|
34
|
+
bundler_version = Regexp.last_match(1) || ">= 0.a"
|
35
|
+
update_index = i
|
36
|
+
end
|
37
|
+
bundler_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def gemfile
|
41
|
+
gemfile = ENV["BUNDLE_GEMFILE"]
|
42
|
+
return gemfile if gemfile && !gemfile.empty?
|
43
|
+
|
44
|
+
File.expand_path("../Gemfile", __dir__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
lockfile =
|
49
|
+
case File.basename(gemfile)
|
50
|
+
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
|
51
|
+
else "#{gemfile}.lock"
|
52
|
+
end
|
53
|
+
File.expand_path(lockfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lockfile_version
|
57
|
+
return unless File.file?(lockfile)
|
58
|
+
|
59
|
+
lockfile_contents = File.read(lockfile)
|
60
|
+
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
61
|
+
|
62
|
+
Regexp.last_match(1)
|
63
|
+
end
|
64
|
+
|
65
|
+
def bundler_version
|
66
|
+
@bundler_version ||= begin
|
67
|
+
env_var_version || cli_arg_version ||
|
68
|
+
lockfile_version || "#{Gem::Requirement.default}.a"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def load_bundler!
|
73
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
74
|
+
|
75
|
+
# must dup string for RG < 1.8 compatibility
|
76
|
+
activate_bundler(bundler_version.dup)
|
77
|
+
end
|
78
|
+
|
79
|
+
def activate_bundler(bundler_version)
|
80
|
+
bundler_version = "< 2" if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
|
81
|
+
gem_error = activation_error_handling do
|
82
|
+
gem "bundler", bundler_version
|
83
|
+
end
|
84
|
+
return if gem_error.nil?
|
85
|
+
|
86
|
+
require_error = activation_error_handling do
|
87
|
+
require "bundler/version"
|
88
|
+
end
|
89
|
+
return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
90
|
+
|
91
|
+
warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
|
92
|
+
exit 42
|
93
|
+
end
|
94
|
+
|
95
|
+
def activation_error_handling
|
96
|
+
yield
|
97
|
+
nil
|
98
|
+
rescue StandardError, LoadError => e
|
99
|
+
e
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
m.load_bundler!
|
104
|
+
|
105
|
+
load Gem.bin_path("bundler", "bundle") if m.invoked_as_script?
|
data/bin/console
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
require "bundler/setup"
|
5
|
+
require "afterpay"
|
6
|
+
|
7
|
+
begin
|
8
|
+
require_relative "../configure"
|
9
|
+
rescue LoadError
|
10
|
+
end
|
11
|
+
|
12
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
13
|
+
# with your gem easier. You can also use a different console, if you like.
|
14
|
+
|
15
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
16
|
+
# require "pry"
|
17
|
+
# Pry.start
|
18
|
+
|
19
|
+
require "irb"
|
20
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/configure.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dotenv"
|
4
|
+
Dotenv.load
|
5
|
+
|
6
|
+
Money.default_currency = ENV["DEFAULT_CURRENCY"] || "USD"
|
7
|
+
|
8
|
+
Afterpay.configure do |config|
|
9
|
+
config.app_id = ENV["APP_ID"]
|
10
|
+
config.secret = ENV["SECRET"]
|
11
|
+
|
12
|
+
# Sets to raise errors when 404
|
13
|
+
# config.raise_errors = true
|
14
|
+
|
15
|
+
# Sets the environment for Afterpay
|
16
|
+
# config.env = "sandbox" # :live
|
17
|
+
end
|
18
|
+
|
19
|
+
Afterpay.config.freeze
|
data/lib/afterpay-sdk.rb
ADDED
data/lib/afterpay.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "money"
|
4
|
+
require_relative "afterpay/utils/money"
|
5
|
+
|
6
|
+
require_relative "afterpay/version"
|
7
|
+
require_relative "afterpay/client"
|
8
|
+
require_relative "afterpay/config"
|
9
|
+
require_relative "afterpay/consumer"
|
10
|
+
require_relative "afterpay/item"
|
11
|
+
require_relative "afterpay/order"
|
12
|
+
require_relative "afterpay/payment"
|
13
|
+
require_relative "afterpay/error"
|
14
|
+
require_relative "afterpay/discount"
|
15
|
+
require_relative "afterpay/address"
|
16
|
+
require_relative "afterpay/refund"
|
17
|
+
require_relative "afterpay/shipping_courier"
|
18
|
+
require_relative "afterpay/payment_event"
|
19
|
+
|
20
|
+
module Afterpay
|
21
|
+
class << self
|
22
|
+
attr_accessor :config
|
23
|
+
end
|
24
|
+
|
25
|
+
# Helper function for Afterpay::Client
|
26
|
+
# Use Afterpay.client to send receive request
|
27
|
+
def self.client
|
28
|
+
Client.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Configure block to setup configuration
|
32
|
+
#
|
33
|
+
# Afterpay.configure do |conf|
|
34
|
+
# conf.app_id = <app_id>
|
35
|
+
# conf.secret = <secret>
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
def self.configure
|
39
|
+
self.config ||= Config.new
|
40
|
+
yield(config) if block_given?
|
41
|
+
config.fetch_remote_config unless config.skip_remote_config
|
42
|
+
config
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.env
|
46
|
+
config.env
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
class Address
|
5
|
+
attr_accessor :name, :line_1, :line_2, :area_1, :area_2, :region, :postcode, :country, :phone
|
6
|
+
|
7
|
+
def initialize(attributes = {})
|
8
|
+
@name = attributes[:name]
|
9
|
+
@line_1 = attributes[:line_1] || ""
|
10
|
+
@line_2 = attributes[:line_2] || ""
|
11
|
+
@area_1 = attributes[:area_1] || ""
|
12
|
+
@area_2 = attributes[:area_2] || ""
|
13
|
+
@region = attributes[:region] || ""
|
14
|
+
@postcode = attributes[:postcode]
|
15
|
+
@country = attributes[:country] || "AU"
|
16
|
+
@phone = attributes[:phone]
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{
|
21
|
+
name: name,
|
22
|
+
line1: line_1,
|
23
|
+
line2: line_2,
|
24
|
+
area_1: area_1,
|
25
|
+
area_2: area_2,
|
26
|
+
region: region,
|
27
|
+
postcode: postcode.to_s,
|
28
|
+
countryCode: country,
|
29
|
+
phoneNumber: phone.to_s
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.from_response(response)
|
34
|
+
return nil if response.nil?
|
35
|
+
|
36
|
+
new(
|
37
|
+
name: response[:name],
|
38
|
+
line_1: response[:line1],
|
39
|
+
line_2: response[:line2],
|
40
|
+
area_1: response[:area1],
|
41
|
+
area_2: response[:area2],
|
42
|
+
region: response[:region],
|
43
|
+
postcode: response[:postcode],
|
44
|
+
country: response[:countryCode],
|
45
|
+
phone: response[:phoneNumber]
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "faraday_middleware"
|
5
|
+
require "base64"
|
6
|
+
require "forwardable"
|
7
|
+
|
8
|
+
module Afterpay
|
9
|
+
# Client object acting as the connection
|
10
|
+
# Enables the Client to call get/post/patch/delete
|
11
|
+
class Client
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
BASE_URL = "https://api.afterpay.com/"
|
15
|
+
SANDBOX_BASE_URL = "https://api.us-sandbox.afterpay.com/v2/"
|
16
|
+
|
17
|
+
class NotFoundError < StandardError; end
|
18
|
+
|
19
|
+
class UnauthorizedError < StandardError; end
|
20
|
+
|
21
|
+
def_delegators :@connection, :get, :put, :post, :delete
|
22
|
+
|
23
|
+
# Decides which URL to use based on env
|
24
|
+
def self.server_url
|
25
|
+
Afterpay.env == "sandbox" ? SANDBOX_BASE_URL : BASE_URL
|
26
|
+
end
|
27
|
+
|
28
|
+
# Auth requires format to be Base64 encoded
|
29
|
+
# `<app_id>:<secret>`
|
30
|
+
def self.auth_token
|
31
|
+
auth_str = "#{Afterpay.config.app_id}:#{Afterpay.config.secret}"
|
32
|
+
Base64.strict_encode64(auth_str)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(connection = nil)
|
36
|
+
@connection = connection || default_connection
|
37
|
+
end
|
38
|
+
|
39
|
+
# The connection object
|
40
|
+
def default_connection
|
41
|
+
# Use local thread to keep connection open to make use of connection reuse.
|
42
|
+
Thread.current[:afterpay_default_connection] ||=
|
43
|
+
Faraday.new(url: self.class.server_url) do |conn|
|
44
|
+
conn.use ErrorMiddleware if Afterpay.config.raise_errors
|
45
|
+
conn.authorization "Basic", self.class.auth_token
|
46
|
+
# conn.headers["User-Agent"] = Afterpay.config.user_agent_header if Afterpay.config.user_agent_header.present?
|
47
|
+
|
48
|
+
conn.request :json
|
49
|
+
conn.response :json, content_type: "application/json", parser_options: { symbolize_names: true }
|
50
|
+
conn.adapter Faraday.default_adapter
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Error middleware for Faraday to raise Afterpay connection errors
|
56
|
+
class ErrorMiddleware
|
57
|
+
def initialize(app)
|
58
|
+
@app = app
|
59
|
+
end
|
60
|
+
|
61
|
+
def call(env)
|
62
|
+
@app.call(env).on_complete do
|
63
|
+
case env[:status]
|
64
|
+
when 404
|
65
|
+
raise Client::NotFoundError, env.body[:message]
|
66
|
+
when 401
|
67
|
+
raise Client::UnauthorizedError, env.body[:message]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
class Config
|
5
|
+
attr_accessor :app_id, :secret, :env, :raise_errors,
|
6
|
+
:type, :maximum_amount, :minimum_amount,
|
7
|
+
:description, :currency, :skip_remote_config,
|
8
|
+
:user_agent_header
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@env = "sandbox"
|
12
|
+
@raise_errors = true
|
13
|
+
@skip_remote_config = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Called only after app_id and secred is set
|
17
|
+
def fetch_remote_config
|
18
|
+
response_body = Afterpay.client.get("/v2/configuration").body
|
19
|
+
@minimum_amount = response_body.dig(:minimumAmount, :amount).to_f
|
20
|
+
@maximum_amount = response_body.dig(:maximumAmount, :amount).to_f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
class Consumer
|
5
|
+
attr_accessor :email, :phone, :first_name, :last_name
|
6
|
+
|
7
|
+
def initialize(email:, phone:, first_name:, last_name:)
|
8
|
+
@email = email
|
9
|
+
@phone = phone
|
10
|
+
@first_name = first_name
|
11
|
+
@last_name = last_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
{
|
16
|
+
phoneNumber: phone,
|
17
|
+
givenNames: first_name,
|
18
|
+
surname: last_name,
|
19
|
+
email: email
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Builds Consumer from response
|
24
|
+
def self.from_response(response)
|
25
|
+
return nil if response.nil?
|
26
|
+
|
27
|
+
new(
|
28
|
+
email: response[:email],
|
29
|
+
first_name: response[:givenNames],
|
30
|
+
last_name: response[:surname],
|
31
|
+
phone: response[:phoneNumber]
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|