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.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +64 -0
- data/.gitignore +47 -0
- data/.rspec +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +5 -0
- data/LICENSE +201 -0
- data/README.md +61 -0
- data/Rakefile +10 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/lib/simpal.rb +45 -0
- data/lib/simpal/api/orders.rb +77 -0
- data/lib/simpal/api/payments/authorizations.rb +67 -0
- data/lib/simpal/api/payments/captures.rb +40 -0
- data/lib/simpal/api/payments/refunds.rb +26 -0
- data/lib/simpal/client.rb +63 -0
- data/lib/simpal/client_error.rb +7 -0
- data/lib/simpal/constants.rb +5 -0
- data/lib/simpal/middleware/authorization.rb +65 -0
- data/lib/simpal/middleware/headers.rb +32 -0
- data/lib/simpal/order.rb +75 -0
- data/lib/simpal/pay_pal_object.rb +92 -0
- data/lib/simpal/payment/authorization.rb +61 -0
- data/lib/simpal/payment/capture.rb +36 -0
- data/lib/simpal/payment/refund.rb +24 -0
- data/simpal.gemspec +39 -0
- metadata +186 -0
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
data/bin/console
ADDED
data/bin/setup
ADDED
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,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
|