Stripe is an Element that integrates Stripe payment processing into the Namazu Elements SDK. It wraps the Stripe Java SDK behind a single StripeService interface covering customers, one-off payments, subscriptions, Stripe-hosted Checkout and the Customer Portal, and usage-based billing via Stripe Billing Meters — and it turns Stripe’s webhook stream into strongly-typed internal events any other Element can subscribe to, without parsing a single webhook payload.
Overview #
Most of what Stripe exposes lives behind one exported Service, StripeService (package dev.getelements.elements.stripe.Service, in the api module). Any Element that declares a dependency on Stripe can inject it via the Service locator and call it directly — creating customers, charging one-off payments, managing subscriptions, resolving prices, and recording metered usage. A REST layer re-exposes the player-facing subset of that surface under an authenticated API, and a webhook receiver keeps Stripe’s own state (payments, invoices, subscriptions) in sync by publishing typed events whenever Stripe calls back.
Credentials and webhook signing secrets can be set as deployment attributes or overridden at runtime through a superuser admin panel, without a rebuild or redeploy. A small MongoDB-backed audit log records every webhook Stripe has ever sent, viewable from the same panel.
Key Features #
- Customer, PaymentIntent, SetupIntent, and Subscription management through one
StripeServiceinterface, plus Stripe-hosted Checkout Sessions and Customer Portal sessions - Usage-based billing via Stripe Billing Meters, including a meter-to-price join Stripe’s own API doesn’t provide (see Products, Prices, and Billing Meters below)
- A signature-verified webhook receiver that publishes both a raw event and, for the most common event types, a strongly-typed record via
Element.publish(see Stripe Webhooks and the Typed Event Bus) - An authenticated REST API for front-end and server-to-server use (see Stripe REST API Reference)
- A superuser admin UI panel for configuring credentials and browsing the webhook event log, with values persisted to MongoDB so they override the Element’s deployment attributes without a rebuild
Module Structure #
| Module | Purpose |
|---|---|
api | Exported interfaces and data types: StripeService, request/response records, typed event records, and the StripeEvents name constants. Consumed by other Elements via a classified jar. |
Element | REST endpoints, Guice wiring, the webhook receiver, Morphia persistence, and the admin UI plugin. Builds the .elm archive. |
debug | Local runner — boots a local MongoDB replica set via Docker, then the Elements runtime with this Element loaded. |
ui | React/TypeScript admin panel (built only under the build-ui Maven Profile) that registers a “Stripe” tab in the superuser dashboard for configuration and event-log viewing. |
integration-test | Tests that exercise the real Stripe API in test mode, driven by environment variables. |
consumer-test | A second, independent Element demonstrating how to consume Stripe’s published events with @ElementEventConsumer. |
Core Concepts #
Customers and Payment Methods #
StripeService.createCustomer(email, name, orgId) creates a Stripe Customer and stamps orgId into its Metadata under the orgId key (StripeService.METADATA_ORG_ID). Later, findCustomerByMetadata(metadataKey, metadataValue) uses the Stripe Customer Search API to look that customer back up by the same key — the recommended find-or-create pattern, so deleting and recreating an org on your side doesn’t create an orphaned duplicate Stripe customer. Neither the key nor the value may contain a single-quote character.
Before charging a customer or starting a subscription, attach a payment method via a SetupIntent: createSetupIntent(customerId) returns a client secret the front end passes to Stripe.js (stripe.confirmCardSetup) to collect a card without an immediate charge. listPaymentMethods(customerId) and the cheaper hasPaymentMethod(customerId) check what’s on file.
Note
createCustomer, createSetupIntent, listPaymentMethods, and hasPaymentMethod are StripeService methods only — they have no REST endpoint. Call them from server-side code in an Element that depends on Stripe’s api module. See Stripe REST API Reference for the full list of what is and isn’t exposed over HTTP.
One-Off Payments with PaymentIntent #
createPaymentIntent(CreatePaymentIntentRequest) creates a Stripe PaymentIntent for a single charge and returns a client secret for the front end to confirm with stripe.confirmCardPayment. The request carries the amount (in the currency’s smallest unit, e.g. cents), an ISO 4217 currency code, the target customer, optional Metadata, and an optional idempotencyKey — supplying one makes retries safe, since Stripe returns the original PaymentIntent instead of creating a second charge. A CreatePaymentIntentRequest.of(amount, currency, customerId) factory covers the common case where nothing else needs to be set.
Subscriptions #
createSubscription(customerId, CreateSubscriptionRequest) starts a recurring subscription at a given price; the customer must already have a default payment method on file. getSubscriptionStatus(subscriptionId), listSubscriptionsByCustomer(customerId, status, limit, startingAfter), and cancelSubscription(subscriptionId) round out the lifecycle — cancellation is immediate, ending access right away rather than at the end of the billing period. For end-of-period cancellation, send the customer to the Customer Portal instead.
Checkout Sessions and the Customer Portal #
Rather than building your own payment form, createCheckoutSession(CreateCheckoutSessionRequest) creates a Stripe-hosted Checkout page and returns its URL; redirecting the customer there hands Stripe the entire payment-collection flow, with Stripe redirecting back to your successUrl or cancelUrl when the customer is done. mode defaults to subscription if omitted; pass payment for a one-off charge. Metadata set on the request is stamped onto both the Checkout Session and the resulting Subscription or PaymentIntent, which is the easiest way to carry an orgId or SKU identifier through to your webhook handlers without a DAO lookup.
createBillingPortalSession(customerId, returnUrl) (exposed over REST as Create a Customer Portal Session) creates a single-use Customer Portal URL where the customer can manage their own subscriptions and payment methods with no server-side billing logic in your game at all.
Products, Prices, and Billing Meters #
listProducts, getProduct, listPrices, and retrievePrice read the Stripe product catalogue. listPrices results are cached in memory for dev.getelements.elements.stripe.price.cache.ttl.ms milliseconds (5 minutes by default) to avoid hitting Stripe on every catalogue page load.
Usage-based billing runs on Stripe Billing Meters. Stripe’s own API has no “list prices by meter” endpoint, so listMeters(activeOnly, limit) fetches meters and active recurring prices separately and joins them in memory, keyed by each price’s recurring.meter field, so each MeterSummary arrives with its billing PriceSummary already attached (or null, if no recurring price references that meter yet).
Two overloads resolve a price from a meter’s event name directly, without needing the REST of the catalogue:
resolvePriceForMeterEventName(eventName)— catalogue-wide, and cached (same TTL aslistPrices). If more than one active price references the same meter — a customer on a legacy tier versus the current one, for example — this can only return an arbitrary match.resolvePriceForMeterEventName(eventName, subscriptionId)— scoped to one subscription. It bypasses the cache and inspects that subscription’s own line items for the price actually attached to it, which is how to disambiguate the legacy-tier case above.
To report usage, call recordMeterEvent(customerId, eventName, value, idempotencyKey) with a BigDecimal quantity (e.g. 0.25) — it’s sent to Stripe as both the meter event’s deduplication identifier and the HTTP idempotency key, so a retried call never double-reports usage. If Stripe has no active meter configured for eventName, this throws NoSuchMeterException rather than surfacing Stripe’s raw error string, so callers can catch a specific type.
Webhooks and the Typed Event Bus #
Stripe calls back into a single signature-verified webhook endpoint, which publishes a raw event for every webhook it receives and, for the most common event types, a strongly-typed record other Elements can subscribe to with @ElementEventConsumer — no webhook JSON parsing required. See Stripe Webhooks and the Typed Event Bus for the full event list, Dashboard setup, and local testing with the Stripe CLI.
Configuration and Persistence #
The Stripe API key and webhook signing secret are required deployment attributes, overridable at runtime from a superuser admin panel without a rebuild. Two MongoDB collections back the Element: one holding that runtime credential override, and one logging every webhook Stripe has ever sent for audit and troubleshooting. See Configuring the Stripe Element.
Related Pages #
- Configuring the Stripe Element — required attributes, credential precedence, multi-environment deployment, and Maven coordinates
- Stripe Webhooks and the Typed Event Bus — webhook verification, Stripe Dashboard setup, the full typed-event table, and consuming events from another Element
- Stripe REST API Reference — every REST endpoint, its request/response shape, and which
StripeServicemethods are Service-only

