Skip to content

Commit 2130a61

Browse files
Merge pull request #342 from stripe/payments-api
feat: added onCurrentUserPaymentUpdate(), getCurrentUserPayments()
2 parents 0b4f2f0 + 0c8d2be commit 2130a61

File tree

7 files changed

+785
-39
lines changed

7 files changed

+785
-39
lines changed

firestore-stripe-web-sdk/etc/firestore-stripe-payments.api.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,20 @@ export interface CreateCheckoutSessionOptions {
4444
// @public
4545
export function getCurrentUserPayment(payments: StripePayments, paymentId: string): Promise<Payment>;
4646

47+
// @public (undocumented)
48+
export function getCurrentUserPayments(payments: StripePayments, options?: GetPaymentsOptions): Promise<Payment[]>;
49+
4750
// @public
4851
export function getCurrentUserSubscription(payments: StripePayments, subscriptionId: string): Promise<Subscription>;
4952

5053
// @public
5154
export function getCurrentUserSubscriptions(payments: StripePayments, options?: GetSubscriptionsOptions): Promise<Subscription[]>;
5255

56+
// @public
57+
export interface GetPaymentsOptions {
58+
status?: PaymentStatus | PaymentStatus[];
59+
}
60+
5361
// @public
5462
export function getPrice(payments: StripePayments, productId: string, priceId: string): Promise<Price>;
5563

@@ -102,6 +110,9 @@ export interface LineItemSessionCreateParams extends CommonSessionCreateParams {
102110
line_items: LineItemParams[];
103111
}
104112

113+
// @public
114+
export function onCurrentUserPaymentUpdate(payments: StripePayments, onUpdate: (snapshot: PaymentSnapshot) => void, onError?: (error: StripePaymentsError) => void): () => void;
115+
105116
// @public
106117
export function onCurrentUserSubscriptionUpdate(payments: StripePayments, onUpdate: (snapshot: SubscriptionSnapshot) => void, onError?: (error: StripePaymentsError) => void): () => void;
107118

@@ -126,15 +137,29 @@ export interface Payment {
126137
product: string;
127138
price: string;
128139
}>;
129-
readonly status: PaymentState;
140+
readonly status: PaymentStatus;
130141
readonly uid: string;
131142
}
132143

144+
// @public
145+
export type PaymentChangeType = "added" | "modified" | "removed";
146+
133147
// @public
134148
export type PaymentMethodType = "card" | "acss_debit" | "afterpay_clearpay" | "alipay" | "bacs_debit" | "bancontact" | "boleto" | "eps" | "fpx" | "giropay" | "grabpay" | "ideal" | "klarna" | "oxxo" | "p24" | "sepa_debit" | "sofort" | "wechat_pay";
135149

136150
// @public
137-
export type PaymentState = "requires_payment_method" | "requires_confirmation" | "requires_action" | "processing" | "requires_capture" | "cancelled" | "succeeded";
151+
export interface PaymentSnapshot {
152+
changes: Array<{
153+
type: PaymentChangeType;
154+
payment: Payment;
155+
}>;
156+
empty: boolean;
157+
payments: Payment[];
158+
size: number;
159+
}
160+
161+
// @public
162+
export type PaymentStatus = "requires_payment_method" | "requires_confirmation" | "requires_action" | "processing" | "requires_capture" | "cancelled" | "succeeded";
138163

