hcbv4 0.1.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/README.md +579 -0
- data/Rakefile +12 -0
- data/lib/hcbv4/client.rb +618 -0
- data/lib/hcbv4/errors.rb +38 -0
- data/lib/hcbv4/models/ach_transfer.rb +48 -0
- data/lib/hcbv4/models/base_transaction_detail.rb +37 -0
- data/lib/hcbv4/models/card_charge.rb +25 -0
- data/lib/hcbv4/models/card_design.rb +21 -0
- data/lib/hcbv4/models/card_grant.rb +100 -0
- data/lib/hcbv4/models/check.rb +72 -0
- data/lib/hcbv4/models/comment.rb +19 -0
- data/lib/hcbv4/models/donation.rb +11 -0
- data/lib/hcbv4/models/donation_transaction.rb +77 -0
- data/lib/hcbv4/models/expense_payout.rb +25 -0
- data/lib/hcbv4/models/invitation.rb +47 -0
- data/lib/hcbv4/models/invoice.rb +30 -0
- data/lib/hcbv4/models/invoice_transaction.rb +47 -0
- data/lib/hcbv4/models/merchant.rb +16 -0
- data/lib/hcbv4/models/organization.rb +164 -0
- data/lib/hcbv4/models/organization_user.rb +33 -0
- data/lib/hcbv4/models/receipt.rb +30 -0
- data/lib/hcbv4/models/resource.rb +23 -0
- data/lib/hcbv4/models/sponsor.rb +43 -0
- data/lib/hcbv4/models/stripe_card.rb +134 -0
- data/lib/hcbv4/models/tag.rb +12 -0
- data/lib/hcbv4/models/transaction.rb +129 -0
- data/lib/hcbv4/models/transaction_list.rb +100 -0
- data/lib/hcbv4/models/transaction_type.rb +11 -0
- data/lib/hcbv4/models/transfer.rb +67 -0
- data/lib/hcbv4/models/user.rb +76 -0
- data/lib/hcbv4/models.rb +37 -0
- data/lib/hcbv4/version.rb +5 -0
- data/lib/hcbv4.rb +10 -0
- data/sig/hcbv4.rbs +4 -0
- metadata +77 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# An HCB organization (also called an "event").
|
|
5
|
+
# Holds funds, issues cards, and manages transactions.
|
|
6
|
+
Organization = Data.define(
|
|
7
|
+
:id, :name, :slug, :country, :icon, :background_image, :created_at,
|
|
8
|
+
:parent_id, :donation_page_available, :playground_mode,
|
|
9
|
+
:playground_mode_meeting_requested, :transparent, :fee_percentage,
|
|
10
|
+
:balance_cents, :fee_balance_cents, :total_spent_cents, :total_raised_cents,
|
|
11
|
+
:account_number, :routing_number, :swift_bic_code, :users, :_client
|
|
12
|
+
) do
|
|
13
|
+
include Resource
|
|
14
|
+
|
|
15
|
+
# @param hash [Hash] API response
|
|
16
|
+
# @param client [Client, nil] for making subsequent requests
|
|
17
|
+
# @return [Organization]
|
|
18
|
+
def self.from_hash(hash, client: nil)
|
|
19
|
+
new(
|
|
20
|
+
id: hash["id"],
|
|
21
|
+
name: hash["name"],
|
|
22
|
+
slug: hash["slug"],
|
|
23
|
+
country: hash["country"],
|
|
24
|
+
icon: hash["icon"],
|
|
25
|
+
background_image: hash["background_image"],
|
|
26
|
+
created_at: hash["created_at"],
|
|
27
|
+
parent_id: hash["parent_id"],
|
|
28
|
+
donation_page_available: hash["donation_page_available"],
|
|
29
|
+
playground_mode: hash["playground_mode"],
|
|
30
|
+
playground_mode_meeting_requested: hash["playground_mode_meeting_requested"],
|
|
31
|
+
transparent: hash["transparent"],
|
|
32
|
+
fee_percentage: hash["fee_percentage"],
|
|
33
|
+
balance_cents: hash["balance_cents"],
|
|
34
|
+
fee_balance_cents: hash["fee_balance_cents"],
|
|
35
|
+
total_spent_cents: hash["total_spent_cents"],
|
|
36
|
+
total_raised_cents: hash["total_raised_cents"],
|
|
37
|
+
account_number: hash["account_number"],
|
|
38
|
+
routing_number: hash["routing_number"],
|
|
39
|
+
swift_bic_code: hash["swift_bic_code"],
|
|
40
|
+
users: hash["users"]&.map { |u| OrganizationUser.from_hash(u) },
|
|
41
|
+
_client: client
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Refreshes organization data from the API.
|
|
46
|
+
# @return [Organization]
|
|
47
|
+
def reload!
|
|
48
|
+
require_client!
|
|
49
|
+
_client.organization(id)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Array<CardGrant>]
|
|
53
|
+
def card_grants(expand: [])
|
|
54
|
+
require_client!
|
|
55
|
+
_client.organization_card_grants(id, expand:)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Array<StripeCard>]
|
|
59
|
+
def stripe_cards(expand: [])
|
|
60
|
+
require_client!
|
|
61
|
+
_client.organization_stripe_cards(id, expand:)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Array<Invoice>]
|
|
65
|
+
def invoices
|
|
66
|
+
require_client!
|
|
67
|
+
_client.invoices(event_id: id)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [Array<Sponsor>]
|
|
71
|
+
def sponsors
|
|
72
|
+
require_client!
|
|
73
|
+
_client.sponsors(event_id: id)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Array<User>] users following this transparent org
|
|
77
|
+
def followers
|
|
78
|
+
require_client!
|
|
79
|
+
_client.organization_followers(id)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns paginated transactions.
|
|
83
|
+
# @return [TransactionList]
|
|
84
|
+
def transactions(**opts)
|
|
85
|
+
require_client!
|
|
86
|
+
_client.transactions(id, **opts)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Array<Organization>] child organizations
|
|
90
|
+
def sub_organizations
|
|
91
|
+
require_client!
|
|
92
|
+
_client.sub_organizations(id)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Creates a card grant for this organization.
|
|
96
|
+
# @return [CardGrant]
|
|
97
|
+
def create_card_grant(amount_cents:, email:, **opts)
|
|
98
|
+
require_client!
|
|
99
|
+
_client.create_card_grant(event_id: id, amount_cents:, email:, **opts)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Creates an invoice for a sponsor.
|
|
103
|
+
# @return [Invoice]
|
|
104
|
+
def create_invoice(sponsor_id:, due_date:, item_description:, item_amount:)
|
|
105
|
+
require_client!
|
|
106
|
+
_client.create_invoice(event_id: id, sponsor_id:, due_date:, item_description:, item_amount:)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Creates a sponsor record.
|
|
110
|
+
# @return [Sponsor]
|
|
111
|
+
def create_sponsor(name:, contact_email:, **address)
|
|
112
|
+
require_client!
|
|
113
|
+
_client.create_sponsor(event_id: id, name:, contact_email:, **address)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Transfers funds to another organization.
|
|
117
|
+
# @return [Transfer]
|
|
118
|
+
def create_disbursement(to_organization_id:, amount_cents:, name:)
|
|
119
|
+
require_client!
|
|
120
|
+
_client.create_disbursement(event_id: id, to_organization_id:, amount_cents:, name:)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Initiates an ACH bank transfer.
|
|
124
|
+
# @return [Hash]
|
|
125
|
+
def create_ach_transfer(routing_number:, account_number:, recipient_name:, amount_money:, payment_for:, **opts)
|
|
126
|
+
require_client!
|
|
127
|
+
_client.create_ach_transfer(event_id: id, routing_number:, account_number:, recipient_name:, amount_money:,
|
|
128
|
+
payment_for:, **opts)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Records an in-person donation.
|
|
132
|
+
# @return [Donation]
|
|
133
|
+
def create_donation(amount_cents:, **opts)
|
|
134
|
+
require_client!
|
|
135
|
+
_client.create_donation(event_id: id, amount_cents:, **opts)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Invites a user to this organization.
|
|
139
|
+
# @return [Invitation]
|
|
140
|
+
def create_invitation(email:, role: nil, **opts)
|
|
141
|
+
require_client!
|
|
142
|
+
_client.create_invitation(event_id: id, email:, role:, **opts)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Creates a sub-organization under this one.
|
|
146
|
+
# @return [Organization]
|
|
147
|
+
def create_sub_organization(name:, email:, **opts)
|
|
148
|
+
require_client!
|
|
149
|
+
_client.create_sub_organization(id, name:, email:, **opts)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return [Boolean]
|
|
153
|
+
def donation_page_available? = !!donation_page_available
|
|
154
|
+
|
|
155
|
+
# @return [Boolean]
|
|
156
|
+
def playground_mode? = !!playground_mode
|
|
157
|
+
|
|
158
|
+
# @return [Boolean]
|
|
159
|
+
def playground_mode_meeting_requested? = !!playground_mode_meeting_requested
|
|
160
|
+
|
|
161
|
+
# @return [Boolean]
|
|
162
|
+
def transparent? = !!transparent
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# A user with organization-specific membership info (role, join date).
|
|
5
|
+
# Returned when expanding :users on an organization.
|
|
6
|
+
# @attr_reader joined_at [String, nil] ISO timestamp when user joined the org
|
|
7
|
+
# @attr_reader role [String, nil] "member" or "manager"
|
|
8
|
+
class OrganizationUser < User
|
|
9
|
+
attr_reader :joined_at, :role
|
|
10
|
+
|
|
11
|
+
def initialize(id:, name: nil, email: nil, avatar: nil, admin: nil, auditor: nil, birthday: nil,
|
|
12
|
+
shipping_address: nil, joined_at: nil, role: nil)
|
|
13
|
+
super(id:, name:, email:, avatar:, admin:, auditor:, birthday:, shipping_address:)
|
|
14
|
+
@joined_at = joined_at
|
|
15
|
+
@role = role
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param hash [Hash] API response
|
|
19
|
+
# @return [OrganizationUser]
|
|
20
|
+
def self.from_hash(hash)
|
|
21
|
+
new(
|
|
22
|
+
id: hash["id"],
|
|
23
|
+
name: hash["name"],
|
|
24
|
+
email: hash["email"],
|
|
25
|
+
avatar: hash["avatar"],
|
|
26
|
+
admin: hash["admin"],
|
|
27
|
+
auditor: hash["auditor"],
|
|
28
|
+
joined_at: hash["joined_at"],
|
|
29
|
+
role: hash["role"]
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# An uploaded receipt image attached to a transaction.
|
|
5
|
+
Receipt = Data.define(:id, :created_at, :url, :preview_url, :filename, :uploader, :_client) do
|
|
6
|
+
include Resource
|
|
7
|
+
|
|
8
|
+
# @param hash [Hash] API response
|
|
9
|
+
# @param client [Client, nil]
|
|
10
|
+
# @return [Receipt]
|
|
11
|
+
def self.from_hash(hash, client: nil)
|
|
12
|
+
new(
|
|
13
|
+
id: hash["id"],
|
|
14
|
+
created_at: hash["created_at"],
|
|
15
|
+
url: hash["url"],
|
|
16
|
+
preview_url: hash["preview_url"],
|
|
17
|
+
filename: hash["filename"],
|
|
18
|
+
uploader: hash["uploader"] ? User.from_hash(hash["uploader"]) : nil,
|
|
19
|
+
_client: client
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Deletes this receipt.
|
|
24
|
+
# @return [nil]
|
|
25
|
+
def delete!
|
|
26
|
+
require_client!
|
|
27
|
+
_client.delete_receipt(id)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
module Resource
|
|
5
|
+
def require_client!
|
|
6
|
+
raise Error, "No client attached to this #{self.class.name.split("::").last}" unless _client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def of_id(id, client:, **extra)
|
|
11
|
+
attrs = members.to_h { |m| [m, nil] }
|
|
12
|
+
attrs[:id] = id
|
|
13
|
+
attrs[:_client] = client
|
|
14
|
+
attrs.merge!(extra.slice(*members))
|
|
15
|
+
new(**attrs)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.included(base)
|
|
20
|
+
base.extend(ClassMethods) if base.respond_to?(:members)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# A sponsor or donor contact for invoicing.
|
|
5
|
+
Sponsor = Data.define(
|
|
6
|
+
:id, :name, :slug, :contact_email, :created_at, :event_id, :stripe_customer_id,
|
|
7
|
+
:address_line1, :address_line2, :address_city, :address_state,
|
|
8
|
+
:address_postal_code, :address_country, :_client
|
|
9
|
+
) do
|
|
10
|
+
include Resource
|
|
11
|
+
|
|
12
|
+
# @param hash [Hash] API response
|
|
13
|
+
# @param client [Client, nil]
|
|
14
|
+
# @return [Sponsor]
|
|
15
|
+
def self.from_hash(hash, client: nil)
|
|
16
|
+
new(
|
|
17
|
+
id: hash["id"], name: hash["name"], slug: hash["slug"], contact_email: hash["contact_email"],
|
|
18
|
+
created_at: hash["created_at"], event_id: hash["event_id"], stripe_customer_id: hash["stripe_customer_id"],
|
|
19
|
+
address_line1: hash["address_line1"], address_line2: hash["address_line2"], address_city: hash["address_city"],
|
|
20
|
+
address_state: hash["address_state"], address_postal_code: hash["address_postal_code"],
|
|
21
|
+
address_country: hash["address_country"],
|
|
22
|
+
_client: client
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Refreshes sponsor data from the API.
|
|
27
|
+
# @return [Sponsor]
|
|
28
|
+
def reload!
|
|
29
|
+
require_client!
|
|
30
|
+
_client.sponsor(id)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Creates an invoice for this sponsor.
|
|
34
|
+
# @param due_date [String] ISO date (YYYY-MM-DD)
|
|
35
|
+
# @param item_description [String]
|
|
36
|
+
# @param item_amount [Integer] amount in cents
|
|
37
|
+
# @return [Invoice]
|
|
38
|
+
def create_invoice(due_date:, item_description:, item_amount:)
|
|
39
|
+
require_client!
|
|
40
|
+
_client.create_invoice(event_id:, sponsor_id: id, due_date:, item_description:, item_amount:)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# Physical card personalization (color, logo).
|
|
5
|
+
CardPersonalization = Data.define(:color, :logo_url) do
|
|
6
|
+
def self.from_hash(hash)
|
|
7
|
+
return nil unless hash
|
|
8
|
+
|
|
9
|
+
new(
|
|
10
|
+
color: hash["color"],
|
|
11
|
+
logo_url: hash["logo_url"]
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Shipping address for physical cards.
|
|
17
|
+
CardShippingAddress = Data.define(:line1, :line2, :city, :state, :country, :postal_code) do
|
|
18
|
+
def self.from_hash(hash)
|
|
19
|
+
return nil unless hash
|
|
20
|
+
|
|
21
|
+
new(
|
|
22
|
+
line1: hash["line1"],
|
|
23
|
+
line2: hash["line2"],
|
|
24
|
+
city: hash["city"],
|
|
25
|
+
state: hash["state"],
|
|
26
|
+
country: hash["country"],
|
|
27
|
+
postal_code: hash["postal_code"]
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Physical card shipping status and tracking.
|
|
33
|
+
CardShipping = Data.define(:status, :eta, :address) do
|
|
34
|
+
def self.from_hash(hash)
|
|
35
|
+
return nil unless hash
|
|
36
|
+
|
|
37
|
+
new(
|
|
38
|
+
status: hash["status"],
|
|
39
|
+
eta: hash["eta"],
|
|
40
|
+
address: CardShippingAddress.from_hash(hash["address"])
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A Stripe Issuing card (virtual or physical).
|
|
46
|
+
StripeCard = Data.define(
|
|
47
|
+
:id, :type, :status, :name, :last4, :exp_month, :exp_year, :created_at,
|
|
48
|
+
:total_spent_cents, :balance_available, :organization, :user,
|
|
49
|
+
:personalization, :shipping, :_client
|
|
50
|
+
) do
|
|
51
|
+
include Resource
|
|
52
|
+
|
|
53
|
+
# @param hash [Hash] API response
|
|
54
|
+
# @param client [Client, nil]
|
|
55
|
+
# @return [StripeCard]
|
|
56
|
+
def self.from_hash(hash, client: nil)
|
|
57
|
+
new(
|
|
58
|
+
id: hash["id"],
|
|
59
|
+
type: hash["type"],
|
|
60
|
+
status: hash["status"],
|
|
61
|
+
name: hash["name"],
|
|
62
|
+
last4: hash["last4"],
|
|
63
|
+
exp_month: hash["exp_month"],
|
|
64
|
+
exp_year: hash["exp_year"],
|
|
65
|
+
created_at: hash["created_at"],
|
|
66
|
+
total_spent_cents: hash["total_spent_cents"],
|
|
67
|
+
balance_available: hash["balance_available"],
|
|
68
|
+
organization: hash["organization"] ? Organization.from_hash(hash["organization"]) : nil,
|
|
69
|
+
user: hash["user"] ? User.from_hash(hash["user"]) : nil,
|
|
70
|
+
personalization: CardPersonalization.from_hash(hash["personalization"]),
|
|
71
|
+
shipping: CardShipping.from_hash(hash["shipping"]),
|
|
72
|
+
_client: client
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Freezes the card, blocking new transactions.
|
|
77
|
+
# @return [StripeCard]
|
|
78
|
+
def freeze!
|
|
79
|
+
require_client!
|
|
80
|
+
_client.update_stripe_card(id, status: "frozen")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Unfreezes a frozen card.
|
|
84
|
+
# @return [StripeCard]
|
|
85
|
+
def defrost!
|
|
86
|
+
require_client!
|
|
87
|
+
_client.update_stripe_card(id, status: "active")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Permanently cancels the card.
|
|
91
|
+
# @return [Hash]
|
|
92
|
+
def cancel!
|
|
93
|
+
require_client!
|
|
94
|
+
_client.cancel_stripe_card(id)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Refreshes card data from the API.
|
|
98
|
+
# @return [StripeCard]
|
|
99
|
+
def reload!
|
|
100
|
+
require_client!
|
|
101
|
+
_client.stripe_card(id)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Activates a physical card after receiving it.
|
|
105
|
+
# @param last4 [String] last 4 digits printed on the card
|
|
106
|
+
# @return [StripeCard]
|
|
107
|
+
def activate!(last4:)
|
|
108
|
+
require_client!
|
|
109
|
+
_client.update_stripe_card(id, status: "active", last4:)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns paginated transactions for this card.
|
|
113
|
+
# @return [TransactionList]
|
|
114
|
+
def transactions(**opts)
|
|
115
|
+
require_client!
|
|
116
|
+
_client.stripe_card_transactions(id, **opts)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns Stripe ephemeral keys for revealing sensitive card details.
|
|
120
|
+
# @param nonce [String] one-time nonce
|
|
121
|
+
# @param stripe_version [String, nil]
|
|
122
|
+
# @return [Hash]
|
|
123
|
+
def ephemeral_keys(nonce:, stripe_version: nil)
|
|
124
|
+
require_client!
|
|
125
|
+
_client.stripe_card_ephemeral_keys(id, nonce:, stripe_version:)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @return [Boolean]
|
|
129
|
+
def virtual? = type == "virtual"
|
|
130
|
+
|
|
131
|
+
# @return [Boolean]
|
|
132
|
+
def physical? = type == "physical"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# A tag applied to a transaction.
|
|
5
|
+
Tag = Data.define(:id, :label, :color, :emoji) do
|
|
6
|
+
# @param hash [Hash] API response
|
|
7
|
+
# @return [Tag]
|
|
8
|
+
def self.from_hash(hash)
|
|
9
|
+
new(id: hash["id"], label: hash["label"], color: hash["color"], emoji: hash["emoji"])
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# A financial transaction on an organization's ledger.
|
|
5
|
+
# Contains type-specific details (card_charge, donation, transfer, etc.).
|
|
6
|
+
Transaction = Data.define(
|
|
7
|
+
:id, :date, :amount_cents, :memo, :has_custom_memo, :pending, :declined, :tags,
|
|
8
|
+
:code, :missing_receipt, :lost_receipt, :appearance,
|
|
9
|
+
:card_charge, :donation, :expense_payout, :invoice, :check, :transfer,
|
|
10
|
+
:ach_transfer, :check_deposit, :organization, :_client
|
|
11
|
+
) do
|
|
12
|
+
include Resource
|
|
13
|
+
include TransactionType
|
|
14
|
+
|
|
15
|
+
# @param hash [Hash] API response
|
|
16
|
+
# @param client [Client, nil]
|
|
17
|
+
# @return [Transaction]
|
|
18
|
+
def self.from_hash(hash, client: nil)
|
|
19
|
+
organization = hash["organization"] ? Organization.from_hash(hash["organization"]) : nil
|
|
20
|
+
|
|
21
|
+
new(
|
|
22
|
+
id: hash["id"],
|
|
23
|
+
date: hash["date"],
|
|
24
|
+
amount_cents: hash["amount_cents"],
|
|
25
|
+
memo: hash["memo"],
|
|
26
|
+
has_custom_memo: hash["has_custom_memo"],
|
|
27
|
+
pending: hash["pending"],
|
|
28
|
+
declined: hash["declined"],
|
|
29
|
+
tags: hash["tags"]&.map { |t| Tag.from_hash(t) },
|
|
30
|
+
code: hash["code"],
|
|
31
|
+
missing_receipt: hash["missing_receipt"],
|
|
32
|
+
lost_receipt: hash["lost_receipt"],
|
|
33
|
+
appearance: hash["appearance"],
|
|
34
|
+
card_charge: hash["card_charge"] ? CardCharge.from_hash(hash["card_charge"]) : nil,
|
|
35
|
+
donation: hash["donation"] ? DonationTransaction.from_hash(hash["donation"]) : nil,
|
|
36
|
+
expense_payout: hash["expense_payout"] ? ExpensePayout.from_hash(hash["expense_payout"]) : nil,
|
|
37
|
+
invoice: hash["invoice"] ? InvoiceTransaction.from_hash(hash["invoice"]) : nil,
|
|
38
|
+
check: hash["check"] ? Check.from_hash(hash["check"]) : nil,
|
|
39
|
+
transfer: hash["transfer"] ? Transfer.from_hash(hash["transfer"], client:, organization:) : nil,
|
|
40
|
+
ach_transfer: hash["ach_transfer"] ? ACHTransfer.from_hash(hash["ach_transfer"]) : nil,
|
|
41
|
+
check_deposit: hash["check_deposit"] ? CheckDeposit.from_hash(hash["check_deposit"]) : nil,
|
|
42
|
+
organization:,
|
|
43
|
+
_client: client
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the transaction type as a symbol.
|
|
48
|
+
# @return [Symbol] :card_charge, :donation, :transfer, :ach_transfer, etc.
|
|
49
|
+
def type
|
|
50
|
+
return :card_charge if card_charge
|
|
51
|
+
return :donation if donation
|
|
52
|
+
return :expense_payout if expense_payout
|
|
53
|
+
return :invoice if invoice
|
|
54
|
+
return :check if check
|
|
55
|
+
return :transfer if transfer
|
|
56
|
+
return :ach_transfer if ach_transfer
|
|
57
|
+
return :check_deposit if check_deposit
|
|
58
|
+
|
|
59
|
+
:unknown
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns the type-specific detail object.
|
|
63
|
+
# @return [CardCharge, DonationTransaction, Transfer, ACHTransfer, Check, nil]
|
|
64
|
+
def details
|
|
65
|
+
card_charge || donation || expense_payout || invoice || check || transfer || ach_transfer || check_deposit
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [Array<Comment>]
|
|
69
|
+
def comments
|
|
70
|
+
require_client!
|
|
71
|
+
_client.comments(organization_id: organization&.id, transaction_id: id)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Array<Receipt>]
|
|
75
|
+
def receipts
|
|
76
|
+
require_client!
|
|
77
|
+
_client.receipts(transaction_id: id)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Refreshes transaction data from the API.
|
|
81
|
+
# @return [Transaction]
|
|
82
|
+
def reload!
|
|
83
|
+
require_client!
|
|
84
|
+
_client.transaction(id)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Updates the transaction memo.
|
|
88
|
+
# @param memo [String]
|
|
89
|
+
# @return [Transaction]
|
|
90
|
+
def update!(memo:)
|
|
91
|
+
require_client!
|
|
92
|
+
_client.update_transaction(id, event_id: organization&.id, memo:)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Adds a comment to this transaction.
|
|
96
|
+
# @param content [String]
|
|
97
|
+
# @param admin_only [Boolean]
|
|
98
|
+
# @param file [File, nil]
|
|
99
|
+
# @return [Comment]
|
|
100
|
+
def add_comment(content:, admin_only: false, file: nil)
|
|
101
|
+
require_client!
|
|
102
|
+
_client.create_comment(organization_id: organization&.id, transaction_id: id, content:, admin_only:, file:)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Uploads a receipt and attaches it to this transaction.
|
|
106
|
+
# @param file [File]
|
|
107
|
+
# @return [Receipt]
|
|
108
|
+
def add_receipt(file:)
|
|
109
|
+
require_client!
|
|
110
|
+
_client.create_receipt(file:, transaction_id: id)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns memo autocomplete suggestions based on past transaction memos.
|
|
114
|
+
# @return [Array<String>] suggested memo strings
|
|
115
|
+
def memo_suggestions
|
|
116
|
+
require_client!
|
|
117
|
+
_client.memo_suggestions(event_id: organization&.id, transaction_id: id)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @return [Boolean]
|
|
121
|
+
def has_custom_memo? = !!has_custom_memo
|
|
122
|
+
|
|
123
|
+
# @return [Boolean]
|
|
124
|
+
def missing_receipt? = !!missing_receipt
|
|
125
|
+
|
|
126
|
+
# @return [Boolean]
|
|
127
|
+
def lost_receipt? = !!lost_receipt
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
# Paginated list of transactions. Implements Enumerable.
|
|
5
|
+
TransactionList = Data.define(:data, :total_count, :has_more, :_client, :_pagination_context) do
|
|
6
|
+
include Resource
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
# @param hash [Hash] API response
|
|
10
|
+
# @param client [Client, nil]
|
|
11
|
+
# @param pagination_context [Hash, nil] internal pagination state
|
|
12
|
+
# @return [TransactionList]
|
|
13
|
+
def self.from_hash(hash, client: nil, pagination_context: nil)
|
|
14
|
+
new(
|
|
15
|
+
data: hash["data"]&.map { |t| Transaction.from_hash(t, client:) } || [],
|
|
16
|
+
total_count: hash["total_count"],
|
|
17
|
+
has_more: hash["has_more"],
|
|
18
|
+
_client: client,
|
|
19
|
+
_pagination_context: pagination_context
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Iterates over transactions in this page.
|
|
24
|
+
# @yield [Transaction]
|
|
25
|
+
def each(&block)
|
|
26
|
+
return enum_for(:each) unless block_given?
|
|
27
|
+
|
|
28
|
+
data.each(&block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Boolean] true if more pages are available
|
|
32
|
+
def has_more? = !!has_more
|
|
33
|
+
|
|
34
|
+
# Fetches the next page of transactions.
|
|
35
|
+
# @return [TransactionList, nil]
|
|
36
|
+
def next_page
|
|
37
|
+
return nil unless has_more? && _client && _pagination_context
|
|
38
|
+
|
|
39
|
+
case _pagination_context[:type]
|
|
40
|
+
when :organization_transactions
|
|
41
|
+
_client.transactions(
|
|
42
|
+
_pagination_context[:organization_id],
|
|
43
|
+
limit: _pagination_context[:limit],
|
|
44
|
+
after: data.last&.id,
|
|
45
|
+
type: _pagination_context[:tx_type],
|
|
46
|
+
filters: _pagination_context[:filters],
|
|
47
|
+
expand: _pagination_context[:expand] || []
|
|
48
|
+
)
|
|
49
|
+
when :missing_receipt
|
|
50
|
+
_client.missing_receipt_transactions(
|
|
51
|
+
limit: _pagination_context[:limit],
|
|
52
|
+
after: data.last&.id
|
|
53
|
+
)
|
|
54
|
+
when :stripe_card_transactions
|
|
55
|
+
_client.stripe_card_transactions(
|
|
56
|
+
_pagination_context[:card_id],
|
|
57
|
+
limit: _pagination_context[:limit],
|
|
58
|
+
after: data.last&.id,
|
|
59
|
+
missing_receipts: _pagination_context[:missing_receipts]
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Iterates through all pages.
|
|
65
|
+
# @yield [TransactionList] each page
|
|
66
|
+
def each_page
|
|
67
|
+
return enum_for(:each_page) unless block_given?
|
|
68
|
+
|
|
69
|
+
page = self
|
|
70
|
+
loop do
|
|
71
|
+
yield page
|
|
72
|
+
break unless page.has_more?
|
|
73
|
+
|
|
74
|
+
page = page.next_page
|
|
75
|
+
break if page.nil?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Lazily iterates through all transactions across pages.
|
|
80
|
+
# @param max_pages [Integer, nil] limit number of pages fetched
|
|
81
|
+
# @yield [Transaction]
|
|
82
|
+
def auto_paginate(max_pages: nil, &block)
|
|
83
|
+
return enum_for(:auto_paginate, max_pages:) unless block_given?
|
|
84
|
+
|
|
85
|
+
pages = 0
|
|
86
|
+
each_page do |page|
|
|
87
|
+
page.each(&block)
|
|
88
|
+
pages += 1
|
|
89
|
+
break if max_pages && pages >= max_pages
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Collects all transactions into an array.
|
|
94
|
+
# @param max_pages [Integer] safety limit (default 100)
|
|
95
|
+
# @return [Array<Transaction>]
|
|
96
|
+
def all(max_pages: 100)
|
|
97
|
+
auto_paginate(max_pages:).to_a
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HCBV4
|
|
4
|
+
module TransactionType
|
|
5
|
+
COMMON_FIELDS = %i[id amount_cents memo status].freeze
|
|
6
|
+
|
|
7
|
+
def transaction? = true
|
|
8
|
+
def pending? = respond_to?(:pending) && pending
|
|
9
|
+
def declined? = respond_to?(:declined) && declined
|
|
10
|
+
end
|
|
11
|
+
end
|