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.
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PushPay
4
+ VERSION = "0.2.0"
5
+ 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