139164
// @public
140165
export interface Price {

firestore-stripe-web-sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stripe/firestore-stripe-payments",
3-
"version": "0.0.5",
3+
"version": "0.0.6",
44
"description": "Client SDK for the firestore-stripe-payments Firebase Extension",
55
"main": "./lib/index.js",
66
"typings": "./lib/index.d.ts",

firestore-stripe-web-sdk/src/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ export {
3838
SessionCreateParams,
3939
} from "./session";
4040

41-
export { Payment, PaymentState, getCurrentUserPayment } from "./payment";
41+
export {
42+
GetPaymentsOptions,
43+
Payment,
44+
PaymentChangeType,
45+
PaymentSnapshot,
46+
PaymentStatus,
47+
getCurrentUserPayment,
48+
getCurrentUserPayments,
49+
onCurrentUserPaymentUpdate,
50+
} from "./payment";
4251

4352
export {
4453
GetProductOptions,

firestore-stripe-web-sdk/src/payment.ts

Lines changed: 200 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,29 @@
1616

1717
import { FirebaseApp } from "@firebase/app";
1818
import {
19+
collection,
20+
CollectionReference,
1921
doc,
22+
DocumentChange,
2023
DocumentData,
2124
DocumentReference,
2225
DocumentSnapshot,
2326
Firestore,
2427
FirestoreDataConverter,
28+
FirestoreError,
2529
getDoc,
30+
getDocs,
2631
getFirestore,
32+
onSnapshot,
33+
query,
34+
Query,
2735
QueryDocumentSnapshot,
36+
QuerySnapshot,
37+
where,
2838
} from "@firebase/firestore";
2939
import { StripePayments, StripePaymentsError } from "./init";
30-
import { getCurrentUser } from "./user";
31-
import { checkNonEmptyString } from "./utils";
40+
import { getCurrentUser, getCurrentUserSync } from "./user";
41+
import { checkNonEmptyArray, checkNonEmptyString } from "./utils";
3242

3343
/**
3444
* Interface of a Stripe payment stored in the app database.
@@ -103,7 +113,7 @@ export interface Payment {
103113
/**
104114
* Status of this payment.
105115
*/
106-
readonly status: PaymentState;
116+
readonly status: PaymentStatus;
107117

108118
/**
109119
* Firebase Auth UID of the user that created the payment.
@@ -116,7 +126,7 @@ export interface Payment {
116126
/**
117127
* Possible states a payment can be in.
118128
*/
119-
export type PaymentState =
129+
export type PaymentStatus =
120130
| "requires_payment_method"
121131
| "requires_confirmation"
122132
| "requires_action"
@@ -144,6 +154,105 @@ export function getCurrentUserPayment(
144154
});
145155
}
146156

157+
/**
158+
* Optional parameters for the {@link getCurrentUserPayments} function.
159+
*/
160+
export interface GetPaymentsOptions {
161+
/**
162+
* Specify one or more payment status values to retrieve. When set only the payments
163+
* with the given status are returned.
164+
*/
165+
status?: PaymentStatus | PaymentStatus[];
166+
}
167+
168+
export function getCurrentUserPayments(
169+
payments: StripePayments,
170+
options?: GetPaymentsOptions
171+
): Promise<Payment[]> {
172+
const queryOptions: { status?: PaymentStatus[] } = {};
173+
if (typeof options?.status !== "undefined") {
174+
queryOptions.status = getStatusAsArray(options.status);
175+
}
176+
177+
return getCurrentUser(payments).then((uid: string) => {
178+
const dao: PaymentDAO = getOrInitPaymentDAO(payments);
179+
return dao.getPayments(uid, queryOptions);
180+
});
181+
}
182+
183+
/**
184+
* Different types of changes that may occur on a payment object.
185+
*/
186+
export type PaymentChangeType = "added" | "modified" | "removed";
187+
188+
/**
189+
* Represents the current state of a set of payments owned by a user.
190+
*/
191+
export interface PaymentSnapshot {
192+
/**
193+
* A list of all currently available payments ordered by the payment ID. Empty
194+
* if no payments are available.
195+
*/
196+
payments: Payment[];
197+
198+
/**
199+
* The list of changes in the payments since the last snapshot.
200+
*/
201+
changes: Array<{
202+
type: PaymentChangeType;
203+
payment: Payment;
204+
}>;
205+
206+
/**
207+
* Number of currently available payments. This is same as the length of the
208+
* `payments` array in the snapshot.
209+
*/
210+
size: number;
211+
212+
/**
213+
* True if there are no payments available. False whenever at least one payment is
214+
* present. When True, the `payments` array is empty, and the `size` is 0.
215+
*/
216+
empty: boolean;
217+
}
218+
219+
/**
220+
* Registers a listener to receive payment update events for the currently signed in
221+
* user. If the user is not signed in throws an `unauthenticated` error, and no listener is
222+
* registered.
223+
*
224+
* Upon successful registration, the `onUpdate` callback will fire once with
225+
* the current state of all the payments. From then onwards, each update to a payment
226+
* will fire the `onUpdate` callback with the latest state of the payments.
227+
*
228+
* @param payments - A valid {@link StripePayments} object.
229+
* @param onUpdate - A callback that will fire whenever the current user's payments
230+
* are updated.
231+
* @param onError - A callback that will fire whenever an error occurs while listening to
232+
* payment updates.
233+
* @returns A function that can be called to cancel and unregister the listener.
234+
*/
235+
export function onCurrentUserPaymentUpdate(
236+
payments: StripePayments,
237+
onUpdate: (snapshot: PaymentSnapshot) => void,
238+
onError?: (error: StripePaymentsError) => void
239+
): () => void {
240+
const uid: string = getCurrentUserSync(payments);
241+
const dao: PaymentDAO = getOrInitPaymentDAO(payments);
242+
return dao.onPaymentUpdate(uid, onUpdate, onError);
243+
}
244+
245+
function getStatusAsArray(
246+
status: PaymentStatus | PaymentStatus[]
247+
): PaymentStatus[] {
248+
if (typeof status === "string") {
249+
return [status];
250+
}
251+
252+
checkNonEmptyArray(status, "status must be a non-empty array.");
253+
return status;
254+
}
255+
147256
/**
148257
* Internal interface for all database interactions pertaining to Stripe payments. Exported
149258
* for testing.
@@ -152,6 +261,15 @@ export function getCurrentUserPayment(
152261
*/
153262
export interface PaymentDAO {
154263
getPayment(uid: string, paymentId: string): Promise<Payment>;
264+
getPayments(
265+
uid: string,
266+
options?: { status?: PaymentStatus[] }
267+
): Promise<Payment[]>;
268+
onPaymentUpdate(
269+
uid: string,
270+
onUpdate: (snapshot: PaymentSnapshot) => void,
271+
onError?: (error: StripePaymentsError) => void
272+
): () => void;
155273
}
156274

