@dineway-ai/plugin-payment 0.2.1

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.
@@ -0,0 +1,38 @@
1
+ import type { StripeMode, StripeTaxStatus } from "../types.js";
2
+
3
+ export type PaymentStripeConfigurationStatus =
4
+ | "unconfigured"
5
+ | "unverified"
6
+ | "valid"
7
+ | "invalid";
8
+ export type PaymentStripeConnectStatus = "not_configured" | "restricted" | "ready";
9
+
10
+ export interface PaymentStripeSettings {
11
+ id: string;
12
+ mode: StripeMode;
13
+ publishableKey: string | null;
14
+ configurationStatus: PaymentStripeConfigurationStatus;
15
+ stripeAccountId: string | null;
16
+ chargesEnabled: boolean | null;
17
+ detailsSubmitted: boolean | null;
18
+ taxStatus: StripeTaxStatus;
19
+ validationErrorCode: string | null;
20
+ lastValidatedAt: string | null;
21
+ connectStatus: PaymentStripeConnectStatus;
22
+ version: number;
23
+ createdAt: string;
24
+ updatedAt: string;
25
+ }
26
+
27
+ export interface UpdatePaymentStripeSettings {
28
+ mode?: StripeMode;
29
+ publishableKey?: string | null;
30
+ configurationStatus?: PaymentStripeConfigurationStatus;
31
+ stripeAccountId?: string | null;
32
+ chargesEnabled?: boolean | null;
33
+ detailsSubmitted?: boolean | null;
34
+ taxStatus?: StripeTaxStatus;
35
+ validationErrorCode?: string | null;
36
+ lastValidatedAt?: string | null;
37
+ connectStatus?: PaymentStripeConnectStatus;
38
+ }
@@ -0,0 +1 @@
1
+ export const PAYMENT_PLUGIN_ID = "dineway-payment";
package/src/index.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { definePlugin, getDb } from "dineway";
2
+ import type { PluginDescriptor, PluginHooks, ResolvedPlugin } from "dineway";
3
+
4
+ import { version as packageVersion } from "../package.json";
5
+ import { PaymentStripeConfigurationRepository } from "./configuration/repository.js";
6
+ import { PAYMENT_PLUGIN_ID } from "./constants.js";
7
+ import { createPaymentRoutes } from "./routes/index.js";
8
+
9
+ const PAYMENT_ADMIN_ENTRY = "@dineway-ai/plugin-payment/admin";
10
+ const PAYMENT_ADMIN_PAGES = [{ path: "/payments", label: "Payments", icon: "credit-card" }] as const;
11
+
12
+ export interface PaymentPluginOptions {
13
+ appName?: string;
14
+ }
15
+
16
+ export function paymentPlugin(
17
+ options: PaymentPluginOptions = {},
18
+ ): PluginDescriptor<PaymentPluginOptions> {
19
+ return {
20
+ id: PAYMENT_PLUGIN_ID,
21
+ version: packageVersion,
22
+ format: "native",
23
+ entrypoint: "@dineway-ai/plugin-payment",
24
+ adminEntry: PAYMENT_ADMIN_ENTRY,
25
+ adminPages: [...PAYMENT_ADMIN_PAGES],
26
+ options,
27
+ capabilities: ["network:request:unrestricted"],
28
+ allowedHosts: ["api.stripe.com"],
29
+ };
30
+ }
31
+
32
+ export function createPlugin(_options: PaymentPluginOptions = {}): ResolvedPlugin {
33
+ const ensurePaymentFoundation = async () => {
34
+ await new PaymentStripeConfigurationRepository(await getDb()).ensureDefaults();
35
+ };
36
+ const hooks: PluginHooks = {
37
+ "plugin:install": ensurePaymentFoundation,
38
+ "plugin:activate": ensurePaymentFoundation,
39
+ };
40
+ return definePlugin({
41
+ id: PAYMENT_PLUGIN_ID,
42
+ version: packageVersion,
43
+ capabilities: ["network:request:unrestricted"],
44
+ allowedHosts: ["api.stripe.com"],
45
+ admin: { entry: PAYMENT_ADMIN_ENTRY, pages: [...PAYMENT_ADMIN_PAGES] },
46
+ hooks,
47
+ routes: createPaymentRoutes(),
48
+ });
49
+ }
50
+
51
+ export * from "./configuration/index.js";
52
+ export { PAYMENT_PLUGIN_ID } from "./constants.js";
53
+ export * from "./stripe/index.js";
54
+ export * from "./routes/index.js";
55
+ export * from "./types.js";
@@ -0,0 +1,85 @@
1
+ import type { z } from "astro/zod";
2
+ import {
3
+ getDb,
4
+ PluginRouteError,
5
+ type PluginRoute,
6
+ type PluginSecretStoreOptions,
7
+ type RouteContext,
8
+ } from "dineway";
9
+
10
+ import {
11
+ getPaymentStripeConfiguration,
12
+ PaymentConfigurationError,
13
+ testPaymentStripeConnection,
14
+ updatePaymentStripeConfiguration,
15
+ } from "../configuration/service.js";
16
+ import { StripeNodeAccountProbe } from "../stripe/account-probe.js";
17
+ import type { StripeAccountProbe } from "../types.js";
18
+ import {
19
+ getPaymentStripeConfigurationSchema,
20
+ testPaymentStripeConnectionSchema,
21
+ updatePaymentStripeConfigurationSchema,
22
+ } from "./schemas.js";
23
+
24
+ const AUTHORIZATION = {
25
+ permission: "content:edit_any" as const,
26
+ anyOfTokenScopes: ["admin"],
27
+ };
28
+
29
+ export interface PaymentRouteOptions {
30
+ accountProbe?: StripeAccountProbe;
31
+ secretOptions?: PluginSecretStoreOptions;
32
+ }
33
+
34
+ export function createPaymentRoutes(options: PaymentRouteOptions = {}): Record<string, PluginRoute> {
35
+ const probe = options.accountProbe ?? new StripeNodeAccountProbe();
36
+ return {
37
+ "stripe/configuration/get": route(getPaymentStripeConfigurationSchema, async () => ({
38
+ configuration: await getPaymentStripeConfiguration(await getDb(), options.secretOptions),
39
+ })),
40
+ "stripe/configuration/update": route(
41
+ updatePaymentStripeConfigurationSchema,
42
+ async (context) => ({
43
+ configuration: await updatePaymentStripeConfiguration(
44
+ await getDb(),
45
+ context.input,
46
+ options.secretOptions,
47
+ ),
48
+ }),
49
+ ),
50
+ "stripe/configuration/test": route(testPaymentStripeConnectionSchema, async () => ({
51
+ configuration: await testPaymentStripeConnection(
52
+ await getDb(),
53
+ probe,
54
+ options.secretOptions,
55
+ ),
56
+ })),
57
+ };
58
+ }
59
+
60
+ function route<T>(
61
+ input: z.ZodType<T>,
62
+ handler: (context: RouteContext<T>) => Promise<unknown>,
63
+ ): PluginRoute {
64
+ return {
65
+ methods: ["POST"],
66
+ input: input as never,
67
+ authorization: AUTHORIZATION,
68
+ handler: (async (context: RouteContext<T>) => runRoute(() => handler(context))) as never,
69
+ };
70
+ }
71
+
72
+ async function runRoute<T>(work: () => Promise<T>): Promise<T> {
73
+ try {
74
+ return await work();
75
+ } catch (error) {
76
+ if (!(error instanceof PaymentConfigurationError)) throw error;
77
+ throw new PluginRouteError(
78
+ error.code,
79
+ error.message,
80
+ error.code === "CONFLICT" || error.code === "INVALID_STATE" ? 409 : 400,
81
+ );
82
+ }
83
+ }
84
+
85
+ export * from "./schemas.js";
@@ -0,0 +1,15 @@
1
+ import { z } from "astro/zod";
2
+
3
+ export const getPaymentStripeConfigurationSchema = z.object({}).strict();
4
+
5
+ export const updatePaymentStripeConfigurationSchema = z
6
+ .object({
7
+ expectedVersion: z.number().int().positive(),
8
+ mode: z.enum(["test", "live"]).optional(),
9
+ publishableKey: z.string().max(512).nullable().optional(),
10
+ secretKey: z.string().max(512).nullable().optional(),
11
+ webhookSecret: z.string().max(512).nullable().optional(),
12
+ })
13
+ .strict();
14
+
15
+ export const testPaymentStripeConnectionSchema = z.object({}).strict();
@@ -0,0 +1,71 @@
1
+ import Stripe from "stripe";
2
+
3
+ import { stripeProviderFailure } from "./errors.js";
4
+ import type {
5
+ StripeAccountProbe,
6
+ StripeAccountProbeInput,
7
+ StripeAccountProbeResult,
8
+ } from "../types.js";
9
+
10
+ export interface StripeAccountProbeClient {
11
+ retrieveCurrentAccount(): Promise<{
12
+ id: string;
13
+ chargesEnabled: boolean;
14
+ detailsSubmitted: boolean;
15
+ }>;
16
+ retrieveTaxSettings(): Promise<{
17
+ livemode: boolean;
18
+ status: "active" | "pending";
19
+ }>;
20
+ }
21
+
22
+ export type StripeAccountProbeClientFactory = (secretKey: string) => StripeAccountProbeClient;
23
+
24
+ export class StripeNodeAccountProbe implements StripeAccountProbe {
25
+ constructor(
26
+ private readonly createClient: StripeAccountProbeClientFactory = createStripeAccountProbeClient,
27
+ ) {}
28
+
29
+ async probe(input: StripeAccountProbeInput): Promise<StripeAccountProbeResult> {
30
+ try {
31
+ const client = this.createClient(input.secretKey);
32
+ const [account, taxSettings] = await Promise.all([
33
+ client.retrieveCurrentAccount(),
34
+ client.retrieveTaxSettings(),
35
+ ]);
36
+ return {
37
+ ok: true,
38
+ accountId: account.id,
39
+ livemode: taxSettings.livemode,
40
+ chargesEnabled: account.chargesEnabled,
41
+ detailsSubmitted: account.detailsSubmitted,
42
+ taxStatus: taxSettings.status,
43
+ };
44
+ } catch (error) {
45
+ return { ok: false, errorCode: stripeProviderFailure(error).errorCode };
46
+ }
47
+ }
48
+ }
49
+
50
+ function createStripeAccountProbeClient(secretKey: string): StripeAccountProbeClient {
51
+ const stripe = new Stripe(secretKey, {
52
+ appInfo: { name: "Dineway Payments" },
53
+ maxNetworkRetries: 1,
54
+ timeout: 15_000,
55
+ typescript: true,
56
+ });
57
+ return {
58
+ async retrieveCurrentAccount() {
59
+ const account = await stripe.accounts.retrieveCurrent();
60
+ return {
61
+ id: account.id,
62
+ chargesEnabled: account.charges_enabled,
63
+ detailsSubmitted: account.details_submitted,
64
+ };
65
+ },
66
+ async retrieveTaxSettings() {
67
+ const settings = await stripe.tax.settings.retrieve();
68
+ return { livemode: settings.livemode, status: settings.status };
69
+ },
70
+ };
71
+ }