@kodice.one/stripefirebase-client 1.0.0
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/README.md +204 -0
- package/dist/example.d.ts +18 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +88 -0
- package/dist/example.js.map +1 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +80 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +131 -0
- package/dist/react.js.map +1 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Firebase + Stripe Payments — Monorepo
|
|
2
|
+
|
|
3
|
+
End-to-end payment integration using **Firebase Functions v2**, **Firestore**, and **Stripe Checkout**, structured as an npm workspaces monorepo.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Project structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
firebase-stripe-payments/
|
|
11
|
+
├── package.json ← workspace root
|
|
12
|
+
├── tsconfig.base.json ← shared TS compiler base
|
|
13
|
+
├── firebase.json ← Firebase project config
|
|
14
|
+
├── firebase/
|
|
15
|
+
│ ├── firestore.rules ← Firestore security rules
|
|
16
|
+
│ └── firestore.indexes.json
|
|
17
|
+
└── packages/
|
|
18
|
+
├── shared/ ← @kodice.one/stripefirebase-shared
|
|
19
|
+
│ ├── package.json
|
|
20
|
+
│ ├── tsconfig.json
|
|
21
|
+
│ └── src/index.ts ← all shared TypeScript types
|
|
22
|
+
│
|
|
23
|
+
├── functions/ ← @kodice.one/stripefirebase-functions
|
|
24
|
+
│ ├── package.json
|
|
25
|
+
│ ├── tsconfig.json
|
|
26
|
+
│ └── src/index.ts ← createCheckout + stripeWebhook (Functions v2)
|
|
27
|
+
│
|
|
28
|
+
└── client/ ← @kodice.one/stripefirebase-client (publishable npm lib)
|
|
29
|
+
├── package.json
|
|
30
|
+
├── tsconfig.json
|
|
31
|
+
├── tsconfig.esm.json
|
|
32
|
+
└── src/
|
|
33
|
+
├── index.ts ← createCheckoutUrl, onPaymentStatusChange, onUserCheckouts
|
|
34
|
+
├── react.ts ← useStripeCheckout, usePaymentStatus, useUserCheckouts
|
|
35
|
+
└── example.tsx ← reference React components
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
React App
|
|
44
|
+
│
|
|
45
|
+
├─ @kodice.one/stripefirebase-client
|
|
46
|
+
│ ├─ createCheckoutUrl() → calls Firebase Function (onCall v2)
|
|
47
|
+
│ ├─ onPaymentStatusChange() → real-time Firestore listener
|
|
48
|
+
│ └─ /react hooks
|
|
49
|
+
│
|
|
50
|
+
Firebase Functions (v2)
|
|
51
|
+
├─ createCheckout (onCall)
|
|
52
|
+
│ ├─ Validates auth + input
|
|
53
|
+
│ ├─ Creates Stripe Checkout Session
|
|
54
|
+
│ └─ Writes checkouts/{uid}/sessions/{sid} status: "pending"
|
|
55
|
+
│
|
|
56
|
+
└─ stripeWebhook (onRequest)
|
|
57
|
+
├─ Verifies Stripe signature
|
|
58
|
+
└─ Updates Firestore status on each event:
|
|
59
|
+
checkout.session.completed → "succeeded"
|
|
60
|
+
checkout.session.expired → "expired"
|
|
61
|
+
payment_intent.payment_failed → "failed"
|
|
62
|
+
customer.subscription.deleted → "canceled"
|
|
63
|
+
|
|
64
|
+
Firestore
|
|
65
|
+
└─ checkouts/{userId}/sessions/{sessionId}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Getting started
|
|
71
|
+
|
|
72
|
+
### 1. Install dependencies
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This installs all workspace packages in one shot.
|
|
79
|
+
|
|
80
|
+
### 2. Set Stripe secrets (stored in Google Cloud Secret Manager)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
firebase functions:secrets:set STRIPE_SECRET_KEY
|
|
84
|
+
# paste sk_live_... or sk_test_...
|
|
85
|
+
|
|
86
|
+
firebase functions:secrets:set STRIPE_WEBHOOK_SECRET
|
|
87
|
+
# paste whsec_... (from the Stripe Dashboard after step 4)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Build & deploy
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Build everything (shared → functions → client)
|
|
94
|
+
npm run build
|
|
95
|
+
|
|
96
|
+
# Deploy Functions + Firestore rules/indexes
|
|
97
|
+
firebase deploy
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 4. Register the Stripe webhook
|
|
101
|
+
|
|
102
|
+
In [Stripe Dashboard → Webhooks](https://dashboard.stripe.com/webhooks) add:
|
|
103
|
+
|
|
104
|
+
- **URL:** `https://<region>-<project-id>.cloudfunctions.net/stripeWebhook`
|
|
105
|
+
- **Events:**
|
|
106
|
+
- `checkout.session.completed`
|
|
107
|
+
- `checkout.session.expired`
|
|
108
|
+
- `payment_intent.payment_failed`
|
|
109
|
+
- `customer.subscription.deleted`
|
|
110
|
+
|
|
111
|
+
Copy the **Signing secret** and save it: `firebase functions:secrets:set STRIPE_WEBHOOK_SECRET`.
|
|
112
|
+
|
|
113
|
+
### 5. Use the client in your React app
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm install @kodice.one/stripefirebase-client
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// main.tsx
|
|
121
|
+
import { initializeApp } from "firebase/app";
|
|
122
|
+
import { getFunctions } from "firebase/functions";
|
|
123
|
+
import { getFirestore } from "firebase/firestore";
|
|
124
|
+
import { initStripeFirebase } from "@kodice.one/stripefirebase-client";
|
|
125
|
+
|
|
126
|
+
const app = initializeApp({ /* your firebase config */ });
|
|
127
|
+
|
|
128
|
+
initStripeFirebase({
|
|
129
|
+
functions: getFunctions(app),
|
|
130
|
+
firestore: getFirestore(app),
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
// PricingPage.tsx
|
|
136
|
+
import { useStripeCheckout } from "@kodice.one/stripefirebase-client/react";
|
|
137
|
+
|
|
138
|
+
export function SubscribeButton({ priceId }: { priceId: string }) {
|
|
139
|
+
const { startCheckout, loading, error } = useStripeCheckout();
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<button
|
|
143
|
+
disabled={loading}
|
|
144
|
+
onClick={() => startCheckout({
|
|
145
|
+
lineItems: [{ priceId, quantity: 1 }],
|
|
146
|
+
mode: "subscription",
|
|
147
|
+
successUrl: `${location.origin}/welcome?session_id={CHECKOUT_SESSION_ID}`,
|
|
148
|
+
cancelUrl: `${location.origin}/pricing`,
|
|
149
|
+
})}
|
|
150
|
+
>
|
|
151
|
+
{loading ? "Loading…" : "Subscribe"}
|
|
152
|
+
</button>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
// SuccessPage.tsx — mounted at your successUrl route
|
|
159
|
+
import { usePaymentStatus } from "@kodice.one/stripefirebase-client/react";
|
|
160
|
+
|
|
161
|
+
export function SuccessPage({ userId }: { userId: string }) {
|
|
162
|
+
const sessionId = new URLSearchParams(location.search).get("session_id");
|
|
163
|
+
const { status, session } = usePaymentStatus({ userId, sessionId });
|
|
164
|
+
|
|
165
|
+
if (status === "succeeded") return <h1>Welcome! 🎉</h1>;
|
|
166
|
+
if (status === "failed") return <p>Payment failed — please retry.</p>;
|
|
167
|
+
return <p>Confirming payment…</p>;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Local development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Start Firebase emulators (Functions + Firestore)
|
|
177
|
+
npm run emulate
|
|
178
|
+
|
|
179
|
+
# In a separate terminal — forward live Stripe events to the local webhook
|
|
180
|
+
stripe listen --forward-to http://localhost:5001/<project-id>/us-central1/stripeWebhook
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Functions v2 highlights
|
|
186
|
+
|
|
187
|
+
| Feature | v1 | v2 (this project) |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| Import path | `firebase-functions` | `firebase-functions/v2/https` |
|
|
190
|
+
| Secrets | `functions.config()` | `defineSecret()` → Secret Manager |
|
|
191
|
+
| Config | `functions.config().stripe.key` | `process.env` / `secret.value()` |
|
|
192
|
+
| Logging | `functions.logger` | `import { logger } from "firebase-functions/v2"` |
|
|
193
|
+
| onCall data | `(data, context)` | `(request)` → `request.data`, `request.auth` |
|
|
194
|
+
| Concurrency | 1 req / instance | Up to 1000 req / instance |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Security notes
|
|
199
|
+
|
|
200
|
+
- **Firestore rules** deny all client writes; only the Admin SDK (Functions) may write.
|
|
201
|
+
- **Auth guard** in `createCheckout` blocks unauthenticated callers.
|
|
202
|
+
- **Webhook signature** verification prevents spoofed Stripe events.
|
|
203
|
+
- `client_reference_id` and `metadata.userId` are set server-side — clients cannot impersonate another user's session.
|
|
204
|
+
- Stripe secrets are stored in **Google Cloud Secret Manager**, never in source code or `firebase.json`.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example React components — illustrates the full client integration.
|
|
3
|
+
* This file is for reference only; do not ship it in production.
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
export declare function BuyButton({ priceId }: {
|
|
7
|
+
priceId: string;
|
|
8
|
+
}): React.JSX.Element;
|
|
9
|
+
export declare function SubscribeButton({ priceId }: {
|
|
10
|
+
priceId: string;
|
|
11
|
+
}): React.JSX.Element;
|
|
12
|
+
export declare function SuccessPage({ userId }: {
|
|
13
|
+
userId: string;
|
|
14
|
+
}): React.JSX.Element | null;
|
|
15
|
+
export declare function OrderHistory({ userId }: {
|
|
16
|
+
userId: string;
|
|
17
|
+
}): React.JSX.Element;
|
|
18
|
+
//# sourceMappingURL=example.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../src/example.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,qBAqBzD;AAID,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,qBAsB/D;AAMD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,4BAuBzD;AAID,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,qBAiB1D"}
|
package/dist/example.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Example React components — illustrates the full client integration.
|
|
4
|
+
* This file is for reference only; do not ship it in production.
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.BuyButton = BuyButton;
|
|
11
|
+
exports.SubscribeButton = SubscribeButton;
|
|
12
|
+
exports.SuccessPage = SuccessPage;
|
|
13
|
+
exports.OrderHistory = OrderHistory;
|
|
14
|
+
const react_1 = __importDefault(require("react"));
|
|
15
|
+
const react_2 = require("./react");
|
|
16
|
+
// ── One-time payment button ──────────────────────────────────────────────────
|
|
17
|
+
function BuyButton({ priceId }) {
|
|
18
|
+
const { startCheckout, loading, error } = (0, react_2.useStripeCheckout)();
|
|
19
|
+
return (react_1.default.createElement("div", null,
|
|
20
|
+
react_1.default.createElement("button", { disabled: loading, onClick: () => startCheckout({
|
|
21
|
+
lineItems: [{ priceId, quantity: 1 }],
|
|
22
|
+
mode: "payment",
|
|
23
|
+
successUrl: `${window.location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
24
|
+
cancelUrl: `${window.location.origin}/pricing`,
|
|
25
|
+
}) }, loading ? "Redirecting…" : "Buy now"),
|
|
26
|
+
error && react_1.default.createElement("p", { style: { color: "red" } }, error.message)));
|
|
27
|
+
}
|
|
28
|
+
// ── Subscription button ──────────────────────────────────────────────────────
|
|
29
|
+
function SubscribeButton({ priceId }) {
|
|
30
|
+
const { startCheckout, loading, error } = (0, react_2.useStripeCheckout)();
|
|
31
|
+
return (react_1.default.createElement("div", null,
|
|
32
|
+
react_1.default.createElement("button", { disabled: loading, onClick: () => startCheckout({
|
|
33
|
+
lineItems: [{ priceId, quantity: 1 }],
|
|
34
|
+
mode: "subscription",
|
|
35
|
+
successUrl: `${window.location.origin}/welcome?session_id={CHECKOUT_SESSION_ID}`,
|
|
36
|
+
cancelUrl: `${window.location.origin}/pricing`,
|
|
37
|
+
metadata: { plan: "pro" },
|
|
38
|
+
}) }, loading ? "Loading…" : "Subscribe"),
|
|
39
|
+
error && react_1.default.createElement("p", { style: { color: "red" } }, error.message)));
|
|
40
|
+
}
|
|
41
|
+
// ── Success page ─────────────────────────────────────────────────────────────
|
|
42
|
+
// Mount at whatever URL you pass as successUrl (e.g. /success).
|
|
43
|
+
// Stripe appends ?session_id=cs_... automatically.
|
|
44
|
+
function SuccessPage({ userId }) {
|
|
45
|
+
const sessionId = new URLSearchParams(window.location.search).get("session_id");
|
|
46
|
+
const { status, session, loading, error } = (0, react_2.usePaymentStatus)({ userId, sessionId });
|
|
47
|
+
if (loading)
|
|
48
|
+
return react_1.default.createElement("p", null, "Confirming payment\u2026");
|
|
49
|
+
if (error)
|
|
50
|
+
return react_1.default.createElement("p", null,
|
|
51
|
+
"Error: ",
|
|
52
|
+
error.message);
|
|
53
|
+
if (status === "succeeded") {
|
|
54
|
+
return (react_1.default.createElement("div", null,
|
|
55
|
+
react_1.default.createElement("h1", null, "Payment confirmed!"),
|
|
56
|
+
react_1.default.createElement("p", null,
|
|
57
|
+
"Mode: ", session === null || session === void 0 ? void 0 :
|
|
58
|
+
session.mode),
|
|
59
|
+
(session === null || session === void 0 ? void 0 : session.mode) === "subscription" && (react_1.default.createElement("p", null,
|
|
60
|
+
"Subscription ID: ",
|
|
61
|
+
session.subscriptionId))));
|
|
62
|
+
}
|
|
63
|
+
if (status === "failed")
|
|
64
|
+
return react_1.default.createElement("p", null, "Payment failed. Please try again.");
|
|
65
|
+
if (status === "pending")
|
|
66
|
+
return react_1.default.createElement("p", null, "Waiting for confirmation\u2026");
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
// ── Order history ─────────────────────────────────────────────────────────────
|
|
70
|
+
function OrderHistory({ userId }) {
|
|
71
|
+
const { sessions, loading, error } = (0, react_2.useUserCheckouts)({ userId });
|
|
72
|
+
if (loading)
|
|
73
|
+
return react_1.default.createElement("p", null, "Loading orders\u2026");
|
|
74
|
+
if (error)
|
|
75
|
+
return react_1.default.createElement("p", null,
|
|
76
|
+
"Error: ",
|
|
77
|
+
error.message);
|
|
78
|
+
if (!sessions.length)
|
|
79
|
+
return react_1.default.createElement("p", null, "No orders yet.");
|
|
80
|
+
return (react_1.default.createElement("ul", null, sessions.map((s) => (react_1.default.createElement("li", { key: s.sessionId },
|
|
81
|
+
react_1.default.createElement("strong", null, s.mode),
|
|
82
|
+
" \u2014 ",
|
|
83
|
+
s.status,
|
|
84
|
+
" \u2014",
|
|
85
|
+
" ",
|
|
86
|
+
new Date(s.createdAt).toLocaleDateString())))));
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=example.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example.js","sourceRoot":"","sources":["../src/example.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kodice.one/stripefirebase-client — core
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic helpers for creating Stripe Checkout sessions and
|
|
5
|
+
* listening to payment status updates via Firestore.
|
|
6
|
+
*
|
|
7
|
+
* Initialize once at app startup, then call createCheckoutUrl() and
|
|
8
|
+
* onPaymentStatusChange() anywhere in your application.
|
|
9
|
+
*/
|
|
10
|
+
import { Functions } from "firebase/functions";
|
|
11
|
+
import { Firestore, Unsubscribe } from "firebase/firestore";
|
|
12
|
+
export type { PaymentMode, CheckoutStatus, LineItem, CreateCheckoutRequest, CreateCheckoutResponse, CheckoutDocument, } from "@kodice.one/stripefirebase-shared";
|
|
13
|
+
import type { CreateCheckoutRequest, CreateCheckoutResponse, CheckoutDocument, CheckoutStatus } from "@kodice.one/stripefirebase-shared";
|
|
14
|
+
/**
|
|
15
|
+
* Initialise the client library. Call once after `initializeApp()`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { initializeApp } from "firebase/app";
|
|
19
|
+
* import { getFunctions } from "firebase/functions";
|
|
20
|
+
* import { getFirestore } from "firebase/firestore";
|
|
21
|
+
* import { initStripeFirebase } from "@kodice.one/stripefirebase-client";
|
|
22
|
+
*
|
|
23
|
+
* const app = initializeApp({ ...firebaseConfig });
|
|
24
|
+
* initStripeFirebase({
|
|
25
|
+
* functions: getFunctions(app),
|
|
26
|
+
* firestore: getFirestore(app),
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
export declare function initStripeFirebase(opts: {
|
|
30
|
+
functions: Functions;
|
|
31
|
+
firestore: Firestore;
|
|
32
|
+
}): void;
|
|
33
|
+
/**
|
|
34
|
+
* Calls the `createCheckout` Firebase Function and returns the Stripe
|
|
35
|
+
* Checkout URL along with the session ID.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const { sessionId, checkoutUrl } = await createCheckoutUrl({
|
|
39
|
+
* lineItems: [{ priceId: "price_abc123", quantity: 1 }],
|
|
40
|
+
* mode: "subscription",
|
|
41
|
+
* successUrl: `${location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
42
|
+
* cancelUrl: `${location.origin}/pricing`,
|
|
43
|
+
* });
|
|
44
|
+
* window.location.href = checkoutUrl;
|
|
45
|
+
*/
|
|
46
|
+
export declare function createCheckoutUrl(request: CreateCheckoutRequest): Promise<CreateCheckoutResponse>;
|
|
47
|
+
export interface PaymentStatusChangeOptions {
|
|
48
|
+
/** Firebase Auth UID of the current user. */
|
|
49
|
+
userId: string;
|
|
50
|
+
/** Stripe Checkout Session ID returned by createCheckoutUrl. */
|
|
51
|
+
sessionId: string;
|
|
52
|
+
/** Invoked on every document change (including the initial read). */
|
|
53
|
+
onStatusChange: (status: CheckoutStatus, document: CheckoutDocument) => void;
|
|
54
|
+
/** Invoked on Firestore errors. Defaults to console.error. */
|
|
55
|
+
onError?: (error: Error) => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Subscribes to real-time Firestore updates for a single Checkout session.
|
|
59
|
+
* Returns an unsubscribe function — call it in cleanup / useEffect teardown.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const unsub = onPaymentStatusChange({
|
|
63
|
+
* userId,
|
|
64
|
+
* sessionId,
|
|
65
|
+
* onStatusChange: (status, doc) => {
|
|
66
|
+
* if (status === "succeeded") showSuccessBanner(doc);
|
|
67
|
+
* if (status === "failed") showErrorBanner();
|
|
68
|
+
* },
|
|
69
|
+
* });
|
|
70
|
+
* return () => unsub(); // cleanup
|
|
71
|
+
*/
|
|
72
|
+
export declare function onPaymentStatusChange(opts: PaymentStatusChangeOptions): Unsubscribe;
|
|
73
|
+
/**
|
|
74
|
+
* Subscribes to all Checkout sessions for a user, newest first.
|
|
75
|
+
* Useful for building an order history page.
|
|
76
|
+
*/
|
|
77
|
+
export declare function onUserCheckouts(opts: {
|
|
78
|
+
userId: string;
|
|
79
|
+
maxSessions?: number;
|
|
80
|
+
onCheckouts: (sessions: CheckoutDocument[]) => void;
|
|
81
|
+
onError?: (error: Error) => void;
|
|
82
|
+
}): Unsubscribe;
|
|
83
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,SAAS,EAGV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,SAAS,EAOT,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,WAAW,EACX,cAAc,EACd,QAAQ,EACR,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACf,MAAM,mCAAmC,CAAC;AAS3C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB,GAAG,IAAI,CAGP;AAgBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,sBAAsB,CAAC,CAejC;AAMD,MAAM,WAAW,0BAA0B;IACzC,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7E,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,0BAA0B,GAC/B,WAAW,CAqBb;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACpD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,GAAG,WAAW,CAed"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @kodice.one/stripefirebase-client — core
|
|
4
|
+
*
|
|
5
|
+
* Framework-agnostic helpers for creating Stripe Checkout sessions and
|
|
6
|
+
* listening to payment status updates via Firestore.
|
|
7
|
+
*
|
|
8
|
+
* Initialize once at app startup, then call createCheckoutUrl() and
|
|
9
|
+
* onPaymentStatusChange() anywhere in your application.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.initStripeFirebase = initStripeFirebase;
|
|
13
|
+
exports.createCheckoutUrl = createCheckoutUrl;
|
|
14
|
+
exports.onPaymentStatusChange = onPaymentStatusChange;
|
|
15
|
+
exports.onUserCheckouts = onUserCheckouts;
|
|
16
|
+
const functions_1 = require("firebase/functions");
|
|
17
|
+
const firestore_1 = require("firebase/firestore");
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
// Singleton state
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
let _functions = null;
|
|
22
|
+
let _firestore = null;
|
|
23
|
+
/**
|
|
24
|
+
* Initialise the client library. Call once after `initializeApp()`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* import { initializeApp } from "firebase/app";
|
|
28
|
+
* import { getFunctions } from "firebase/functions";
|
|
29
|
+
* import { getFirestore } from "firebase/firestore";
|
|
30
|
+
* import { initStripeFirebase } from "@kodice.one/stripefirebase-client";
|
|
31
|
+
*
|
|
32
|
+
* const app = initializeApp({ ...firebaseConfig });
|
|
33
|
+
* initStripeFirebase({
|
|
34
|
+
* functions: getFunctions(app),
|
|
35
|
+
* firestore: getFirestore(app),
|
|
36
|
+
* });
|
|
37
|
+
*/
|
|
38
|
+
function initStripeFirebase(opts) {
|
|
39
|
+
_functions = opts.functions;
|
|
40
|
+
_firestore = opts.firestore;
|
|
41
|
+
}
|
|
42
|
+
function requireFunctions() {
|
|
43
|
+
if (!_functions)
|
|
44
|
+
throw new Error("[kodice.one/stripefirebase-client] Call initStripeFirebase() first.");
|
|
45
|
+
return _functions;
|
|
46
|
+
}
|
|
47
|
+
function requireFirestore() {
|
|
48
|
+
if (!_firestore)
|
|
49
|
+
throw new Error("[kodice.one/stripefirebase-client] Call initStripeFirebase() first.");
|
|
50
|
+
return _firestore;
|
|
51
|
+
}
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
// createCheckoutUrl
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Calls the `createCheckout` Firebase Function and returns the Stripe
|
|
57
|
+
* Checkout URL along with the session ID.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const { sessionId, checkoutUrl } = await createCheckoutUrl({
|
|
61
|
+
* lineItems: [{ priceId: "price_abc123", quantity: 1 }],
|
|
62
|
+
* mode: "subscription",
|
|
63
|
+
* successUrl: `${location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
64
|
+
* cancelUrl: `${location.origin}/pricing`,
|
|
65
|
+
* });
|
|
66
|
+
* window.location.href = checkoutUrl;
|
|
67
|
+
*/
|
|
68
|
+
async function createCheckoutUrl(request) {
|
|
69
|
+
const fn = (0, functions_1.httpsCallable)(requireFunctions(), "createCheckout");
|
|
70
|
+
let result;
|
|
71
|
+
try {
|
|
72
|
+
result = await fn(request);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const msg = err instanceof Error ? err.message : "Failed to create checkout";
|
|
76
|
+
throw new Error(`[kodice.one/stripefirebase-client] ${msg}`);
|
|
77
|
+
}
|
|
78
|
+
return result.data;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Subscribes to real-time Firestore updates for a single Checkout session.
|
|
82
|
+
* Returns an unsubscribe function — call it in cleanup / useEffect teardown.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* const unsub = onPaymentStatusChange({
|
|
86
|
+
* userId,
|
|
87
|
+
* sessionId,
|
|
88
|
+
* onStatusChange: (status, doc) => {
|
|
89
|
+
* if (status === "succeeded") showSuccessBanner(doc);
|
|
90
|
+
* if (status === "failed") showErrorBanner();
|
|
91
|
+
* },
|
|
92
|
+
* });
|
|
93
|
+
* return () => unsub(); // cleanup
|
|
94
|
+
*/
|
|
95
|
+
function onPaymentStatusChange(opts) {
|
|
96
|
+
const ref = (0, firestore_1.doc)(requireFirestore(), "checkouts", opts.userId, "sessions", opts.sessionId);
|
|
97
|
+
return (0, firestore_1.onSnapshot)(ref, (snap) => {
|
|
98
|
+
if (!snap.exists())
|
|
99
|
+
return;
|
|
100
|
+
const data = snap.data();
|
|
101
|
+
opts.onStatusChange(data.status, data);
|
|
102
|
+
}, (err) => {
|
|
103
|
+
if (opts.onError)
|
|
104
|
+
opts.onError(err);
|
|
105
|
+
else
|
|
106
|
+
console.error("[kodice.one/stripefirebase-client] Firestore error:", err);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
// onUserCheckouts
|
|
111
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Subscribes to all Checkout sessions for a user, newest first.
|
|
114
|
+
* Useful for building an order history page.
|
|
115
|
+
*/
|
|
116
|
+
function onUserCheckouts(opts) {
|
|
117
|
+
var _a;
|
|
118
|
+
const q = (0, firestore_1.query)((0, firestore_1.collection)(requireFirestore(), "checkouts", opts.userId, "sessions"), (0, firestore_1.orderBy)("createdAt", "desc"), (0, firestore_1.limit)((_a = opts.maxSessions) !== null && _a !== void 0 ? _a : 20));
|
|
119
|
+
return (0, firestore_1.onSnapshot)(q, (snap) => opts.onCheckouts(snap.docs.map((d) => d.data())), (err) => {
|
|
120
|
+
if (opts.onError)
|
|
121
|
+
opts.onError(err);
|
|
122
|
+
else
|
|
123
|
+
console.error("[kodice.one/stripefirebase-client] Firestore error:", err);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kodice.one/stripefirebase-client/react
|
|
3
|
+
*
|
|
4
|
+
* React hooks built on top of the core library.
|
|
5
|
+
* Import: `import { useStripeCheckout } from "@kodice.one/stripefirebase-client/react"`
|
|
6
|
+
*/
|
|
7
|
+
import type { CreateCheckoutRequest, CreateCheckoutResponse, CheckoutDocument, CheckoutStatus } from "@kodice.one/stripefirebase-shared";
|
|
8
|
+
export interface UseStripeCheckoutOptions {
|
|
9
|
+
/**
|
|
10
|
+
* When true (default), the hook automatically redirects to the Stripe
|
|
11
|
+
* Checkout page after a session is created.
|
|
12
|
+
*/
|
|
13
|
+
autoRedirect?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface UseStripeCheckoutReturn {
|
|
16
|
+
/** Initiates the checkout flow. Resolves with the session data or null on error. */
|
|
17
|
+
startCheckout: (req: CreateCheckoutRequest) => Promise<CreateCheckoutResponse | null>;
|
|
18
|
+
loading: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
/** Session ID of the most recently created checkout — use with usePaymentStatus. */
|
|
21
|
+
sessionId: string | null;
|
|
22
|
+
checkoutUrl: string | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Hook that wraps createCheckoutUrl with loading/error state and optional
|
|
26
|
+
* auto-redirect to the Stripe Checkout page.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const { startCheckout, loading, error } = useStripeCheckout();
|
|
30
|
+
*
|
|
31
|
+
* <button onClick={() => startCheckout({
|
|
32
|
+
* lineItems: [{ priceId: "price_abc", quantity: 1 }],
|
|
33
|
+
* mode: "subscription",
|
|
34
|
+
* successUrl: `${location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
35
|
+
* cancelUrl: `${location.origin}/pricing`,
|
|
36
|
+
* })}>
|
|
37
|
+
* Subscribe
|
|
38
|
+
* </button>
|
|
39
|
+
*/
|
|
40
|
+
export declare function useStripeCheckout(options?: UseStripeCheckoutOptions): UseStripeCheckoutReturn;
|
|
41
|
+
export interface UsePaymentStatusReturn {
|
|
42
|
+
status: CheckoutStatus | null;
|
|
43
|
+
session: CheckoutDocument | null;
|
|
44
|
+
loading: boolean;
|
|
45
|
+
error: Error | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes to real-time status updates for a specific Checkout session.
|
|
49
|
+
* Mount this on your `/success` page to confirm payment before showing
|
|
50
|
+
* the confirmation UI.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // /success page — Stripe appends ?session_id=... to your successUrl
|
|
54
|
+
* const sessionId = new URLSearchParams(location.search).get("session_id");
|
|
55
|
+
* const { status, session } = usePaymentStatus({ userId: user.uid, sessionId });
|
|
56
|
+
*
|
|
57
|
+
* if (status === "succeeded") return <SuccessBanner plan={session?.metadata.plan} />;
|
|
58
|
+
* if (status === "failed") return <RetryButton />;
|
|
59
|
+
*/
|
|
60
|
+
export declare function usePaymentStatus(opts: {
|
|
61
|
+
userId: string | null | undefined;
|
|
62
|
+
sessionId: string | null | undefined;
|
|
63
|
+
}): UsePaymentStatusReturn;
|
|
64
|
+
export interface UseUserCheckoutsReturn {
|
|
65
|
+
sessions: CheckoutDocument[];
|
|
66
|
+
loading: boolean;
|
|
67
|
+
error: Error | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Subscribes to all Checkout sessions for the current user, newest first.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const { sessions } = useUserCheckouts({ userId: user.uid });
|
|
74
|
+
* return <OrderList orders={sessions} />;
|
|
75
|
+
*/
|
|
76
|
+
export declare function useUserCheckouts(opts: {
|
|
77
|
+
userId: string | null | undefined;
|
|
78
|
+
maxSessions?: number;
|
|
79
|
+
}): UseUserCheckoutsReturn;
|
|
80
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACf,MAAM,mCAAmC,CAAC;AAM3C,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,oFAAoF;IACpF,aAAa,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IACtF,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,oFAAoF;IACpF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,uBAAuB,CA+BzB;AAMD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC,GAAG,sBAAsB,CAgCzB;AAMD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,sBAAsB,CA4BzB"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @kodice.one/stripefirebase-client/react
|
|
4
|
+
*
|
|
5
|
+
* React hooks built on top of the core library.
|
|
6
|
+
* Import: `import { useStripeCheckout } from "@kodice.one/stripefirebase-client/react"`
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.useStripeCheckout = useStripeCheckout;
|
|
10
|
+
exports.usePaymentStatus = usePaymentStatus;
|
|
11
|
+
exports.useUserCheckouts = useUserCheckouts;
|
|
12
|
+
const react_1 = require("react");
|
|
13
|
+
const index_1 = require("./index");
|
|
14
|
+
/**
|
|
15
|
+
* Hook that wraps createCheckoutUrl with loading/error state and optional
|
|
16
|
+
* auto-redirect to the Stripe Checkout page.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const { startCheckout, loading, error } = useStripeCheckout();
|
|
20
|
+
*
|
|
21
|
+
* <button onClick={() => startCheckout({
|
|
22
|
+
* lineItems: [{ priceId: "price_abc", quantity: 1 }],
|
|
23
|
+
* mode: "subscription",
|
|
24
|
+
* successUrl: `${location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
25
|
+
* cancelUrl: `${location.origin}/pricing`,
|
|
26
|
+
* })}>
|
|
27
|
+
* Subscribe
|
|
28
|
+
* </button>
|
|
29
|
+
*/
|
|
30
|
+
function useStripeCheckout(options = {}) {
|
|
31
|
+
const { autoRedirect = true } = options;
|
|
32
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
33
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
34
|
+
const [sessionId, setSessionId] = (0, react_1.useState)(null);
|
|
35
|
+
const [checkoutUrl, setCheckoutUrl] = (0, react_1.useState)(null);
|
|
36
|
+
const startCheckout = (0, react_1.useCallback)(async (req) => {
|
|
37
|
+
setLoading(true);
|
|
38
|
+
setError(null);
|
|
39
|
+
try {
|
|
40
|
+
const response = await (0, index_1.createCheckoutUrl)(req);
|
|
41
|
+
setSessionId(response.sessionId);
|
|
42
|
+
setCheckoutUrl(response.checkoutUrl);
|
|
43
|
+
if (autoRedirect)
|
|
44
|
+
window.location.href = response.checkoutUrl;
|
|
45
|
+
return response;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
49
|
+
setError(e);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}, [autoRedirect]);
|
|
56
|
+
return { startCheckout, loading, error, sessionId, checkoutUrl };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Subscribes to real-time status updates for a specific Checkout session.
|
|
60
|
+
* Mount this on your `/success` page to confirm payment before showing
|
|
61
|
+
* the confirmation UI.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // /success page — Stripe appends ?session_id=... to your successUrl
|
|
65
|
+
* const sessionId = new URLSearchParams(location.search).get("session_id");
|
|
66
|
+
* const { status, session } = usePaymentStatus({ userId: user.uid, sessionId });
|
|
67
|
+
*
|
|
68
|
+
* if (status === "succeeded") return <SuccessBanner plan={session?.metadata.plan} />;
|
|
69
|
+
* if (status === "failed") return <RetryButton />;
|
|
70
|
+
*/
|
|
71
|
+
function usePaymentStatus(opts) {
|
|
72
|
+
const [status, setStatus] = (0, react_1.useState)(null);
|
|
73
|
+
const [session, setSession] = (0, react_1.useState)(null);
|
|
74
|
+
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
75
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
76
|
+
(0, react_1.useEffect)(() => {
|
|
77
|
+
if (!opts.userId || !opts.sessionId) {
|
|
78
|
+
setLoading(false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setLoading(true);
|
|
82
|
+
const unsub = (0, index_1.onPaymentStatusChange)({
|
|
83
|
+
userId: opts.userId,
|
|
84
|
+
sessionId: opts.sessionId,
|
|
85
|
+
onStatusChange: (newStatus, doc) => {
|
|
86
|
+
setStatus(newStatus);
|
|
87
|
+
setSession(doc);
|
|
88
|
+
setLoading(false);
|
|
89
|
+
},
|
|
90
|
+
onError: (err) => {
|
|
91
|
+
setError(err);
|
|
92
|
+
setLoading(false);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
return () => unsub();
|
|
96
|
+
}, [opts.userId, opts.sessionId]);
|
|
97
|
+
return { status, session, loading, error };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Subscribes to all Checkout sessions for the current user, newest first.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* const { sessions } = useUserCheckouts({ userId: user.uid });
|
|
104
|
+
* return <OrderList orders={sessions} />;
|
|
105
|
+
*/
|
|
106
|
+
function useUserCheckouts(opts) {
|
|
107
|
+
const [sessions, setSessions] = (0, react_1.useState)([]);
|
|
108
|
+
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
109
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
110
|
+
(0, react_1.useEffect)(() => {
|
|
111
|
+
if (!opts.userId) {
|
|
112
|
+
setLoading(false);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const unsub = (0, index_1.onUserCheckouts)({
|
|
116
|
+
userId: opts.userId,
|
|
117
|
+
maxSessions: opts.maxSessions,
|
|
118
|
+
onCheckouts: (docs) => {
|
|
119
|
+
setSessions(docs);
|
|
120
|
+
setLoading(false);
|
|
121
|
+
},
|
|
122
|
+
onError: (err) => {
|
|
123
|
+
setError(err);
|
|
124
|
+
setLoading(false);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
return () => unsub();
|
|
128
|
+
}, [opts.userId, opts.maxSessions]);
|
|
129
|
+
return { sessions, loading, error };
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kodice.one/stripefirebase-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "React client library for Firebase + Stripe payment integration",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.esm.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.esm.js",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./react": {
|
|
16
|
+
"import": "./dist/react.esm.js",
|
|
17
|
+
"require": "./dist/react.js",
|
|
18
|
+
"types": "./dist/react.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.esm.json",
|
|
23
|
+
"build:watch": "tsc --watch",
|
|
24
|
+
"clean": "rm -rf dist"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=24.0.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@kodice.one/stripefirebase-shared": "^1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"firebase": ">=10.0.0",
|
|
34
|
+
"react": ">=17.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^24.0.0",
|
|
38
|
+
"@types/react": "^18.0.0",
|
|
39
|
+
"typescript": "^5.4.0"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT"
|
|
45
|
+
}
|