Module: PaymentKit
- Defined in:
- lib/payment_kit.rb,
lib/payment_kit/client.rb,
lib/payment_kit/engine.rb,
lib/payment_kit/errors.rb,
lib/payment_kit/version.rb,
lib/payment_kit/webhook.rb,
lib/payment_kit/namespace.rb,
lib/payment_kit/configuration.rb,
lib/payment_kit/instrumentation.rb,
lib/payment_kit/resources/catalog.rb,
lib/payment_kit/resources/invoices.rb,
lib/payment_kit/resources/payments.rb,
lib/payment_kit/resources/customers.rb,
lib/payment_kit/notification_adapter.rb,
lib/payment_kit/resources/subscriptions.rb,
app/controllers/payment_kit/webhook_controller.rb
Overview
Ruby HTTP client and webhook event bus for the PaymentKit REST API.
HTTP API access goes through PaymentKit::Client. Inbound webhooks are
verified and then fanned out via ActiveSupport::Notifications
(+subscribe+ / instrument / all).
Defined Under Namespace
Modules: Instrumentation, Resources, Webhook Classes: APIConnectionError, APIError, AuthenticationError, CardError, Client, Configuration, ConflictError, Engine, Error, InvalidRequestError, Namespace, NotificationAdapter, PermissionError, RateLimitError, SignatureVerificationError, WebhookController
Constant Summary collapse
- DEFAULT_EVENT_FILTER =
Identity filter: dispatches every event. Replace via
event_filter=. ->(event) { event }
- DEFAULT_EVENT_RETRIEVER =
Runs after signature verification and before dispatch. Return the event to continue, or
nilto drop it. PaymentKit redelivers on retry, so hosts should deduplicate here on the event id — the same value PaymentKit sends in theX-Webhook-Event-Idheader:PaymentKit.event_retriever = lambda do |event| key = "payment_kit:webhook:#{event["id"]}" Sidekiq.redis { |r| r.set(key, "1", nx: true, ex: 3 * 24 * 60 * 60) } ? event : nil end ->(event) { event }
- VERSION =
Gem version.
"1.0.0"
Class Attribute Summary collapse
-
.adapter ⇒ Object
Wraps each subscriber so it receives a single event argument.
-
.backend ⇒ Object
Pub/sub backend.
-
.error_handler ⇒ Object
Callable (exception, request) used by WebhookController to report a failed delivery and answer
200instead of500. -
.event_filter ⇒ Object
Callable run before dispatch.
-
.event_retriever ⇒ Object
Callable run after verification, before dispatch — the deduplication hook.
-
.namespace ⇒ Object
Namespace prefixing instrumented event names.
-
.subscriber_error_handler ⇒ Object
Callable (exception, event) that isolates a failing subscriber, so one raising handler does not fail the whole delivery.
Class Method Summary collapse
-
.all(callable = nil) ⇒ Object
Subscribe to every namespaced PaymentKit event.
-
.configuration ⇒ Object
Global configuration object, created on first use.
-
.configure(&block) ⇒ Object
Two forms, chosen by block arity:.
-
.instrument(event) ⇒ Object
Run
event_filter, then notify subscribers underpayment_kit.<type>. -
.listening?(name) ⇒ Boolean
Whether any subscriber is currently listening for
name. -
.process_webhook(payload, signature, secrets: nil) ⇒ Object
Verify webhook signature(s), run the retriever, instrument the event.
-
.reset_configuration! ⇒ Object
Restores configuration and the event-bus hooks to their defaults.
-
.signing_secret ⇒ Object
First configured signing secret.
-
.signing_secret=(value) ⇒ Object
Assigns a single signing secret, replacing any already configured.
-
.signing_secrets ⇒ Object
All configured signing secrets, as an Array.
-
.signing_secrets=(value) ⇒ Object
Assigns the full list of signing secrets.
-
.subscribe(name, callable = nil, &block) ⇒ Object
Subscribe to an event type or type prefix (e.g.
"invoice.paid","invoice.").
Class Attribute Details
.adapter ⇒ Object
Wraps each subscriber so it receives a single event argument. Defaults to NotificationAdapter.
24 25 26 |
# File 'lib/payment_kit.rb', line 24 def adapter @adapter end |
.backend ⇒ Object
Pub/sub backend. Defaults to ActiveSupport::Notifications; any object
answering instrument(name, payload) and subscribe(pattern, callable) works.
28 29 30 |
# File 'lib/payment_kit.rb', line 28 def backend @backend end |
.error_handler ⇒ Object
Callable (exception, request) used by WebhookController to report a
failed delivery and answer 200 instead of 500.
43 44 45 |
# File 'lib/payment_kit.rb', line 43 def error_handler @error_handler end |
.event_filter ⇒ Object
Callable run before dispatch. Return the event to continue, nil to drop it.
35 36 37 |
# File 'lib/payment_kit.rb', line 35 def event_filter @event_filter end |
.event_retriever ⇒ Object
Callable run after verification, before dispatch — the deduplication hook.
Return the event to continue, nil to drop a redelivery.
39 40 41 |
# File 'lib/payment_kit.rb', line 39 def event_retriever @event_retriever end |
.namespace ⇒ Object
Namespace prefixing instrumented event names. Defaults to Namespace.new("payment_kit.").
32 33 34 |
# File 'lib/payment_kit.rb', line 32 def namespace @namespace end |
.subscriber_error_handler ⇒ Object
Callable (exception, event) that isolates a failing subscriber, so one raising handler does not fail the whole delivery.
47 48 49 |
# File 'lib/payment_kit.rb', line 47 def subscriber_error_handler @subscriber_error_handler end |
Class Method Details
.all(callable = nil) ⇒ Object
Subscribe to every namespaced PaymentKit event.
113 114 115 |
# File 'lib/payment_kit.rb', line 113 def all(callable = nil, &) subscribe(nil, callable, &) end |
.configuration ⇒ Object
Global configuration object, created on first use.
50 51 52 |
# File 'lib/payment_kit.rb', line 50 def configuration @configuration ||= Configuration.new end |
.configure(&block) ⇒ Object
Two forms, chosen by block arity:
PaymentKit.configure { |config| config.secret_key = "st_..." } # settings
PaymentKit.configure { subscribe("invoice.paid") { |e| ... } } # subscriber DSL
The arity-0 form runs against the module itself, so subscriptions can be
registered without repeating the PaymentKit. receiver.
61 62 63 64 65 66 |
# File 'lib/payment_kit.rb', line 61 def configure(&block) raise ArgumentError, "PaymentKit.configure requires a block" if block.nil? block.arity.zero? ? instance_eval(&block) : yield(configuration) configuration end |
.instrument(event) ⇒ Object
Run event_filter, then notify subscribers under payment_kit.<type>.
Returns the (possibly filtered) event, or nil when the filter drops it.
119 120 121 122 123 124 125 126 127 128 |
# File 'lib/payment_kit.rb', line 119 def instrument(event) filtered = event_filter.call(event) return if filtered.nil? type = event_type(filtered) raise InvalidRequestError, "PaymentKit event is missing a type" if type.empty? backend.instrument(namespace.call(type), filtered) filtered end |
.listening?(name) ⇒ Boolean
Whether any subscriber is currently listening for name.
144 145 146 147 148 149 |
# File 'lib/payment_kit.rb', line 144 def listening?(name) notifier = backend.notifier return false unless notifier.respond_to?(:listening?) notifier.listening?(namespace.call(name)) end |
.process_webhook(payload, signature, secrets: nil) ⇒ Object
Verify webhook signature(s), run the retriever, instrument the event.
Returns the dispatched event, or nil when event_retriever dropped it
(for example a duplicate delivery).
134 135 136 137 138 139 140 141 |
# File 'lib/payment_kit.rb', line 134 def process_webhook(payload, signature, secrets: nil) event = Webhook.construct_event(payload, signature, secrets || signing_secrets) event = event_retriever.call(event) return if event.nil? instrument(event) event end |
.reset_configuration! ⇒ Object
Restores configuration and the event-bus hooks to their defaults. Intended for test suites.
70 71 72 73 74 75 76 77 |
# File 'lib/payment_kit.rb', line 70 def reset_configuration! @configuration = Configuration.new self.event_filter = DEFAULT_EVENT_FILTER self.event_retriever = DEFAULT_EVENT_RETRIEVER self.error_handler = nil self.subscriber_error_handler = nil configuration end |
.signing_secret ⇒ Object
First configured signing secret.
82 83 84 |
# File 'lib/payment_kit.rb', line 82 def signing_secret configuration.signing_secret end |
.signing_secret=(value) ⇒ Object
Assigns a single signing secret, replacing any already configured.
87 88 89 |
# File 'lib/payment_kit.rb', line 87 def signing_secret=(value) configuration.signing_secret = value end |
.signing_secrets ⇒ Object
All configured signing secrets, as an Array. During a roll-secret grace
period both the old and new secret are live and are tried in order.
93 94 95 |
# File 'lib/payment_kit.rb', line 93 def signing_secrets Array(configuration.signing_secrets).compact end |
.signing_secrets=(value) ⇒ Object
Assigns the full list of signing secrets.
98 99 100 |
# File 'lib/payment_kit.rb', line 98 def signing_secrets=(value) configuration.signing_secrets = value.nil? ? nil : Array(value) end |
.subscribe(name, callable = nil, &block) ⇒ Object
Subscribe to an event type or type prefix (e.g. "invoice.paid", "invoice.").
105 106 107 108 109 110 |
# File 'lib/payment_kit.rb', line 105 def subscribe(name, callable = nil, &block) handler = callable || block raise ArgumentError, "subscriber callable or block required" if handler.nil? backend.subscribe(namespace.to_regexp(name), adapter.call(isolate(handler))) end |