nombaone 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/CHANGELOG.md +36 -0
- data/LICENSE +21 -0
- data/README.md +230 -0
- data/lib/nombaone/client.rb +158 -0
- data/lib/nombaone/errors.rb +286 -0
- data/lib/nombaone/internal/http_client.rb +230 -0
- data/lib/nombaone/internal/util.rb +155 -0
- data/lib/nombaone/object.rb +152 -0
- data/lib/nombaone/pagination.rb +116 -0
- data/lib/nombaone/resources/base_resource.rb +40 -0
- data/lib/nombaone/resources/coupons.rb +84 -0
- data/lib/nombaone/resources/customers.rb +160 -0
- data/lib/nombaone/resources/events.rb +46 -0
- data/lib/nombaone/resources/invoices.rb +61 -0
- data/lib/nombaone/resources/mandates.rb +71 -0
- data/lib/nombaone/resources/metrics.rb +24 -0
- data/lib/nombaone/resources/organization.rb +91 -0
- data/lib/nombaone/resources/payment_methods.rb +102 -0
- data/lib/nombaone/resources/plans.rb +129 -0
- data/lib/nombaone/resources/prices.rb +53 -0
- data/lib/nombaone/resources/sandbox.rb +81 -0
- data/lib/nombaone/resources/settlements.rb +91 -0
- data/lib/nombaone/resources/subscriptions.rb +310 -0
- data/lib/nombaone/resources/webhook_endpoints.rb +140 -0
- data/lib/nombaone/version.rb +7 -0
- data/lib/nombaone/webhook_event.rb +64 -0
- data/lib/nombaone/webhooks.rb +167 -0
- data/lib/nombaone.rb +58 -0
- data/sig/nombaone/client.rbs +36 -0
- data/sig/nombaone/errors.rbs +68 -0
- data/sig/nombaone/internal.rbs +51 -0
- data/sig/nombaone/object.rbs +22 -0
- data/sig/nombaone/pagination.rbs +23 -0
- data/sig/nombaone/resources.rbs +243 -0
- data/sig/nombaone/webhooks.rbs +18 -0
- data/sig/nombaone.rbs +24 -0
- metadata +87 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# Direct-debit mandates (NIBSS). Creation is **asynchronous**: the mandate
|
|
6
|
+
# starts `consent_pending` and activates only after the customer authorizes
|
|
7
|
+
# it with their bank — the engine sweeps for activation and fires
|
|
8
|
+
# `payment_method.attached` / `payment_method.updated`. Don't poll in a tight
|
|
9
|
+
# loop; listen for the webhook, and don't charge before it's active
|
|
10
|
+
# (`MANDATE_NOT_ACTIVE` / `MANDATE_CONSENT_PENDING`).
|
|
11
|
+
class Mandates < BaseResource
|
|
12
|
+
# Create a mandate. Requires an `Idempotency-Key` (sent automatically).
|
|
13
|
+
#
|
|
14
|
+
# @param customer_ref [String] the customer this mandate belongs to (`nbo…cus`).
|
|
15
|
+
# @param customer_account_number [String]
|
|
16
|
+
# @param bank_code [String] CBN 3-digit bank code (058 GTB · 044 Access · 033 UBA · …).
|
|
17
|
+
# @param customer_name [String]
|
|
18
|
+
# @param customer_account_name [String]
|
|
19
|
+
# @param customer_phone_number [String]
|
|
20
|
+
# @param customer_address [String]
|
|
21
|
+
# @param narration [String] shown on the customer's statement.
|
|
22
|
+
# @param max_amount_in_kobo [Integer] hard per-debit ceiling, **integer
|
|
23
|
+
# kobo** (₦1.00 = 100). Charges above it fail with `MANDATE_MAX_AMOUNT_EXCEEDED`.
|
|
24
|
+
# @param frequency [String] defaults to `"monthly"` server-side.
|
|
25
|
+
# @param start_date [String] local date-time (no zone); defaults to tomorrow.
|
|
26
|
+
# @param end_date [String] local date-time (no zone); defaults to one year out.
|
|
27
|
+
# @param request_options [Hash]
|
|
28
|
+
# @return [NombaObject] the pending mandate setup; relay its
|
|
29
|
+
# `consent_instruction` to the customer, then wait for the webhook.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# mandate = nombaone.mandates.create(
|
|
33
|
+
# customer_ref: customer.id, customer_account_number: "0123456789", bank_code: "058",
|
|
34
|
+
# customer_name: "Ada Lovelace", customer_account_name: "Ada Lovelace",
|
|
35
|
+
# customer_phone_number: "+2348012345678", customer_address: "1 Marina, Lagos",
|
|
36
|
+
# narration: "Acme Pro subscription", max_amount_in_kobo: 500_000
|
|
37
|
+
# )
|
|
38
|
+
def create(customer_ref:, customer_account_number:, bank_code:, customer_name:,
|
|
39
|
+
customer_account_name:, customer_phone_number:, customer_address:, narration:,
|
|
40
|
+
max_amount_in_kobo:, frequency: OMIT, start_date: OMIT, end_date: OMIT,
|
|
41
|
+
request_options: {})
|
|
42
|
+
request(:post, "/mandates",
|
|
43
|
+
body: {
|
|
44
|
+
customer_ref: customer_ref,
|
|
45
|
+
customer_account_number: customer_account_number,
|
|
46
|
+
bank_code: bank_code,
|
|
47
|
+
customer_name: customer_name,
|
|
48
|
+
customer_account_name: customer_account_name,
|
|
49
|
+
customer_phone_number: customer_phone_number,
|
|
50
|
+
customer_address: customer_address,
|
|
51
|
+
narration: narration,
|
|
52
|
+
max_amount_in_kobo: max_amount_in_kobo,
|
|
53
|
+
frequency: frequency,
|
|
54
|
+
start_date: start_date,
|
|
55
|
+
end_date: end_date,
|
|
56
|
+
},
|
|
57
|
+
options: request_options)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check a mandate's current standing. Returns the underlying
|
|
61
|
+
# **payment-method** row (its `status` moves `consent_pending` → `active`).
|
|
62
|
+
#
|
|
63
|
+
# @param id [String] `nbo…pmt`
|
|
64
|
+
# @param request_options [Hash]
|
|
65
|
+
# @return [NombaObject] a payment method.
|
|
66
|
+
def retrieve(id, request_options: {})
|
|
67
|
+
request(:get, "/mandates/#{encode(id)}", options: request_options)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# Metrics — MRR, churn, and the dunning funnel, computed from the ledger on
|
|
6
|
+
# read (never stored, never stale).
|
|
7
|
+
class Metrics < BaseResource
|
|
8
|
+
# Billing KPIs over a window (defaults to a recent window server-side).
|
|
9
|
+
#
|
|
10
|
+
# @param from [String] ISO-8601 date-time, start of the window.
|
|
11
|
+
# @param to [String] ISO-8601 date-time, end of the window.
|
|
12
|
+
# @param request_options [Hash]
|
|
13
|
+
# @return [NombaObject] with `mrr_in_kobo`, `active_count`, churn counts,
|
|
14
|
+
# and the `dunning_funnel`.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# metrics = nombaone.metrics.billing
|
|
18
|
+
# puts "MRR ₦#{metrics.mrr_in_kobo / 100}"
|
|
19
|
+
def billing(from: OMIT, to: OMIT, request_options: {})
|
|
20
|
+
request(:get, "/metrics/billing", query: { from: from, to: to }, options: request_options)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# Billing + dunning policy under `nombaone.organization.billing`.
|
|
6
|
+
class OrganizationBilling < BaseResource
|
|
7
|
+
# Read the org's billing + dunning policy.
|
|
8
|
+
#
|
|
9
|
+
# @param request_options [Hash]
|
|
10
|
+
# @return [NombaObject]
|
|
11
|
+
def retrieve(request_options: {})
|
|
12
|
+
request(:get, "/organization/billing", options: request_options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Update the billing policy. PUT semantics, but only supplied keys change.
|
|
16
|
+
#
|
|
17
|
+
# @param partial_collection_enabled [Boolean]
|
|
18
|
+
# @param proration_credit_policy [String] `"credit_next_cycle"` or `"none"`.
|
|
19
|
+
# @param dunning_max_attempts [Integer] 1–10.
|
|
20
|
+
# @param dunning_intervals_hours [Array<Integer>]
|
|
21
|
+
# @param dunning_max_window_hours [Integer] must be ≥ the largest interval.
|
|
22
|
+
# @param grace_period_hours [Integer]
|
|
23
|
+
# @param payday_days [Array<Integer>] days of month, 1–31.
|
|
24
|
+
# @param payday_pull_forward_days [Integer] 0–28.
|
|
25
|
+
# @param payday_bias_enabled [Boolean]
|
|
26
|
+
# @param default_collection_method [String] `"charge_automatically"` or `"send_invoice"`.
|
|
27
|
+
# @param comms_enabled [Boolean]
|
|
28
|
+
# @param request_options [Hash]
|
|
29
|
+
# @return [NombaObject]
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# nombaone.organization.billing.update(payday_bias_enabled: true, payday_days: [25, 28, 30])
|
|
33
|
+
def update(partial_collection_enabled: OMIT, proration_credit_policy: OMIT,
|
|
34
|
+
dunning_max_attempts: OMIT, dunning_intervals_hours: OMIT,
|
|
35
|
+
dunning_max_window_hours: OMIT, grace_period_hours: OMIT, payday_days: OMIT,
|
|
36
|
+
payday_pull_forward_days: OMIT, payday_bias_enabled: OMIT,
|
|
37
|
+
default_collection_method: OMIT, comms_enabled: OMIT, request_options: {})
|
|
38
|
+
request(:put, "/organization/billing",
|
|
39
|
+
body: {
|
|
40
|
+
partial_collection_enabled: partial_collection_enabled,
|
|
41
|
+
proration_credit_policy: proration_credit_policy,
|
|
42
|
+
dunning_max_attempts: dunning_max_attempts,
|
|
43
|
+
dunning_intervals_hours: dunning_intervals_hours,
|
|
44
|
+
dunning_max_window_hours: dunning_max_window_hours,
|
|
45
|
+
grace_period_hours: grace_period_hours,
|
|
46
|
+
payday_days: payday_days,
|
|
47
|
+
payday_pull_forward_days: payday_pull_forward_days,
|
|
48
|
+
payday_bias_enabled: payday_bias_enabled,
|
|
49
|
+
default_collection_method: default_collection_method,
|
|
50
|
+
comms_enabled: comms_enabled,
|
|
51
|
+
},
|
|
52
|
+
options: request_options)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Organization settings — configuration, not a billing object.
|
|
57
|
+
class Organization < BaseResource
|
|
58
|
+
# Billing + dunning policy.
|
|
59
|
+
# @return [OrganizationBilling]
|
|
60
|
+
def billing
|
|
61
|
+
@billing ||= OrganizationBilling.new(@client)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Read org-level settings (limits, settlement mode, branding, statuses).
|
|
65
|
+
#
|
|
66
|
+
# @param request_options [Hash]
|
|
67
|
+
# @return [NombaObject]
|
|
68
|
+
def retrieve(request_options: {})
|
|
69
|
+
request(:get, "/organization", options: request_options)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Update tenant-editable settings. At least one field is required.
|
|
73
|
+
#
|
|
74
|
+
# @param monthly_request_quota [Integer]
|
|
75
|
+
# @param settlement_mode [String] `"split_at_collection"` or `"collect_then_payout"`.
|
|
76
|
+
# @param branding [Hash] `display_name`, `support_email`, `logo_url`, `primary_color_hex`.
|
|
77
|
+
# @param request_options [Hash]
|
|
78
|
+
# @return [NombaObject]
|
|
79
|
+
def update(monthly_request_quota: OMIT, settlement_mode: OMIT, branding: OMIT,
|
|
80
|
+
request_options: {})
|
|
81
|
+
request(:put, "/organization",
|
|
82
|
+
body: {
|
|
83
|
+
monthly_request_quota: monthly_request_quota,
|
|
84
|
+
settlement_mode: settlement_mode,
|
|
85
|
+
branding: branding,
|
|
86
|
+
},
|
|
87
|
+
options: request_options)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# Payment methods — cards (via hosted checkout), direct-debit mandates (see
|
|
6
|
+
# `nombaone.mandates`), and virtual accounts for the transfer rail.
|
|
7
|
+
#
|
|
8
|
+
# Card and mandate are **pull** rails (the engine initiates the debit); a
|
|
9
|
+
# virtual account is the **push** rail (the customer sends a transfer and
|
|
10
|
+
# the engine matches it — never treat a collect as instantly final).
|
|
11
|
+
class PaymentMethods < BaseResource
|
|
12
|
+
# Start a hosted-checkout card capture. Card entry happens on the PCI
|
|
13
|
+
# hosted page — no card data ever touches your servers. The method appears
|
|
14
|
+
# as `setup_pending` until the customer completes checkout.
|
|
15
|
+
#
|
|
16
|
+
# @param customer_ref [String] the customer this card will belong to (`nbo…cus`).
|
|
17
|
+
# @param amount_in_kobo [Integer] the validation charge, **integer kobo** (₦1.00 = 100).
|
|
18
|
+
# @param callback_url [String] where the hosted checkout returns the customer.
|
|
19
|
+
# @param request_options [Hash]
|
|
20
|
+
# @return [NombaObject] with a `checkout_link` to redirect the customer to.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# setup = nombaone.payment_methods.setup(
|
|
24
|
+
# customer_ref: customer.id, amount_in_kobo: 5_000, callback_url: "https://example.com/return"
|
|
25
|
+
# )
|
|
26
|
+
# # redirect the customer to setup.checkout_link
|
|
27
|
+
def setup(customer_ref:, amount_in_kobo:, callback_url:, request_options: {})
|
|
28
|
+
request(:post, "/payment-methods/setup",
|
|
29
|
+
body: {
|
|
30
|
+
customer_ref: customer_ref,
|
|
31
|
+
amount_in_kobo: amount_in_kobo,
|
|
32
|
+
callback_url: callback_url,
|
|
33
|
+
},
|
|
34
|
+
options: request_options)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Issue a dedicated virtual account (NUBAN) so the customer can pay by
|
|
38
|
+
# bank transfer. The engine matches inbound transfers to invoices by
|
|
39
|
+
# reference and exact integer-kobo amount.
|
|
40
|
+
#
|
|
41
|
+
# @param customer_ref [String] the customer to issue the account for (`nbo…cus`).
|
|
42
|
+
# @param expected_amount [Integer] optional expected amount hint, integer kobo.
|
|
43
|
+
# @param expiry_date [String] optional ISO date the account should expire.
|
|
44
|
+
# @param request_options [Hash]
|
|
45
|
+
# @return [NombaObject]
|
|
46
|
+
def create_virtual_account(customer_ref:, expected_amount: OMIT, expiry_date: OMIT,
|
|
47
|
+
request_options: {})
|
|
48
|
+
request(:post, "/payment-methods/virtual-account",
|
|
49
|
+
body: {
|
|
50
|
+
customer_ref: customer_ref,
|
|
51
|
+
expected_amount: expected_amount,
|
|
52
|
+
expiry_date: expiry_date,
|
|
53
|
+
},
|
|
54
|
+
options: request_options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Retrieve a payment method by id.
|
|
58
|
+
#
|
|
59
|
+
# @param id [String] `nbo…pmt`
|
|
60
|
+
# @param request_options [Hash]
|
|
61
|
+
# @return [NombaObject]
|
|
62
|
+
# @raise [Nombaone::NotFoundError] 404 `PAYMENT_METHOD_NOT_FOUND`
|
|
63
|
+
def retrieve(id, request_options: {})
|
|
64
|
+
request(:get, "/payment-methods/#{encode(id)}", options: request_options)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# List payment methods, newest first.
|
|
68
|
+
#
|
|
69
|
+
# @param customer_ref [String] filter to one customer (`nbo…cus`). Note
|
|
70
|
+
# the wire filter name is `customerRef`.
|
|
71
|
+
# @param limit [Integer] page size, 1–100 (API default 20).
|
|
72
|
+
# @param cursor [String] opaque cursor from a previous page.
|
|
73
|
+
# @param request_options [Hash]
|
|
74
|
+
# @return [Page<NombaObject>]
|
|
75
|
+
def list(customer_ref: OMIT, limit: OMIT, cursor: OMIT, request_options: {})
|
|
76
|
+
request_page("/payment-methods",
|
|
77
|
+
query: { customer_ref: customer_ref, limit: limit, cursor: cursor },
|
|
78
|
+
options: request_options)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Make this the customer's default payment method.
|
|
82
|
+
#
|
|
83
|
+
# @param id [String] `nbo…pmt`
|
|
84
|
+
# @param request_options [Hash]
|
|
85
|
+
# @return [NombaObject]
|
|
86
|
+
def set_default(id, request_options: {})
|
|
87
|
+
request(:post, "/payment-methods/#{encode(id)}/default", body: {}, options: request_options)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Detach a payment method. Subscriptions still billing against it will
|
|
91
|
+
# need a replacement (`SUBSCRIPTION_PAYMENT_METHOD_REQUIRED` at next charge
|
|
92
|
+
# otherwise).
|
|
93
|
+
#
|
|
94
|
+
# @param id [String] `nbo…pmt`
|
|
95
|
+
# @param request_options [Hash]
|
|
96
|
+
# @return [NombaObject]
|
|
97
|
+
def remove(id, request_options: {})
|
|
98
|
+
request(:delete, "/payment-methods/#{encode(id)}", options: request_options)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# Prices nested under a plan (create/list); reach `nombaone.prices` for
|
|
6
|
+
# reads and deactivation.
|
|
7
|
+
class PlanPrices < BaseResource
|
|
8
|
+
# Create a price under a plan. Prices are immutable once created.
|
|
9
|
+
#
|
|
10
|
+
# @param plan_id [String] `nbo…pln`
|
|
11
|
+
# @param unit_amount_in_kobo [Integer] amount per unit per interval,
|
|
12
|
+
# **integer kobo** (₦1.00 = 100). `250_000` is ₦2,500 — not ₦250,000.
|
|
13
|
+
# @param interval [String] `"day"` `"week"` `"month"` `"year"`.
|
|
14
|
+
# @param interval_count [Integer] bill every N intervals (default 1).
|
|
15
|
+
# @param usage_type [String] `"licensed"` (default) or `"metered"`.
|
|
16
|
+
# @param billing_scheme [String] `"per_unit"` (default) or `"tiered"`.
|
|
17
|
+
# @param trial_period_days [Integer] free-trial days at subscribe time.
|
|
18
|
+
# @param metadata [Hash]
|
|
19
|
+
# @param request_options [Hash]
|
|
20
|
+
# @return [NombaObject]
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# price = nombaone.plans.prices.create(plan.id, unit_amount_in_kobo: 250_000, interval: "month")
|
|
24
|
+
def create(plan_id, unit_amount_in_kobo:, interval:, interval_count: OMIT, usage_type: OMIT,
|
|
25
|
+
billing_scheme: OMIT, trial_period_days: OMIT, metadata: OMIT, request_options: {})
|
|
26
|
+
request(:post, "/plans/#{encode(plan_id)}/prices",
|
|
27
|
+
body: {
|
|
28
|
+
unit_amount_in_kobo: unit_amount_in_kobo,
|
|
29
|
+
interval: interval,
|
|
30
|
+
interval_count: interval_count,
|
|
31
|
+
usage_type: usage_type,
|
|
32
|
+
billing_scheme: billing_scheme,
|
|
33
|
+
trial_period_days: trial_period_days,
|
|
34
|
+
metadata: metadata,
|
|
35
|
+
},
|
|
36
|
+
options: request_options)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# List a plan's prices, newest first.
|
|
40
|
+
#
|
|
41
|
+
# @param plan_id [String] `nbo…pln`
|
|
42
|
+
# @param limit [Integer] page size, 1–100 (API default 20).
|
|
43
|
+
# @param cursor [String] opaque cursor from a previous page.
|
|
44
|
+
# @param request_options [Hash]
|
|
45
|
+
# @return [Page<NombaObject>]
|
|
46
|
+
def list(plan_id, limit: OMIT, cursor: OMIT, request_options: {})
|
|
47
|
+
request_page("/plans/#{encode(plan_id)}/prices",
|
|
48
|
+
query: { limit: limit, cursor: cursor }, options: request_options)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Plans — your catalog. A plan holds the name and description; its prices
|
|
53
|
+
# (amount + cadence) live underneath it (`nombaone.plans.prices`).
|
|
54
|
+
#
|
|
55
|
+
# @example
|
|
56
|
+
# plan = nombaone.plans.create(name: "Pro")
|
|
57
|
+
# price = nombaone.plans.prices.create(plan.id, unit_amount_in_kobo: 250_000, interval: "month")
|
|
58
|
+
class Plans < BaseResource
|
|
59
|
+
# Prices nested under a plan.
|
|
60
|
+
# @return [PlanPrices]
|
|
61
|
+
def prices
|
|
62
|
+
@prices ||= PlanPrices.new(@client)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Create a plan.
|
|
66
|
+
#
|
|
67
|
+
# @param name [String] unique within your organization (`PLAN_NAME_TAKEN` on reuse).
|
|
68
|
+
# @param description [String]
|
|
69
|
+
# @param metadata [Hash]
|
|
70
|
+
# @param request_options [Hash]
|
|
71
|
+
# @return [NombaObject]
|
|
72
|
+
# @raise [Nombaone::ConflictError] 409 `PLAN_NAME_TAKEN`
|
|
73
|
+
def create(name:, description: OMIT, metadata: OMIT, request_options: {})
|
|
74
|
+
request(:post, "/plans",
|
|
75
|
+
body: { name: name, description: description, metadata: metadata },
|
|
76
|
+
options: request_options)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Retrieve a plan by id.
|
|
80
|
+
#
|
|
81
|
+
# @param id [String] `nbo…pln`
|
|
82
|
+
# @param request_options [Hash]
|
|
83
|
+
# @return [NombaObject]
|
|
84
|
+
# @raise [Nombaone::NotFoundError] 404 `PLAN_NOT_FOUND`
|
|
85
|
+
def retrieve(id, request_options: {})
|
|
86
|
+
request(:get, "/plans/#{encode(id)}", options: request_options)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Update a plan's mutable fields. At least one field is required.
|
|
90
|
+
#
|
|
91
|
+
# @param id [String] `nbo…pln`
|
|
92
|
+
# @param name [String]
|
|
93
|
+
# @param description [String, nil] pass `nil` to clear the description.
|
|
94
|
+
# @param metadata [Hash]
|
|
95
|
+
# @param request_options [Hash]
|
|
96
|
+
# @return [NombaObject]
|
|
97
|
+
def update(id, name: OMIT, description: OMIT, metadata: OMIT, request_options: {})
|
|
98
|
+
request(:patch, "/plans/#{encode(id)}",
|
|
99
|
+
body: { name: name, description: description, metadata: metadata },
|
|
100
|
+
options: request_options)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# List plans, newest first.
|
|
104
|
+
#
|
|
105
|
+
# @param status [String] `"active"` or `"archived"`.
|
|
106
|
+
# @param limit [Integer] page size, 1–100 (API default 20).
|
|
107
|
+
# @param cursor [String] opaque cursor from a previous page.
|
|
108
|
+
# @param request_options [Hash]
|
|
109
|
+
# @return [Page<NombaObject>]
|
|
110
|
+
def list(status: OMIT, limit: OMIT, cursor: OMIT, request_options: {})
|
|
111
|
+
request_page("/plans",
|
|
112
|
+
query: { status: status, limit: limit, cursor: cursor },
|
|
113
|
+
options: request_options)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Archive a plan — it stops being subscribable but its history stays.
|
|
117
|
+
#
|
|
118
|
+
# @param id [String] `nbo…pln`
|
|
119
|
+
# @param request_options [Hash]
|
|
120
|
+
# @return [NombaObject]
|
|
121
|
+
# @raise [Nombaone::ConflictError] 409 `PLAN_ALREADY_ARCHIVED`
|
|
122
|
+
# @raise [Nombaone::ConflictError] 409 `PLAN_HAS_ACTIVE_SUBSCRIBERS` —
|
|
123
|
+
# migrate or cancel those subscriptions first.
|
|
124
|
+
def archive(id, request_options: {})
|
|
125
|
+
request(:post, "/plans/#{encode(id)}/archive", body: {}, options: request_options)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# Prices — the amounts and cadences plans are sold at. Create and list them
|
|
6
|
+
# under a plan via `nombaone.plans.prices`; this namespace reads and
|
|
7
|
+
# deactivates them directly.
|
|
8
|
+
#
|
|
9
|
+
# Prices are **immutable** once created — to change pricing, create a new
|
|
10
|
+
# price and deactivate the old one. Existing subscriptions keep the price
|
|
11
|
+
# they were sold at.
|
|
12
|
+
class Prices < BaseResource
|
|
13
|
+
# Retrieve a price by id.
|
|
14
|
+
#
|
|
15
|
+
# @param id [String] `nbo…prc`
|
|
16
|
+
# @param request_options [Hash]
|
|
17
|
+
# @return [NombaObject]
|
|
18
|
+
# @raise [Nombaone::NotFoundError] 404 `PRICE_NOT_FOUND`
|
|
19
|
+
def retrieve(id, request_options: {})
|
|
20
|
+
request(:get, "/prices/#{encode(id)}", options: request_options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# List prices across all plans, newest first.
|
|
24
|
+
#
|
|
25
|
+
# @param plan_ref [String] filter to one plan's prices (`nbo…pln`). Note
|
|
26
|
+
# the wire filter name is `planRef`.
|
|
27
|
+
# @param active [Boolean] filter by the active flag.
|
|
28
|
+
# @param limit [Integer] page size, 1–100 (API default 20).
|
|
29
|
+
# @param cursor [String] opaque cursor from a previous page.
|
|
30
|
+
# @param request_options [Hash]
|
|
31
|
+
# @return [Page<NombaObject>]
|
|
32
|
+
#
|
|
33
|
+
# @example
|
|
34
|
+
# page = nombaone.prices.list(plan_ref: plan.id, active: true)
|
|
35
|
+
def list(plan_ref: OMIT, active: OMIT, limit: OMIT, cursor: OMIT, request_options: {})
|
|
36
|
+
request_page("/prices",
|
|
37
|
+
query: { plan_ref: plan_ref, active: active, limit: limit, cursor: cursor },
|
|
38
|
+
options: request_options)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Deactivate a price so no new subscriptions can be created against it.
|
|
42
|
+
# Existing subscriptions are unaffected — prices are immutable history.
|
|
43
|
+
#
|
|
44
|
+
# @param id [String] `nbo…prc`
|
|
45
|
+
# @param request_options [Hash]
|
|
46
|
+
# @return [NombaObject]
|
|
47
|
+
# @raise [Nombaone::ConflictError] 409 `PRICE_ALREADY_INACTIVE`
|
|
48
|
+
def deactivate(id, request_options: {})
|
|
49
|
+
request(:post, "/prices/#{encode(id)}/deactivate", body: {}, options: request_options)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# **Sandbox only.** Simulation instruments that make billing outcomes happen
|
|
6
|
+
# on demand — no cron waits, no real cards. These endpoints exist only on
|
|
7
|
+
# the sandbox deployment; calling any of them with a live key raises
|
|
8
|
+
# locally, **before any network request**.
|
|
9
|
+
class Sandbox < BaseResource
|
|
10
|
+
# **Sandbox only.** Mint a ready, chargeable test payment method whose
|
|
11
|
+
# `behavior` decides every future charge outcome deterministically.
|
|
12
|
+
#
|
|
13
|
+
# @param customer_id [String] `nbo…cus`
|
|
14
|
+
# @param behavior [String] `"success"` (default), `"decline_insufficient_funds"`,
|
|
15
|
+
# `"decline_expired_card"`, `"decline_do_not_honor"`, or `"requires_otp"`.
|
|
16
|
+
# @param kind [String] `"card"` (default) or `"mandate"`.
|
|
17
|
+
# @param request_options [Hash]
|
|
18
|
+
# @return [NombaObject] a payment method.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# method = nombaone.sandbox.create_payment_method(
|
|
22
|
+
# customer_id: customer.id, behavior: "decline_insufficient_funds"
|
|
23
|
+
# )
|
|
24
|
+
def create_payment_method(customer_id:, behavior: OMIT, kind: OMIT, request_options: {})
|
|
25
|
+
assert_sandbox!
|
|
26
|
+
request(:post, "/sandbox/payment-methods",
|
|
27
|
+
body: { customer_id: customer_id, behavior: behavior, kind: kind },
|
|
28
|
+
options: request_options)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# **Sandbox only.** The test clock: run the subscription's next billing
|
|
32
|
+
# cycle right now, through the real engine — invoice, charge, ledger,
|
|
33
|
+
# webhooks and all.
|
|
34
|
+
#
|
|
35
|
+
# @param subscription_id [String] `nbo…sub`
|
|
36
|
+
# @param request_options [Hash]
|
|
37
|
+
# @return [NombaObject] the cycle `outcome` and the `invoice` it produced.
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# result = nombaone.sandbox.advance_cycle(subscription.id)
|
|
41
|
+
# result.outcome # => "paid"
|
|
42
|
+
def advance_cycle(subscription_id, request_options: {})
|
|
43
|
+
assert_sandbox!
|
|
44
|
+
request(:post, "/sandbox/subscriptions/#{encode(subscription_id)}/advance-cycle",
|
|
45
|
+
body: {}, options: request_options)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# **Sandbox only.** Emit a real, signed catalog event to your registered
|
|
49
|
+
# endpoints — the genuine pipeline (real secret, real signature, real
|
|
50
|
+
# retries), not a mock. This is how you rehearse your handler.
|
|
51
|
+
#
|
|
52
|
+
# @param type [String] any catalog event type, e.g. `"invoice.payment_failed"`.
|
|
53
|
+
# @param payload [Hash] shapes the delivery's `data` object.
|
|
54
|
+
# @param request_options [Hash]
|
|
55
|
+
# @return [NombaObject]
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# nombaone.sandbox.simulate_webhook(
|
|
59
|
+
# type: "invoice.payment_failed",
|
|
60
|
+
# payload: { reference: invoice.id, reason: "insufficient_funds" }
|
|
61
|
+
# )
|
|
62
|
+
def simulate_webhook(type:, payload: OMIT, request_options: {})
|
|
63
|
+
assert_sandbox!
|
|
64
|
+
request(:post, "/sandbox/webhooks/simulate",
|
|
65
|
+
body: { type: type, payload: payload }, options: request_options)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Fail locally, before any network call, when constructed with a live key.
|
|
71
|
+
def assert_sandbox!
|
|
72
|
+
return unless @client.mode == "live"
|
|
73
|
+
|
|
74
|
+
raise Error,
|
|
75
|
+
"nombaone.sandbox.* only works with a sandbox key (nbo_sandbox_…) — the " \
|
|
76
|
+
"/v1/sandbox endpoints do not exist on the live API. Use your sandbox key to " \
|
|
77
|
+
"rehearse, then go live without the sandbox calls."
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nombaone
|
|
4
|
+
module Resources
|
|
5
|
+
# Settlements — where collected money lands, and how it leaves (refunds,
|
|
6
|
+
# payouts) under the escrow lock. Collections split into a non-refundable
|
|
7
|
+
# platform fee plus the net to your tenant sub-account.
|
|
8
|
+
class Settlements < BaseResource
|
|
9
|
+
# Retrieve a settlement by id.
|
|
10
|
+
#
|
|
11
|
+
# @param id [String] `nbo…stl`
|
|
12
|
+
# @param request_options [Hash]
|
|
13
|
+
# @return [NombaObject]
|
|
14
|
+
# @raise [Nombaone::NotFoundError] 404 `SETTLEMENT_NOT_FOUND`
|
|
15
|
+
def retrieve(id, request_options: {})
|
|
16
|
+
request(:get, "/settlements/#{encode(id)}", options: request_options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# List settlements, newest first.
|
|
20
|
+
#
|
|
21
|
+
# @param status [String] filter by settlement status.
|
|
22
|
+
# @param limit [Integer] page size, 1–100 (API default 20).
|
|
23
|
+
# @param cursor [String] opaque cursor from a previous page.
|
|
24
|
+
# @param request_options [Hash]
|
|
25
|
+
# @return [Page<NombaObject>]
|
|
26
|
+
def list(status: OMIT, limit: OMIT, cursor: OMIT, request_options: {})
|
|
27
|
+
request_page("/settlements",
|
|
28
|
+
query: { status: status, limit: limit, cursor: cursor },
|
|
29
|
+
options: request_options)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Your escrow lock and available-to-withdraw balance.
|
|
33
|
+
#
|
|
34
|
+
# @param request_options [Hash]
|
|
35
|
+
# @return [NombaObject]
|
|
36
|
+
def retrieve_escrow(request_options: {})
|
|
37
|
+
request(:get, "/settlements/escrow", options: request_options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Refund a settlement's tenant share. The platform fee is never refunded.
|
|
41
|
+
#
|
|
42
|
+
# **Money moves here.** The API requires an `Idempotency-Key`; the SDK
|
|
43
|
+
# sends one automatically, but pass your own stable
|
|
44
|
+
# `request_options[:idempotency_key]` so a retry from a *new process*
|
|
45
|
+
# cannot refund twice.
|
|
46
|
+
#
|
|
47
|
+
# @param id [String] `nbo…stl`
|
|
48
|
+
# @param amount_in_kobo [Integer] **integer kobo**; defaults server-side to
|
|
49
|
+
# the full remaining refundable amount.
|
|
50
|
+
# @param request_options [Hash]
|
|
51
|
+
# @return [NombaObject]
|
|
52
|
+
# @raise [Nombaone::ConflictError] 409 `REFUND_ALREADY_REFUNDED`
|
|
53
|
+
# @raise [Nombaone::ValidationError] 422 `REFUND_AMOUNT_EXCEEDS_NET`
|
|
54
|
+
def refund(id, amount_in_kobo: OMIT, request_options: {})
|
|
55
|
+
request(:post, "/settlements/#{encode(id)}/refund",
|
|
56
|
+
body: { amount_in_kobo: amount_in_kobo }, options: request_options)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Withdraw settled funds to your bank account.
|
|
60
|
+
#
|
|
61
|
+
# **Money moves here, and the `Idempotency-Key` doubles as the payout's
|
|
62
|
+
# durable `merchantTxRef`.** Always pass an explicit, stable
|
|
63
|
+
# `request_options[:idempotency_key]` (e.g. your own payout id) — an
|
|
64
|
+
# auto-generated key protects SDK-level retries, but a brand-new process
|
|
65
|
+
# retrying with a fresh key would create a **second** payout.
|
|
66
|
+
#
|
|
67
|
+
# @param amount_in_kobo [Integer] **integer kobo** (₦1.00 = 100).
|
|
68
|
+
# @param bank_code [String] CBN 3-digit bank code.
|
|
69
|
+
# @param account_number [String]
|
|
70
|
+
# @param request_options [Hash]
|
|
71
|
+
# @return [NombaObject]
|
|
72
|
+
# @raise [Nombaone::ConflictError] 409 `ESCROW_LOCKED`
|
|
73
|
+
# @raise [Nombaone::ValidationError] 422 `PAYOUT_EXCEEDS_AVAILABLE`
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# payout = nombaone.settlements.create_payout(
|
|
77
|
+
# amount_in_kobo: 5_000_000, bank_code: "058", account_number: "0123456789",
|
|
78
|
+
# request_options: { idempotency_key: "payout-#{my_payout_row.id}" }
|
|
79
|
+
# )
|
|
80
|
+
def create_payout(amount_in_kobo:, bank_code:, account_number:, request_options: {})
|
|
81
|
+
request(:post, "/settlements/payout",
|
|
82
|
+
body: {
|
|
83
|
+
amount_in_kobo: amount_in_kobo,
|
|
84
|
+
bank_code: bank_code,
|
|
85
|
+
account_number: account_number,
|
|
86
|
+
},
|
|
87
|
+
options: request_options)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|