afterpay-sdk 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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