157275
const PAYMENT_CONVERTER: FirestoreDataConverter<Payment> = {
@@ -209,6 +327,67 @@ class FirestorePaymentDAO implements PaymentDAO {
209327
return snap.data();
210328
}
211329

330+
public async getPayments(
331+
uid: string,
332+
options?: { status?: PaymentStatus[] }
333+
): Promise<Payment[]> {
334+
const querySnap: QuerySnapshot<Payment> = await this.getPaymentSnapshots(
335+
uid,
336+
options?.status
337+
);
338+
const payments: Payment[] = [];
339+
querySnap.forEach((snap: QueryDocumentSnapshot<Payment>) => {
340+
payments.push(snap.data());
341+
});
342+
343+
return payments;
344+
}
345+
346+
public onPaymentUpdate(
347+
uid: string,
348+
onUpdate: (snapshot: PaymentSnapshot) => void,
349+
onError?: (error: StripePaymentsError) => void
350+
): () => void {
351+
const payments: CollectionReference<Payment> = collection(
352+
this.firestore,
353+
this.customersCollection,
354+
uid,
355+
PAYMENTS_COLLECTION
356+
).withConverter(PAYMENT_CONVERTER);
357+
return onSnapshot(
358+
payments,
359+
(querySnap: QuerySnapshot<Payment>) => {
360+
const snapshot: PaymentSnapshot = {
361+
payments: [],
362+
changes: [],
363+
size: querySnap.size,
364+
empty: querySnap.empty,
365+
};
366+
querySnap.forEach((snap: QueryDocumentSnapshot<Payment>) => {
367+
snapshot.payments.push(snap.data());
368+
});
369+
querySnap.docChanges().forEach((change: DocumentChange<Payment>) => {
370+
snapshot.changes.push({
371+
type: change.type,
372+
payment: change.doc.data(),
373+
});
374+
});
375+
376+
onUpdate(snapshot);
377+
},
378+
(err: FirestoreError) => {
379+
if (onError) {
380+
const arg: StripePaymentsError = new StripePaymentsError(
381+
"internal",
382+
`Error while listening to database updates: ${err.message}`,
383+
err
384+
);
385+
onError(arg);
386+
}
387+
}
388+
);
389+
}
390+
212391
private async getPaymentSnapshotIfExists(
213392
uid: string,
214393
paymentId: string
@@ -233,6 +412,23 @@ class FirestorePaymentDAO implements PaymentDAO {
233412
return snapshot;
234413
}
235414

415+
private async getPaymentSnapshots(
416+
uid: string,
417+
status?: PaymentStatus[]
418+
): Promise<QuerySnapshot<Payment>> {
419+
let paymentsQuery: Query<Payment> = collection(
420+
this.firestore,
421+
this.customersCollection,
422+
uid,
423+
PAYMENTS_COLLECTION
424+
).withConverter(PAYMENT_CONVERTER);
425+
if (status) {
426+
paymentsQuery = query(paymentsQuery, where("status", "in", status));
427+
}
428+
429+
return await this.queryFirestore(() => getDocs(paymentsQuery));
430+
}
431+
236432
private async queryFirestore<T>(fn: () => Promise<T>): Promise<T> {
237433
try {
238434
return await fn();

0 commit comments

Comments
 (0)