@leaddreamer/inventorylogic 0.0.3-beta.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.
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Typedefs, constants, support functions and database operations related to product creation stages
3
+ * users
4
+ * @module Markups
5
+ */
6
+ import { updateRecord } from "@leaddreamer/firebase-wrapper";
7
+ import { Forms, fillForm } from "@leaddreamer/staticforms";
8
+
9
+ import { MARKUPS, STAGE, FABRICATION, WHOLESALE, RETAIL, BASE, HIGHVALUE, SERVICE, MATERIAL } from "../boilerplate";
10
+
11
+ //////////////////////////////////////////////////////////////////////
12
+ // Constants
13
+ /**
14
+ * Database Structure
15
+ * Product or User or Fundamentals (document)
16
+ * \-> Markups (collection)
17
+ * \-> [ParentId] (sole document)
18
+ * \-> [Stage Name(s)] (field, object)
19
+ * \-> [Material Name(s)] (field, value [Markup])
20
+ */
21
+ /**
22
+ * @typedef {number} Markup
23
+ * @typedef {"base"|"highValue"|"service"} MaterialLevels
24
+ * @typedef {"fabrication"|"wholesale"|"retail"} StageLevels
25
+ * @typedef {Object.<MaterialLevels, Markup>} Material
26
+ * @typedef {Object.<StageLevels, Material>} Markups
27
+ */
28
+
29
+ /**
30
+ * @readonly
31
+ * @constant
32
+ * @type {Material}
33
+ * @default
34
+ */
35
+ const MaterialDefault = {
36
+ [BASE]: 1.0,
37
+ [HIGHVALUE]: 1.0,
38
+ [SERVICE]: 1.0,
39
+ };
40
+
41
+ /**
42
+ * @readonly
43
+ * @constant
44
+ * @type {Markups}
45
+ * @default
46
+ */
47
+ const MarkupsDefault = {
48
+ [FABRICATION]: {
49
+ [BASE]: 1.0,
50
+ [HIGHVALUE]: 1.0,
51
+ [SERVICE]: 1.0,
52
+ },
53
+ [WHOLESALE]: {
54
+ [BASE]: 1.0,
55
+ [HIGHVALUE]: 1.0,
56
+ [SERVICE]: 1.0,
57
+ },
58
+ [RETAIL]: {
59
+ [BASE]: 1.0,
60
+ [HIGHVALUE]: 1.0,
61
+ [SERVICE]: 1.0,
62
+ },
63
+ };
64
+
65
+ /**
66
+ * @readonly
67
+ * @constant
68
+ * @type {Material}
69
+ * @default
70
+ */
71
+ const MaterialDefaultForm = {
72
+ [BASE]: "number",
73
+ [HIGHVALUE]: "number",
74
+ [SERVICE]: "number",
75
+ };
76
+
77
+ /**
78
+ * @readonly
79
+ * @constant
80
+ * @type {Stage}
81
+ * @default
82
+ */
83
+ const MarkupsDefaultForm = {
84
+ [FABRICATION]: `subrecord:${MATERIAL}`,
85
+ [WHOLESALE]: `subrecord:${MATERIAL}`,
86
+ [RETAIL]: `subrecord:${MATERIAL}`,
87
+ };
88
+
89
+ Forms.Base[MATERIAL] = MaterialDefaultForm;
90
+ Forms.BaseForm[MATERIAL] = MaterialDefaultForm;
91
+ Forms.CreateForm[MATERIAL] = MaterialDefaultForm;
92
+ //Forms.Save[MATERIAL] = updateMaterial;
93
+ Forms.SearchBy[MATERIAL] = MATERIAL;
94
+
95
+ Forms.Base[MARKUPS] = MarkupsDefault;
96
+ Forms.BaseForm[MARKUPS] = MarkupsDefaultForm;
97
+ Forms.CreateForm[MARKUPS] = MarkupsDefaultForm;
98
+ Forms.Save[MARKUPS] = updateMarkups;
99
+
100
+ /**
101
+ * @readonly
102
+ * @constant
103
+ * @default
104
+ *
105
+ */
106
+ export const MaterialChoices = {
107
+ [BASE]: {
108
+ Id: BASE,
109
+ label: BASE,
110
+ description: "Base",
111
+ order: 1,
112
+ },
113
+ [HIGHVALUE]: {
114
+ Id: HIGHVALUE,
115
+ label: HIGHVALUE,
116
+ description: "High Value",
117
+ order: 2,
118
+ },
119
+ [SERVICE]: {
120
+ Id: SERVICE,
121
+ label: SERVICE,
122
+ description: "Service",
123
+ order: 3,
124
+ },
125
+ };
126
+
127
+ /**
128
+ * @readonly
129
+ * @constant
130
+ * @default
131
+ */
132
+ export const StageChoices = {
133
+ [FABRICATION]: {
134
+ Id: FABRICATION,
135
+ label: FABRICATION,
136
+ description: "Fabrication Stage",
137
+ order: 1,
138
+ },
139
+ [WHOLESALE]: {
140
+ Id: WHOLESALE,
141
+ label: WHOLESALE,
142
+ description: "Wholesale Stage",
143
+ order: 2,
144
+ },
145
+ [RETAIL]: {
146
+ Id: RETAIL,
147
+ label: RETAIL,
148
+ description: "Retail Stage",
149
+ order: 3,
150
+ },
151
+ };
152
+
153
+ /**
154
+ * @readonly
155
+ * @constant
156
+ * @default
157
+ */
158
+ Forms.Choices[STAGE] = StageChoices;
159
+
160
+ /**
161
+ * @readonly
162
+ * @constant
163
+ * @default
164
+ */
165
+ Forms.Choices[MATERIAL] = MaterialChoices
166
+
167
+ //////////////////////////////////////////////////////////////////////
168
+ // record operations
169
+ /**
170
+ * writes record back to database. Assumes only Id is valid
171
+ * @param {LineItem} stage a partially-populated product record for updates
172
+ * @param {Record} parent minimal record of product, user (or none) this stage belongs to
173
+ * @returns {promise.LineItem} a copy of the data written
174
+ */
175
+ async function updateMarkups(markups, parent = null) {
176
+ return updateRecord(
177
+ fillForm(markups, MARKUPS), //saves only APPROPRIATE fields -does NOT populate missing fields
178
+ parent,
179
+ MARKUPS
180
+ );
181
+ }
182
+
183
+
@@ -0,0 +1,516 @@
1
+ /**
2
+ * "Payment Descriptor" handling operations. These descriptors represent paymentMethods, each tied
3
+ * to an individual user. These do not *particularly* represent a paymentProvider - they are meant
4
+ * as an abstraction above a specific payment processor.
5
+ * @module Payment
6
+ */
7
+ import {
8
+ typedWrite,
9
+ writeBack,
10
+ fetchRecord,
11
+ collectRecords,
12
+ collectRecordsByFilter,
13
+ deleteRecord,
14
+ ownerByChild,
15
+ ownerByOwnerType,
16
+ serverTimestampFieldValue,
17
+ } from "@leaddreamer/firebase-wrapper";
18
+
19
+ import { Forms, cleanType } from "@leaddreamer/staticforms";
20
+ import { PAYMENT, RECEIPTS, CARDS } from "../boilerplate";
21
+
22
+ //////////////////////////////////////////////////////////////////////
23
+ // definitions
24
+ /**
25
+ * @constant
26
+ * @type {{CARD_ERROR: string, VALIDATION_ERROR: string}}
27
+ * @default
28
+ */
29
+ export const ERROR_TYPES = {
30
+ CARD_ERROR: `card_error`,
31
+ VALIDATION_ERROR: `validation_error`,
32
+ };
33
+
34
+ /**
35
+ * @typedef {object} PaymentData
36
+ * @property {string} Id
37
+ * @property {string} payment_gateway
38
+ * @property {string} status
39
+ */
40
+ /**
41
+ * @constant
42
+ * @type {PaymentData}
43
+ * @default
44
+ */
45
+ export const PaymentDefault = {
46
+ Id: null,
47
+ payment_gateway: null,
48
+ status: null,
49
+ };
50
+
51
+ /**
52
+ * @typedef {object} PaymentDescriptorType
53
+ * @property {!string} Id document Id of this Card Descriptor (same as fingerprint)
54
+ * @property {!string} paymentMethod Id of payment system card item
55
+ * @property {!string} fingerprint Unique hash of card info - for checking uniqueness
56
+ * @property {!string} label Brand & last4, or other descriptive
57
+ * @property {!string} expiry expiry date of payment method (if any)
58
+ * @property {timestamp} timestamp last create or modify server time
59
+ */
60
+ /**
61
+ * @typedef {object} PDSummary
62
+ * @property {!string} Id document Id of this Card Descriptor
63
+ * @property {!string} label Brand & last4, or other descriptive
64
+ */
65
+ /**
66
+ * @constant
67
+ * @type {PaymentDescriptorType}
68
+ * @default
69
+ */
70
+ export const paymentDescriptor = {
71
+ Id: null,
72
+ paymentMethod: null,
73
+ fingerprint: null,
74
+ label: null,
75
+ nickname: null,
76
+ };
77
+
78
+ /**
79
+ * @typedef {object} ReceiptType
80
+ * @property {!string} Id same Id as the associated pledgeTicket
81
+ * @property {!number} amount
82
+ * @property {object} payment
83
+ * @property {!string} link Id of associated payment_intent
84
+ * @property {!string} paidQ string describing status
85
+ * @property {?string} description supplemental information
86
+ */
87
+ /**
88
+ * @constant
89
+ * @type {ReceiptType}
90
+ * @default
91
+ */
92
+ export const receiptDefault = {
93
+ Id: null, //pledgeTicketId for user receipts
94
+ amount: 0,
95
+ payment: {
96
+ link: null, //paymentIntentId
97
+ paidQ: null, //current status
98
+ },
99
+ description: null, //supplemental information
100
+ };
101
+
102
+ Forms.Base[PAYMENT] = PaymentDefault;
103
+
104
+ //////////////////////////////////////////////////////////////////////
105
+ // Utilities
106
+
107
+ /**
108
+ * @typedef {UserObject} AccountObject
109
+ */
110
+
111
+ /**
112
+ * Creates the Organization Payment information document
113
+ * @async
114
+ * @function
115
+ * @param {AccountObject} account
116
+ * @param {?WriteBatch|Transaction} batch batching object. Transaction will be added to the batch
117
+ * @return {Promise<WriteBatch|Transaction|void>} WriteBatch, Transaction or Void
118
+ */
119
+ export async function createPaymentRecord(account, batch = null) {
120
+ return typedWrite(
121
+ cleanType(account, PAYMENT), //sanitized data - unused fields added
122
+ account, //parent
123
+ PAYMENT, //type
124
+ batch,
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Saves a payment data record to a given account (user)
130
+ * ALL OF fans, artists and venues save the payment data in the same collection
131
+ * name under the parent, with the SAME Id as the parent
132
+ * PaymentData *COMES FROM* payement processor, and should INCLUDE the record Id and
133
+ * refPath
134
+ * @param {PaymentDataRecord} paymentData
135
+ */
136
+ export async function savePaymentData(paymentData) {
137
+ //ALL OF fans, artists and venues save the payment data in the same collection
138
+ // name under the parent, with the SAME Id as the parent
139
+ return writeBack({
140
+ ...paymentData,
141
+ });
142
+ }
143
+
144
+ /**
145
+ * fetches a paymentData record from the database for an account
146
+ * ALL OF fans, artists and venues, etc save the payment data in the same collection
147
+ * name under the parent, with the SAME Id as the parent
148
+ * @param {AccountType} account
149
+ * @param {Promise.PaymentData} paymentData
150
+ */
151
+ export async function fetchPayment(account) {
152
+ //payment data is saved under the same collection name for fans, artists
153
+ //and venues, with the parent ID
154
+ let payment_record = null;
155
+ try {
156
+ payment_record = await fetchRecord(PAYMENT, account.Id, account.refPath);
157
+ } catch (err) { }
158
+ return payment_record;
159
+ }
160
+
161
+ /**
162
+ * fetches a paymentData record from the database for an account when only accountID and type are known
163
+ * ALL OF fans, artists and venues save the payment data in the same collection
164
+ * name under the parent, with the SAME Id as the parent
165
+ * @param {!string} accountId
166
+ * @param {!string} type
167
+ * @param {Promise.PaymentData} paymentData
168
+ */
169
+ export async function fetchPaymentByUser(accountId) {
170
+ return fetchPayment({
171
+ Id: accountId,
172
+ refPath: `${USER}/${accountId}`,
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Adds a specific paymentDescriptor to an account payment data
178
+ * @param {AccountType} account
179
+ * @param {!string} account.Id
180
+ * @param {!string} account.refPath
181
+ * @param {PaymentDescriptorType} descriptor
182
+ * @param {!string} descriptor.paymentMethod
183
+ * @param {!string} descriptor.fingerprint
184
+ * @param {!string} descriptor.label
185
+ * @param {?WriteBatch|Transaction} batch batching object. Transaction will be added to the batch
186
+ * @return {Promise.PaymentDescriptorType}
187
+ */
188
+ export async function addPaymentDescriptor(account, descriptor, batch = null) {
189
+ return typedWrite(
190
+ {
191
+ ...descriptor,
192
+ timestamp: serverTimestampFieldValue,
193
+ },
194
+ {
195
+ //Payment documents BY DEFINITION have the same Id as the parent
196
+ refPath: `${account.refPath}/${PAYMENT}/${account.Id}`,
197
+ },
198
+ CARDS,
199
+ batch,
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Saves a modified paymentDescriptor back to the database
205
+ * @param {PaymentDescriptorType} descriptor
206
+ * @param {!string} descriptor.Id
207
+ * @param {!string} descriptor.refPath
208
+ * @return {Promise.PaymentDescriptorType}
209
+ */
210
+ export async function savePaymentDescriptor(descriptor) {
211
+ //BOTH fans, artists and venues save the payment data in the same collection
212
+ // name under the parent, with the SAME Id as the parent
213
+ return writeBack({
214
+ ...descriptor,
215
+ timestamp: serverTimestampFieldValue,
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Removes a paymentDescriptor from an account's paymentData
221
+ * @param {AccountType} account
222
+ * @param {PaymentDescriptorType} descriptor
223
+ * @returns {Promise<void>}
224
+ */
225
+ export async function removePaymentDescriptor(account, descriptor) {
226
+ //MASSIVE CAVEAT
227
+ //A paymentMethod *might* be removed with e same *fingerprint* as a current descriptor
228
+ //but is still a different *paymentMethod*. PaymentDescriptors are unique on the fingerprint
229
+ //Therefore, we need to *first* confirm that is is the same, so...
230
+ //READ FIRST...
231
+ const existingPD = await fetchPDByFingerprint(
232
+ account,
233
+ descriptor.fingerprint,
234
+ );
235
+ //COMPARE PAYEMNTMETHOD, THEN DELETE IF NEEDED
236
+ return existingPD.paymentMethod === descriptor.paymentMethod
237
+ ? deleteRecord(
238
+ descriptor,
239
+ CARDS,
240
+ `/${account.refPath}/${PAYMENT}/${account.Id}`,
241
+ )
242
+ : Promise.resolve(null); //don't delete if not identical
243
+ }
244
+
245
+ /**
246
+ * fetch all customer card records IN OUR DATABASE (not payment database)
247
+ * @param {AccountType} account
248
+ * @param {!string} account.refPath
249
+ * @returns {Promise.Array.PaymentDescriptor}
250
+ *
251
+ */
252
+ export async function fetchPaymentDescriptors(account) {
253
+ return collectRecords(CARDS, `${account.refPath}/${PAYMENT}/${account.Id}`);
254
+ }
255
+
256
+ /**
257
+ * fetch the front-end list of paymentDescriptors
258
+ * @param {AccountType} account
259
+ * @param {!string} account.Id
260
+ * @param {!string} account.refPath
261
+ * @return {Promise.Array.PDSummary}
262
+ */
263
+ export async function listPDSummaries(account) {
264
+ const full = await fetchPaymentDescriptors(account);
265
+ return full.map((pd) => ({
266
+ Id: pd.Id,
267
+ label: pd.label,
268
+ nickname: pd.nickname,
269
+ }));
270
+ }
271
+
272
+ /**
273
+ * @param {AccountType} account
274
+ * @param {!string} account.Id
275
+ * @param {!string} account.refPath
276
+ * @returns {Promise.PaymentDescriptorType}
277
+ */
278
+ export async function fetchLatestPDSummary(account) {
279
+ const sortArray = [
280
+ {
281
+ fieldRef: "timestamp",
282
+ dirStr: "desc",
283
+ },
284
+ ];
285
+
286
+ const shortlist = await collectRecordsByFilter(
287
+ CARDS,
288
+ `/${account.refPath}/${PAYMENT}/${account.Id}`,
289
+ null, //filterArray
290
+ sortArray,
291
+ 1, //limit
292
+ );
293
+ const latest = shortlist?.[0];
294
+
295
+ return { Id: latest?.Id, label: latest?.label };
296
+ }
297
+
298
+ /**
299
+ * retreives a PD, given the account Id and Type
300
+ * @param {AccountType} account
301
+ * @param {!string} fingerprint
302
+ * @returns {Promise<PDSummary>}
303
+ */
304
+ export async function fetchPDByFingerprint(account, fingerprint) {
305
+ return fetchRecord(
306
+ CARDS, //tableName
307
+ fingerprint, //Id/DocId
308
+ `/${account.refPath}/${PAYMENT}/${account.Id}`, //refPath
309
+ );
310
+ }
311
+
312
+ /**
313
+ * retreives a PD, given the account Id and Type
314
+ * @param {!string} accountId
315
+ * @param {!string} type
316
+ * @param {!string} fingerprint
317
+ * @returns {Promise<PDSummary>}
318
+ */
319
+ export async function fetchPDByOwnerType(accountId, type, fingerprint) {
320
+ return fetchRecord(
321
+ CARDS, //tableName
322
+ fingerprint, //Id/DocId
323
+ `/${type}/${accountId}/${PAYMENT}/${accountId}`, //refPath
324
+ );
325
+ }
326
+
327
+ /**
328
+ * Copies a PDSummary tobe the new default (convenience function)
329
+ * @param {AccountType} account
330
+ * @param {?PDSummary} PDSummary
331
+ * @returns {Promise.AccountObject}
332
+ */
333
+ export async function replaceDefaultPDSummary(account, PDSummary = null) {
334
+ //if not passed a PaymentDescriptorType, use the latest available
335
+ const lastestPayment = await (PDSummary
336
+ ? Promise.resolve(PDSummary)
337
+ : fetchLatestPDSummary(account));
338
+ return writeBack({
339
+ ...account,
340
+ payment_method: lastestPayment,
341
+ });
342
+ }
343
+
344
+ //////////////////////////////////////////////////////////////////////
345
+ //Receipt support
346
+
347
+ /**
348
+ * Savess a Receipt to an account
349
+ * @param {AccountType} account
350
+ * @param {?ReceiptType} receipt
351
+ * @param {?WriteBatch|Transaction} batch batching object. Transaction will be added to the batch
352
+ * @returns {Promise.ReceiptType}
353
+ */
354
+ export async function saveReceiptRecord(account, receipt, batch = null) {
355
+ return typedWrite(receipt, account, RECEIPTS, batch);
356
+ }
357
+
358
+ /**
359
+ * Updates a (changed?) Receipt record
360
+ * @param {?ReceiptType} receipt
361
+ * @returns {Promise.ReceiptType}
362
+ */
363
+ export async function updateReceipt(receipt) {
364
+ return writeBack(receipt);
365
+ }
366
+
367
+ /**
368
+ * Saves a new receipt to an account referrred by ID and type
369
+ * @param {!string} ownerId
370
+ * @param {string} type either ORGANIZATIONS or USER
371
+ * @param {ReceiptType} receipt
372
+ * @param {?WriteBatch|Transaction} batch batching object. Transaction will be added to the batch
373
+ * @returns {Promise.ReceiptType}
374
+ */
375
+ export async function saveReceiptRecordByOwnerType(
376
+ ownerId,
377
+ ownerType,
378
+ receipt,
379
+ batch = null,
380
+ ) {
381
+ return saveReceiptRecord(
382
+ ownerByOwnerType(ownerId, ownerType),
383
+ receipt,
384
+ batch,
385
+ );
386
+ }
387
+
388
+ /**
389
+ * Fetches an array of receipts in any particular month by monthStampt
390
+ * @param {AccountObject} account
391
+ * @param {dateString} monthStamp
392
+ * @returns {Promise.Array.ReceiptType}
393
+ */
394
+ export async function anyReceiptsByMonthStamp(account, monthStamp) {
395
+ const filterArray = [
396
+ { fieldRef: "monthStamp", opStr: "==", value: monthStamp },
397
+ ];
398
+ try {
399
+ const receipts = await fetchReceiptsFiltered(account, filterArray);
400
+ return !!receipts.length; //true if more than zero
401
+ } catch (err) {
402
+ return false;
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Saves a receipt by a child record reference
408
+ * @param {RecordDocument} child
409
+ * @param {ReceiptType} receipt
410
+ * @param {?WriteBatch|Transaction} batch batching object. Transaction will be added to the batch
411
+ * @returns {Promise.ReceiptType}
412
+ */
413
+ export async function saveReceiptRecordByChild(child, receipt, batch = null) {
414
+ return saveReceiptRecord(ownerByChild(child), receipt, batch);
415
+ }
416
+
417
+ /**
418
+ * Fetches receipts based on a filter set
419
+ * @param {AccountObject} account
420
+ * @param {!string} account.refPath
421
+ * @param {Array.filterObject} filterArray
422
+ * @param {Array.sortObject} sortArray
423
+ * @param {number} limit
424
+ * @return {Promise.Array.ReceiptType}
425
+ */
426
+ export async function fetchReceiptsFiltered(
427
+ account,
428
+ filterArray = null,
429
+ sortArray = null,
430
+ limit = null,
431
+ ) {
432
+ return collectRecordsByFilter(
433
+ RECEIPTS, //tablePath
434
+ account.refPath, //refPath
435
+ filterArray, //filter array
436
+ sortArray, //sort Array
437
+ limit, //limit
438
+ );
439
+ }
440
+
441
+ /**
442
+ * @param {AccountObject} account
443
+ * @param {!string} account.refPath
444
+ * @param {!string} receiptId
445
+ * @return {Promise.ReceiptType}
446
+ */
447
+ async function fetchReceipt(account, receiptId) {
448
+ return fetchRecord(RECEIPTS, receiptId, account.refPath);
449
+ }
450
+
451
+ /**
452
+ * @param {AccountObject} account
453
+ * @param {!string} account.refPath
454
+ * @return {Promise.Array.ReceiptType}
455
+ */
456
+ export async function fetchProblemReceipts(account) {
457
+ //const in_status_array = [
458
+ //`requires_payment_method`,
459
+ //`requires_confirmation`,
460
+ //`requires_action`,
461
+ //`processing`,
462
+ //`requires_capture`,
463
+ //`canceled`,
464
+ //`succeeded`,
465
+ //`none`
466
+ //];
467
+ const not_in_status_array = [`succeeded`, `none`];
468
+ const filterArray = [
469
+ { fieldRef: "payment.paidQ", opStr: "not-in", value: not_in_status_array },
470
+ ];
471
+ return fetchReceiptsFiltered(account, filterArray, null, null);
472
+ }
473
+
474
+ /**
475
+ * Fetches the receipt for a pledgeTicket
476
+ * @param {PledgeTicket} pledgeTicket
477
+ * @returns {Promise.ReceiptType}
478
+ */
479
+ export async function fetchPledgeTicketReceipt(pledgeTicket) {
480
+ const account = {
481
+ Id: pledgeTicket.fan.Id,
482
+ refPath: `/${USER}/${pledgeTicket.fan.Id}`,
483
+ };
484
+ const receiptId = pledgeTicket.Id;
485
+ return fetchReceipt(account, receiptId);
486
+ }
487
+
488
+ /**
489
+ * Changes the paidQ label on a receipt associated with a pledgeTicket
490
+ * @param {pledgeTicketType} pledgeTicket
491
+ * @param {?string} label
492
+ * @returns {Promise.ReceiptType}
493
+ */
494
+ export async function markReceiptFromPledgeTicket(pledgeTicket, label) {
495
+ const receipt = await fetchPledgeTicketReceipt(pledgeTicket);
496
+ return markReceipt(receipt, label);
497
+ }
498
+
499
+ /**
500
+ * Changes the paidQ label on a receipt associated with a pledgeTicket
501
+ * @param {receiptType} receipt
502
+ * @param {?string} label
503
+ * @returns {Promise.ReceiptType}
504
+ */
505
+ export async function markReceipt(receipt, label) {
506
+ return updateReceipt({
507
+ ...receipt,
508
+ payment: {
509
+ ...receipt.payment,
510
+ paidQ: label,
511
+ },
512
+ });
513
+ }
514
+
515
+ //////////////////////////////////////////////////////////////////////
516
+ // utilities