affirm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/reverbdotcom/affirm-ruby.png?branch=master)](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: []
|