simpal 0.1.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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Simpal
2
+
3
+ A simple, lightweight wrapper around PayPal's REST API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'simpal'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install simpal
20
+
21
+ ## Usage
22
+
23
+ First, create a client ID and secret from the [Applications](https://developer.paypal.com/developer/applications) page
24
+ of your PayPal account dashboard.
25
+
26
+ Then, create a client which uses these credentials:
27
+
28
+ ```ruby
29
+ Simpal.client = Simpal::Client.new(
30
+ client_id: 'CLIENT_ID',
31
+ client_secret: 'CLIENT_SECRET',
32
+ sandbox: true
33
+ )
34
+ ```
35
+
36
+ The value of **Simpal.client** is to make requests, unless the **client** attribute is specified on a per-request basis.
37
+ The **sandbox** attribute defaults to **false** if omitted.
38
+
39
+ Once the client has been setup, it's easy to retrieve an order:
40
+
41
+ ```ruby
42
+ Simpal::Order.retrieve('ORDER_ID')
43
+ ```
44
+
45
+ ## Development
46
+
47
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
48
+
49
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
+
51
+ ## Contributing
52
+
53
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nialtoservices/simpal. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nialtoservices/simpal/blob/main/CODE_OF_CONDUCT.md).
54
+
55
+ ## License
56
+
57
+ The gem is available as open source under the terms of the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
58
+
59
+ ## Code of Conduct
60
+
61
+ Everyone interacting in the Simpal project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/simpal/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'simpal'
6
+ require 'pry'
7
+
8
+ Pry.start
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/lib/simpal.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'json'
6
+ require 'singleton'
7
+ require 'time'
8
+
9
+ require_relative 'simpal/constants'
10
+ require_relative 'simpal/middleware/authorization'
11
+ require_relative 'simpal/middleware/headers'
12
+ require_relative 'simpal/client_error'
13
+ require_relative 'simpal/client'
14
+ require_relative 'simpal/pay_pal_object'
15
+ require_relative 'simpal/api/orders'
16
+ require_relative 'simpal/api/payments/authorizations'
17
+ require_relative 'simpal/api/payments/captures'
18
+ require_relative 'simpal/api/payments/refunds'
19
+ require_relative 'simpal/order'
20
+ require_relative 'simpal/payment/authorization'
21
+ require_relative 'simpal/payment/capture'
22
+ require_relative 'simpal/payment/refund'
23
+
24
+ # A simple, lightweight wrapper around PayPal's REST API.
25
+ #
26
+ module Simpal
27
+ class << self
28
+ # @return [Simpal::Client] The default client to use when performing API requests.
29
+ #
30
+ attr_accessor :client
31
+
32
+ # Returns the API client to use for a set of request parameters.
33
+ #
34
+ # @param client [Simpal::Client, nil] The preferred client, or `nil`.
35
+ # @return [Simpal::Client] The client to make the request with.
36
+ # @raise [Simpal::ClientError] When an acceptable `Simpal::Client` can't be found.
37
+ #
38
+ def client_for(client)
39
+ client ||= self.client
40
+ return client if client.is_a?(Simpal::Client)
41
+
42
+ raise ClientError, 'API client is missing. Did you forget to set `Simpal.client`?'
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ module API
5
+ # @see https://developer.paypal.com/docs/api/orders/v2/
6
+ #
7
+ module Orders
8
+ # Create an order.
9
+ #
10
+ # @param params [Hash] The parameters for the create request.
11
+ # @param headers [Hash] The custom headers to add to the request.
12
+ # @param client [Simpal::Client] The API client to make the request with.
13
+ # @return [Hash] A Hash representing the order.
14
+ #
15
+ def self.create(params = {}, headers: {}, client: nil)
16
+ client = Simpal.client_for(client)
17
+ response = client.connection.post('/v2/checkout/orders', params, headers)
18
+ response.body
19
+ end
20
+
21
+ # Update an order.
22
+ #
23
+ # @param id [String] The ID of an existing order.
24
+ # @param params [Array<Hash>] The collection of patches to apply to the order.
25
+ # @param headers [Hash] The custom headers to add to the request.
26
+ # @param client [Simpal::Client] The API client to make the request with.
27
+ # @return [Hash] An empty hash.
28
+ #
29
+ def self.update(id, params = [], headers: {}, client: nil)
30
+ client = Simpal.client_for(client)
31
+ response = client.connection.patch("/v2/checkout/orders/#{id}", params, headers)
32
+ response.body
33
+ end
34
+
35
+ # Retrieve an order.
36
+ #
37
+ # @param id [String] The ID of an existing order.
38
+ # @param headers [Hash] The custom headers to add to the request.
39
+ # @param client [Simpal::Client] The API client to make the request with.
40
+ # @return [Hash] A Hash representing the order.
41
+ #
42
+ def self.retrieve(id, headers: {}, client: nil)
43
+ client = Simpal.client_for(client)
44
+ response = client.connection.get("/v2/checkout/orders/#{id}", headers)
45
+ response.body
46
+ end
47
+
48
+ # Authorize the payment for an order.
49
+ #
50
+ # @param id [String] The ID of an existing order.
51
+ # @param params [Hash] The parameters for the authorize request.
52
+ # @param headers [Hash] The custom headers to add to the request.
53
+ # @param client [Simpal::Client] The API client to make the request with.
54
+ # @return [Hash] A Hash representing the order.
55
+ #
56
+ def self.authorize(id, params = {}, headers: {}, client: nil)
57
+ client = Simpal.client_for(client)
58
+ response = client.connection.post("/v2/checkout/orders/#{id}/authorize", params, headers)
59
+ response.body
60
+ end
61
+
62
+ # Capture the payment for an order.
63
+ #
64
+ # @param id [String] The ID of an existing order.
65
+ # @param params [Hash] The parameters for the capture request.
66
+ # @param headers [Hash] The custom headers to add to the request.
67
+ # @param client [Simpal::Client] The API client to make the request with.
68
+ # @return [Hash] A Hash representing the order.
69
+ #
70
+ def self.capture(id, params = {}, headers: {}, client: nil)
71
+ client = Simpal.client_for(client)
72
+ response = client.connection.post("/v2/checkout/orders/#{id}/capture", params, headers)
73
+ response.body
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ module API
5
+ # @see https://developer.paypal.com/docs/api/payments/v2/
6
+ #
7
+ module Payments
8
+ # @see https://developer.paypal.com/docs/api/payments/v2/#authorizations
9
+ #
10
+ module Authorizations
11
+ # Retrieve an authorized payment.
12
+ #
13
+ # @param id [String] The ID of an authorized payment.
14
+ # @param headers [Hash] The custom headers to add to the request.
15
+ # @param client [Simpal::Client] The API client to make the request with.
16
+ # @return [Hash] A Hash representing the authorized payment.
17
+ #
18
+ def self.retrieve(id, headers: {}, client: nil)
19
+ client = Simpal.client_for(client)
20
+ response = client.connection.get("/v2/payments/authorizations/#{id}", headers)
21
+ response.body
22
+ end
23
+
24
+ # Capture an authorized payment.
25
+ #
26
+ # @param id [String] The ID of an authorized payment.
27
+ # @param params [Hash] The parameters for the capture request.
28
+ # @param headers [Hash] The custom headers to add to the request.
29
+ # @param client [Simpal::Client] The API client to make the request with.
30
+ # @return [Hash] A Hash representing the captured payment.
31
+ #
32
+ def self.capture(id, params = {}, headers: {}, client: nil)
33
+ client = Simpal.client_for(client)
34
+ response = client.connection.post("/v2/payments/authorizations/#{id}/capture", params, headers)
35
+ response.body
36
+ end
37
+
38
+ # Reauthorize an authorized payment.
39
+ #
40
+ # @param id [String] The ID of an authorized payment.
41
+ # @param params [Hash] The parameters for the reauthorize request.
42
+ # @param headers [Hash] The custom headers to add to the request.
43
+ # @param client [Simpal::Client] The API client to make the request with.
44
+ # @return [Hash] A Hash representing the reauthorized payment.
45
+ #
46
+ def self.reauthorize(id, params = {}, headers: {}, client: nil)
47
+ client = Simpal.client_for(client)
48
+ response = client.connection.post("/v2/payments/authorizations/#{id}/reauthorize", params, headers)
49
+ response.body
50
+ end
51
+
52
+ # Void an authorized payment.
53
+ #
54
+ # @param id [String] The ID of an authorized payment.
55
+ # @param headers [Hash] The custom headers to add to the request.
56
+ # @param client [Simpal::Client] The API client to make the request with.
57
+ # @return [Hash] An empty hash.
58
+ #
59
+ def self.void(id, headers: {}, client: nil)
60
+ client = Simpal.client_for(client)
61
+ response = client.connection.post("/v2/payments/authorizations/#{id}/void", nil, headers)
62
+ response.body
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ module API
5
+ # @see https://developer.paypal.com/docs/api/payments/v2/
6
+ #
7
+ module Payments
8
+ # @see https://developer.paypal.com/docs/api/payments/v2/#captures
9
+ #
10
+ module Captures
11
+ # Retrieve a captured payment.
12
+ #
13
+ # @param id [String] The ID of a captured payment.
14
+ # @param headers [Hash] The custom headers to add to the request.
15
+ # @param client [Simpal::Client] The API client to make the request with.
16
+ # @return [Hash] A Hash representing the captured payment.
17
+ #
18
+ def self.retrieve(id, headers: {}, client: nil)
19
+ client = Simpal.client_for(client)
20
+ response = client.connection.get("/v2/payments/captures/#{id}", headers)
21
+ response.body
22
+ end
23
+
24
+ # Refund a captured payment.
25
+ #
26
+ # @param id [String] The ID of a captured payment.
27
+ # @param params [Hash] The parameters for the refund request.
28
+ # @param headers [Hash] The custom headers to add to the request.
29
+ # @param client [Simpal::Client] The API client to make the request with.
30
+ # @return [Hash] A Hash representing the captured payment.
31
+ #
32
+ def self.refund(id, params = {}, headers: {}, client: nil)
33
+ client = Simpal.client_for(client)
34
+ response = client.connection.post("/v2/payments/captures/#{id}/refund", params, headers)
35
+ response.body
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ module API
5
+ # @see https://developer.paypal.com/docs/api/payments/v2/
6
+ #
7
+ module Payments
8
+ # @see https://developer.paypal.com/docs/api/payments/v2/#refunds
9
+ #
10
+ module Refunds
11
+ # Retrieve a refunded payment.
12
+ #
13
+ # @param id [String] The ID of a refunded payment.
14
+ # @param headers [Hash] The custom headers to add to the request.
15
+ # @param client [Simpal::Client] The API client to make the request with.
16
+ # @return [Hash] A Hash representing the refunded payment.
17
+ #
18
+ def self.retrieve(id, headers: {}, client: nil)
19
+ client = Simpal.client_for(client)
20
+ response = client.connection.get("/v2/payments/refunds/#{id}", headers)
21
+ response.body
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ # Provides an API client for performing requests under an account within a particular environment.
5
+ #
6
+ # PayPal API credentials can be obtained from the following URL:
7
+ # => https://developer.paypal.com/developer/applications
8
+ #
9
+ class Client
10
+ # @return [String] The 'Client ID' from your PayPal account dashboard.
11
+ #
12
+ attr_reader :client_id
13
+
14
+ # @return [String] The 'Client Secret' from your PayPal account dashboard.
15
+ #
16
+ attr_reader :client_secret
17
+
18
+ # @return [Boolean] `true` when using the sandbox API, `false` when using the production API.
19
+ #
20
+ attr_reader :sandbox
21
+
22
+ # Create a new API client.
23
+ #
24
+ # @parameter client_id [String] The 'Client ID' from your PayPal account dashboard.
25
+ # @parameter client_secret [String] The 'Client Secret' from your PayPal account dashboard.
26
+ # @parameter sandbox [Boolean] `true` when using the sandbox API, `false` when using the live API.
27
+ #
28
+ def initialize(client_id:, client_secret:, sandbox: false)
29
+ @client_id = client_id
30
+ @client_secret = client_secret
31
+ @sandbox = sandbox
32
+ end
33
+
34
+ # @return [String] The URL for the PayPal API service.
35
+ #
36
+ def service_url
37
+ if sandbox
38
+ 'https://api-m.sandbox.paypal.com'
39
+ else
40
+ 'https://api-m.paypal.com'
41
+ end
42
+ end
43
+
44
+ # @return [Hash] The default headers, which should be merged into every API request.
45
+ #
46
+ def headers
47
+ { 'User-Agent' => "Simpal/#{Simpal::VERSION}" }
48
+ end
49
+
50
+ # @return [Faraday::Connection] The connection to use when executing an API request.
51
+ #
52
+ def connection
53
+ @connection ||= Faraday.new(service_url, headers: headers) do |connection|
54
+ connection.use Faraday::Response::RaiseError
55
+ connection.use Simpal::Middleware::Headers
56
+ connection.use FaradayMiddleware::EncodeJson
57
+ connection.use FaradayMiddleware::ParseJson
58
+ connection.use Simpal::Middleware::Authorization, self
59
+ connection.adapter Faraday.default_adapter
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ # Indicates an error related to a `Simpal::Client`.
5
+ #
6
+ class ClientError < StandardError; end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simpal
4
+ module Middleware
5
+ # Requests an OAuth2 access token for a `Simpal::Client`, adding the resulting access token into each request.
6
+ #
7
+ class Authorization < Faraday::Middleware
8
+ # @return [Simpal::Client] The client which we're handling the 'Authorization' header for.
9
+ #
10
+ attr_reader :client
11
+
12
+ # @return [String] The access token to include in each request.
13
+ #
14
+ attr_reader :access_token
15
+
16
+ # @return [Time] The time at which the access token expires.
17
+ #
18
+ attr_reader :access_token_expires_at
19
+
20
+ def initialize(app, client)
21
+ super(app)
22
+ @client = client
23
+ end
24
+
25
+ def call(request_env)
26
+ retryable = true
27
+ refresh_access_token
28
+
29
+ begin
30
+ request_env[:request_headers].merge!('Authorization' => "Bearer #{access_token}")
31
+ super(request_env)
32
+ rescue Faraday::UnauthorizedError
33
+ raise unless retryable
34
+
35
+ retryable = false
36
+ refresh_access_token!
37
+ retry
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def connection
44
+ @connection ||= Faraday.new(client.service_url, headers: client.headers) do |connection|
45
+ connection.use Faraday::Request::UrlEncoded
46
+ connection.use Faraday::Response::RaiseError
47
+ connection.use FaradayMiddleware::ParseJson
48
+ connection.basic_auth(client.client_id, client.client_secret)
49
+ end
50
+ end
51
+
52
+ def refresh_access_token!
53
+ response = connection.post('/v1/oauth2/token', grant_type: :client_credentials)
54
+ @access_token = response.body['access_token']
55
+ @access_token_expires_at = Time.httpdate(response.headers['date']) + response.body['expires_in']
56
+ end
57
+
58
+ def refresh_access_token
59
+ return if access_token && access_token_expires_at && (access_token_expires_at - Time.now) >= 1
60
+
61
+ refresh_access_token!
62
+ end
63
+ end
64
+ end
65
+ end