pushpay-ruby 0.2.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/CHANGELOG.md +50 -0
- data/LICENSE.txt +21 -0
- data/README.md +244 -0
- data/lib/pushpay/anticipated_payment.rb +15 -0
- data/lib/pushpay/base.rb +25 -0
- data/lib/pushpay/batch.rb +25 -0
- data/lib/pushpay/client.rb +136 -0
- data/lib/pushpay/configuration.rb +33 -0
- data/lib/pushpay/errors.rb +51 -0
- data/lib/pushpay/fund.rb +40 -0
- data/lib/pushpay/merchant.rb +28 -0
- data/lib/pushpay/organization.rb +30 -0
- data/lib/pushpay/payment.rb +20 -0
- data/lib/pushpay/recurring_payment.rb +25 -0
- data/lib/pushpay/settlement.rb +25 -0
- data/lib/pushpay/version.rb +5 -0
- data/lib/pushpay/webhook.rb +30 -0
- data/lib/pushpay.rb +39 -0
- data/spec/examples.txt +63 -0
- data/spec/pushpay/anticipated_payment_spec.rb +31 -0
- data/spec/pushpay/batch_spec.rb +41 -0
- data/spec/pushpay/client_spec.rb +173 -0
- data/spec/pushpay/configuration_spec.rb +65 -0
- data/spec/pushpay/fund_spec.rb +62 -0
- data/spec/pushpay/merchant_spec.rb +54 -0
- data/spec/pushpay/organization_spec.rb +41 -0
- data/spec/pushpay/payment_spec.rb +58 -0
- data/spec/pushpay/recurring_payment_spec.rb +41 -0
- data/spec/pushpay/settlement_spec.rb +41 -0
- data/spec/pushpay/webhook_spec.rb +63 -0
- data/spec/spec_helper.rb +50 -0
- metadata +191 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PushPay
|
|
4
|
+
class Organization < Base
|
|
5
|
+
# Get a specific organization
|
|
6
|
+
def find(organization_key)
|
|
7
|
+
client.get("/v1/organization/#{organization_key}")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Search organizations
|
|
11
|
+
def search(**params)
|
|
12
|
+
client.get("/v1/organizations", params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List organizations accessible to the current application
|
|
16
|
+
def in_scope(**params)
|
|
17
|
+
client.get("/v1/organizations/in-scope", params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# List campuses for an organization
|
|
21
|
+
def campuses(organization_key: nil, **params)
|
|
22
|
+
client.get("#{organization_path(organization_key)}/campuses", params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# List merchant listings for an organization
|
|
26
|
+
def merchant_listings(organization_key: nil, **params)
|
|
27
|
+
client.get("#{organization_path(organization_key)}/merchantlistings", params)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PushPay
|
|
4
|
+
class Payment < Base
|
|
5
|
+
# Get a single payment by token
|
|
6
|
+
def find(payment_token, merchant_key: nil)
|
|
7
|
+
client.get("#{merchant_path(merchant_key)}/payment/#{payment_token}")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List payments for a merchant
|
|
11
|
+
def list(merchant_key: nil, **params)
|
|
12
|
+
client.get("#{merchant_path(merchant_key)}/payments", params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List payments across an organization
|
|
16
|
+
def list_for_organization(organization_key: nil, **params)
|
|
17
|
+
client.get("#{organization_path(organization_key)}/payments", params)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PushPay
|
|
4
|
+
class RecurringPayment < Base
|
|
5
|
+
# Get a single recurring payment by token
|
|
6
|
+
def find(payment_token, merchant_key: nil)
|
|
7
|
+
client.get("#{merchant_path(merchant_key)}/recurringpayment/#{payment_token}")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List recurring payments for a merchant
|
|
11
|
+
def list(merchant_key: nil, **params)
|
|
12
|
+
client.get("#{merchant_path(merchant_key)}/recurringpayments", params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List recurring payments across an organization
|
|
16
|
+
def list_for_organization(organization_key: nil, **params)
|
|
17
|
+
client.get("#{organization_path(organization_key)}/recurringpayments", params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# List payments linked to a recurring payment schedule
|
|
21
|
+
def payments(payment_token, merchant_key: nil, **params)
|
|
22
|
+
client.get("#{merchant_path(merchant_key)}/recurringpayment/#{payment_token}/payments", params)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PushPay
|
|
4
|
+
class Settlement < Base
|
|
5
|
+
# Get a specific settlement
|
|
6
|
+
def find(settlement_key)
|
|
7
|
+
client.get("/v1/settlement/#{settlement_key}")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List settlements for a merchant
|
|
11
|
+
def list(merchant_key: nil, **params)
|
|
12
|
+
client.get("#{merchant_path(merchant_key)}/settlements", params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List settlements for an organization
|
|
16
|
+
def list_for_organization(organization_key: nil, **params)
|
|
17
|
+
client.get("#{organization_path(organization_key)}/settlements", params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get payments within a settlement
|
|
21
|
+
def payments(settlement_key, **params)
|
|
22
|
+
client.get("/v1/settlement/#{settlement_key}/payments", params)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PushPay
|
|
4
|
+
class Webhook < Base
|
|
5
|
+
# Get a specific webhook
|
|
6
|
+
def find(webhook_token, merchant_key: nil)
|
|
7
|
+
client.get("#{merchant_path(merchant_key)}/webhook/#{webhook_token}")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List webhooks for a merchant
|
|
11
|
+
def list(merchant_key: nil, **params)
|
|
12
|
+
client.get("#{merchant_path(merchant_key)}/webhooks", params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Create a webhook
|
|
16
|
+
def create(params, merchant_key: nil)
|
|
17
|
+
client.post("#{merchant_path(merchant_key)}/webhooks", params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Update a webhook
|
|
21
|
+
def update(webhook_token, params, merchant_key: nil)
|
|
22
|
+
client.put("#{merchant_path(merchant_key)}/webhook/#{webhook_token}", params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Delete a webhook
|
|
26
|
+
def delete(webhook_token, merchant_key: nil)
|
|
27
|
+
client.delete("#{merchant_path(merchant_key)}/webhook/#{webhook_token}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/pushpay.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
require_relative 'pushpay/version'
|
|
7
|
+
require_relative 'pushpay/errors'
|
|
8
|
+
require_relative 'pushpay/configuration'
|
|
9
|
+
require_relative 'pushpay/client'
|
|
10
|
+
require_relative 'pushpay/base'
|
|
11
|
+
require_relative 'pushpay/payment'
|
|
12
|
+
require_relative 'pushpay/recurring_payment'
|
|
13
|
+
require_relative 'pushpay/anticipated_payment'
|
|
14
|
+
require_relative 'pushpay/merchant'
|
|
15
|
+
require_relative 'pushpay/organization'
|
|
16
|
+
require_relative 'pushpay/fund'
|
|
17
|
+
require_relative 'pushpay/settlement'
|
|
18
|
+
require_relative 'pushpay/batch'
|
|
19
|
+
require_relative 'pushpay/webhook'
|
|
20
|
+
|
|
21
|
+
module PushPay
|
|
22
|
+
class << self
|
|
23
|
+
attr_accessor :configuration
|
|
24
|
+
|
|
25
|
+
def configure
|
|
26
|
+
self.configuration ||= Configuration.new
|
|
27
|
+
yield(configuration) if block_given?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def client
|
|
31
|
+
@client ||= Client.new(configuration)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reset!
|
|
35
|
+
@client = nil
|
|
36
|
+
@configuration = nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/spec/examples.txt
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
example_id | status | run_time |
|
|
2
|
+
------------------------------------------------- | ------ | --------------- |
|
|
3
|
+
./spec/pushpay/anticipated_payment_spec.rb[1:1:1] | passed | 0.00053 seconds |
|
|
4
|
+
./spec/pushpay/anticipated_payment_spec.rb[1:2:1] | passed | 0.00041 seconds |
|
|
5
|
+
./spec/pushpay/batch_spec.rb[1:1:1] | passed | 0.00046 seconds |
|
|
6
|
+
./spec/pushpay/batch_spec.rb[1:2:1] | passed | 0.0005 seconds |
|
|
7
|
+
./spec/pushpay/batch_spec.rb[1:3:1] | passed | 0.00047 seconds |
|
|
8
|
+
./spec/pushpay/client_spec.rb[1:1:1] | passed | 0.00034 seconds |
|
|
9
|
+
./spec/pushpay/client_spec.rb[1:1:2:1] | passed | 0.00012 seconds |
|
|
10
|
+
./spec/pushpay/client_spec.rb[1:2:1] | passed | 0.00031 seconds |
|
|
11
|
+
./spec/pushpay/client_spec.rb[1:2:2] | passed | 0.00026 seconds |
|
|
12
|
+
./spec/pushpay/client_spec.rb[1:2:3] | passed | 0.00074 seconds |
|
|
13
|
+
./spec/pushpay/client_spec.rb[1:2:4] | passed | 0.00079 seconds |
|
|
14
|
+
./spec/pushpay/client_spec.rb[1:2:5] | passed | 0.00027 seconds |
|
|
15
|
+
./spec/pushpay/client_spec.rb[1:2:6] | passed | 0.0003 seconds |
|
|
16
|
+
./spec/pushpay/client_spec.rb[1:3:1] | passed | 0.00038 seconds |
|
|
17
|
+
./spec/pushpay/client_spec.rb[1:3:2] | passed | 0.00056 seconds |
|
|
18
|
+
./spec/pushpay/client_spec.rb[1:3:3] | passed | 0.00192 seconds |
|
|
19
|
+
./spec/pushpay/client_spec.rb[1:4:1] | passed | 0.0004 seconds |
|
|
20
|
+
./spec/pushpay/client_spec.rb[1:5:1] | passed | 0.0004 seconds |
|
|
21
|
+
./spec/pushpay/client_spec.rb[1:6:1] | passed | 0.00044 seconds |
|
|
22
|
+
./spec/pushpay/client_spec.rb[1:7:1] | passed | 0.00044 seconds |
|
|
23
|
+
./spec/pushpay/client_spec.rb[1:7:2] | passed | 0.00045 seconds |
|
|
24
|
+
./spec/pushpay/client_spec.rb[1:7:3] | passed | 0.00141 seconds |
|
|
25
|
+
./spec/pushpay/client_spec.rb[1:7:4] | passed | 0.00054 seconds |
|
|
26
|
+
./spec/pushpay/client_spec.rb[1:7:5] | passed | 0.00044 seconds |
|
|
27
|
+
./spec/pushpay/configuration_spec.rb[1:1:1] | passed | 0.00006 seconds |
|
|
28
|
+
./spec/pushpay/configuration_spec.rb[1:2:1] | passed | 0.00006 seconds |
|
|
29
|
+
./spec/pushpay/configuration_spec.rb[1:3:1] | passed | 0.00006 seconds |
|
|
30
|
+
./spec/pushpay/configuration_spec.rb[1:3:2] | passed | 0.00035 seconds |
|
|
31
|
+
./spec/pushpay/configuration_spec.rb[1:3:3] | passed | 0.00006 seconds |
|
|
32
|
+
./spec/pushpay/configuration_spec.rb[1:3:4] | passed | 0.00006 seconds |
|
|
33
|
+
./spec/pushpay/configuration_spec.rb[1:4:1] | passed | 0.00068 seconds |
|
|
34
|
+
./spec/pushpay/configuration_spec.rb[1:4:2] | passed | 0.00056 seconds |
|
|
35
|
+
./spec/pushpay/configuration_spec.rb[1:4:3] | passed | 0.00007 seconds |
|
|
36
|
+
./spec/pushpay/fund_spec.rb[1:1:1] | passed | 0.00052 seconds |
|
|
37
|
+
./spec/pushpay/fund_spec.rb[1:2:1] | passed | 0.00052 seconds |
|
|
38
|
+
./spec/pushpay/fund_spec.rb[1:3:1] | passed | 0.00051 seconds |
|
|
39
|
+
./spec/pushpay/fund_spec.rb[1:4:1] | passed | 0.00057 seconds |
|
|
40
|
+
./spec/pushpay/fund_spec.rb[1:5:1] | passed | 0.00043 seconds |
|
|
41
|
+
./spec/pushpay/merchant_spec.rb[1:1:1] | passed | 0.00046 seconds |
|
|
42
|
+
./spec/pushpay/merchant_spec.rb[1:2:1] | passed | 0.0005 seconds |
|
|
43
|
+
./spec/pushpay/merchant_spec.rb[1:3:1] | passed | 0.00047 seconds |
|
|
44
|
+
./spec/pushpay/merchant_spec.rb[1:4:1] | passed | 0.00074 seconds |
|
|
45
|
+
./spec/pushpay/organization_spec.rb[1:1:1] | passed | 0.00054 seconds |
|
|
46
|
+
./spec/pushpay/organization_spec.rb[1:2:1] | passed | 0.00044 seconds |
|
|
47
|
+
./spec/pushpay/organization_spec.rb[1:3:1] | passed | 0.00052 seconds |
|
|
48
|
+
./spec/pushpay/payment_spec.rb[1:1:1] | passed | 0.00062 seconds |
|
|
49
|
+
./spec/pushpay/payment_spec.rb[1:1:2] | passed | 0.00124 seconds |
|
|
50
|
+
./spec/pushpay/payment_spec.rb[1:2:1] | passed | 0.00048 seconds |
|
|
51
|
+
./spec/pushpay/payment_spec.rb[1:2:2] | passed | 0.00063 seconds |
|
|
52
|
+
./spec/pushpay/payment_spec.rb[1:3:1] | passed | 0.00047 seconds |
|
|
53
|
+
./spec/pushpay/recurring_payment_spec.rb[1:1:1] | passed | 0.00047 seconds |
|
|
54
|
+
./spec/pushpay/recurring_payment_spec.rb[1:2:1] | passed | 0.00098 seconds |
|
|
55
|
+
./spec/pushpay/recurring_payment_spec.rb[1:3:1] | passed | 0.00049 seconds |
|
|
56
|
+
./spec/pushpay/settlement_spec.rb[1:1:1] | passed | 0.00165 seconds |
|
|
57
|
+
./spec/pushpay/settlement_spec.rb[1:2:1] | passed | 0.00048 seconds |
|
|
58
|
+
./spec/pushpay/settlement_spec.rb[1:3:1] | passed | 0.0006 seconds |
|
|
59
|
+
./spec/pushpay/webhook_spec.rb[1:1:1] | passed | 0.00048 seconds |
|
|
60
|
+
./spec/pushpay/webhook_spec.rb[1:2:1] | passed | 0.00085 seconds |
|
|
61
|
+
./spec/pushpay/webhook_spec.rb[1:3:1] | passed | 0.00048 seconds |
|
|
62
|
+
./spec/pushpay/webhook_spec.rb[1:4:1] | passed | 0.01884 seconds |
|
|
63
|
+
./spec/pushpay/webhook_spec.rb[1:5:1] | passed | 0.00042 seconds |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe PushPay::AnticipatedPayment do
|
|
6
|
+
let(:anticipated) { described_class.new(PushPay.client) }
|
|
7
|
+
let(:base_url) { "https://api.pushpay.io" }
|
|
8
|
+
|
|
9
|
+
describe "#create" do
|
|
10
|
+
it "creates an anticipated payment" do
|
|
11
|
+
stub_request(:post, "#{base_url}/v1/merchant/test_merchant_key/anticipatedpayments")
|
|
12
|
+
.with(body: { amount: "50.00" }.to_json)
|
|
13
|
+
.to_return(status: 201, body: { status: "Unassociated" }.to_json,
|
|
14
|
+
headers: { "Content-Type" => "application/json" })
|
|
15
|
+
|
|
16
|
+
result = anticipated.create({ amount: "50.00" })
|
|
17
|
+
expect(result["status"]).to eq("Unassociated")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "#list" do
|
|
22
|
+
it "lists anticipated payments" do
|
|
23
|
+
stub_request(:get, "#{base_url}/v1/merchant/test_merchant_key/anticipatedpayments")
|
|
24
|
+
.to_return(status: 200, body: { items: [] }.to_json,
|
|
25
|
+
headers: { "Content-Type" => "application/json" })
|
|
26
|
+
|
|
27
|
+
result = anticipated.list
|
|
28
|
+
expect(result["items"]).to eq([])
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe PushPay::Batch do
|
|
6
|
+
let(:batches) { described_class.new(PushPay.client) }
|
|
7
|
+
let(:base_url) { "https://api.pushpay.io" }
|
|
8
|
+
|
|
9
|
+
describe "#find" do
|
|
10
|
+
it "gets a batch by key" do
|
|
11
|
+
stub_request(:get, "#{base_url}/v1/merchant/test_merchant_key/batch/batch_123")
|
|
12
|
+
.to_return(status: 200, body: { key: "batch_123", name: "March Batch" }.to_json,
|
|
13
|
+
headers: { "Content-Type" => "application/json" })
|
|
14
|
+
|
|
15
|
+
result = batches.find("batch_123")
|
|
16
|
+
expect(result["name"]).to eq("March Batch")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#list" do
|
|
21
|
+
it "lists batches for a merchant" do
|
|
22
|
+
stub_request(:get, "#{base_url}/v1/merchant/test_merchant_key/batches")
|
|
23
|
+
.to_return(status: 200, body: { items: [] }.to_json,
|
|
24
|
+
headers: { "Content-Type" => "application/json" })
|
|
25
|
+
|
|
26
|
+
result = batches.list
|
|
27
|
+
expect(result["items"]).to eq([])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#payments" do
|
|
32
|
+
it "lists payments within a batch" do
|
|
33
|
+
stub_request(:get, "#{base_url}/v1/merchant/test_merchant_key/batch/batch_123/payments")
|
|
34
|
+
.to_return(status: 200, body: { items: [] }.to_json,
|
|
35
|
+
headers: { "Content-Type" => "application/json" })
|
|
36
|
+
|
|
37
|
+
result = batches.payments("batch_123")
|
|
38
|
+
expect(result["items"]).to eq([])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
RSpec.describe PushPay::Client do
|
|
7
|
+
let(:client) { PushPay.client }
|
|
8
|
+
|
|
9
|
+
describe "#initialize" do
|
|
10
|
+
it "creates a client instance" do
|
|
11
|
+
expect(client).to be_a(described_class)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "with missing credentials" do
|
|
15
|
+
it "raises a ConfigurationError" do
|
|
16
|
+
config = PushPay::Configuration.new
|
|
17
|
+
expect { described_class.new(config) }.to raise_error(PushPay::ConfigurationError)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#authenticate!" do
|
|
23
|
+
it "sends Basic auth header with base64 encoded credentials" do
|
|
24
|
+
client.authenticate!
|
|
25
|
+
|
|
26
|
+
expected_auth = Base64.strict_encode64("test_client_id:test_client_secret")
|
|
27
|
+
expect(WebMock).to have_requested(:post, "https://auth.pushpay.com/pushpay/oauth/token")
|
|
28
|
+
.with(headers: { "Authorization" => "Basic #{expected_auth}" })
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "sets access_token from response" do
|
|
32
|
+
client.authenticate!
|
|
33
|
+
expect(client.access_token).to eq("test_token")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "sets token expiration" do
|
|
37
|
+
client.authenticate!
|
|
38
|
+
expect(client.token_expires_at).to be > Time.now
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "raises AuthenticationError on failure" do
|
|
42
|
+
stub_request(:post, "https://auth.pushpay.com/pushpay/oauth/token")
|
|
43
|
+
.to_return(status: 401, body: "Unauthorized")
|
|
44
|
+
|
|
45
|
+
expect { client.authenticate! }.to raise_error(PushPay::AuthenticationError)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "sends read scope by default" do
|
|
49
|
+
client.authenticate!
|
|
50
|
+
|
|
51
|
+
expect(WebMock).to have_requested(:post, "https://auth.pushpay.com/pushpay/oauth/token")
|
|
52
|
+
.with(body: hash_including(scope: "read"))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "includes custom scopes when configured" do
|
|
56
|
+
PushPay.configuration.scopes = %w[read merchant:view_payments]
|
|
57
|
+
client.authenticate!
|
|
58
|
+
|
|
59
|
+
expect(WebMock).to have_requested(:post, "https://auth.pushpay.com/pushpay/oauth/token")
|
|
60
|
+
.with(body: hash_including(scope: "read merchant:view_payments"))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "#get" do
|
|
65
|
+
it "makes authenticated GET request" do
|
|
66
|
+
stub_request(:get, "https://api.pushpay.io/v1/test")
|
|
67
|
+
.to_return(status: 200, body: { data: "ok" }.to_json,
|
|
68
|
+
headers: { "Content-Type" => "application/json" })
|
|
69
|
+
|
|
70
|
+
result = client.get("/v1/test")
|
|
71
|
+
expect(result).to eq("data" => "ok")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "sends Accept: application/hal+json header" do
|
|
75
|
+
stub_request(:get, "https://api.pushpay.io/v1/test")
|
|
76
|
+
.to_return(status: 200, body: {}.to_json,
|
|
77
|
+
headers: { "Content-Type" => "application/json" })
|
|
78
|
+
|
|
79
|
+
client.get("/v1/test")
|
|
80
|
+
expect(WebMock).to have_requested(:get, "https://api.pushpay.io/v1/test")
|
|
81
|
+
.with(headers: { "Accept" => "application/hal+json" })
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "passes query parameters" do
|
|
85
|
+
stub_request(:get, "https://api.pushpay.io/v1/test")
|
|
86
|
+
.with(query: { page: "0", pageSize: "25" })
|
|
87
|
+
.to_return(status: 200, body: {}.to_json,
|
|
88
|
+
headers: { "Content-Type" => "application/json" })
|
|
89
|
+
|
|
90
|
+
client.get("/v1/test", page: "0", pageSize: "25")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "#post" do
|
|
95
|
+
it "makes authenticated POST request with JSON body" do
|
|
96
|
+
stub_request(:post, "https://api.pushpay.io/v1/test")
|
|
97
|
+
.with(body: { name: "test" }.to_json,
|
|
98
|
+
headers: { "Content-Type" => "application/json" })
|
|
99
|
+
.to_return(status: 201, body: { id: "123" }.to_json,
|
|
100
|
+
headers: { "Content-Type" => "application/json" })
|
|
101
|
+
|
|
102
|
+
result = client.post("/v1/test", name: "test")
|
|
103
|
+
expect(result).to eq("id" => "123")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe "#put" do
|
|
108
|
+
it "makes authenticated PUT request" do
|
|
109
|
+
stub_request(:put, "https://api.pushpay.io/v1/test")
|
|
110
|
+
.to_return(status: 200, body: { success: true }.to_json,
|
|
111
|
+
headers: { "Content-Type" => "application/json" })
|
|
112
|
+
|
|
113
|
+
result = client.put("/v1/test", name: "updated")
|
|
114
|
+
expect(result).to eq("success" => true)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe "#delete" do
|
|
119
|
+
it "makes authenticated DELETE request" do
|
|
120
|
+
stub_request(:delete, "https://api.pushpay.io/v1/test")
|
|
121
|
+
.to_return(status: 204, body: nil)
|
|
122
|
+
|
|
123
|
+
client.delete("/v1/test")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe "error handling" do
|
|
128
|
+
it "raises AuthenticationError on 401 and clears token" do
|
|
129
|
+
stub_request(:get, "https://api.pushpay.io/v1/expired")
|
|
130
|
+
.to_return(status: 401, body: "Unauthorized")
|
|
131
|
+
|
|
132
|
+
expect { client.get("/v1/expired") }.to raise_error(PushPay::AuthenticationError)
|
|
133
|
+
expect(client.access_token).to be_nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "raises NotFoundError on 404" do
|
|
137
|
+
stub_request(:get, "https://api.pushpay.io/v1/missing")
|
|
138
|
+
.to_return(status: 404, body: "Not Found")
|
|
139
|
+
|
|
140
|
+
expect { client.get("/v1/missing") }.to raise_error(PushPay::NotFoundError)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "raises RateLimitError on 429 with retry-after" do
|
|
144
|
+
stub_request(:get, "https://api.pushpay.io/v1/limited")
|
|
145
|
+
.to_return(status: 429, body: "Too Many Requests",
|
|
146
|
+
headers: { "retry-after" => "30" })
|
|
147
|
+
|
|
148
|
+
expect { client.get("/v1/limited") }.to raise_error(PushPay::RateLimitError) do |error|
|
|
149
|
+
expect(error.retry_after).to eq("30")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "raises ValidationError on 400 with validation failures" do
|
|
154
|
+
stub_request(:post, "https://api.pushpay.io/v1/bad")
|
|
155
|
+
.to_return(
|
|
156
|
+
status: 400,
|
|
157
|
+
body: { message: "Bad Request", validationFailures: { "name" => ["is required"] } }.to_json,
|
|
158
|
+
headers: { "Content-Type" => "application/json" }
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
expect { client.post("/v1/bad", {}) }.to raise_error(PushPay::ValidationError) do |error|
|
|
162
|
+
expect(error.errors).to eq("name" => ["is required"])
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "raises APIError on 500" do
|
|
167
|
+
stub_request(:get, "https://api.pushpay.io/v1/error")
|
|
168
|
+
.to_return(status: 500, body: "Internal Server Error")
|
|
169
|
+
|
|
170
|
+
expect { client.get("/v1/error") }.to raise_error(PushPay::APIError)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe PushPay::Configuration do
|
|
6
|
+
let(:config) { described_class.new }
|
|
7
|
+
|
|
8
|
+
describe "#initialize" do
|
|
9
|
+
it "sets default values" do
|
|
10
|
+
expect(config.base_url).to eq("https://api.pushpay.io")
|
|
11
|
+
expect(config.auth_url).to eq("https://auth.pushpay.com/pushpay/oauth/token")
|
|
12
|
+
expect(config.timeout).to eq(30)
|
|
13
|
+
expect(config.scopes).to eq(["read"])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "#sandbox!" do
|
|
18
|
+
it "sets sandbox URLs" do
|
|
19
|
+
config.sandbox!
|
|
20
|
+
expect(config.base_url).to eq("https://sandbox-api.pushpay.io")
|
|
21
|
+
expect(config.auth_url).to eq("https://auth.pushpay.com/pushpay-sandbox/oauth/token")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "#valid?" do
|
|
26
|
+
it "returns true when both credentials are present" do
|
|
27
|
+
config.client_id = "id"
|
|
28
|
+
config.client_secret = "secret"
|
|
29
|
+
expect(config.valid?).to be true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "returns false when client_id is missing" do
|
|
33
|
+
config.client_secret = "secret"
|
|
34
|
+
expect(config.valid?).to be false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns false when client_secret is missing" do
|
|
38
|
+
config.client_id = "id"
|
|
39
|
+
expect(config.valid?).to be false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns false with empty strings" do
|
|
43
|
+
config.client_id = ""
|
|
44
|
+
config.client_secret = ""
|
|
45
|
+
expect(config.valid?).to be false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe "#missing_credentials" do
|
|
50
|
+
it "returns all missing fields when none set" do
|
|
51
|
+
expect(config.missing_credentials).to contain_exactly("client_id", "client_secret")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "returns empty array when all present" do
|
|
55
|
+
config.client_id = "id"
|
|
56
|
+
config.client_secret = "secret"
|
|
57
|
+
expect(config.missing_credentials).to be_empty
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "returns only missing fields" do
|
|
61
|
+
config.client_id = "id"
|
|
62
|
+
expect(config.missing_credentials).to contain_exactly("client_secret")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe PushPay::Fund do
|
|
6
|
+
let(:funds) { described_class.new(PushPay.client) }
|
|
7
|
+
let(:base_url) { "https://api.pushpay.io" }
|
|
8
|
+
|
|
9
|
+
describe "#find" do
|
|
10
|
+
it "gets a fund by key" do
|
|
11
|
+
stub_request(:get, "#{base_url}/v1/organization/test_org_key/fund/fund_123")
|
|
12
|
+
.to_return(status: 200, body: { key: "fund_123", name: "General" }.to_json,
|
|
13
|
+
headers: { "Content-Type" => "application/json" })
|
|
14
|
+
|
|
15
|
+
result = funds.find("fund_123")
|
|
16
|
+
expect(result["name"]).to eq("General")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#list" do
|
|
21
|
+
it "lists funds for a merchant" do
|
|
22
|
+
stub_request(:get, "#{base_url}/v1/merchant/test_merchant_key/funds")
|
|
23
|
+
.to_return(status: 200, body: { items: [] }.to_json,
|
|
24
|
+
headers: { "Content-Type" => "application/json" })
|
|
25
|
+
|
|
26
|
+
result = funds.list
|
|
27
|
+
expect(result["items"]).to eq([])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#create" do
|
|
32
|
+
it "creates a fund" do
|
|
33
|
+
stub_request(:post, "#{base_url}/v1/organization/test_org_key/funds")
|
|
34
|
+
.with(body: { name: "Missions", taxDeductible: true }.to_json)
|
|
35
|
+
.to_return(status: 201, body: { key: "fund_new", name: "Missions" }.to_json,
|
|
36
|
+
headers: { "Content-Type" => "application/json" })
|
|
37
|
+
|
|
38
|
+
result = funds.create({ name: "Missions", taxDeductible: true })
|
|
39
|
+
expect(result["name"]).to eq("Missions")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "#update" do
|
|
44
|
+
it "updates a fund" do
|
|
45
|
+
stub_request(:put, "#{base_url}/v1/organization/test_org_key/fund/fund_123")
|
|
46
|
+
.to_return(status: 200, body: { key: "fund_123", name: "Updated" }.to_json,
|
|
47
|
+
headers: { "Content-Type" => "application/json" })
|
|
48
|
+
|
|
49
|
+
result = funds.update("fund_123", { name: "Updated" })
|
|
50
|
+
expect(result["name"]).to eq("Updated")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "#delete" do
|
|
55
|
+
it "deletes a fund" do
|
|
56
|
+
stub_request(:delete, "#{base_url}/v1/organization/test_org_key/fund/fund_123")
|
|
57
|
+
.to_return(status: 204, body: nil)
|
|
58
|
+
|
|
59
|
+
funds.delete("fund_123")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe PushPay::Merchant do
|
|
6
|
+
let(:merchants) { described_class.new(PushPay.client) }
|
|
7
|
+
let(:base_url) { "https://api.pushpay.io" }
|
|
8
|
+
|
|
9
|
+
describe "#find" do
|
|
10
|
+
it "gets a merchant by key" do
|
|
11
|
+
stub_request(:get, "#{base_url}/v1/merchant/mk_123")
|
|
12
|
+
.to_return(status: 200, body: { key: "mk_123", name: "Test Church" }.to_json,
|
|
13
|
+
headers: { "Content-Type" => "application/json" })
|
|
14
|
+
|
|
15
|
+
result = merchants.find("mk_123")
|
|
16
|
+
expect(result["name"]).to eq("Test Church")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#search" do
|
|
21
|
+
it "searches merchants by name" do
|
|
22
|
+
stub_request(:get, "#{base_url}/v1/merchants")
|
|
23
|
+
.with(query: { name: "Test" })
|
|
24
|
+
.to_return(status: 200, body: { items: [] }.to_json,
|
|
25
|
+
headers: { "Content-Type" => "application/json" })
|
|
26
|
+
|
|
27
|
+
result = merchants.search(name: "Test")
|
|
28
|
+
expect(result["items"]).to eq([])
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#in_scope" do
|
|
33
|
+
it "lists accessible merchants" do
|
|
34
|
+
stub_request(:get, "#{base_url}/v1/merchants/in-scope")
|
|
35
|
+
.to_return(status: 200, body: { items: [] }.to_json,
|
|
36
|
+
headers: { "Content-Type" => "application/json" })
|
|
37
|
+
|
|
38
|
+
result = merchants.in_scope
|
|
39
|
+
expect(result["items"]).to eq([])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "#near" do
|
|
44
|
+
it "searches nearby merchants" do
|
|
45
|
+
stub_request(:get, "#{base_url}/v1/merchants/near")
|
|
46
|
+
.with(query: { latitude: "37.7749", longitude: "-122.4194", country: "US" })
|
|
47
|
+
.to_return(status: 200, body: { items: [] }.to_json,
|
|
48
|
+
headers: { "Content-Type" => "application/json" })
|
|
49
|
+
|
|
50
|
+
result = merchants.near(latitude: "37.7749", longitude: "-122.4194", country: "US")
|
|
51
|
+
expect(result["items"]).to eq([])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|