← All @molecule/* packages · App templates
@molecule/api-resource-payment-methodAPI resource · resource-payment-method · API (Node) · v1.0.1 · Apache-2.0
Saved Stripe PaymentMethods with metadata + default flag.
npm install @molecule/api-resource-payment-method@molecule/api-resource-payment-method is an API resource: the routes, validation and storage for resource-payment-method, built on the database and auth cores so it runs on whichever providers your app has bonded.
import { routes, requestHandlerMap } from '@molecule/api-resource-payment-method'
// Mount via mlcl-generated router; service-level usage:
import {
createSetupIntent,
attachPaymentMethod,
listPaymentMethods,
setDefaultPaymentMethod,
deletePaymentMethod,
} from '@molecule/api-resource-payment-method'Works with: @molecule/api-bond, @molecule/api-i18n, @molecule/api-payments, @molecule/api-resource, @molecule/api-secrets
Secrets: STRIPE_SECRET_KEY
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Saved payment-method resource for molecule.dev.
Wraps the Stripe SetupIntent flow (and any future card-style provider) into a database-backed list of saved payment methods with a per-user default.
import { routes, requestHandlerMap } from '@molecule/api-resource-payment-method'
// Mount via mlcl-generated router; service-level usage:
import {
createSetupIntent,
attachPaymentMethod,
listPaymentMethods,
setDefaultPaymentMethod,
deletePaymentMethod,
} from '@molecule/api-resource-payment-method'
resource
npm install @molecule/api-resource-payment-method @molecule/api-bond @molecule/api-database @molecule/api-i18n @molecule/api-logger @molecule/api-payments @molecule/api-resource @molecule/api-secrets
AttachPaymentMethodInputInput body for POST /me/payment-methods after the SetupIntent confirms.
interface AttachPaymentMethodInput {
/** Provider payment-method ID returned by the frontend SDK after confirmation. */
providerPaymentMethodId: string
/** Optional flag — when `true`, mark this method as default after attaching. */
setDefault?: boolean
}
PaymentMethodA saved payment method as returned to API consumers.
interface PaymentMethod {
/** Unique identifier for the saved payment method (UUID). */
id: string
/** Owner of this payment method. */
userId: string
/** Provider that issued the payment method (`stripe`, etc.). */
provider: PaymentMethodProvider
/** Provider customer ID (e.g. Stripe `cus_...`). */
providerCustomerId: string
/** Provider payment-method ID (e.g. Stripe `pm_...`). */
providerPaymentMethodId: string
/** Last four digits of the card. */
last4: string
/** Card brand (e.g. `visa`, `mastercard`). */
brand: string
/** Two-digit expiry month (1–12). */
expMonth: number
/** Four-digit expiry year. */
expYear: number
/** Whether this is the user's default saved payment method. */
isDefault: boolean
/** ISO 8601 creation timestamp. */
createdAt: string
}
PaymentMethodRowInternal database row for a saved payment method.
interface PaymentMethodRow {
id: string
userId: string
provider: string
providerCustomerId: string
providerPaymentMethodId: string
last4: string
brand: string
expMonth: number
expYear: number
isDefault: boolean
createdAt: string
}
SetupIntentResponseResult returned to the client after creating a SetupIntent.
interface SetupIntentResponse {
/** Provider SetupIntent ID. */
id: string
/** Client secret consumed by the frontend SDK to confirm the SetupIntent. */
clientSecret: string
/** Provider customer ID this SetupIntent is attached to. */
customerId: string
/** Provider that issued the SetupIntent. */
provider: PaymentMethodProvider
}
PaymentMethodProviderSupported saved-payment-method providers.
Open string type so additional rails can be added without editing this
package. Well-known values include stripe.
type PaymentMethodProvider = string
attachPaymentMethod(userId, providerPaymentMethodId)Records a saved payment method after the SetupIntent confirms client-side.
Looks the payment method up via the bonded payments provider to capture brand/last4/exp, then persists it. If the user has no other saved methods, the new method is marked as default.
function attachPaymentMethod(
userId: string,
providerPaymentMethodId: string,
): Promise<PaymentMethod>
userId — The owning user.providerPaymentMethodId — The payment-method ID returned by the frontend SDK.Returns: The persisted payment method.
createSetupIntent(userId)Creates a SetupIntent for the saved-card flow.
Reuses the user's provider customer ID if one already exists; otherwise the
provider creates a new customer and the resulting ID is returned to the
client (and persisted on the next attachPaymentMethod call).
function createSetupIntent(userId: string): Promise<SetupIntentResponse>
userId — The user the SetupIntent is being created for.Returns: The SetupIntent payload to forward to the frontend SDK.
deletePaymentMethod(id, userId)Deletes a saved payment method, detaching it from the provider first.
The local row is removed even if the provider detach call fails — the provider error is logged but does not abort the delete (orphaned records at the provider are preferable to a method we can't remove from our own UI).
function deletePaymentMethod(id: string, userId: string): Promise<boolean>
id — The payment-method row to delete.userId — The user expected to own the row.Returns: true on success, false if the row was not found or not owned by userId.
getPaymentMethod(id, userId)Looks up a single saved payment method, enforcing ownership.
function getPaymentMethod(id: string, userId: string): Promise<PaymentMethod | null>
id — The payment-method row ID.userId — The user expected to own the row.Returns: The payment method, or null if not found / not owned by userId.
listPaymentMethods(userId)Lists every saved payment method for a user, newest first.
function listPaymentMethods(userId: string): Promise<PaymentMethod[]>
userId — The owning user.Returns: The user's saved payment methods.
setDefaultPaymentMethod(userId, id)Marks a payment method as the user's default, clearing the flag from any other saved methods belonging to the same user.
function setDefaultPaymentMethod(userId: string, id: string): Promise<PaymentMethod | null>
userId — The owning user.id — The payment-method row to promote.Returns: The updated default payment method, or null if not found / not owned.
toPaymentMethod(row)Converts a raw database row into a typed {@link PaymentMethod}.
function toPaymentMethod(row: PaymentMethodRow): PaymentMethod
row — The database row.Returns: The deserialized payment method.
i18nRegisteredWhether i18n registration has been attempted. Always true; this module is
a placeholder for symmetry with locale-bonded resources.
const i18nRegistered: true
PROVIDER_NAMEThe provider name used by this resource. Currently fixed to stripe —
a future cross-rail rollout would dispatch on the user's selection.
const PROVIDER_NAME: 'stripe'
requestHandlerMapHandler map for the payment-method resource routes.
const requestHandlerMap: {
readonly createSetupIntent: typeof createSetupIntent
readonly listPaymentMethods: typeof listPaymentMethods
readonly setDefaultPaymentMethod: typeof setDefaultPaymentMethod
readonly deletePaymentMethod: typeof deletePaymentMethod
}
resourcePaymentMethodSecretDefinitionsSecret definitions required by the payment-method resource.
const resourcePaymentMethodSecretDefinitions: SecretDefinition[]
routesSaved payment-method routes.
const routes: readonly [
{
readonly method: 'post'
readonly path: '/me/payment-methods/setup-intent'
readonly handler: 'createSetupIntent'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'get'
readonly path: '/me/payment-methods'
readonly handler: 'listPaymentMethods'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'put'
readonly path: '/me/payment-methods/:id/default'
readonly handler: 'setDefaultPaymentMethod'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'delete'
readonly path: '/me/payment-methods/:id'
readonly handler: 'deletePaymentMethod'
readonly middlewares: readonly ['authenticate']
},
]
TABLE_NAMEThe database table name for saved payment methods.
const TABLE_NAME: 'payment_methods'
Peer dependencies:
@molecule/api-bond ^1.0.1@molecule/api-database ^1.0.1@molecule/api-i18n ^1.0.1@molecule/api-logger ^1.0.1@molecule/api-payments ^1.0.1@molecule/api-resource ^1.0.1@molecule/api-secrets ^1.0.1STRIPE_SECRET_KEY (required) — Stripe secret key
sk_test_...@molecule/api-bond@molecule/api-database@molecule/api-i18n@molecule/api-logger@molecule/api-payments@molecule/api-resource@molecule/api-secretsBond ordering: the service resolves the provider with
get('payments', 'stripe') — wire @molecule/api-payments-stripe under the
name stripe (bond('payments', 'stripe', provider)) BEFORE any route
runs, and set STRIPE_SECRET_KEY. The provider name is currently fixed
(PROVIDER_NAME = 'stripe'); other card-style providers plug in by
implementing the same SetupIntent-shaped PaymentProvider surface.
Table: src/__setup__/payment_methods.sql creates payment_methods. An
mlcl-scaffolded API replays __setup__/*.sql automatically on migrate;
anywhere else run it once.
All routes are SELF-scoped under /me/payment-methods and read the
authenticated user from res.locals.session (401 without a session) — a
user can only list/attach/default/delete their OWN methods; never accept a
target userId from the client. Raw card data never touches your API: the
client confirms the SetupIntent with the provider and only the provider's
payment-method id is attached and stored.
SECURITY / PCI-critical verification — drive the real UI (live preview, no
mocks) and adapt each item to this app's actual card screens. Check every
box off one by one; a box you can't check is a security bug to fix, not a
skip. NEVER type a real card number anywhere — use the provider's TEST card
(Stripe test mode, 4242 4242 4242 4242), and it must go to the provider
SDK, never to your API:
providerPaymentMethodId like pm_…, providerCustomerId like cus_…)
plus safe display fields (brand, last4, expMonth, expYear). Confirm
with GET /me/payment-methods that the response — and the payment_methods
table schema — carry NO full card number (PAN) and NO CVV: the raw PAN/CVV
must appear NOWHERE in the DB row, the API response, or the server logs.brand + •••• 4242) — never the
full number and never the CVV.isDefault
to false, so only one method has isDefault: true (a partial unique index
enforces this at the DB level).DELETE /me/payment-methods/:id → 204,
gone from the list) AND detaches it at the provider, so it can no longer be
charged./me/…-scoped to the session user: a
user lists/adds/defaults/deletes only their OWN methods. Guessing another
user's payment-method id into PUT /me/payment-methods/:id/default or
DELETE /me/payment-methods/:id returns 404 (never touches their card),
and no endpoint accepts a target userId from the client.STRIPE_SECRET_KEY) stays server-side only —
never shipped to the browser bundle (this package is server-only). The card
is tokenized client-side by the provider SDK using the SetupIntent client
secret, so the raw card never touches your server.