@bash-app/bash-common 30.236.0 → 30.238.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/dist/__tests__/statusEnums.test.js +88 -7
- package/dist/__tests__/statusEnums.test.js.map +1 -1
- package/dist/definitions.d.ts +39 -8
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +31 -10
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +6 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/mirroredPrismaEnums.d.ts +145 -0
- package/dist/mirroredPrismaEnums.d.ts.map +1 -0
- package/dist/mirroredPrismaEnums.js +136 -0
- package/dist/mirroredPrismaEnums.js.map +1 -0
- package/dist/utils/__tests__/nonprofitUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/nonprofitUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/nonprofitUtils.test.js +84 -0
- package/dist/utils/__tests__/nonprofitUtils.test.js.map +1 -0
- package/dist/utils/__tests__/paymentUtils.test.js +87 -0
- package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
- package/dist/utils/nonprofitUtils.d.ts +21 -0
- package/dist/utils/nonprofitUtils.d.ts.map +1 -0
- package/dist/utils/nonprofitUtils.js +29 -0
- package/dist/utils/nonprofitUtils.js.map +1 -0
- package/dist/utils/paymentUtils.d.ts +23 -0
- package/dist/utils/paymentUtils.d.ts.map +1 -1
- package/dist/utils/paymentUtils.js +24 -7
- package/dist/utils/paymentUtils.js.map +1 -1
- package/dist/utils/service/__tests__/billingCapUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.js +120 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js +1 -1
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js.map +1 -1
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts +2 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.js +243 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.js.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.js +333 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.js +52 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.js +111 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js +155 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts +2 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.js +77 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.js.map +1 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js +157 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.js +175 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js +118 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js.map +1 -0
- package/dist/utils/service/apiServiceBookingApiUtils.d.ts.map +1 -1
- package/dist/utils/service/apiServiceBookingApiUtils.js +4 -0
- package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -1
- package/dist/utils/service/billingCapUtils.d.ts +86 -0
- package/dist/utils/service/billingCapUtils.d.ts.map +1 -0
- package/dist/utils/service/billingCapUtils.js +96 -0
- package/dist/utils/service/billingCapUtils.js.map +1 -0
- package/dist/utils/service/cancellationPolicyRefundResolver.d.ts +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.d.ts.map +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.js +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.js.map +1 -1
- package/dist/utils/service/eventPromoterUtils.d.ts +319 -0
- package/dist/utils/service/eventPromoterUtils.d.ts.map +1 -0
- package/dist/utils/service/eventPromoterUtils.js +372 -0
- package/dist/utils/service/eventPromoterUtils.js.map +1 -0
- package/dist/utils/service/offerAllowListUtils.d.ts +42 -0
- package/dist/utils/service/offerAllowListUtils.d.ts.map +1 -0
- package/dist/utils/service/offerAllowListUtils.js +49 -0
- package/dist/utils/service/offerAllowListUtils.js.map +1 -0
- package/dist/utils/service/offerComparisonUtils.d.ts +69 -0
- package/dist/utils/service/offerComparisonUtils.d.ts.map +1 -0
- package/dist/utils/service/offerComparisonUtils.js +110 -0
- package/dist/utils/service/offerComparisonUtils.js.map +1 -0
- package/dist/utils/service/serviceBookingExposureUtils.d.ts +111 -0
- package/dist/utils/service/serviceBookingExposureUtils.d.ts.map +1 -0
- package/dist/utils/service/serviceBookingExposureUtils.js +133 -0
- package/dist/utils/service/serviceBookingExposureUtils.js.map +1 -0
- package/dist/utils/service/serviceBookingTypes.d.ts +1 -1
- package/dist/utils/service/serviceBookingTypes.d.ts.map +1 -1
- package/dist/utils/service/serviceUtils.d.ts +2 -1
- package/dist/utils/service/serviceUtils.d.ts.map +1 -1
- package/dist/utils/service/serviceUtils.js +17 -16
- package/dist/utils/service/serviceUtils.js.map +1 -1
- package/dist/utils/service/unifiedPricingResolver.d.ts +67 -0
- package/dist/utils/service/unifiedPricingResolver.d.ts.map +1 -0
- package/dist/utils/service/unifiedPricingResolver.js +75 -0
- package/dist/utils/service/unifiedPricingResolver.js.map +1 -0
- package/dist/utils/service/venueFBSettlementUtils.d.ts +122 -0
- package/dist/utils/service/venueFBSettlementUtils.d.ts.map +1 -0
- package/dist/utils/service/venueFBSettlementUtils.js +106 -0
- package/dist/utils/service/venueFBSettlementUtils.js.map +1 -0
- package/dist/utils/service/venueSettlementUtils.d.ts +107 -0
- package/dist/utils/service/venueSettlementUtils.d.ts.map +1 -0
- package/dist/utils/service/venueSettlementUtils.js +117 -0
- package/dist/utils/service/venueSettlementUtils.js.map +1 -0
- package/dist/utils/service/volunteerCompensationUtils.d.ts +75 -0
- package/dist/utils/service/volunteerCompensationUtils.d.ts.map +1 -0
- package/dist/utils/service/volunteerCompensationUtils.js +88 -0
- package/dist/utils/service/volunteerCompensationUtils.js.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts +2 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js +67 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts +2 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js +71 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js.map +1 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.d.ts +33 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.js +49 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.js.map +1 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts +35 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.js +74 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.js.map +1 -0
- package/dist/venueLoyaltyRedemption.d.ts +146 -1
- package/dist/venueLoyaltyRedemption.d.ts.map +1 -1
- package/dist/venueLoyaltyRedemption.js +18 -2
- package/dist/venueLoyaltyRedemption.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +609 -214
- package/src/__tests__/statusEnums.test.ts +119 -11
- package/src/definitions.ts +57 -14
- package/src/extendedSchemas.ts +6 -0
- package/src/index.ts +16 -6
- package/src/mirroredPrismaEnums.ts +181 -0
- package/src/utils/__tests__/nonprofitUtils.test.ts +117 -0
- package/src/utils/__tests__/paymentUtils.test.ts +93 -0
- package/src/utils/nonprofitUtils.ts +44 -0
- package/src/utils/paymentUtils.ts +33 -7
- package/src/utils/service/__tests__/billingCapUtils.test.ts +175 -0
- package/src/utils/service/__tests__/cancellationPolicyRefundResolver.test.ts +1 -1
- package/src/utils/service/__tests__/eventPromoterPhase3.test.ts +296 -0
- package/src/utils/service/__tests__/eventPromoterUtils.test.ts +396 -0
- package/src/utils/service/__tests__/offerAllowListUtils.test.ts +76 -0
- package/src/utils/service/__tests__/offerComparisonUtils.test.ts +124 -0
- package/src/utils/service/__tests__/serviceBookingExposureUtils.test.ts +171 -0
- package/src/utils/service/__tests__/unifiedPricingResolver.test.ts +83 -0
- package/src/utils/service/__tests__/venueSettlementUtils.test.ts +219 -0
- package/src/utils/service/__tests__/volunteerCompensationUtils.test.ts +172 -0
- package/src/utils/service/apiServiceBookingApiUtils.ts +4 -0
- package/src/utils/service/billingCapUtils.ts +145 -0
- package/src/utils/service/cancellationPolicyRefundResolver.ts +1 -1
- package/src/utils/service/eventPromoterUtils.ts +644 -0
- package/src/utils/service/offerAllowListUtils.ts +81 -0
- package/src/utils/service/offerComparisonUtils.ts +197 -0
- package/src/utils/service/serviceBookingExposureUtils.ts +218 -0
- package/src/utils/service/serviceBookingTypes.ts +4 -0
- package/src/utils/service/serviceUtils.ts +18 -21
- package/src/utils/service/unifiedPricingResolver.ts +149 -0
- package/src/utils/service/venueSettlementUtils.ts +198 -0
- package/src/utils/service/volunteerCompensationUtils.ts +145 -0
- package/src/utils/venueLoyalty/__tests__/eligibilityUtils.test.ts +103 -0
- package/src/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.ts +92 -0
- package/src/utils/venueLoyalty/eligibilityUtils.ts +84 -0
- package/src/utils/venueLoyalty/redemptionCodeUtils.ts +94 -0
- package/src/venueLoyaltyRedemption.ts +177 -2
|
@@ -147,6 +147,99 @@ describe("PaymentUtils - Core Functions", () => {
|
|
|
147
147
|
expect(r.applicationFeeAmountCents).toBe(r.stripeProcessingCents + 300);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
test("computeTicketApplicationFeeAmountCents — explicit isVerifiedNonprofitHost: false matches default", () => {
|
|
151
|
+
const baseArgs = {
|
|
152
|
+
discountedSubtotalCents: 10000,
|
|
153
|
+
platformFeeEnabled: true,
|
|
154
|
+
platformFeeRate: 0.03,
|
|
155
|
+
feeHandling: "HostAbsorbs" as const,
|
|
156
|
+
bashPassWaivesGuestFee: false,
|
|
157
|
+
};
|
|
158
|
+
const defaulted = computeTicketApplicationFeeAmountCents(baseArgs);
|
|
159
|
+
const explicitFalse = computeTicketApplicationFeeAmountCents({
|
|
160
|
+
...baseArgs,
|
|
161
|
+
isVerifiedNonprofitHost: false,
|
|
162
|
+
});
|
|
163
|
+
expect(explicitFalse).toEqual(defaulted);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("computeTicketApplicationFeeAmountCents — verified nonprofit host: zero Bash platform, Stripe untouched", () => {
|
|
167
|
+
const r = computeTicketApplicationFeeAmountCents({
|
|
168
|
+
discountedSubtotalCents: 10000,
|
|
169
|
+
platformFeeEnabled: true,
|
|
170
|
+
platformFeeRate: 0.03,
|
|
171
|
+
feeHandling: "GuestPays",
|
|
172
|
+
bashPassWaivesGuestFee: false,
|
|
173
|
+
isVerifiedNonprofitHost: true,
|
|
174
|
+
});
|
|
175
|
+
expect(r.bashPlatformTotalCents).toBe(0);
|
|
176
|
+
expect(r.guestPlatformLineCents).toBe(0);
|
|
177
|
+
expect(r.totalChargedCents).toBe(10000);
|
|
178
|
+
expect(r.stripeProcessingCents).toBe(calculateStripeProcessingFeeCents(10000));
|
|
179
|
+
expect(r.applicationFeeAmountCents).toBe(r.stripeProcessingCents);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("computeTicketApplicationFeeAmountCents — verified nonprofit waiver overrides feeHandling=GuestPays", () => {
|
|
183
|
+
const nonNonprofit = computeTicketApplicationFeeAmountCents({
|
|
184
|
+
discountedSubtotalCents: 5000,
|
|
185
|
+
platformFeeEnabled: true,
|
|
186
|
+
platformFeeRate: 0.05,
|
|
187
|
+
feeHandling: "GuestPays",
|
|
188
|
+
bashPassWaivesGuestFee: false,
|
|
189
|
+
});
|
|
190
|
+
expect(nonNonprofit.bashPlatformTotalCents).toBe(250);
|
|
191
|
+
expect(nonNonprofit.guestPlatformLineCents).toBe(250);
|
|
192
|
+
expect(nonNonprofit.totalChargedCents).toBe(5250);
|
|
193
|
+
|
|
194
|
+
const nonprofit = computeTicketApplicationFeeAmountCents({
|
|
195
|
+
discountedSubtotalCents: 5000,
|
|
196
|
+
platformFeeEnabled: true,
|
|
197
|
+
platformFeeRate: 0.05,
|
|
198
|
+
feeHandling: "GuestPays",
|
|
199
|
+
bashPassWaivesGuestFee: false,
|
|
200
|
+
isVerifiedNonprofitHost: true,
|
|
201
|
+
});
|
|
202
|
+
expect(nonprofit.bashPlatformTotalCents).toBe(0);
|
|
203
|
+
expect(nonprofit.guestPlatformLineCents).toBe(0);
|
|
204
|
+
expect(nonprofit.totalChargedCents).toBe(5000);
|
|
205
|
+
expect(nonprofit.applicationFeeAmountCents).toBe(nonprofit.stripeProcessingCents);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("computeTicketApplicationFeeAmountCents — verified nonprofit waiver beats any membership-tier math", () => {
|
|
209
|
+
const tierCases = [
|
|
210
|
+
{ feeHandling: "HostAbsorbs" as const, bashPassWaivesGuestFee: false },
|
|
211
|
+
{ feeHandling: "Split" as const, bashPassWaivesGuestFee: false },
|
|
212
|
+
{ feeHandling: "GuestPays" as const, bashPassWaivesGuestFee: true },
|
|
213
|
+
];
|
|
214
|
+
for (const tier of tierCases) {
|
|
215
|
+
const r = computeTicketApplicationFeeAmountCents({
|
|
216
|
+
discountedSubtotalCents: 12345,
|
|
217
|
+
platformFeeEnabled: true,
|
|
218
|
+
platformFeeRate: 0.04,
|
|
219
|
+
feeHandling: tier.feeHandling,
|
|
220
|
+
bashPassWaivesGuestFee: tier.bashPassWaivesGuestFee,
|
|
221
|
+
isVerifiedNonprofitHost: true,
|
|
222
|
+
});
|
|
223
|
+
expect(r.bashPlatformTotalCents).toBe(0);
|
|
224
|
+
expect(r.guestPlatformLineCents).toBe(0);
|
|
225
|
+
expect(r.totalChargedCents).toBe(12345);
|
|
226
|
+
expect(r.applicationFeeAmountCents).toBe(r.stripeProcessingCents);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("computeTicketApplicationFeeAmountCents — verified nonprofit + platformFeeEnabled=false still zeroes the take", () => {
|
|
231
|
+
const r = computeTicketApplicationFeeAmountCents({
|
|
232
|
+
discountedSubtotalCents: 2500,
|
|
233
|
+
platformFeeEnabled: false,
|
|
234
|
+
platformFeeRate: 0.03,
|
|
235
|
+
feeHandling: "HostAbsorbs",
|
|
236
|
+
bashPassWaivesGuestFee: false,
|
|
237
|
+
isVerifiedNonprofitHost: true,
|
|
238
|
+
});
|
|
239
|
+
expect(r.bashPlatformTotalCents).toBe(0);
|
|
240
|
+
expect(r.applicationFeeAmountCents).toBe(r.stripeProcessingCents);
|
|
241
|
+
});
|
|
242
|
+
|
|
150
243
|
test("userHasActiveBashPass", () => {
|
|
151
244
|
expect(userHasActiveBashPass({ bashPassStripeSubscriptionId: null, bashPassCurrentPeriodEnd: null })).toBe(false);
|
|
152
245
|
expect(userHasActiveBashPass({ bashPassStripeSubscriptionId: "sub_1", bashPassCurrentPeriodEnd: null })).toBe(true);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Organization } from "@prisma/client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Subset of `Organization` fields the verified-nonprofit helpers need. Callers
|
|
5
|
+
* may pass any prisma payload that selects (or includes) at least these
|
|
6
|
+
* columns — keeps consumers free to use narrow `select` shapes.
|
|
7
|
+
*/
|
|
8
|
+
export type NonprofitVerificationFields = Pick<
|
|
9
|
+
Organization,
|
|
10
|
+
| "nonprofitCountry"
|
|
11
|
+
| "nonprofitRegistrationNumber"
|
|
12
|
+
| "nonprofitVerifiedAt"
|
|
13
|
+
| "stripeNonprofitPricingStatus"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Verified-nonprofit status is determined solely by an admin stamp:
|
|
18
|
+
* `nonprofitVerifiedAt` is set (in the past or now — future timestamps do
|
|
19
|
+
* NOT count) AND we captured `nonprofitCountry` + `nonprofitRegistrationNumber`.
|
|
20
|
+
* The Stripe nonprofit-pricing status is informational only and DOES NOT gate
|
|
21
|
+
* verification (a charity can be verified by Bash even before applying to
|
|
22
|
+
* Stripe).
|
|
23
|
+
*
|
|
24
|
+
* IMPORTANT: `BashEvent.nonProfit` is a user-entered flag and MUST NOT be
|
|
25
|
+
* passed in here. Fee-waiver code reads only the Organization's verification
|
|
26
|
+
* fields — see Phase 3 of the donation-followups-leak-fix plan.
|
|
27
|
+
*/
|
|
28
|
+
export function isVerifiedNonprofitOrganization(
|
|
29
|
+
org: NonprofitVerificationFields | null | undefined
|
|
30
|
+
): boolean {
|
|
31
|
+
if (!org) return false;
|
|
32
|
+
if (!org.nonprofitVerifiedAt) return false;
|
|
33
|
+
if (org.nonprofitVerifiedAt.getTime() > Date.now()) return false;
|
|
34
|
+
if (!org.nonprofitCountry || org.nonprofitCountry.trim().length === 0) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (
|
|
38
|
+
!org.nonprofitRegistrationNumber ||
|
|
39
|
+
org.nonprofitRegistrationNumber.trim().length === 0
|
|
40
|
+
) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
@@ -275,26 +275,52 @@ export function splitBashPlatformFeeForTicketCheckout(args: {
|
|
|
275
275
|
return { totalPlatformCents: total, guestPlatformLineCents: guestLine };
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Compute the Stripe Connect `application_fee_amount` for a ticket-style charge,
|
|
280
|
+
* along with its constituent parts.
|
|
281
|
+
*
|
|
282
|
+
* `isVerifiedNonprofitHost` waives ONLY Bash's platform take
|
|
283
|
+
* (`bashPlatformTotalCents`) — Stripe's pass-through (2.9% + $0.30) is untouched
|
|
284
|
+
* because that's not ours to waive. The host applies for Stripe nonprofit
|
|
285
|
+
* pricing directly. When `true`:
|
|
286
|
+
* - `bashPlatformTotalCents` and `guestPlatformLineCents` are both zero
|
|
287
|
+
* - `totalChargedCents` collapses to the discounted subtotal (no guest-line fee added)
|
|
288
|
+
* - `applicationFeeAmountCents` equals `stripeProcessingCents`
|
|
289
|
+
*
|
|
290
|
+
* Default (`false`) preserves existing math bit-exact for every current caller.
|
|
291
|
+
*/
|
|
278
292
|
export function computeTicketApplicationFeeAmountCents(args: {
|
|
279
293
|
discountedSubtotalCents: number;
|
|
280
294
|
platformFeeEnabled: boolean;
|
|
281
295
|
platformFeeRate: number;
|
|
282
296
|
feeHandling: string | null | undefined;
|
|
283
297
|
bashPassWaivesGuestFee: boolean;
|
|
298
|
+
/**
|
|
299
|
+
* Verified-nonprofit hosts pay no Bash platform fee. Sourced from
|
|
300
|
+
* `isVerifiedNonprofitOrganization()` over the host's Organization row —
|
|
301
|
+
* NEVER from `BashEvent.nonProfit`, which is user-entered.
|
|
302
|
+
*/
|
|
303
|
+
isVerifiedNonprofitHost?: boolean;
|
|
284
304
|
}): {
|
|
285
305
|
totalChargedCents: number;
|
|
306
|
+
/** Stripe's 2.9% + $0.30 pass-through; unchanged by the nonprofit waiver. */
|
|
286
307
|
stripeProcessingCents: number;
|
|
308
|
+
/** Bash's platform take. Zero when `isVerifiedNonprofitHost` is true. */
|
|
287
309
|
bashPlatformTotalCents: number;
|
|
288
310
|
applicationFeeAmountCents: number;
|
|
311
|
+
/** Guest-visible portion of the platform line. Zero when `isVerifiedNonprofitHost` is true. */
|
|
289
312
|
guestPlatformLineCents: number;
|
|
290
313
|
} {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
314
|
+
const isVerifiedNonprofitHost = args.isVerifiedNonprofitHost === true;
|
|
315
|
+
const split = isVerifiedNonprofitHost
|
|
316
|
+
? { totalPlatformCents: 0, guestPlatformLineCents: 0 }
|
|
317
|
+
: splitBashPlatformFeeForTicketCheckout({
|
|
318
|
+
grossCents: args.discountedSubtotalCents,
|
|
319
|
+
platformFeeEnabled: args.platformFeeEnabled,
|
|
320
|
+
platformFeeRate: args.platformFeeRate,
|
|
321
|
+
feeHandling: args.feeHandling,
|
|
322
|
+
bashPassWaivesGuestFee: args.bashPassWaivesGuestFee,
|
|
323
|
+
});
|
|
298
324
|
const guestPlatformLineCents = split.guestPlatformLineCents;
|
|
299
325
|
const bashPlatformTotalCents = split.totalPlatformCents;
|
|
300
326
|
const totalChargedCents = args.discountedSubtotalCents + guestPlatformLineCents;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MAX_BILLABLE_MULTIPLIER,
|
|
3
|
+
DEFAULT_MAX_BILLABLE_ROUNDING_CENTS,
|
|
4
|
+
DEFAULT_VARIABLE_PAYOUT_TIMING_NOTE,
|
|
5
|
+
defaultMaxBillableBashPoints,
|
|
6
|
+
defaultMaxBillableCents,
|
|
7
|
+
isFlatPricingModel,
|
|
8
|
+
} from "../billingCapUtils";
|
|
9
|
+
|
|
10
|
+
describe("defaultMaxBillableCents", () => {
|
|
11
|
+
it("computes 1.5x of expectedAttendees * baseRateCents, rounded up to $10", () => {
|
|
12
|
+
// 200 * $5 * 1.5 = $1500 — already a $10 multiple.
|
|
13
|
+
expect(
|
|
14
|
+
defaultMaxBillableCents({ expectedAttendees: 200, baseRateCents: 500 })
|
|
15
|
+
).toBe(150_000);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("rounds the cap UP to the nearest $10", () => {
|
|
19
|
+
// 217 * 500 * 1.5 = 162,750 cents = $1,627.50 → round up to $1,630.
|
|
20
|
+
expect(
|
|
21
|
+
defaultMaxBillableCents({ expectedAttendees: 217, baseRateCents: 500 })
|
|
22
|
+
).toBe(163_000);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns 0 when expectedAttendees is zero", () => {
|
|
26
|
+
expect(
|
|
27
|
+
defaultMaxBillableCents({ expectedAttendees: 0, baseRateCents: 500 })
|
|
28
|
+
).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns 0 when expectedAttendees is null or undefined", () => {
|
|
32
|
+
expect(
|
|
33
|
+
defaultMaxBillableCents({ expectedAttendees: null, baseRateCents: 500 })
|
|
34
|
+
).toBe(0);
|
|
35
|
+
expect(
|
|
36
|
+
defaultMaxBillableCents({
|
|
37
|
+
expectedAttendees: undefined,
|
|
38
|
+
baseRateCents: 500,
|
|
39
|
+
})
|
|
40
|
+
).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("returns null when baseRateCents is missing or zero", () => {
|
|
44
|
+
expect(
|
|
45
|
+
defaultMaxBillableCents({ expectedAttendees: 100, baseRateCents: null })
|
|
46
|
+
).toBeNull();
|
|
47
|
+
expect(
|
|
48
|
+
defaultMaxBillableCents({ expectedAttendees: 100, baseRateCents: 0 })
|
|
49
|
+
).toBeNull();
|
|
50
|
+
expect(
|
|
51
|
+
defaultMaxBillableCents({
|
|
52
|
+
expectedAttendees: 100,
|
|
53
|
+
baseRateCents: undefined,
|
|
54
|
+
})
|
|
55
|
+
).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("clamps negative expectedAttendees to zero", () => {
|
|
59
|
+
expect(
|
|
60
|
+
defaultMaxBillableCents({ expectedAttendees: -50, baseRateCents: 500 })
|
|
61
|
+
).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("respects an explicit multiplier override", () => {
|
|
65
|
+
// 100 * 500 * 2.0 = 100,000 → $1,000.
|
|
66
|
+
expect(
|
|
67
|
+
defaultMaxBillableCents({
|
|
68
|
+
expectedAttendees: 100,
|
|
69
|
+
baseRateCents: 500,
|
|
70
|
+
multiplier: 2,
|
|
71
|
+
})
|
|
72
|
+
).toBe(100_000);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("falls back to default multiplier when override is invalid", () => {
|
|
76
|
+
const out = defaultMaxBillableCents({
|
|
77
|
+
expectedAttendees: 100,
|
|
78
|
+
baseRateCents: 500,
|
|
79
|
+
multiplier: 0,
|
|
80
|
+
});
|
|
81
|
+
expect(out).toBe(100 * 500 * DEFAULT_MAX_BILLABLE_MULTIPLIER);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("respects a custom rounding granularity", () => {
|
|
85
|
+
// 200 * 500 * 1.5 = 150,000 cents = $1,500 — already a $100 multiple.
|
|
86
|
+
expect(
|
|
87
|
+
defaultMaxBillableCents({
|
|
88
|
+
expectedAttendees: 200,
|
|
89
|
+
baseRateCents: 500,
|
|
90
|
+
roundingCents: 10_000,
|
|
91
|
+
})
|
|
92
|
+
).toBe(150_000);
|
|
93
|
+
// 101 * 500 * 1.5 = 75,750 cents = $757.50 → round up to nearest $100 = $800 = 80_000 cents.
|
|
94
|
+
expect(
|
|
95
|
+
defaultMaxBillableCents({
|
|
96
|
+
expectedAttendees: 101,
|
|
97
|
+
baseRateCents: 500,
|
|
98
|
+
roundingCents: 10_000,
|
|
99
|
+
})
|
|
100
|
+
).toBe(80_000);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("uses the platform default rounding when override is invalid", () => {
|
|
104
|
+
const out = defaultMaxBillableCents({
|
|
105
|
+
expectedAttendees: 217,
|
|
106
|
+
baseRateCents: 500,
|
|
107
|
+
roundingCents: 0,
|
|
108
|
+
});
|
|
109
|
+
// Falls back to DEFAULT rounding ($10) → same as the rounded case above.
|
|
110
|
+
expect(out).toBe(
|
|
111
|
+
Math.ceil((217 * 500 * 1.5) / DEFAULT_MAX_BILLABLE_ROUNDING_CENTS) *
|
|
112
|
+
DEFAULT_MAX_BILLABLE_ROUNDING_CENTS
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("defaultMaxBillableBashPoints", () => {
|
|
118
|
+
it("computes the BP parallel of the USD cap, rounded up to nearest 100 BP", () => {
|
|
119
|
+
// 100 * 50 * 1.5 = 7,500 — already a 100-multiple.
|
|
120
|
+
expect(
|
|
121
|
+
defaultMaxBillableBashPoints({
|
|
122
|
+
expectedAttendees: 100,
|
|
123
|
+
baseRateBashPoints: 50,
|
|
124
|
+
})
|
|
125
|
+
).toBe(7_500);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("rounds up to the nearest 100 BashPoints", () => {
|
|
129
|
+
// 33 * 50 * 1.5 = 2,475 → round up to 2,500.
|
|
130
|
+
expect(
|
|
131
|
+
defaultMaxBillableBashPoints({
|
|
132
|
+
expectedAttendees: 33,
|
|
133
|
+
baseRateBashPoints: 50,
|
|
134
|
+
})
|
|
135
|
+
).toBe(2_500);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("returns null when baseRateBashPoints is missing", () => {
|
|
139
|
+
expect(
|
|
140
|
+
defaultMaxBillableBashPoints({
|
|
141
|
+
expectedAttendees: 100,
|
|
142
|
+
baseRateBashPoints: null,
|
|
143
|
+
})
|
|
144
|
+
).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("isFlatPricingModel", () => {
|
|
149
|
+
it("recognizes the three flat models", () => {
|
|
150
|
+
expect(isFlatPricingModel("FLAT_FEE")).toBe(true);
|
|
151
|
+
expect(isFlatPricingModel("HOURLY")).toBe(true);
|
|
152
|
+
expect(isFlatPricingModel("PER_DAY")).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("returns false for variable models", () => {
|
|
156
|
+
expect(isFlatPricingModel("PER_ATTENDEE")).toBe(false);
|
|
157
|
+
expect(isFlatPricingModel("PER_PAID_ATTENDEE")).toBe(false);
|
|
158
|
+
expect(isFlatPricingModel("PER_BASHPOINTS_ATTENDEE")).toBe(false);
|
|
159
|
+
expect(isFlatPricingModel("TICKET_REVENUE_PERCENTAGE")).toBe(false);
|
|
160
|
+
expect(isFlatPricingModel("TIERED")).toBe(false);
|
|
161
|
+
expect(isFlatPricingModel("CUSTOM")).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("returns false for null / undefined / empty", () => {
|
|
165
|
+
expect(isFlatPricingModel(null)).toBe(false);
|
|
166
|
+
expect(isFlatPricingModel(undefined)).toBe(false);
|
|
167
|
+
expect(isFlatPricingModel("")).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("DEFAULT_VARIABLE_PAYOUT_TIMING_NOTE", () => {
|
|
172
|
+
it("mentions the ~3 business day window", () => {
|
|
173
|
+
expect(DEFAULT_VARIABLE_PAYOUT_TIMING_NOTE).toMatch(/3 business days/i);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 3 of the unified-pricing roadmap — tests for the
|
|
3
|
+
* - counting basis chooser,
|
|
4
|
+
* - locked-models set / validator update,
|
|
5
|
+
* - mixed USD + BashPoints settlement math,
|
|
6
|
+
* - BashPoints payout source resolver (pool-then-wallet).
|
|
7
|
+
*
|
|
8
|
+
* Lives next to `eventPromoterUtils.test.ts` so it picks up the same Jest
|
|
9
|
+
* config; pure-function-only, no Prisma.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, it } from "@jest/globals";
|
|
12
|
+
import {
|
|
13
|
+
EVENT_PROMOTER_LOCKED_PRICING_MODELS,
|
|
14
|
+
computeEventPromoterMixedSettlement,
|
|
15
|
+
eventPromoterCountingBasisFor,
|
|
16
|
+
pickCheckedInForBasis,
|
|
17
|
+
resolveBashPointsPayoutSources,
|
|
18
|
+
validateEventPromoterPricing,
|
|
19
|
+
} from "../eventPromoterUtils.js";
|
|
20
|
+
|
|
21
|
+
describe("EVENT_PROMOTER_LOCKED_PRICING_MODELS", () => {
|
|
22
|
+
it("contains PER_ATTENDEE, PER_PAID_ATTENDEE, PER_BASHPOINTS_ATTENDEE in that order", () => {
|
|
23
|
+
expect([...EVENT_PROMOTER_LOCKED_PRICING_MODELS]).toEqual([
|
|
24
|
+
"PER_ATTENDEE",
|
|
25
|
+
"PER_PAID_ATTENDEE",
|
|
26
|
+
"PER_BASHPOINTS_ATTENDEE",
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("validateEventPromoterPricing (Phase 3 model set)", () => {
|
|
32
|
+
it("accepts PER_ATTENDEE for Promoter subtype", () => {
|
|
33
|
+
expect(
|
|
34
|
+
validateEventPromoterPricing({
|
|
35
|
+
eventServiceSubType: "Promoter",
|
|
36
|
+
pricingModel: "PER_ATTENDEE",
|
|
37
|
+
}).ok
|
|
38
|
+
).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it("accepts PER_PAID_ATTENDEE for Promoter subtype", () => {
|
|
41
|
+
expect(
|
|
42
|
+
validateEventPromoterPricing({
|
|
43
|
+
eventServiceSubType: "Promoter",
|
|
44
|
+
pricingModel: "PER_PAID_ATTENDEE",
|
|
45
|
+
}).ok
|
|
46
|
+
).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it("accepts PER_BASHPOINTS_ATTENDEE for Promoter subtype", () => {
|
|
49
|
+
expect(
|
|
50
|
+
validateEventPromoterPricing({
|
|
51
|
+
eventServiceSubType: "Promoter",
|
|
52
|
+
pricingModel: "PER_BASHPOINTS_ATTENDEE",
|
|
53
|
+
}).ok
|
|
54
|
+
).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it("rejects FLAT_FEE for Promoter subtype", () => {
|
|
57
|
+
const r = validateEventPromoterPricing({
|
|
58
|
+
eventServiceSubType: "Promoter",
|
|
59
|
+
pricingModel: "FLAT_FEE",
|
|
60
|
+
});
|
|
61
|
+
expect(r.ok).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("eventPromoterCountingBasisFor", () => {
|
|
66
|
+
it("PER_ATTENDEE → AllCheckedIn", () => {
|
|
67
|
+
expect(
|
|
68
|
+
eventPromoterCountingBasisFor({ pricingModel: "PER_ATTENDEE" })
|
|
69
|
+
).toBe("AllCheckedIn");
|
|
70
|
+
});
|
|
71
|
+
it("PER_PAID_ATTENDEE without BP knob → PaidUsdOnly", () => {
|
|
72
|
+
expect(
|
|
73
|
+
eventPromoterCountingBasisFor({
|
|
74
|
+
pricingModel: "PER_PAID_ATTENDEE",
|
|
75
|
+
countsBashPointsTickets: false,
|
|
76
|
+
})
|
|
77
|
+
).toBe("PaidUsdOnly");
|
|
78
|
+
});
|
|
79
|
+
it("PER_PAID_ATTENDEE with countsBashPointsTickets → PaidAny", () => {
|
|
80
|
+
expect(
|
|
81
|
+
eventPromoterCountingBasisFor({
|
|
82
|
+
pricingModel: "PER_PAID_ATTENDEE",
|
|
83
|
+
countsBashPointsTickets: true,
|
|
84
|
+
})
|
|
85
|
+
).toBe("PaidAny");
|
|
86
|
+
});
|
|
87
|
+
it("PER_BASHPOINTS_ATTENDEE → PaidBpOnly regardless of knob", () => {
|
|
88
|
+
expect(
|
|
89
|
+
eventPromoterCountingBasisFor({
|
|
90
|
+
pricingModel: "PER_BASHPOINTS_ATTENDEE",
|
|
91
|
+
countsBashPointsTickets: true,
|
|
92
|
+
})
|
|
93
|
+
).toBe("PaidBpOnly");
|
|
94
|
+
});
|
|
95
|
+
it("unknown / null pricing → AllCheckedIn (legacy fallback)", () => {
|
|
96
|
+
expect(eventPromoterCountingBasisFor({ pricingModel: null })).toBe(
|
|
97
|
+
"AllCheckedIn"
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("pickCheckedInForBasis", () => {
|
|
103
|
+
const SNAPS = {
|
|
104
|
+
totalCheckedInAttendees: 220,
|
|
105
|
+
paidAttendeesAtSettle: 180,
|
|
106
|
+
paidInBashPointsAttendeesAtSettle: 30,
|
|
107
|
+
};
|
|
108
|
+
it("AllCheckedIn → total", () => {
|
|
109
|
+
expect(pickCheckedInForBasis("AllCheckedIn", SNAPS)).toBe(220);
|
|
110
|
+
});
|
|
111
|
+
it("PaidUsdOnly → paid USD", () => {
|
|
112
|
+
expect(pickCheckedInForBasis("PaidUsdOnly", SNAPS)).toBe(180);
|
|
113
|
+
});
|
|
114
|
+
it("PaidBpOnly → paid BP", () => {
|
|
115
|
+
expect(pickCheckedInForBasis("PaidBpOnly", SNAPS)).toBe(30);
|
|
116
|
+
});
|
|
117
|
+
it("PaidAny → USD + BP", () => {
|
|
118
|
+
expect(pickCheckedInForBasis("PaidAny", SNAPS)).toBe(210);
|
|
119
|
+
});
|
|
120
|
+
it("treats missing snapshots as zero", () => {
|
|
121
|
+
expect(
|
|
122
|
+
pickCheckedInForBasis("PaidUsdOnly", {
|
|
123
|
+
totalCheckedInAttendees: 100,
|
|
124
|
+
})
|
|
125
|
+
).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("computeEventPromoterMixedSettlement", () => {
|
|
130
|
+
const baseSnaps = {
|
|
131
|
+
totalCheckedInAttendees: 250,
|
|
132
|
+
paidAttendeesAtSettle: 200,
|
|
133
|
+
paidInBashPointsAttendeesAtSettle: 50,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
it("computes a positive USD delta when more USD attendees show up than expected", () => {
|
|
137
|
+
const r = computeEventPromoterMixedSettlement({
|
|
138
|
+
baseRateCents: 500,
|
|
139
|
+
expectedPaidAttendeesAtBooking: 150,
|
|
140
|
+
snapshots: baseSnaps,
|
|
141
|
+
});
|
|
142
|
+
expect(r.usdTotalCents).toBe(200 * 500);
|
|
143
|
+
expect(r.usdDeltaCents).toBe((200 - 150) * 500);
|
|
144
|
+
expect(r.cappedUsd).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("respects the USD ceiling (maxUsdAttendeesAttributable)", () => {
|
|
148
|
+
const r = computeEventPromoterMixedSettlement({
|
|
149
|
+
baseRateCents: 500,
|
|
150
|
+
expectedPaidAttendeesAtBooking: 150,
|
|
151
|
+
maxUsdAttendeesAttributable: 175,
|
|
152
|
+
snapshots: baseSnaps,
|
|
153
|
+
});
|
|
154
|
+
expect(r.usdTotalCents).toBe(175 * 500);
|
|
155
|
+
expect(r.cappedUsd).toBe(false); // ceiling, not dollar cap
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("flags cappedUsd when maxBillableCents bites", () => {
|
|
159
|
+
const r = computeEventPromoterMixedSettlement({
|
|
160
|
+
baseRateCents: 500,
|
|
161
|
+
expectedPaidAttendeesAtBooking: 150,
|
|
162
|
+
maxBillableCents: 80_000, // < 200 * 500 = 100_000
|
|
163
|
+
snapshots: baseSnaps,
|
|
164
|
+
});
|
|
165
|
+
expect(r.usdTotalCents).toBe(80_000);
|
|
166
|
+
expect(r.cappedUsd).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("computes the BP side independently from USD", () => {
|
|
170
|
+
const r = computeEventPromoterMixedSettlement({
|
|
171
|
+
baseRateBashPoints: 10,
|
|
172
|
+
expectedBpAttendeesAtBooking: 30,
|
|
173
|
+
snapshots: baseSnaps,
|
|
174
|
+
});
|
|
175
|
+
expect(r.usdTotalCents).toBe(0);
|
|
176
|
+
expect(r.bpTotal).toBe(50 * 10);
|
|
177
|
+
expect(r.bpDelta).toBe(20 * 10);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("caps BP via maxBillableBashPoints", () => {
|
|
181
|
+
const r = computeEventPromoterMixedSettlement({
|
|
182
|
+
baseRateBashPoints: 10,
|
|
183
|
+
expectedBpAttendeesAtBooking: 30,
|
|
184
|
+
maxBillableBashPoints: 400,
|
|
185
|
+
snapshots: baseSnaps,
|
|
186
|
+
});
|
|
187
|
+
expect(r.bpTotal).toBe(400);
|
|
188
|
+
expect(r.cappedBp).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("returns all zeros when neither side has a rate", () => {
|
|
192
|
+
const r = computeEventPromoterMixedSettlement({
|
|
193
|
+
snapshots: baseSnaps,
|
|
194
|
+
});
|
|
195
|
+
expect(r.usdTotalCents).toBe(0);
|
|
196
|
+
expect(r.usdDeltaCents).toBe(0);
|
|
197
|
+
expect(r.bpTotal).toBe(0);
|
|
198
|
+
expect(r.bpDelta).toBe(0);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("resolveBashPointsPayoutSources", () => {
|
|
203
|
+
it("HostWallet path drains wallet only", () => {
|
|
204
|
+
expect(
|
|
205
|
+
resolveBashPointsPayoutSources({
|
|
206
|
+
source: "HostWallet",
|
|
207
|
+
payoutBp: 100,
|
|
208
|
+
hostWalletBp: 500,
|
|
209
|
+
bashPoolBp: 9999, // ignored
|
|
210
|
+
})
|
|
211
|
+
).toEqual({
|
|
212
|
+
fromPoolBp: 0,
|
|
213
|
+
fromWalletBp: 100,
|
|
214
|
+
shortfallBp: 0,
|
|
215
|
+
walletDebited: true,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("HostWallet path with insufficient wallet returns shortfall", () => {
|
|
220
|
+
expect(
|
|
221
|
+
resolveBashPointsPayoutSources({
|
|
222
|
+
source: "HostWallet",
|
|
223
|
+
payoutBp: 100,
|
|
224
|
+
hostWalletBp: 60,
|
|
225
|
+
})
|
|
226
|
+
).toEqual({
|
|
227
|
+
fromPoolBp: 0,
|
|
228
|
+
fromWalletBp: 60,
|
|
229
|
+
shortfallBp: 40,
|
|
230
|
+
walletDebited: true,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("BashBpPool drains pool first, wallet covers the rest", () => {
|
|
235
|
+
expect(
|
|
236
|
+
resolveBashPointsPayoutSources({
|
|
237
|
+
source: "BashBpPool",
|
|
238
|
+
payoutBp: 100,
|
|
239
|
+
hostWalletBp: 500,
|
|
240
|
+
bashPoolBp: 70,
|
|
241
|
+
})
|
|
242
|
+
).toEqual({
|
|
243
|
+
fromPoolBp: 70,
|
|
244
|
+
fromWalletBp: 30,
|
|
245
|
+
shortfallBp: 0,
|
|
246
|
+
walletDebited: true,
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("BashBpPool fully covered by pool leaves wallet untouched", () => {
|
|
251
|
+
expect(
|
|
252
|
+
resolveBashPointsPayoutSources({
|
|
253
|
+
source: "BashBpPool",
|
|
254
|
+
payoutBp: 50,
|
|
255
|
+
hostWalletBp: 500,
|
|
256
|
+
bashPoolBp: 100,
|
|
257
|
+
})
|
|
258
|
+
).toEqual({
|
|
259
|
+
fromPoolBp: 50,
|
|
260
|
+
fromWalletBp: 0,
|
|
261
|
+
shortfallBp: 0,
|
|
262
|
+
walletDebited: false,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("BashBpPool with empty pool falls through to wallet", () => {
|
|
267
|
+
expect(
|
|
268
|
+
resolveBashPointsPayoutSources({
|
|
269
|
+
source: "BashBpPool",
|
|
270
|
+
payoutBp: 100,
|
|
271
|
+
hostWalletBp: 50,
|
|
272
|
+
bashPoolBp: 0,
|
|
273
|
+
})
|
|
274
|
+
).toEqual({
|
|
275
|
+
fromPoolBp: 0,
|
|
276
|
+
fromWalletBp: 50,
|
|
277
|
+
shortfallBp: 50,
|
|
278
|
+
walletDebited: true,
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("clamps negative inputs to zero", () => {
|
|
283
|
+
expect(
|
|
284
|
+
resolveBashPointsPayoutSources({
|
|
285
|
+
source: "HostWallet",
|
|
286
|
+
payoutBp: -10,
|
|
287
|
+
hostWalletBp: -5,
|
|
288
|
+
})
|
|
289
|
+
).toEqual({
|
|
290
|
+
fromPoolBp: 0,
|
|
291
|
+
fromWalletBp: 0,
|
|
292
|
+
shortfallBp: 0,
|
|
293
|
+
walletDebited: false,
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|