17
17
import { FirebaseApp } from "@firebase/app" ;
18
18
import {
19
19
collection ,
20
+ CollectionReference ,
20
21
doc ,
22
+ DocumentChange ,
21
23
DocumentData ,
22
24
DocumentReference ,
23
25
DocumentSnapshot ,
24
26
Firestore ,
25
27
FirestoreDataConverter ,
28
+ FirestoreError ,
26
29
getDoc ,
27
30
getDocs ,
28
31
getFirestore ,
32
+ onSnapshot ,
29
33
query ,
30
34
Query ,
31
35
QueryDocumentSnapshot ,
32
36
QuerySnapshot ,
33
37
where ,
34
38
} from "@firebase/firestore" ;
35
39
import { StripePayments , StripePaymentsError } from "./init" ;
36
- import { getCurrentUser } from "./user" ;
40
+ import { getCurrentUser , getCurrentUserSync } from "./user" ;
37
41
import { checkNonEmptyArray , checkNonEmptyString } from "./utils" ;
38
42
39
43
/**
@@ -176,6 +180,68 @@ export function getCurrentUserPayments(
176
180
} ) ;
177
181
}
178
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
+
179
245
function getStatusAsArray (
180
246
status : PaymentStatus | PaymentStatus [ ]
181
247
) : PaymentStatus [ ] {
@@ -199,6 +265,11 @@ export interface PaymentDAO {
199
265
uid : string ,
200
266
options ?: { status ?: PaymentStatus [ ] }
201
267
) : Promise < Payment [ ] > ;
268
+ onPaymentUpdate (
269
+ uid : string ,
270
+ onUpdate : ( snapshot : PaymentSnapshot ) => void ,
271
+ onError ?: ( error : StripePaymentsError ) => void
272
+ ) : ( ) => void ;
202
273
}
203
274
204
275
const PAYMENT_CONVERTER : FirestoreDataConverter < Payment > = {
@@ -272,6 +343,51 @@ class FirestorePaymentDAO implements PaymentDAO {
272
343
return payments ;
273
344
}
274
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
+
275
391
private async getPaymentSnapshotIfExists (
276
392
uid : string ,
277
393
paymentId : string
0 commit comments