@driveflux/api-functions 1.0.25 → 1.0.26
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/reservation/invoice.js +68 -81
- package/package.json +7 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { config } from '@driveflux/config/backend';
|
|
2
2
|
import { loadCoupon, PROBLEM_APPLICABLE_NOT_FOUND } from '@driveflux/coupon';
|
|
3
|
-
import { prisma
|
|
3
|
+
import { prisma } from '@driveflux/db';
|
|
4
4
|
import { generateId } from '@driveflux/db/id';
|
|
5
5
|
import { EMPTY_BILLING_ADDRESS } from '@driveflux/db/models/other';
|
|
6
6
|
import { PURPOSE_RESERVATION } from '@driveflux/db/models/subscription';
|
|
@@ -14,16 +14,15 @@ import { isAfter } from 'date-fns/isAfter';
|
|
|
14
14
|
import { subMinutes } from 'date-fns/subMinutes';
|
|
15
15
|
import { createScopedLogger } from '../create-logger.js';
|
|
16
16
|
const log = createScopedLogger('reservation:invoice');
|
|
17
|
-
export const updateReservationInvoiceIfNeeded = async (oldReservationInvoice, newParams)
|
|
17
|
+
export const updateReservationInvoiceIfNeeded = async (oldReservationInvoice, newParams)=>{
|
|
18
18
|
const couponIsDifferent = oldReservationInvoice.couponCode !== newParams.couponCode;
|
|
19
|
-
if (!couponIsDifferent &&
|
|
20
|
-
oldReservationInvoice.stripePaymentIntentId === newParams.paymentIntentId) {
|
|
19
|
+
if (!couponIsDifferent && oldReservationInvoice.stripePaymentIntentId === newParams.paymentIntentId) {
|
|
21
20
|
return new Ok(oldReservationInvoice);
|
|
22
21
|
}
|
|
23
22
|
// If the coupon or payment intent are differnt we should update the invoice
|
|
24
23
|
let update = {
|
|
25
24
|
stripePaymentIntentId: newParams.paymentIntentId,
|
|
26
|
-
couponCode: newParams.couponCode
|
|
25
|
+
couponCode: newParams.couponCode
|
|
27
26
|
};
|
|
28
27
|
// If the coupon is different, we need to apply the coupon to the invoice
|
|
29
28
|
if (couponIsDifferent) {
|
|
@@ -31,63 +30,56 @@ export const updateReservationInvoiceIfNeeded = async (oldReservationInvoice, ne
|
|
|
31
30
|
const result = await coupons.applyCouponToUnsavedInvoice(newParams.couponCode, oldReservationInvoice);
|
|
32
31
|
if (result.ok) {
|
|
33
32
|
const { discounts, ...rest } = result.val;
|
|
34
|
-
const toDisconnect = discounts
|
|
35
|
-
|
|
36
|
-
:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
: undefined;
|
|
33
|
+
const toDisconnect = discounts ? oldReservationInvoice.discountIds?.filter((id)=>!discounts.connect?.some((d)=>d.id === id)) : undefined;
|
|
34
|
+
const disconnectClauses = toDisconnect?.length ? {
|
|
35
|
+
disconnect: toDisconnect.map((id)=>({
|
|
36
|
+
id
|
|
37
|
+
}))
|
|
38
|
+
} : undefined;
|
|
42
39
|
update = {
|
|
43
40
|
...update,
|
|
44
41
|
...rest,
|
|
45
|
-
...
|
|
46
|
-
|
|
47
|
-
discounts
|
|
48
|
-
|
|
49
|
-
...(disconnectClauses || {}),
|
|
50
|
-
},
|
|
42
|
+
...discounts || disconnectClauses ? {
|
|
43
|
+
discounts: {
|
|
44
|
+
...discounts || {},
|
|
45
|
+
...disconnectClauses || {}
|
|
51
46
|
}
|
|
52
|
-
|
|
47
|
+
} : {},
|
|
53
48
|
metadata: {
|
|
54
49
|
...update.metadata,
|
|
55
|
-
couponCode: newParams.couponCode
|
|
50
|
+
couponCode: newParams.couponCode
|
|
56
51
|
},
|
|
57
52
|
providerMetadata: {
|
|
58
53
|
...update.providerMetadata,
|
|
59
|
-
couponCode: newParams.couponCode
|
|
60
|
-
}
|
|
54
|
+
couponCode: newParams.couponCode
|
|
55
|
+
}
|
|
61
56
|
};
|
|
62
57
|
}
|
|
63
58
|
if (result.err && result.val.code !== PROBLEM_APPLICABLE_NOT_FOUND) {
|
|
64
59
|
return result;
|
|
65
60
|
}
|
|
66
|
-
}
|
|
67
|
-
else if (oldReservationInvoice.discountIds.length) {
|
|
61
|
+
} else if (oldReservationInvoice.discountIds.length) {
|
|
68
62
|
// No discounts, so we disconnect all of them
|
|
69
63
|
update = {
|
|
70
64
|
discounts: {
|
|
71
|
-
disconnect: oldReservationInvoice.discountIds.map((id)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
65
|
+
disconnect: oldReservationInvoice.discountIds.map((id)=>({
|
|
66
|
+
id
|
|
67
|
+
}))
|
|
68
|
+
}
|
|
75
69
|
};
|
|
76
70
|
}
|
|
77
71
|
}
|
|
78
72
|
let updated = await wrapInResult(biller.updateInvoice(oldReservationInvoice.id, update));
|
|
79
73
|
// If the invoice was locked due to pending status and we're more than 1 minute old, we unlock it
|
|
80
74
|
if (updated.err && updated.val.code === 'invoice_pending') {
|
|
81
|
-
if ((oldReservationInvoice.lockedAt
|
|
82
|
-
isAfter(oldReservationInvoice.lockedAt, subMinutes(new Date(), 1))) ||
|
|
83
|
-
!oldReservationInvoice.lockedAt) {
|
|
75
|
+
if (oldReservationInvoice.lockedAt && isAfter(oldReservationInvoice.lockedAt, subMinutes(new Date(), 1)) || !oldReservationInvoice.lockedAt) {
|
|
84
76
|
updated = await wrapInResult(biller.updateInvoice(oldReservationInvoice.id, {
|
|
85
77
|
...update,
|
|
86
78
|
locked: false,
|
|
87
79
|
lockedAt: null,
|
|
88
80
|
lockReason: null,
|
|
89
81
|
unlockAt: null,
|
|
90
|
-
status: 'draft'
|
|
82
|
+
status: 'draft'
|
|
91
83
|
}));
|
|
92
84
|
}
|
|
93
85
|
}
|
|
@@ -96,7 +88,7 @@ export const updateReservationInvoiceIfNeeded = async (oldReservationInvoice, ne
|
|
|
96
88
|
}
|
|
97
89
|
return new Ok(updated.val);
|
|
98
90
|
};
|
|
99
|
-
export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mileagePackage, referralCode, reservationId, paymentIntentId, freeReservation, freeReservationReason, analytics, vehicle, payer, subscribingUser
|
|
91
|
+
export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mileagePackage, referralCode, reservationId, paymentIntentId, freeReservation, freeReservationReason, analytics, vehicle, payer, subscribingUser })=>{
|
|
100
92
|
if (freeReservation) {
|
|
101
93
|
// It's a free reservation, so we don't need to add a coupon code
|
|
102
94
|
return await wrapInResult(biller.createInvoice(getInvoiceCreateDetails({
|
|
@@ -109,21 +101,17 @@ export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mi
|
|
|
109
101
|
freeReservation,
|
|
110
102
|
freeReservationReason,
|
|
111
103
|
analytics,
|
|
112
|
-
reservationId
|
|
104
|
+
reservationId
|
|
113
105
|
})));
|
|
114
106
|
}
|
|
115
107
|
let finalCouponCode = couponCode;
|
|
116
108
|
// If we don't have a reservation coupon ID, we apply the referral discount if any
|
|
117
|
-
const referral = referralCode &&
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (!finalCouponCode &&
|
|
124
|
-
referral &&
|
|
125
|
-
referral.discountToReceiverCouponId &&
|
|
126
|
-
referral.discountToReceiverCouponApplicationName === 'reservationFee') {
|
|
109
|
+
const referral = referralCode && await prisma.referral.findUnique({
|
|
110
|
+
where: {
|
|
111
|
+
id: referralCode
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
if (!finalCouponCode && referral && referral.discountToReceiverCouponId && referral.discountToReceiverCouponApplicationName === 'reservationFee') {
|
|
127
115
|
const referralCoupon = await loadCoupon(referral.discountToReceiverCouponId);
|
|
128
116
|
if (referralCoupon) {
|
|
129
117
|
finalCouponCode = referralCoupon.code;
|
|
@@ -142,15 +130,15 @@ export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mi
|
|
|
142
130
|
freeReservation,
|
|
143
131
|
freeReservationReason,
|
|
144
132
|
analytics,
|
|
145
|
-
reservationId
|
|
133
|
+
reservationId
|
|
146
134
|
}),
|
|
147
135
|
lines: [
|
|
148
136
|
{
|
|
149
137
|
amount: reservationAmount,
|
|
150
138
|
description: `Reservation fee for ${vehicleName(vehicle)}`,
|
|
151
|
-
chargingFor: 'reservationFee'
|
|
152
|
-
}
|
|
153
|
-
]
|
|
139
|
+
chargingFor: 'reservationFee'
|
|
140
|
+
}
|
|
141
|
+
]
|
|
154
142
|
};
|
|
155
143
|
console.log('invoiceCreateDetails', invoiceCreateDetails);
|
|
156
144
|
if (finalCouponCode) {
|
|
@@ -161,10 +149,9 @@ export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mi
|
|
|
161
149
|
vehicleMake: vehicle.make,
|
|
162
150
|
userId: subscribingUser.id,
|
|
163
151
|
businessId: payer.object === 'business' ? payer.id : undefined,
|
|
164
|
-
vehicleType: vehicle.type
|
|
152
|
+
vehicleType: vehicle.type
|
|
165
153
|
});
|
|
166
|
-
if (withCoupon.err &&
|
|
167
|
-
withCoupon.val.code !== PROBLEM_APPLICABLE_NOT_FOUND) {
|
|
154
|
+
if (withCoupon.err && withCoupon.val.code !== PROBLEM_APPLICABLE_NOT_FOUND) {
|
|
168
155
|
return withCoupon;
|
|
169
156
|
}
|
|
170
157
|
if (withCoupon.ok) {
|
|
@@ -172,12 +159,12 @@ export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mi
|
|
|
172
159
|
...withCoupon.val,
|
|
173
160
|
metadata: {
|
|
174
161
|
...withCoupon.val.metadata,
|
|
175
|
-
couponCode: finalCouponCode
|
|
162
|
+
couponCode: finalCouponCode
|
|
176
163
|
},
|
|
177
164
|
providerMetadata: {
|
|
178
165
|
...withCoupon.val.providerMetadata,
|
|
179
|
-
couponCode: finalCouponCode
|
|
180
|
-
}
|
|
166
|
+
couponCode: finalCouponCode
|
|
167
|
+
}
|
|
181
168
|
};
|
|
182
169
|
}
|
|
183
170
|
}
|
|
@@ -186,8 +173,8 @@ export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mi
|
|
|
186
173
|
...invoiceCreateDetails,
|
|
187
174
|
providerMetadata: {
|
|
188
175
|
...invoiceCreateDetails.providerMetadata,
|
|
189
|
-
invoiceId: invoiceCreateDetails.id
|
|
190
|
-
}
|
|
176
|
+
invoiceId: invoiceCreateDetails.id
|
|
177
|
+
}
|
|
191
178
|
}));
|
|
192
179
|
if (result.err) {
|
|
193
180
|
return result;
|
|
@@ -196,24 +183,23 @@ export const createReservationInvoice = async ({ invoiceId, couponCode, plan, mi
|
|
|
196
183
|
if (paymentIntentId) {
|
|
197
184
|
log.warn({
|
|
198
185
|
paymentIntentId,
|
|
199
|
-
invoiceId: result.val.id
|
|
186
|
+
invoiceId: result.val.id
|
|
200
187
|
}, 'Payment intent provided but invoice ID is not set in the provider metadata');
|
|
201
188
|
const stripe = biller.getPaymentProvider('stripe').getStripe();
|
|
202
189
|
try {
|
|
203
190
|
await stripe.paymentIntents.update(paymentIntentId, {
|
|
204
191
|
metadata: {
|
|
205
192
|
...result.val.providerMetadata,
|
|
206
|
-
invoiceId: result.val.id
|
|
207
|
-
}
|
|
193
|
+
invoiceId: result.val.id
|
|
194
|
+
}
|
|
208
195
|
});
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
196
|
+
} catch (error) {
|
|
211
197
|
log.error(error, 'Error updating payment intent metadata');
|
|
212
198
|
}
|
|
213
199
|
}
|
|
214
200
|
return new Ok(result.val);
|
|
215
201
|
};
|
|
216
|
-
const getInvoiceCreateDetails = ({ invoiceId, payer, date, reservationDiscountId, vehicle, plan, mileagePackage, couponCode, paymentIntentId, freeReservation, freeReservationReason, analytics, reservationId
|
|
202
|
+
const getInvoiceCreateDetails = ({ invoiceId, payer, date, reservationDiscountId, vehicle, plan, mileagePackage, couponCode, paymentIntentId, freeReservation, freeReservationReason, analytics, reservationId })=>{
|
|
217
203
|
return {
|
|
218
204
|
id: invoiceId || generateId('Invoice'),
|
|
219
205
|
taxPercentage: 0,
|
|
@@ -229,15 +215,13 @@ const getInvoiceCreateDetails = ({ invoiceId, payer, date, reservationDiscountId
|
|
|
229
215
|
description: 'Reservation fee for a FLUX subscription',
|
|
230
216
|
footer: `Invoice for a FLUX subscription reservation. Due by ${format(date ?? new Date())}`,
|
|
231
217
|
invoiceUrl: `${config.appUrl}/invoice-preview/${invoiceId}`,
|
|
232
|
-
...
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
},
|
|
238
|
-
},
|
|
218
|
+
...reservationDiscountId ? {
|
|
219
|
+
discounts: {
|
|
220
|
+
connect: {
|
|
221
|
+
id: reservationDiscountId
|
|
222
|
+
}
|
|
239
223
|
}
|
|
240
|
-
|
|
224
|
+
} : {},
|
|
241
225
|
lines: [],
|
|
242
226
|
subscriptionDetails: {
|
|
243
227
|
vehicleId: vehicle.id,
|
|
@@ -246,7 +230,7 @@ const getInvoiceCreateDetails = ({ invoiceId, payer, date, reservationDiscountId
|
|
|
246
230
|
variant: vehicle.variant,
|
|
247
231
|
year: vehicle.year,
|
|
248
232
|
plan: plan,
|
|
249
|
-
mileagePackage: mileagePackage
|
|
233
|
+
mileagePackage: mileagePackage
|
|
250
234
|
},
|
|
251
235
|
// Legacy field:
|
|
252
236
|
stripePaymentIntentId: paymentIntentId,
|
|
@@ -256,14 +240,18 @@ const getInvoiceCreateDetails = ({ invoiceId, payer, date, reservationDiscountId
|
|
|
256
240
|
mileagePackage,
|
|
257
241
|
vehicleId: vehicle.id,
|
|
258
242
|
vehicleName: vehicleName(vehicle),
|
|
259
|
-
...
|
|
260
|
-
|
|
243
|
+
...reservationId ? {
|
|
244
|
+
reservationId
|
|
245
|
+
} : {},
|
|
246
|
+
...couponCode ? {
|
|
247
|
+
couponCode
|
|
248
|
+
} : {}
|
|
261
249
|
},
|
|
262
250
|
autoRetry: {
|
|
263
251
|
enabled: false,
|
|
264
252
|
interval: 0,
|
|
265
253
|
maxAttempts: 0,
|
|
266
|
-
attempts: 0
|
|
254
|
+
attempts: 0
|
|
267
255
|
},
|
|
268
256
|
metadata: {
|
|
269
257
|
purpose: PURPOSE_RESERVATION,
|
|
@@ -272,11 +260,10 @@ const getInvoiceCreateDetails = ({ invoiceId, payer, date, reservationDiscountId
|
|
|
272
260
|
plan,
|
|
273
261
|
mileagePackage,
|
|
274
262
|
couponCode,
|
|
275
|
-
...
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
analytics
|
|
279
|
-
}
|
|
263
|
+
...freeReservation && freeReservationReason ? {
|
|
264
|
+
freeReservationReason
|
|
265
|
+
} : {},
|
|
266
|
+
analytics
|
|
267
|
+
}
|
|
280
268
|
};
|
|
281
269
|
};
|
|
282
|
-
//# sourceMappingURL=invoice.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driveflux/api-functions",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./notion": {
|
|
@@ -76,17 +76,17 @@
|
|
|
76
76
|
"@driveflux/auth": "4.0.22",
|
|
77
77
|
"@driveflux/billing": "7.0.0-develop.70",
|
|
78
78
|
"@driveflux/config": "3.0.3",
|
|
79
|
-
"@driveflux/coupon": "8.0.0-develop.
|
|
80
|
-
"@driveflux/db": "3.0.0-develop.
|
|
81
|
-
"@driveflux/email": "6.0.0-develop.
|
|
82
|
-
"@driveflux/email-templates": "0.0.2-develop.
|
|
83
|
-
"@driveflux/engine": "0.1.2-develop.
|
|
79
|
+
"@driveflux/coupon": "8.0.0-develop.93",
|
|
80
|
+
"@driveflux/db": "3.0.0-develop.106",
|
|
81
|
+
"@driveflux/email": "6.0.0-develop.87",
|
|
82
|
+
"@driveflux/email-templates": "0.0.2-develop.91",
|
|
83
|
+
"@driveflux/engine": "0.1.2-develop.133",
|
|
84
84
|
"@driveflux/fetch": "8.0.0",
|
|
85
85
|
"@driveflux/format-money": "7.0.0",
|
|
86
86
|
"@driveflux/problem": "6.0.0",
|
|
87
87
|
"@driveflux/reporter": "7.0.1",
|
|
88
88
|
"@driveflux/result": "6.0.0",
|
|
89
|
-
"@driveflux/scheduler": "7.0.0-develop.
|
|
89
|
+
"@driveflux/scheduler": "7.0.0-develop.92",
|
|
90
90
|
"@driveflux/singleton": "3.0.0",
|
|
91
91
|
"@driveflux/subscription": "8.0.0-develop.52",
|
|
92
92
|
"@driveflux/logger": "0.0.2-develop.40",
|