afterpay-sdk 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Bundler naming convention
4
+ # This gem is called afterpay_ruby
5
+ # thus bundler will require afterpay_ruby
6
+ require "afterpay"
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