StripeKit 0.1.1
Stripe integration toolkit for C++
Loading...
Searching...
No Matches
Drogon.h
1/** Drogon.h
2 *
3 * Drogon runtime adapter for StripeKit outbound requests and webhooks.
4 *
5 * @author Hans de Ruiter
6 *
7 * @License See LICENSE for details.
8 */
9
10#pragma once
11
12#include "StripeKit/HTTPClient.h"
13#include "StripeKit/Logging.h"
14#include "StripeKit/StripeApiClient.h"
15#include "StripeKit/WebhookProcessor.h"
16#include "StripeKit/Webhooks.h"
17
18#include <drogon/HttpClient.h>
19
20#include <chrono>
21#include <memory>
22#include <string>
23
24namespace trantor {
25class EventLoop;
26class EventLoopThread;
27} // namespace trantor
28
29namespace stripekit::drogon {
30
31/**
32 * @addtogroup drogon_adapter
33 * @{
34 */
35
36/**
37 * Chooses which event loop outbound Stripe requests run on.
38 *
39 * Use drogon_framework() inside a Drogon server so StripeKit shares the loop
40 * the application is already running, rather than starting a second one that
41 * competes with it. The default, own(), suits command-line tools and tests
42 * where there is no Drogon server.
43 */
45 /** Which loop to use. */
46 enum class Mode {
47 Own, /**< StripeKit starts and owns a loop thread. */
48 DrogonFramework, /**< Use the loop of the running Drogon application. */
49 External /**< Use a loop the application already owns. */
50 };
51
52 /**
53 * Runs requests on a loop StripeKit starts and owns.
54 *
55 * @return Options selecting Mode::Own.
56 */
58
59 /**
60 * Runs requests on the loop of the running Drogon application.
61 *
62 * Requires Drogon to be running; use this from inside a Drogon server.
63 *
64 * @return Options selecting Mode::DrogonFramework.
65 */
67
68 /**
69 * Runs requests on a loop the application already owns.
70 *
71 * @param loop The loop to use. It must outlive the HTTP client.
72 * @return Options selecting Mode::External.
73 */
74 static EventLoopOptions external(trantor::EventLoop& loop);
75
76 /** Which loop to use. */
78
79 /** The loop to use when #mode is Mode::External. Ignored otherwise. */
80 trantor::EventLoop* loop = nullptr;
81};
82
83/** Settings for make_stripe_client(). */
85 /**
86 * Stripe secret key, of the form "sk_test_..." or "sk_live_...".
87 *
88 * Read it from configuration or the environment, never from source, and
89 * never log it.
90 */
91 std::string api_key;
92
93 /** Stripe API base URL. HTTPS is required except for loopback mock servers. */
94 std::string api_base_url = "https://api.stripe.com";
95
96 /** Which event loop outbound requests run on. */
98};
99
100/** Settings for register_webhook_handler(). */
102 /** Route to register, for example "/stripe/webhook". */
103 std::string path;
104
105 /**
106 * Signing secret for this endpoint, of the form "whsec_...".
107 *
108 * The Stripe CLI prints one for local testing; a registered endpoint has
109 * its own, shown in the Stripe dashboard.
110 */
111 std::string endpoint_secret;
112
113 /**
114 * Where processed event ids are recorded, so each event runs once.
115 *
116 * Must outlive the handler, so use an application-scoped or `static`
117 * object rather than a local.
118 */
120
121 /** Called for claimed events after verification. Failed attempts may be retried. */
123
124 /** Where to report failures. No logging when null. */
125 Logger* logger = nullptr;
126
127 /** How far the signature's timestamp may be from the current time. */
128 std::chrono::seconds tolerance = std::chrono::minutes(5);
129
130 /** How long before a claim left behind by a crashed process is reclaimed. */
132};
133
134/**
135 * Sends Stripe API requests through Drogon's HTTP client.
136 *
137 * Construct this directly only when supplying a custom
138 * stripekit::StripeResponseParser; otherwise make_stripe_client() does it for
139 * you.
140 *
141 * Safe to use from several threads.
142 */
143class HTTPClient final : public stripekit::HTTPClient {
144public:
145 /**
146 * Creates a client for a Stripe account.
147 *
148 * @param api_key Stripe secret key. Sent as the bearer token on every
149 * request; never logged.
150 * @param api_base_url Stripe API base URL. HTTPS is required except for a
151 * loopback mock server.
152 * @param event_loop Which event loop requests run on. Inside a Drogon
153 * server, pass EventLoopOptions::drogon_framework().
154 */
155 explicit HTTPClient(std::string api_key,
156 std::string api_base_url = "https://api.stripe.com",
157 EventLoopOptions event_loop = {});
158
159 /** Destroys the client, stopping its event loop thread if it owns one. */
160 ~HTTPClient() override;
161
162 HTTPClient(const HTTPClient&) = delete;
163 HTTPClient& operator=(const HTTPClient&) = delete;
164
165 /**
166 * Sends a request to Stripe.
167 *
168 * @param request What to send, including the options to honour.
169 * @return A task producing Stripe's response. An error status is reported
170 * in the response rather than thrown.
171 * @throws std::exception Stripe could not be reached.
172 */
173 Task<HttpResponse> send_async(const HttpRequest& request) override;
174
175 /**
176 * Sends a request to Stripe, reporting the result through a callback.
177 *
178 * The callback runs exactly once, on the client's event loop.
179 *
180 * @param request What to send, including the options to honour.
181 * @param completion Receives Stripe's response, or the transport failure.
182 */
183 void send(const HttpRequest& request, Completion completion) override;
184
185private:
186 std::string api_key;
187 std::unique_ptr<trantor::EventLoopThread> event_loop_thread;
188 ::drogon::HttpClientPtr client;
189};
190
191/**
192 * A Drogon route that receives Stripe webhook deliveries.
193 *
194 * Construct this directly only when webhook handling is driven by a
195 * stripekit::EventDispatcher; otherwise register_webhook_handler() is simpler.
196 *
197 * The processor is referenced, not owned, so it must outlive the registered
198 * Drogon route.
199 */
200class WebhookEndpoint final {
201public:
202 /**
203 * Creates an endpoint.
204 *
205 * @param processor Verifies, de-duplicates, and dispatches deliveries. Must
206 * outlive the endpoint.
207 * @param endpoint_secret Signing secret for this endpoint.
208 */
209 WebhookEndpoint(WebhookProcessor& processor, std::string endpoint_secret);
210
211 /**
212 * Adds the route to the Drogon application.
213 *
214 * Call this before Drogon starts running.
215 *
216 * @param path Route to register, for example "/stripe/webhook".
217 */
218 void register_handler(std::string path) const;
219
220private:
221 WebhookProcessor& processor;
222 std::string endpoint_secret;
223};
224
225/**
226 * Sends StripeKit's log entries through Drogon's logging.
227 *
228 * Pass one to WebhookHandlerOptions::logger so StripeKit's diagnostics appear
229 * alongside the rest of the application's output.
230 */
231class LogBridge final : public stripekit::Logger {
232public:
233 /**
234 * Creates a bridge.
235 *
236 * @param threshold Lowest severity to pass on. Entries below it are dropped
237 * before reaching Drogon.
238 */
239 explicit LogBridge(LogLevel threshold = LogLevel::Info);
240
241 /**
242 * Reports whether entries at this level are wanted.
243 *
244 * @param level Severity being considered.
245 * @return True when it is at or above the configured threshold.
246 */
247 bool should_log(LogLevel level) const override;
248
249 /**
250 * Writes one entry through Drogon's matching log level.
251 *
252 * @param level Severity of the entry.
253 * @param message The text to record.
254 * @param context Identifiers to include in the entry.
255 */
256 void log(LogLevel level, std::string_view message, const LogContext& context) override;
257
258private:
259 LogLevel threshold;
260};
261
262/**
263 * Creates a Stripe client that talks through Drogon.
264 *
265 * This is the usual way to get a stripekit::StripeClient in a Drogon
266 * application. One client is enough for the whole application: it is safe to
267 * share across threads and reuses connections to Stripe.
268 *
269 * @code
270 * auto stripe = stripekit::drogon::make_stripe_client({
271 * .api_key = std::getenv("STRIPE_SECRET_KEY")
272 * });
273 * @endcode
274 *
275 * @param options The API key, and optionally the base URL and event loop.
276 * @return The client. It owns its transport, so keeping this alive is enough.
277 * @throws ConfigurationException The API key is missing, or the requested event
278 * loop is not available.
279 *
280 * @sa @ref quickstart
281 */
282std::unique_ptr<StripeClient> make_stripe_client(ClientOptions options);
283
284/**
285 * Adds a Stripe webhook route to the Drogon application.
286 *
287 * The route verifies Stripe's signature, reserves the event id in the store,
288 * and only then calls the handler. Completed events are skipped; failed or
289 * interrupted attempts may run again, so the handler must be safe to retry.
290 * Replies 200 when handled or already handled, 500 on handler or store failures,
291 * and 400 when the signature does not check out.
292 *
293 * Call this before Drogon starts running.
294 *
295 * @code
296 * static stripekit::SqliteWebhookDedupStore dedup_store("stripe-webhooks.sqlite3");
297 *
298 * stripekit::drogon::register_webhook_handler({
299 * .path = "/stripe/webhook",
300 * .endpoint_secret = std::getenv("STRIPE_WEBHOOK_SECRET"),
301 * .dedup_store = dedup_store,
302 * .handler = [](const stripekit::webhooks::Event& event)
303 * -> stripekit::Task<stripekit::webhooks::ProcessResult> {
304 * co_return stripekit::webhooks::ProcessResult::Handled;
305 * }
306 * });
307 * @endcode
308 *
309 * @param options The route, signing secret, store, and handler. The store must
310 * outlive the running application.
311 *
312 * @sa @ref quickstart
313 */
315
316/** @} */
317
318} // namespace stripekit::drogon
Sends StripeKit's requests to Stripe.
Definition HTTPClient.h:110
std::function< void(std::variant< HttpResponse, std::exception_ptr >)> Completion
Completion callback for the callback form of send().
Definition HTTPClient.h:113
Receives StripeKit's log entries.
Definition Logging.h:74
The result of an asynchronous StripeKit operation.
Definition Async.h:45
Remembers which webhook events have been handled.
Handles incoming webhook deliveries through an EventDispatcher.
void send(const HttpRequest &request, Completion completion) override
Sends a request to Stripe, reporting the result through a callback.
HTTPClient(std::string api_key, std::string api_base_url="https://api.stripe.com", EventLoopOptions event_loop={})
Creates a client for a Stripe account.
Task< HttpResponse > send_async(const HttpRequest &request) override
Sends a request to Stripe.
~HTTPClient() override
Destroys the client, stopping its event loop thread if it owns one.
LogBridge(LogLevel threshold=LogLevel::Info)
Creates a bridge.
bool should_log(LogLevel level) const override
Reports whether entries at this level are wanted.
void log(LogLevel level, std::string_view message, const LogContext &context) override
Writes one entry through Drogon's matching log level.
void register_handler(std::string path) const
Adds the route to the Drogon application.
WebhookEndpoint(WebhookProcessor &processor, std::string endpoint_secret)
Creates an endpoint.
std::unique_ptr< StripeClient > make_stripe_client(ClientOptions options)
Creates a Stripe client that talks through Drogon.
void register_webhook_handler(WebhookHandlerOptions options)
Adds a Stripe webhook route to the Drogon application.
LogLevel
Severity of a log entry.
Definition Logging.h:26
@ Info
Normal, noteworthy activity.
Definition Logging.h:29
constexpr std::chrono::minutes defaultStaleInflightTimeout
How long an unfinished claim is honoured before another process may take it.
std::function< Task< ProcessResult >(const Event &)> EventHandler
The application's webhook handler.
Definition Webhooks.h:59
Drogon.h.
Definition Drogon.h:24
A request StripeKit wants sent to Stripe.
Definition HTTPClient.h:42
Extra identifiers attached to a log entry.
Definition Logging.h:44
Settings for make_stripe_client().
Definition Drogon.h:84
std::string api_base_url
Stripe API base URL.
Definition Drogon.h:94
std::string api_key
Stripe secret key, of the form "sk_test_..." or "sk_live_...".
Definition Drogon.h:91
EventLoopOptions event_loop
Which event loop outbound requests run on.
Definition Drogon.h:97
Chooses which event loop outbound Stripe requests run on.
Definition Drogon.h:44
static EventLoopOptions own()
Runs requests on a loop StripeKit starts and owns.
static EventLoopOptions external(trantor::EventLoop &loop)
Runs requests on a loop the application already owns.
Mode mode
Which loop to use.
Definition Drogon.h:77
trantor::EventLoop * loop
The loop to use when mode is Mode::External.
Definition Drogon.h:80
static EventLoopOptions drogon_framework()
Runs requests on the loop of the running Drogon application.
@ DrogonFramework
Use the loop of the running Drogon application.
Definition Drogon.h:48
@ Own
StripeKit starts and owns a loop thread.
Definition Drogon.h:47
@ External
Use a loop the application already owns.
Definition Drogon.h:49
Settings for register_webhook_handler().
Definition Drogon.h:101
std::string path
Route to register, for example "/stripe/webhook".
Definition Drogon.h:103
std::chrono::minutes stale_inflight_timeout
How long before a claim left behind by a crashed process is reclaimed.
Definition Drogon.h:131
webhooks::EventHandler handler
Called for claimed events after verification.
Definition Drogon.h:122
std::chrono::seconds tolerance
How far the signature's timestamp may be from the current time.
Definition Drogon.h:128
std::string endpoint_secret
Signing secret for this endpoint, of the form "whsec_...".
Definition Drogon.h:111
WebhookDedupStore & dedup_store
Where processed event ids are recorded, so each event runs once.
Definition Drogon.h:119
Logger * logger
Where to report failures.
Definition Drogon.h:125