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