@stratacommerce/stripe-connect 0.0.0-stage → 0.1.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.
package/dist/schema.js ADDED
@@ -0,0 +1,26 @@
1
+ import { pgSchema, uuid, text, jsonb, timestamp, integer, uniqueIndex, index } from 'drizzle-orm/pg-core';
2
+ // The extension's tables, in its own schema. Core ids (the store link, the
3
+ // approving owner) are plain columns: extension tables don't reference core.
4
+ export const connectSchema = pgSchema('ext_stripe_connect');
5
+ const created = () => timestamp('created_at', { withTimezone: true }).notNull().defaultNow();
6
+ // An enrollment keeps the store link it was made through. Revoking a link
7
+ // keeps the enrollment, so its history keeps its link.
8
+ export const enrollments = connectSchema.table('enrollments', {
9
+ id: uuid('id').primaryKey(), linkId: uuid('link_id').notNull(),
10
+ scope: jsonb('scope').$type().notNull(), policy: jsonb('policy').$type().notNull(),
11
+ state: text('state').$type().notNull(),
12
+ nonceHash: text('nonce_hash').notNull(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
13
+ tokenBox: text('token_box'), publicKeys: jsonb('public_keys').$type().notNull().default({}),
14
+ recoveryBindingIds: jsonb('recovery_binding_ids').$type().notNull().default([]),
15
+ approvedBy: uuid('approved_by'), approvedAt: timestamp('approved_at', { withTimezone: true }), createdAt: created(),
16
+ });
17
+ export const receipts = connectSchema.table('receipts', {
18
+ deliveryId: uuid('delivery_id').primaryKey(), originalBindingId: uuid('original_binding_id').notNull(), recipientBindingId: uuid('recipient_binding_id').notNull(),
19
+ fact: jsonb('fact').$type().notNull(), state: text('state').$type().notNull().default('pending'),
20
+ errorCode: text('error_code'), nextAttemptAt: timestamp('next_attempt_at', { withTimezone: true }).notNull().defaultNow(), createdAt: created(),
21
+ }, (t) => [index('receipts_pending').on(t.state, t.nextAttemptAt)]);
22
+ export const financialFacts = connectSchema.table('financial_facts', {
23
+ id: uuid('id').primaryKey().defaultRandom(), originalBindingId: uuid('original_binding_id').notNull(), paymentIntentId: text('payment_intent_id').notNull(),
24
+ kind: text('kind').notNull(), objectId: text('object_id').notNull(), version: integer('version').notNull(),
25
+ fact: jsonb('fact').$type().notNull(), createdAt: created(),
26
+ }, (t) => [uniqueIndex('financial_fact_identity').on(t.originalBindingId, t.kind, t.objectId)]);
@@ -0,0 +1,18 @@
1
+ import { type ComponentType } from 'react';
2
+ /** What the site's checkout passes to a provider component. */
3
+ export interface CheckoutPaymentProps {
4
+ client: Record<string, unknown>;
5
+ orderId: string;
6
+ amountDueCents: number;
7
+ currency: string;
8
+ returnUrl: string;
9
+ }
10
+ /** List this as `'stripe-connect:card'` in the site's src/payments.tsx. */
11
+ export declare const stripeConnectCheckout: {
12
+ id: string;
13
+ namespace: string;
14
+ i18nKey: string;
15
+ Component: ComponentType<CheckoutPaymentProps>;
16
+ messages: Record<string, Record<string, unknown>>;
17
+ };
18
+ export default stripeConnectCheckout;
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useMemo, useState } from 'react';
4
+ import { useLocale, useT } from '@stratacommerce/i18n';
5
+ import { Elements, PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js';
6
+ import { checkoutStripe } from './stripe-context.js';
7
+ const NAMESPACE = 'ext.stripe-connect';
8
+ const messages = {
9
+ 'en-US': {
10
+ label: 'Credit Card, Apple Pay, Google Pay',
11
+ title: 'Payment',
12
+ processing: 'Processing...',
13
+ pay: 'Pay',
14
+ paymentFailed: 'Payment failed. Please try again.',
15
+ notConfigured: 'This payment method is not set up. Please contact the store owner.',
16
+ },
17
+ 'es-MX': {
18
+ label: 'Tarjeta, Apple Pay, Google Pay',
19
+ title: 'Pago',
20
+ processing: 'Procesando...',
21
+ pay: 'Pagar',
22
+ paymentFailed: 'El pago falló. Intenta de nuevo.',
23
+ notConfigured: 'Este método de pago no está configurado. Contacta al dueño de la tienda.',
24
+ },
25
+ };
26
+ function StripeConnectForm({ amountDueCents, currency, returnUrl }) {
27
+ const t = useT(NAMESPACE);
28
+ const { locale } = useLocale();
29
+ const stripe = useStripe();
30
+ const elements = useElements();
31
+ const [submitting, setSubmitting] = useState(false);
32
+ const [error, setError] = useState(null);
33
+ const amount = new Intl.NumberFormat(locale ?? 'en-US', { style: 'currency', currency: currency.toUpperCase() }).format(amountDueCents / 100);
34
+ const handleSubmit = async (e) => {
35
+ e.preventDefault();
36
+ if (!stripe || !elements)
37
+ return;
38
+ setSubmitting(true);
39
+ setError(null);
40
+ const { error: stripeError } = await stripe.confirmPayment({ elements, confirmParams: { return_url: returnUrl } });
41
+ if (stripeError) {
42
+ setError(stripeError.message ?? t('paymentFailed'));
43
+ setSubmitting(false);
44
+ }
45
+ };
46
+ return (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-6", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-foreground mb-4", children: t('title') }), _jsx(PaymentElement, {})] }), error && (_jsx("p", { className: "text-sm text-red-600", role: "alert", children: error })), _jsx("button", { type: "submit", disabled: !stripe || submitting, className: "w-full rounded-md bg-foreground text-background py-3 px-6 text-sm font-medium transition-opacity hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed", children: submitting ? t('processing') : _jsxs(_Fragment, { children: [t('pay'), " ", _jsx("span", { children: amount })] }) })] }));
47
+ }
48
+ function StripeConnectCheckout({ client, ...props }) {
49
+ const t = useT(NAMESPACE);
50
+ const setup = useMemo(() => {
51
+ try {
52
+ if (typeof client.clientSecret !== 'string' || !client.clientSecret)
53
+ return null;
54
+ return { clientSecret: client.clientSecret, stripe: checkoutStripe(client) };
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }, [client]);
60
+ if (!setup)
61
+ return _jsx("p", { className: "text-sm text-red-600", role: "alert", children: t('notConfigured') });
62
+ return (_jsx(Elements, { stripe: setup.stripe, options: { clientSecret: setup.clientSecret, appearance: { theme: 'stripe' } }, children: _jsx(StripeConnectForm, { ...props }) }, setup.clientSecret));
63
+ }
64
+ /** List this as `'stripe-connect:card'` in the site's src/payments.tsx. */
65
+ export const stripeConnectCheckout = {
66
+ id: 'stripe-connect:card',
67
+ namespace: NAMESPACE,
68
+ i18nKey: 'label',
69
+ Component: StripeConnectCheckout,
70
+ messages,
71
+ };
72
+ export default stripeConnectCheckout;
@@ -0,0 +1 @@
1
+ export declare function checkoutStripe(client: Record<string, unknown>): Promise<import("@stripe/stripe-js").Stripe | null>;
@@ -0,0 +1,10 @@
1
+ import { loadStripe } from '@stripe/stripe-js';
2
+ const text = (value) => typeof value === 'string' ? value : '';
3
+ // Loads Stripe for the connected account in the session's client data, after
4
+ // checking the publishable key matches the account's mode.
5
+ export function checkoutStripe(client) {
6
+ const stripeAccount = text(client.stripeAccount), publishableKey = text(client.publishableKey), livemode = client.livemode;
7
+ if (!stripeAccount.startsWith('acct_') || !text(client.contextId) || typeof livemode !== 'boolean' || !publishableKey.startsWith(livemode ? 'pk_live_' : 'pk_test_'))
8
+ throw new Error('Checkout payment configuration does not match its account and mode');
9
+ return loadStripe(publishableKey, { stripeAccount });
10
+ }
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@stratacommerce/stripe-connect",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "Card payments through Strata Hub’s Stripe Connect platform, as a StrataCommerce payment extension.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/strata-commerce/stratacommerce.git",
9
+ "directory": "extensions/stripe-connect"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./admin": {
20
+ "types": "./dist/admin/index.d.ts",
21
+ "default": "./dist/admin/index.js"
22
+ },
23
+ "./storefront": {
24
+ "types": "./dist/storefront/index.d.ts",
25
+ "default": "./dist/storefront/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@stripe/react-stripe-js": "^6.1.0",
37
+ "@stripe/stripe-js": "^9.1.0",
38
+ "zod": "^3",
39
+ "@stratacommerce/hub-protocol": "^0.1.0",
40
+ "@stratacommerce/extension-sdk": "^1.0.0"
41
+ },
42
+ "peerDependencies": {
43
+ "drizzle-orm": ">=0.39 <1",
44
+ "react": "^18.3.0 || ^19.0.0",
45
+ "@stratacommerce/i18n": "^0.1.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@stratacommerce/i18n": {
49
+ "optional": true
50
+ },
51
+ "drizzle-orm": {
52
+ "optional": true
53
+ },
54
+ "react": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "devDependencies": {
59
+ "@testing-library/dom": "^10",
60
+ "@testing-library/react": "^16",
61
+ "@types/node": "^20.19.39",
62
+ "@types/react": "^19",
63
+ "drizzle-kit": "^0.30",
64
+ "drizzle-orm": "^0.39",
65
+ "jsdom": "^25",
66
+ "react": "^19",
67
+ "react-dom": "^19",
68
+ "typescript": "^5",
69
+ "vitest": "^3",
70
+ "@stratacommerce/i18n": "0.1.0"
71
+ },
72
+ "scripts": {
73
+ "lint": "tsc --noEmit",
74
+ "test": "vitest run --passWithNoTests",
75
+ "test:integration": "vitest run --config vitest.integration.config.ts"
76
+ }
6
77
  }