affirm 0.0.1
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/README.md +125 -0
- data/affirm.gemspec +18 -0
- data/lib/affirm/api.rb +20 -0
- data/lib/affirm/charges.rb +103 -0
- data/lib/affirm/client.rb +69 -0
- data/lib/affirm/errors/authentication_error.rb +3 -0
- data/lib/affirm/errors/error.rb +23 -0
- data/lib/affirm/errors/resource_not_found_error.rb +3 -0
- data/lib/affirm/errors/server_error.rb +3 -0
- data/lib/affirm/response.rb +39 -0
- data/lib/affirm.rb +11 -0
- data/spec/charges_spec.rb +170 -0
- data/spec/client_spec.rb +141 -0
- data/spec/fixtures/charges/authorize.json +45 -0
- data/spec/fixtures/charges/capture.json +8 -0
- data/spec/fixtures/charges/get.json +45 -0
- data/spec/fixtures/charges/invalid_request.json +5 -0
- data/spec/fixtures/charges/refund.json +8 -0
- data/spec/fixtures/charges/update.json +7 -0
- data/spec/fixtures/charges/void.json +6 -0
- data/spec/spec_helper.rb +38 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 689c049744aaff6ed260bd43c0bcdf0219e6689a
|
4
|
+
data.tar.gz: 90142f082b195d4b12b593ba8dbe8f0a9d0a01b1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6b0c3a679864712c47a0053eff6135b0fc2ba4c6a24cb49f9d66b019c95b1ca3b724f2eff90d0a4a8e07f46a5654c8667b44a9e87edbb3d817995b25eca1369b
|
7
|
+
data.tar.gz: 0836887d3303dbb09d741b13e8894b5f6ec7c987c8218cc1a744e053b4b99a86fb440a14ecb93ba9aabb80051fab200833c211ff7d0e3071d54c35e6c84110a4
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# affirm-ruby
|
2
|
+
Ruby client library for integrating with Affirm financing (https://www.affirm.com/)
|
3
|
+
|
4
|
+
Requires Ruby 2.1 or greater.
|
5
|
+
|
6
|
+
[](https://travis-ci.org/reverbdotcom/affirm-ruby)
|
7
|
+
|
8
|
+
## Install
|
9
|
+
Add to your gemfile:
|
10
|
+
```ruby
|
11
|
+
gem 'affirm-ruby'
|
12
|
+
```
|
13
|
+
and `bundle install`.
|
14
|
+
|
15
|
+
*Note*: This gem is not yet registered with Rubygems. In the meantime, you can use the 'git' option in your Gemfile.
|
16
|
+
|
17
|
+
## Initialize
|
18
|
+
Initialize the client with your credentials (if you're using rails, this goes in `config/initializers`).
|
19
|
+
```ruby
|
20
|
+
Affirm::API.public_key = "xxx"
|
21
|
+
Affirm::API.secret_key = "xxx"
|
22
|
+
Affirm::API.api_url = "https://sandbox.affirm.com/api/v2/"
|
23
|
+
```
|
24
|
+
|
25
|
+
## Charges
|
26
|
+
The charges resource can be accessed through the api class:
|
27
|
+
```ruby
|
28
|
+
Affirm::API.charges
|
29
|
+
```
|
30
|
+
|
31
|
+
### Authorizing a charge
|
32
|
+
```ruby
|
33
|
+
Affirm::API.charges.authorize(checkout_token: "token")
|
34
|
+
Affirm::API.charges.authorize!(checkout_token: "token") # raises Affirm::Error on failure
|
35
|
+
```
|
36
|
+
|
37
|
+
### Reading a charge
|
38
|
+
```ruby
|
39
|
+
Affirm::API.charges.get(charge_id: "abcd")
|
40
|
+
```
|
41
|
+
|
42
|
+
### Capturing a charge
|
43
|
+
Optionally takes an `order_id`, `shipping_carrier`, and `shipping_confirmation`.
|
44
|
+
```ruby
|
45
|
+
Affirm::API.charges.capture(charge_id: "abcd")
|
46
|
+
Affirm::API.charges.capture(charge_id: "abcd", order_id: "1234", shipping_carrier: "USPS", shipping_confirmation: "ABCD1234")
|
47
|
+
Affirm::API.charges.capture!(charge_id: "abcd") # raises Affirm::Error on failure
|
48
|
+
```
|
49
|
+
|
50
|
+
### Voiding a charge
|
51
|
+
```ruby
|
52
|
+
Affirm::API.charges.void(charge_id: "abcd")
|
53
|
+
Affirm::API.charges.void!(charge_id: "abcd") # raises Affirm::Error on failure
|
54
|
+
```
|
55
|
+
|
56
|
+
### Refunding a charge
|
57
|
+
Optionally takes an `amount` to refund (in cents).
|
58
|
+
```ruby
|
59
|
+
Affirm::API.charges.refund(charge_id: "abcd")
|
60
|
+
Affirm::API.charges.refund(charge_id: "abcd", amount: 5000)
|
61
|
+
Affirm::API.charges.refund!(charge_id: "abcd") # raises Affirm::Error on failure
|
62
|
+
```
|
63
|
+
|
64
|
+
### Updating tracking fields
|
65
|
+
Optionally takes an `order_id`, `shipping_carrier`, and `shipping_confirmation`.
|
66
|
+
```ruby
|
67
|
+
Affirm::API.charges.update(charge_id: "abcd", order_id: "1234", shipping_carrier: "USPS", shipping_confirmation: "ABCD1234")
|
68
|
+
Affirm::API.charges.update!(charge_id: "abcd", order_id: "1234") # raises Affirm::Error on failure
|
69
|
+
```
|
70
|
+
|
71
|
+
## Responses
|
72
|
+
On successful api calls, the response json is
|
73
|
+
```ruby
|
74
|
+
response = Affirm::API.charges.get(charge_id: "abcd")
|
75
|
+
|
76
|
+
response.success? # => true
|
77
|
+
response.error? # => false
|
78
|
+
|
79
|
+
response.status_code # => 200
|
80
|
+
|
81
|
+
response.body # => hash of values
|
82
|
+
response.body["id"] # eg "abcd"
|
83
|
+
response.body["details"]["shipping_amount"] # eg 400
|
84
|
+
```
|
85
|
+
|
86
|
+
### Error responses
|
87
|
+
Unsuccessful responses have a few methods built in:
|
88
|
+
```ruby
|
89
|
+
response.code # eg "auth-declined"
|
90
|
+
response.type # eg "invalid_request"
|
91
|
+
response.message # eg "Invalid phone number format"
|
92
|
+
response.field # eg "shipping_address.phone"
|
93
|
+
```
|
94
|
+
See https://docs.affirm.com/v2/api/errors/#error-object.
|
95
|
+
|
96
|
+
### Exceptions
|
97
|
+
Exceptions are raised for 5xx, 404 and 401 responses, yielding an `Affirm::ServerError`,
|
98
|
+
`Affirm::ResourceNotFoundError` and `Affirm::AuthenticationError`, respectively. These are subclassed from
|
99
|
+
`Affirm::Error`.
|
100
|
+
```ruby
|
101
|
+
begin
|
102
|
+
Affirm::API.charges.authorize(checkout_token: "token")
|
103
|
+
rescue Affirm::ServerError => e
|
104
|
+
Logger.info e.code
|
105
|
+
Logger.info e.message
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
## Running specs
|
110
|
+
After bundling, run `bundle exec rspec` to run the tests.
|
111
|
+
|
112
|
+
## License
|
113
|
+
Copyright 2015 Reverb.com, LLC
|
114
|
+
|
115
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
116
|
+
you may not use this file except in compliance with the License.
|
117
|
+
You may obtain a copy of the License at
|
118
|
+
|
119
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
120
|
+
|
121
|
+
Unless required by applicable law or agreed to in writing, software
|
122
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
123
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
124
|
+
See the License for the specific language governing permissions and
|
125
|
+
limitations under the License.
|
data/affirm.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "affirm"
|
3
|
+
s.summary = "Affirm Ruby Client Library"
|
4
|
+
s.description = "Ruby client library for integrating with Affirm financing payments"
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.license = "Apache License Version 2.0"
|
7
|
+
s.author = "Reverb.com"
|
8
|
+
s.email = "dev@reverb.com"
|
9
|
+
s.has_rdoc = false
|
10
|
+
s.files = Dir.glob ["README.md", "lib/**/*.{rb}", "spec/**/*", "*.gemspec"]
|
11
|
+
|
12
|
+
s.add_dependency "typhoeus"
|
13
|
+
|
14
|
+
s.add_development_dependency "bundler"
|
15
|
+
s.add_development_dependency "rake"
|
16
|
+
s.add_development_dependency "rspec", "3.2.0"
|
17
|
+
s.add_development_dependency "webmock", "1.21.0"
|
18
|
+
end
|
data/lib/affirm/api.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Affirm
|
2
|
+
class API
|
3
|
+
class << self
|
4
|
+
attr_accessor :public_key, :secret_key, :api_url
|
5
|
+
@@client = nil
|
6
|
+
|
7
|
+
def charges
|
8
|
+
@@charges ||= Affirm::Charges.new(client)
|
9
|
+
end
|
10
|
+
|
11
|
+
def client
|
12
|
+
@@client ||= Affirm::Client.new(
|
13
|
+
public_key: public_key,
|
14
|
+
secret_key: secret_key,
|
15
|
+
api_url: api_url
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Affirm
|
2
|
+
class Charges
|
3
|
+
def initialize(client)
|
4
|
+
@client = client
|
5
|
+
@namespace = "charges"
|
6
|
+
end
|
7
|
+
|
8
|
+
######
|
9
|
+
# GET
|
10
|
+
#
|
11
|
+
def get(charge_id:)
|
12
|
+
make_request(charge_id, :get)
|
13
|
+
end
|
14
|
+
|
15
|
+
######
|
16
|
+
# AUTHORIZE
|
17
|
+
#
|
18
|
+
# checkout_token - (required) string. The charge token passed through the confirmation response.
|
19
|
+
def authorize(checkout_token:)
|
20
|
+
make_request("/", :post, checkout_token: checkout_token)
|
21
|
+
end
|
22
|
+
|
23
|
+
def authorize!(checkout_token:)
|
24
|
+
response = authorize(checkout_token: checkout_token)
|
25
|
+
assert_success(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
######
|
29
|
+
# CAPTURE
|
30
|
+
#
|
31
|
+
# order_id - (optional) string. Your internal order id. This is stored for your own future reference.
|
32
|
+
# shipping_carrier - (optional) string. The shipping carrier used to ship the items in the charge.
|
33
|
+
# shipping_confirmation - (optional) string. The shipping confirmation for the shipment.
|
34
|
+
def capture(charge_id:, order_id: nil, shipping_carrier: nil, shipping_confirmation: nil)
|
35
|
+
make_request("#{charge_id}/capture", :post, {
|
36
|
+
order_id: order_id,
|
37
|
+
shipping_carrier: shipping_carrier,
|
38
|
+
shipping_confirmation: shipping_confirmation
|
39
|
+
})
|
40
|
+
end
|
41
|
+
|
42
|
+
def capture!(charge_id:, order_id: nil, shipping_carrier: nil, shipping_confirmation: nil)
|
43
|
+
response = capture(charge_id: charge_id, order_id: order_id, shipping_carrier: shipping_carrier, shipping_confirmation: shipping_confirmation)
|
44
|
+
assert_success(response)
|
45
|
+
end
|
46
|
+
|
47
|
+
######
|
48
|
+
# VOID
|
49
|
+
#
|
50
|
+
def void(charge_id:)
|
51
|
+
make_request("#{charge_id}/void", :post)
|
52
|
+
end
|
53
|
+
|
54
|
+
def void!(charge_id:)
|
55
|
+
response = void(charge_id: charge_id)
|
56
|
+
assert_success(response)
|
57
|
+
end
|
58
|
+
|
59
|
+
######
|
60
|
+
# REFUND
|
61
|
+
#
|
62
|
+
# amount - (optional) integer or null. The amount to refund in cents. The default amount is the remaining balance on the charge.
|
63
|
+
def refund(charge_id:, amount: nil)
|
64
|
+
make_request("#{charge_id}/refund", :post, amount: amount)
|
65
|
+
end
|
66
|
+
|
67
|
+
def refund!(charge_id:, amount: nil)
|
68
|
+
response = refund(charge_id: charge_id, amount: amount)
|
69
|
+
assert_success(response)
|
70
|
+
end
|
71
|
+
|
72
|
+
######
|
73
|
+
# UPDATE
|
74
|
+
#
|
75
|
+
# order_id - (optional) string. Your internal order id. This is stored for your own future reference.
|
76
|
+
# shipping_carrier - (optional) string. The shipping carrier used to ship the items in the charge.
|
77
|
+
# shipping_confirmation - (optional) string. The shipping confirmation for the shipment.
|
78
|
+
def update(charge_id:, order_id: nil, shipping_carrier: nil, shipping_confirmation: nil)
|
79
|
+
make_request("#{charge_id}/update", :post, {
|
80
|
+
order_id: order_id,
|
81
|
+
shipping_carrier: shipping_carrier,
|
82
|
+
shipping_confirmation: shipping_confirmation
|
83
|
+
})
|
84
|
+
end
|
85
|
+
|
86
|
+
def update!(charge_id:, order_id: nil, shipping_carrier: nil, shipping_confirmation: nil)
|
87
|
+
response = update(charge_id: charge_id, order_id: order_id, shipping_carrier: shipping_carrier, shipping_confirmation: shipping_confirmation)
|
88
|
+
assert_success(response)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def make_request(path, method, data={})
|
94
|
+
@client.make_request(File.join(@namespace, path), method, data)
|
95
|
+
end
|
96
|
+
|
97
|
+
def assert_success(response)
|
98
|
+
raise Affirm::Error.from_response(response) if response.error?
|
99
|
+
|
100
|
+
response
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Affirm
|
2
|
+
class Client
|
3
|
+
def initialize(public_key:, secret_key:, api_url: Affirm::API.api_url)
|
4
|
+
@public_key = public_key
|
5
|
+
@secret_key = secret_key
|
6
|
+
@api_url = api_url
|
7
|
+
end
|
8
|
+
|
9
|
+
def post(path, data={})
|
10
|
+
make_request(path, :post, data)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(path, data={})
|
14
|
+
make_request(path, :get, data)
|
15
|
+
end
|
16
|
+
|
17
|
+
def make_request(path, method, data={})
|
18
|
+
response = Typhoeus::Request.new(
|
19
|
+
url(path),
|
20
|
+
method: method,
|
21
|
+
body: data.to_json,
|
22
|
+
headers: affirm_headers(data),
|
23
|
+
userpwd: user_password
|
24
|
+
).run
|
25
|
+
|
26
|
+
affirm_response = parse_response(response)
|
27
|
+
|
28
|
+
handle_errors(affirm_response)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_response(response)
|
34
|
+
Affirm::Response.new(
|
35
|
+
success: response.success?,
|
36
|
+
status_code: response.code,
|
37
|
+
body: response.body
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def handle_errors(affirm_response)
|
42
|
+
if affirm_response.status_code == 401
|
43
|
+
raise_error(Affirm::AuthenticationError, affirm_response)
|
44
|
+
elsif affirm_response.status_code == 404
|
45
|
+
raise_error(Affirm::ResourceNotFoundError, affirm_response)
|
46
|
+
elsif affirm_response.status_code >= 500
|
47
|
+
raise_error(Affirm::ServerError, affirm_response)
|
48
|
+
end
|
49
|
+
|
50
|
+
affirm_response
|
51
|
+
end
|
52
|
+
|
53
|
+
def raise_error(error_class, affirm_response)
|
54
|
+
raise error_class.from_response(affirm_response)
|
55
|
+
end
|
56
|
+
|
57
|
+
def affirm_headers(data)
|
58
|
+
{ "Content-Type" => "application/json" } if data.length > 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def user_password
|
62
|
+
"#{@public_key}:#{@secret_key}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def url(path)
|
66
|
+
File.join(@api_url, path)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Affirm
|
2
|
+
class Error < RuntimeError
|
3
|
+
attr_reader :status_code, :code, :message
|
4
|
+
|
5
|
+
def self.from_response(response)
|
6
|
+
new(
|
7
|
+
status_code: response.status_code,
|
8
|
+
code: response.code,
|
9
|
+
message: response.message
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(status_code:, code:, message:)
|
14
|
+
@status_code = status_code
|
15
|
+
@code = code
|
16
|
+
@message = message
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"#{status_code} #{code}: #{message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Affirm
|
2
|
+
class Response
|
3
|
+
attr_reader :status_code
|
4
|
+
|
5
|
+
def initialize(success:, status_code:, body:)
|
6
|
+
@success = success
|
7
|
+
@status_code = status_code.to_i
|
8
|
+
@body = body
|
9
|
+
end
|
10
|
+
|
11
|
+
def success?
|
12
|
+
@success
|
13
|
+
end
|
14
|
+
|
15
|
+
def error?
|
16
|
+
!success?
|
17
|
+
end
|
18
|
+
|
19
|
+
def body
|
20
|
+
JSON.parse(@body)
|
21
|
+
end
|
22
|
+
|
23
|
+
def type
|
24
|
+
body["type"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def code
|
28
|
+
body["code"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def message
|
32
|
+
body["message"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def field
|
36
|
+
body["field"]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/affirm.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'affirm/api'
|
4
|
+
require 'affirm/client'
|
5
|
+
require 'affirm/response'
|
6
|
+
require 'affirm/charges'
|
7
|
+
|
8
|
+
require 'affirm/errors/error'
|
9
|
+
require 'affirm/errors/authentication_error'
|
10
|
+
require 'affirm/errors/resource_not_found_error'
|
11
|
+
require 'affirm/errors/server_error'
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Affirm::Charges do
|
4
|
+
let!(:request) do
|
5
|
+
stub_request(request_method, request_url).to_return(status: response_code, body: response_body)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:response_code) { 200 }
|
9
|
+
|
10
|
+
describe "self.get" do
|
11
|
+
let(:request_method) { :get }
|
12
|
+
let(:request_url) { "#{TEST_URL}/charges/ABCD" }
|
13
|
+
let(:response_body) { load_fixture("charges/get.json") }
|
14
|
+
|
15
|
+
it "is successful" do
|
16
|
+
response = Affirm::API.charges.get(charge_id: "ABCD")
|
17
|
+
response.should be_success
|
18
|
+
response.body["amount"].should == 6100
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "self.authorize" do
|
23
|
+
let(:request_method) { :post }
|
24
|
+
let(:request_url) { "#{TEST_URL}/charges/" }
|
25
|
+
let(:response_body) { load_fixture("charges/authorize.json") }
|
26
|
+
|
27
|
+
it "is successful" do
|
28
|
+
response = Affirm::API.charges.authorize(checkout_token: "token")
|
29
|
+
response.should be_success
|
30
|
+
end
|
31
|
+
|
32
|
+
it "sends the correct body" do
|
33
|
+
Affirm::API.charges.authorize(checkout_token: "token")
|
34
|
+
|
35
|
+
expect(WebMock).to have_requested(request_method, request_url).with(body: {
|
36
|
+
checkout_token: "token"
|
37
|
+
}.to_json)
|
38
|
+
end
|
39
|
+
|
40
|
+
context "bang method" do
|
41
|
+
let(:response_code) { 422 }
|
42
|
+
let(:response_body) { load_fixture("charges/invalid_request.json") }
|
43
|
+
|
44
|
+
it "raises an error on failure" do
|
45
|
+
expect { Affirm::API.charges.authorize!(checkout_token: "token") }.to raise_error(Affirm::Error)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "self.capture" do
|
51
|
+
let(:request_method) { :post }
|
52
|
+
let(:request_url) { "#{TEST_URL}/charges/ABCD/capture" }
|
53
|
+
let(:response_body) { load_fixture("charges/capture.json") }
|
54
|
+
|
55
|
+
it "is successful" do
|
56
|
+
response = Affirm::API.charges.capture(charge_id: "ABCD")
|
57
|
+
response.should be_success
|
58
|
+
end
|
59
|
+
|
60
|
+
it "optionally allows tracking params" do
|
61
|
+
params = { order_id: "order_id", shipping_carrier: "carrier", shipping_confirmation: "confirmation" }
|
62
|
+
Affirm::API.charges.capture({charge_id: "ABCD"}.merge(params))
|
63
|
+
|
64
|
+
expect(WebMock).to have_requested(request_method, request_url).with(body: params.to_json)
|
65
|
+
end
|
66
|
+
|
67
|
+
context "bang method" do
|
68
|
+
let(:response_code) { 422 }
|
69
|
+
let(:response_body) { load_fixture("charges/invalid_request.json") }
|
70
|
+
|
71
|
+
it "raises an error on failure" do
|
72
|
+
expect { Affirm::API.charges.capture!(charge_id: "ABCD") }.to raise_error(Affirm::Error)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "self.void" do
|
78
|
+
let(:request_method) { :post }
|
79
|
+
let(:request_url) { "#{TEST_URL}/charges/ABCD/void" }
|
80
|
+
let(:response_body) { load_fixture("charges/void.json") }
|
81
|
+
|
82
|
+
it "is successful" do
|
83
|
+
response = Affirm::API.charges.void(charge_id: "ABCD")
|
84
|
+
response.should be_success
|
85
|
+
end
|
86
|
+
|
87
|
+
context "bang method" do
|
88
|
+
let(:response_code) { 422 }
|
89
|
+
let(:response_body) { load_fixture("charges/invalid_request.json") }
|
90
|
+
|
91
|
+
it "raises an error on failure" do
|
92
|
+
expect { Affirm::API.charges.void!(charge_id: "ABCD") }.to raise_error(Affirm::Error)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "self.refund" do
|
98
|
+
let(:request_method) { :post }
|
99
|
+
let(:request_url) { "#{TEST_URL}/charges/ABCD/refund" }
|
100
|
+
let(:response_body) { load_fixture("charges/refund.json") }
|
101
|
+
|
102
|
+
it "is successful" do
|
103
|
+
response = Affirm::API.charges.refund(charge_id: "ABCD")
|
104
|
+
response.should be_success
|
105
|
+
end
|
106
|
+
|
107
|
+
context "with amount" do
|
108
|
+
it "sends the correct body" do
|
109
|
+
Affirm::API.charges.refund(charge_id: "ABCD", amount: 100)
|
110
|
+
|
111
|
+
expect(WebMock).to have_requested(request_method, request_url).with(body: {
|
112
|
+
amount: 100
|
113
|
+
}.to_json)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "bang method" do
|
118
|
+
let(:response_code) { 422 }
|
119
|
+
let(:response_body) { load_fixture("charges/invalid_request.json") }
|
120
|
+
|
121
|
+
it "raises an error on failure" do
|
122
|
+
expect { Affirm::API.charges.refund!(charge_id: "ABCD") }.to raise_error(Affirm::Error)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "self.update" do
|
128
|
+
let(:request_method) { :post }
|
129
|
+
let(:request_url) { "#{TEST_URL}/charges/ABCD/update" }
|
130
|
+
let(:response_body) { load_fixture("charges/update.json") }
|
131
|
+
|
132
|
+
it "is successful" do
|
133
|
+
response = Affirm::API.charges.update(charge_id: "ABCD", order_id: "order_id", shipping_carrier: "carrier", shipping_confirmation: "confirmation")
|
134
|
+
response.should be_success
|
135
|
+
end
|
136
|
+
|
137
|
+
it "sends the correct body" do
|
138
|
+
params = { order_id: "order_id", shipping_carrier: "carrier", shipping_confirmation: "confirmation" }
|
139
|
+
Affirm::API.charges.update({charge_id: "ABCD"}.merge(params))
|
140
|
+
|
141
|
+
expect(WebMock).to have_requested(request_method, request_url).with(body: params.to_json)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "doesn't require the params" do
|
145
|
+
response = Affirm::API.charges.update(charge_id: "ABCD")
|
146
|
+
response.should be_success
|
147
|
+
end
|
148
|
+
|
149
|
+
context "bang method" do
|
150
|
+
let(:response_code) { 422 }
|
151
|
+
let(:response_body) { load_fixture("charges/invalid_request.json") }
|
152
|
+
|
153
|
+
it "raises an error on failure" do
|
154
|
+
expect { Affirm::API.charges.update!(charge_id: "ABCD") }.to raise_error(Affirm::Error)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "with specified client" do
|
160
|
+
let(:client) { Affirm::Client.new(public_key: "other_public", secret_key: "other_secret") }
|
161
|
+
|
162
|
+
let(:request_method) { :get }
|
163
|
+
let(:response_body) { load_fixture("charges/get.json") }
|
164
|
+
let(:request_url) { /.*other_public:other_secret.*/ }
|
165
|
+
|
166
|
+
it "uses the client's creds" do
|
167
|
+
described_class.new(client).get(charge_id: "ABCD")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Affirm::Client do
|
4
|
+
let(:client) { described_class.new(public_key: public_key, secret_key: secret_key, api_url: api_url) }
|
5
|
+
let(:public_key) { "public_key" }
|
6
|
+
let(:secret_key) { "secret_key" }
|
7
|
+
let(:api_url) { "https://testaffirm.com" }
|
8
|
+
|
9
|
+
let(:request) { double("request", run: response) }
|
10
|
+
let(:response) { double("response", success?: true, body: "", code: 200) }
|
11
|
+
let(:affirm_url) { "https://public_key:secret_key@testaffirm.com/testpath" }
|
12
|
+
|
13
|
+
describe "make_request" do
|
14
|
+
before do
|
15
|
+
Typhoeus::Request.stub(:new) { request }
|
16
|
+
end
|
17
|
+
|
18
|
+
it "makes the request to the api url" do
|
19
|
+
client.make_request("testpath", :get)
|
20
|
+
|
21
|
+
Typhoeus::Request.should have_received(:new) do |url, options|
|
22
|
+
url.should == "https://testaffirm.com/testpath"
|
23
|
+
options[:method].should == :get
|
24
|
+
end
|
25
|
+
|
26
|
+
request.should have_received(:run)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "sends keys as basic auth" do
|
30
|
+
client.make_request("testpath", :get)
|
31
|
+
|
32
|
+
Typhoeus::Request.should have_received(:new) do |url, options|
|
33
|
+
options[:userpwd].should == "public_key:secret_key"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with params" do
|
38
|
+
let(:params) { {"foo" => "bar"} }
|
39
|
+
|
40
|
+
it "sends the params" do
|
41
|
+
client.make_request("testpath", :post, params)
|
42
|
+
|
43
|
+
Typhoeus::Request.should have_received(:new) do |url, options|
|
44
|
+
options[:method].should == :post
|
45
|
+
JSON.parse(options[:body]).should == params
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "sets content type to application/json" do
|
50
|
+
client.make_request("testpath", :post, params)
|
51
|
+
|
52
|
+
Typhoeus::Request.should have_received(:new) do |url, options|
|
53
|
+
options[:headers]["Content-Type"].should == "application/json"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "authentication error" do
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "get" do
|
64
|
+
before do
|
65
|
+
Typhoeus::Request.stub(:new) { request }
|
66
|
+
end
|
67
|
+
|
68
|
+
it "sends a get request" do
|
69
|
+
client.get("testpath")
|
70
|
+
|
71
|
+
Typhoeus::Request.should have_received(:new) do |url, options|
|
72
|
+
options[:method].should == :get
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "post" do
|
78
|
+
before do
|
79
|
+
Typhoeus::Request.stub(:new) { request }
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sends a post request" do
|
83
|
+
client.post("testpath")
|
84
|
+
|
85
|
+
Typhoeus::Request.should have_received(:new) do |url, options|
|
86
|
+
options[:method].should == :post
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "response" do
|
92
|
+
before do
|
93
|
+
stub_request(:get, affirm_url).
|
94
|
+
to_return(status: 200, body: {foo: "bar"}.to_json)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "parses the response" do
|
98
|
+
response = client.get("testpath")
|
99
|
+
|
100
|
+
response.should be_a(Affirm::Response)
|
101
|
+
response.should be_success
|
102
|
+
response.status_code.should == 200
|
103
|
+
response.body["foo"].should == "bar"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "errors" do
|
108
|
+
context "authentication error" do
|
109
|
+
before do
|
110
|
+
stub_request(:get, affirm_url).
|
111
|
+
to_return(status: 401, body: {status_code: 401, type: "unauthorized", code: "public-api-key-invalid"}.to_json)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "raises Affirm::AuthenticationError" do
|
115
|
+
expect { client.get("testpath") }.to raise_error(Affirm::AuthenticationError)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "server error" do
|
120
|
+
before do
|
121
|
+
stub_request(:get, affirm_url).
|
122
|
+
to_return(status: 500, body: {status_code: 500}.to_json)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "raises Affirm::ServerError" do
|
126
|
+
expect { client.get("testpath") }.to raise_error(Affirm::ServerError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "resource not found" do
|
131
|
+
before do
|
132
|
+
stub_request(:get, affirm_url).
|
133
|
+
to_return(status: 404, body: {status_code: 404}.to_json)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "raises Affirm::ResourceNotFoundError" do
|
137
|
+
expect { client.get("testpath") }.to raise_error(Affirm::ResourceNotFoundError)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
{
|
2
|
+
"id":"TEST-ALO4-UVGR",
|
3
|
+
"created":"2014-03-18T19:19:04Z",
|
4
|
+
"currency":"USD",
|
5
|
+
"amount":6100,
|
6
|
+
"auth_hold":6100,
|
7
|
+
"payable":0,
|
8
|
+
"void":false,
|
9
|
+
"events":[
|
10
|
+
{
|
11
|
+
"created":"2014-03-20T14:00:33Z",
|
12
|
+
"currency":"USD",
|
13
|
+
"id":"UI1ZOXSXQ44QUXQL",
|
14
|
+
"transaction_id":"TpR3Xrx8TkvuGio0",
|
15
|
+
"type":"auth"
|
16
|
+
}
|
17
|
+
],
|
18
|
+
"details":{
|
19
|
+
"items":{
|
20
|
+
"sweater-a92123":{
|
21
|
+
"sku":"sweater-a92123",
|
22
|
+
"display_name":"Sweater",
|
23
|
+
"qty":1,
|
24
|
+
"item_type":"physical",
|
25
|
+
"item_image_url":"http://placehold.it/350x150",
|
26
|
+
"item_url":"http://placehold.it/350x150",
|
27
|
+
"unit_price":5000
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"shipping_amount":400,
|
31
|
+
"tax_amount":700,
|
32
|
+
"shipping":{
|
33
|
+
"name":{
|
34
|
+
"full":"John Doe"
|
35
|
+
},
|
36
|
+
"address":{
|
37
|
+
"line1":"325 Pacific Ave",
|
38
|
+
"city":"San Francisco",
|
39
|
+
"state":"CA",
|
40
|
+
"zipcode":"94112",
|
41
|
+
"country":"USA"
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
{
|
2
|
+
"id":"TEST-ALO4-UVGR",
|
3
|
+
"created":"2014-03-18T19:19:04Z",
|
4
|
+
"currency":"USD",
|
5
|
+
"amount":6100,
|
6
|
+
"auth_hold":6100,
|
7
|
+
"payable":0,
|
8
|
+
"void":false,
|
9
|
+
"events":[
|
10
|
+
{
|
11
|
+
"created":"2014-03-20T14:00:33Z",
|
12
|
+
"currency":"USD",
|
13
|
+
"id":"UI1ZOXSXQ44QUXQL",
|
14
|
+
"transaction_id":"TpR3Xrx8TkvuGio0",
|
15
|
+
"type":"auth"
|
16
|
+
}
|
17
|
+
],
|
18
|
+
"details":{
|
19
|
+
"items":{
|
20
|
+
"sweater-a92123":{
|
21
|
+
"sku":"sweater-a92123",
|
22
|
+
"display_name":"Sweater",
|
23
|
+
"qty":1,
|
24
|
+
"item_type":"physical",
|
25
|
+
"item_image_url":"http://placehold.it/350x150",
|
26
|
+
"item_url":"http://placehold.it/350x150",
|
27
|
+
"unit_price":5000
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"shipping_amount":400,
|
31
|
+
"tax_amount":700,
|
32
|
+
"shipping":{
|
33
|
+
"name":{
|
34
|
+
"full":"John Doe"
|
35
|
+
},
|
36
|
+
"address":{
|
37
|
+
"line1":"325 Pacific Ave",
|
38
|
+
"city":"San Francisco",
|
39
|
+
"state":"CA",
|
40
|
+
"zipcode":"94112",
|
41
|
+
"country":"USA"
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'webmock'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
require 'affirm'
|
4
|
+
|
5
|
+
include WebMock::API
|
6
|
+
|
7
|
+
TEST_URL = "https://public_key:secret_key@test.affirm.com"
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.before(:suite) do
|
11
|
+
Affirm::API.public_key = "public_key"
|
12
|
+
Affirm::API.secret_key = "secret_key"
|
13
|
+
Affirm::API.api_url = "https://test.affirm.com"
|
14
|
+
end
|
15
|
+
|
16
|
+
config.expect_with :rspec do |expectations|
|
17
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
18
|
+
expectations.syntax = [:should, :expect]
|
19
|
+
end
|
20
|
+
|
21
|
+
config.mock_with :rspec do |mocks|
|
22
|
+
mocks.verify_partial_doubles = true
|
23
|
+
mocks.syntax = [:should, :expect]
|
24
|
+
end
|
25
|
+
|
26
|
+
config.filter_run :focus
|
27
|
+
config.run_all_when_everything_filtered = true
|
28
|
+
|
29
|
+
if config.files_to_run.one?
|
30
|
+
config.default_formatter = 'doc'
|
31
|
+
end
|
32
|
+
|
33
|
+
config.order = :random
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_fixture(path)
|
37
|
+
File.read(File.join(__dir__, "fixtures", path))
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: affirm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Reverb.com
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: typhoeus
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.2.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.2.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.21.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.21.0
|
83
|
+
description: Ruby client library for integrating with Affirm financing payments
|
84
|
+
email: dev@reverb.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- README.md
|
90
|
+
- affirm.gemspec
|
91
|
+
- lib/affirm.rb
|
92
|
+
- lib/affirm/api.rb
|
93
|
+
- lib/affirm/charges.rb
|
94
|
+
- lib/affirm/client.rb
|
95
|
+
- lib/affirm/errors/authentication_error.rb
|
96
|
+
- lib/affirm/errors/error.rb
|
97
|
+
- lib/affirm/errors/resource_not_found_error.rb
|
98
|
+
- lib/affirm/errors/server_error.rb
|
99
|
+
- lib/affirm/response.rb
|
100
|
+
- spec/charges_spec.rb
|
101
|
+
- spec/client_spec.rb
|
102
|
+
- spec/fixtures/charges/authorize.json
|
103
|
+
- spec/fixtures/charges/capture.json
|
104
|
+
- spec/fixtures/charges/get.json
|
105
|
+
- spec/fixtures/charges/invalid_request.json
|
106
|
+
- spec/fixtures/charges/refund.json
|
107
|
+
- spec/fixtures/charges/update.json
|
108
|
+
- spec/fixtures/charges/void.json
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
homepage:
|
111
|
+
licenses:
|
112
|
+
- Apache License Version 2.0
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.2.2
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Affirm Ruby Client Library
|
134
|
+
test_files: []
|