@go-avro/avro-js 0.0.48 → 0.0.50
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/billing/fees.d.ts +40 -0
- package/dist/billing/fees.js +155 -0
- package/dist/client/QueryClient.d.ts +0 -21
- package/dist/client/QueryClient.js +0 -18
- package/dist/client/hooks/email.d.ts +0 -28
- package/dist/client/hooks/email.js +0 -25
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types/api/Bill.d.ts +4 -1
- package/dist/types/api/Company.d.ts +1 -0
- package/dist/types/api/LineItem.d.ts +1 -0
- package/dist/types/api/LineItem.js +1 -0
- package/dist/types/api/ProcessingFeeLineItem.d.ts +21 -0
- package/dist/types/api/ProcessingFeeLineItem.js +1 -0
- package/dist/types/api.d.ts +1 -0
- package/dist/types/api.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PaymentType } from '../types/api/PaymentType';
|
|
2
|
+
export interface PassOnFeeBreakdown {
|
|
3
|
+
/** Stripe processing fee on the grossed-up amount, in cents. */
|
|
4
|
+
stripeFee: number;
|
|
5
|
+
/** Avro application fee on the net (gross - stripeFee), in cents. */
|
|
6
|
+
avroFee: number;
|
|
7
|
+
/** Total fee added on top of the subtotal: stripeFee + avroFee. */
|
|
8
|
+
totalFee: number;
|
|
9
|
+
/** Gross amount the customer is charged: subtotal + totalFee. */
|
|
10
|
+
gross: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Compute Stripe's processing fee on a gross amount, in cents.
|
|
14
|
+
* Ceiling matches Stripe's worst-case rounding so the merchant is never short.
|
|
15
|
+
*/
|
|
16
|
+
export declare function computeStripeFee(grossCents: number, method: PaymentType): number;
|
|
17
|
+
/**
|
|
18
|
+
* Compute Avro's application fee on a net amount (gross - stripeFee), in cents.
|
|
19
|
+
*/
|
|
20
|
+
export declare function computeAvroFee(netCents: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* Compute the pass-on fee (in cents) such that, after Stripe and Avro deduct
|
|
23
|
+
* their fees from the gross amount the customer is charged, the merchant
|
|
24
|
+
* receives at least `subtotalCents`.
|
|
25
|
+
*
|
|
26
|
+
* Returns `ZERO_FEE` when `subtotalCents <= 0`.
|
|
27
|
+
*
|
|
28
|
+
* Closed form: a finite set of candidate grosses is derived from each fee
|
|
29
|
+
* regime (avro at the 5¢ floor vs. percentage; ACH stripe at the $5 cap vs.
|
|
30
|
+
* percentage). The smallest candidate that satisfies the invariant
|
|
31
|
+
* `gross - stripeFee(gross) - avroFee(gross - stripeFee(gross)) >= subtotal`
|
|
32
|
+
* is selected. No iteration.
|
|
33
|
+
*/
|
|
34
|
+
export declare function computePassOnFee(subtotalCents: number, method: PaymentType): PassOnFeeBreakdown;
|
|
35
|
+
/**
|
|
36
|
+
* Pick the payment method to use when computing the persisted ("default")
|
|
37
|
+
* fee line item amount on a bill. Card if enabled (worst case for the
|
|
38
|
+
* customer), otherwise ACH, otherwise null (no fee should be persisted).
|
|
39
|
+
*/
|
|
40
|
+
export declare function defaultPassOnFeeMethod(enabledPaymentMethods: PaymentType[] | null | undefined): PaymentType | null;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { PaymentType } from '../types/api/PaymentType';
|
|
2
|
+
/**
|
|
3
|
+
* Pass-on (surcharge) fee calculations.
|
|
4
|
+
*
|
|
5
|
+
* When `pass_on_fees` is enabled on a bill, the customer is charged a gross
|
|
6
|
+
* amount that, after Stripe's processing fees and Avro's application fee, leaves
|
|
7
|
+
* the merchant with the bill's subtotal. The fee added on top of the subtotal
|
|
8
|
+
* is exposed as a single read-only line item on the bill.
|
|
9
|
+
*
|
|
10
|
+
* Stripe US standard pricing (online):
|
|
11
|
+
* - Card: 2.9% of gross + 30¢
|
|
12
|
+
* - ACH (us_bank): 0.8% of gross, capped at $5.00
|
|
13
|
+
* Avro application fee:
|
|
14
|
+
* - 0.4% of net (gross - stripe), minimum 5¢
|
|
15
|
+
*
|
|
16
|
+
* All amounts are integer cents. All math is closed-form (no iteration).
|
|
17
|
+
*
|
|
18
|
+
* The integer-ceiling on the gross-up guarantees the merchant nets at
|
|
19
|
+
* least the requested subtotal. In rare regime-boundary cases the merchant
|
|
20
|
+
* may net a sub-cent more than requested — never less.
|
|
21
|
+
*/
|
|
22
|
+
// --- Rate constants (per 1000 for integer math where applicable) ---
|
|
23
|
+
const CARD_PCT_PER_1000 = 29; // 2.9%
|
|
24
|
+
const CARD_FIXED_CENTS = 30; // 30¢
|
|
25
|
+
const ACH_PCT_PER_1000 = 8; // 0.8%
|
|
26
|
+
const ACH_CAP_CENTS = 500; // $5.00
|
|
27
|
+
const AVRO_PCT_PER_1000 = 4; // 0.4%
|
|
28
|
+
const AVRO_MIN_CENTS = 5; // 5¢
|
|
29
|
+
const ZERO_FEE = {
|
|
30
|
+
stripeFee: 0,
|
|
31
|
+
avroFee: 0,
|
|
32
|
+
totalFee: 0,
|
|
33
|
+
gross: 0,
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Compute Stripe's processing fee on a gross amount, in cents.
|
|
37
|
+
* Ceiling matches Stripe's worst-case rounding so the merchant is never short.
|
|
38
|
+
*/
|
|
39
|
+
export function computeStripeFee(grossCents, method) {
|
|
40
|
+
if (grossCents <= 0)
|
|
41
|
+
return 0;
|
|
42
|
+
if (method === PaymentType.CARD) {
|
|
43
|
+
return Math.ceil((CARD_PCT_PER_1000 * grossCents) / 1000) + CARD_FIXED_CENTS;
|
|
44
|
+
}
|
|
45
|
+
// us_bank_account: percentage capped at $5.00
|
|
46
|
+
return Math.min(Math.ceil((ACH_PCT_PER_1000 * grossCents) / 1000), ACH_CAP_CENTS);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Compute Avro's application fee on a net amount (gross - stripeFee), in cents.
|
|
50
|
+
*/
|
|
51
|
+
export function computeAvroFee(netCents) {
|
|
52
|
+
if (netCents <= 0)
|
|
53
|
+
return AVRO_MIN_CENTS;
|
|
54
|
+
return Math.max(Math.ceil((AVRO_PCT_PER_1000 * netCents) / 1000), AVRO_MIN_CENTS);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Compute the pass-on fee (in cents) such that, after Stripe and Avro deduct
|
|
58
|
+
* their fees from the gross amount the customer is charged, the merchant
|
|
59
|
+
* receives at least `subtotalCents`.
|
|
60
|
+
*
|
|
61
|
+
* Returns `ZERO_FEE` when `subtotalCents <= 0`.
|
|
62
|
+
*
|
|
63
|
+
* Closed form: a finite set of candidate grosses is derived from each fee
|
|
64
|
+
* regime (avro at the 5¢ floor vs. percentage; ACH stripe at the $5 cap vs.
|
|
65
|
+
* percentage). The smallest candidate that satisfies the invariant
|
|
66
|
+
* `gross - stripeFee(gross) - avroFee(gross - stripeFee(gross)) >= subtotal`
|
|
67
|
+
* is selected. No iteration.
|
|
68
|
+
*/
|
|
69
|
+
export function computePassOnFee(subtotalCents, method) {
|
|
70
|
+
if (subtotalCents <= 0)
|
|
71
|
+
return { ...ZERO_FEE };
|
|
72
|
+
const B = subtotalCents;
|
|
73
|
+
const candidates = method === PaymentType.CARD ? cardCandidates(B) : achCandidates(B);
|
|
74
|
+
let best = Number.POSITIVE_INFINITY;
|
|
75
|
+
for (const g of candidates) {
|
|
76
|
+
const stripe = computeStripeFee(g, method);
|
|
77
|
+
const avro = computeAvroFee(g - stripe);
|
|
78
|
+
if (g - stripe - avro >= B && g < best) {
|
|
79
|
+
best = g;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isFinite(best)) {
|
|
83
|
+
// Should never happen — the candidate set covers every regime — but
|
|
84
|
+
// fall back to a safe overshoot rather than throwing.
|
|
85
|
+
best = B + Math.ceil(B * 0.05) + CARD_FIXED_CENTS + AVRO_MIN_CENTS;
|
|
86
|
+
}
|
|
87
|
+
const stripeFee = computeStripeFee(best, method);
|
|
88
|
+
const avroFee = computeAvroFee(best - stripeFee);
|
|
89
|
+
return {
|
|
90
|
+
stripeFee,
|
|
91
|
+
avroFee,
|
|
92
|
+
totalFee: best - B,
|
|
93
|
+
gross: best,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Card regime candidates.
|
|
98
|
+
*
|
|
99
|
+
* Stripe = ceil(0.029 * G) + 30; avro = max(ceil(0.004 * (G - stripe)), 5).
|
|
100
|
+
*
|
|
101
|
+
* The two `ceil`s can each add up to 1¢ relative to the exact rational form,
|
|
102
|
+
* so each candidate carries a baked-in 2¢ (percentage regime) or 1¢ (floor
|
|
103
|
+
* regime) safety margin to guarantee the invariant holds for every B in
|
|
104
|
+
* integer cents — no post-hoc iteration required.
|
|
105
|
+
*
|
|
106
|
+
* 1) Percentage avro: G * 0.967116 ≥ B + 29.88 + 2
|
|
107
|
+
* => G = ceil((1_000_000 * B + 31_880_000) / 967_116)
|
|
108
|
+
* 2) Floor avro (5¢): G * 0.971 ≥ B + 35 + 1
|
|
109
|
+
* => G = ceil((1000 * B + 36_000) / 971)
|
|
110
|
+
*/
|
|
111
|
+
function cardCandidates(B) {
|
|
112
|
+
return [
|
|
113
|
+
Math.ceil((1000000 * B + 31880000) / 967116),
|
|
114
|
+
Math.ceil((1000 * B + 36000) / 971),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* ACH regime candidates.
|
|
119
|
+
*
|
|
120
|
+
* Stripe = min(ceil(0.008 * G), 500); avro = max(ceil(0.004 * (G - stripe)), 5).
|
|
121
|
+
*
|
|
122
|
+
* Same ceiling-safety logic as card. Stripe at the $5 cap is a fixed integer
|
|
123
|
+
* so its ceiling-slack disappears in the capped regimes.
|
|
124
|
+
*
|
|
125
|
+
* 1) No-cap percentage avro: G * 0.988032 ≥ B + 2
|
|
126
|
+
* => G = ceil((1_000_000 * (B + 2)) / 988_032)
|
|
127
|
+
* 2) No-cap floor avro: G * 0.992 ≥ B + 5 + 1
|
|
128
|
+
* => G = ceil((1000 * (B + 6)) / 992)
|
|
129
|
+
* 3) Capped percentage avro: G * 0.996 ≥ B + 498 + 1
|
|
130
|
+
* => G = ceil((1000 * (B + 499)) / 996)
|
|
131
|
+
* 4) Capped floor avro: G = B + 505
|
|
132
|
+
*/
|
|
133
|
+
function achCandidates(B) {
|
|
134
|
+
return [
|
|
135
|
+
Math.ceil((1000000 * (B + 2)) / 988032),
|
|
136
|
+
Math.ceil((1000 * (B + 6)) / 992),
|
|
137
|
+
Math.ceil((1000 * (B + 499)) / 996),
|
|
138
|
+
B + 505,
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Pick the payment method to use when computing the persisted ("default")
|
|
143
|
+
* fee line item amount on a bill. Card if enabled (worst case for the
|
|
144
|
+
* customer), otherwise ACH, otherwise null (no fee should be persisted).
|
|
145
|
+
*/
|
|
146
|
+
export function defaultPassOnFeeMethod(enabledPaymentMethods) {
|
|
147
|
+
if (!enabledPaymentMethods || enabledPaymentMethods.length === 0)
|
|
148
|
+
return null;
|
|
149
|
+
if (enabledPaymentMethods.includes(PaymentType.CARD))
|
|
150
|
+
return PaymentType.CARD;
|
|
151
|
+
if (enabledPaymentMethods.includes(PaymentType.US_BANK_ACCOUNT)) {
|
|
152
|
+
return PaymentType.US_BANK_ACCOUNT;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
@@ -9,17 +9,9 @@ import { CacheData } from '../types/cache';
|
|
|
9
9
|
import { Waiver } from '../types/api/Waiver';
|
|
10
10
|
import type { EmailSucceededPayload, EmailFailedPayload, EmailType } from '../types/api/EmailNotification';
|
|
11
11
|
import type { BulkDeleteBillsResponse, BulkEmailBillsResponse } from '../client/hooks/bills';
|
|
12
|
-
/** Callbacks for a tracked email request. */
|
|
13
12
|
export interface TrackEmailOptions {
|
|
14
13
|
emailType?: EmailType;
|
|
15
|
-
/**
|
|
16
|
-
* Pre-existing request_id to register. When omitted, a fresh uuid is
|
|
17
|
-
* generated. Pass this when the request_id was produced elsewhere
|
|
18
|
-
* (e.g. a bulk endpoint returns one in its synchronous response, or
|
|
19
|
-
* the caller wants to pre-generate it and ship it in the POST body).
|
|
20
|
-
*/
|
|
21
14
|
requestId?: string;
|
|
22
|
-
/** How long to wait before firing onTimeout (ms). Default 30 000. */
|
|
23
15
|
timeout?: number;
|
|
24
16
|
onSuccess?: (data: EmailSucceededPayload) => void;
|
|
25
17
|
onFailure?: (data: EmailFailedPayload) => void;
|
|
@@ -757,20 +749,7 @@ export declare class AvroQueryClient {
|
|
|
757
749
|
query?: string;
|
|
758
750
|
offset?: number;
|
|
759
751
|
}, cancelToken?: CancelToken, headers?: Record<string, string>): Promise<any>;
|
|
760
|
-
/**
|
|
761
|
-
* Lazily register socket listeners for email_succeeded / email_failed.
|
|
762
|
-
* Listeners live on the socket (not in a React effect) so they survive
|
|
763
|
-
* component unmounts.
|
|
764
|
-
*/
|
|
765
752
|
private _initEmailListeners;
|
|
766
|
-
/**
|
|
767
|
-
* Track an outbound email request.
|
|
768
|
-
*
|
|
769
|
-
* Generates a `request_id`, registers socket listeners (once), and returns
|
|
770
|
-
* the ID so callers can pass it in the HTTP body. The backend emits
|
|
771
|
-
* `email_succeeded` / `email_failed` to the user's GUID room; this method
|
|
772
|
-
* correlates the event by `request_id` and fires the appropriate callback.
|
|
773
|
-
*/
|
|
774
753
|
trackEmail(options?: TrackEmailOptions): string;
|
|
775
754
|
sendEmail(emailId: string, formData: FormData, progressUpdateCallback?: (loaded: number, total: number) => void): Promise<void>;
|
|
776
755
|
sendBillEmail(billId: string, body?: {
|
|
@@ -1100,12 +1100,6 @@ export class AvroQueryClient {
|
|
|
1100
1100
|
throw new StandardError(500, 'Failed to fetch sessions');
|
|
1101
1101
|
});
|
|
1102
1102
|
}
|
|
1103
|
-
/* ── Email delivery tracking ──────────────────────────────────────── */
|
|
1104
|
-
/**
|
|
1105
|
-
* Lazily register socket listeners for email_succeeded / email_failed.
|
|
1106
|
-
* Listeners live on the socket (not in a React effect) so they survive
|
|
1107
|
-
* component unmounts.
|
|
1108
|
-
*/
|
|
1109
1103
|
_initEmailListeners() {
|
|
1110
1104
|
if (this._emailListenersInit)
|
|
1111
1105
|
return;
|
|
@@ -1114,7 +1108,6 @@ export class AvroQueryClient {
|
|
|
1114
1108
|
const entry = this._emailTracking.get(data.request_id);
|
|
1115
1109
|
if (!entry)
|
|
1116
1110
|
return;
|
|
1117
|
-
// Reset timeout — more recipients may follow for the same request_id
|
|
1118
1111
|
clearTimeout(entry.timerId);
|
|
1119
1112
|
entry.timerId = setTimeout(() => {
|
|
1120
1113
|
this._emailTracking.delete(data.request_id);
|
|
@@ -1132,21 +1125,10 @@ export class AvroQueryClient {
|
|
|
1132
1125
|
entry.onFailure?.(data);
|
|
1133
1126
|
});
|
|
1134
1127
|
}
|
|
1135
|
-
/**
|
|
1136
|
-
* Track an outbound email request.
|
|
1137
|
-
*
|
|
1138
|
-
* Generates a `request_id`, registers socket listeners (once), and returns
|
|
1139
|
-
* the ID so callers can pass it in the HTTP body. The backend emits
|
|
1140
|
-
* `email_succeeded` / `email_failed` to the user's GUID room; this method
|
|
1141
|
-
* correlates the event by `request_id` and fires the appropriate callback.
|
|
1142
|
-
*/
|
|
1143
1128
|
trackEmail(options = {}) {
|
|
1144
1129
|
this._initEmailListeners();
|
|
1145
1130
|
const requestId = options.requestId ?? uuidv4();
|
|
1146
1131
|
const { timeout = 30000 } = options;
|
|
1147
|
-
// If this request_id is already being tracked, replace the existing
|
|
1148
|
-
// entry so the latest caller's handlers win. (Callers that reuse a
|
|
1149
|
-
// request_id are explicitly opting in to this behaviour.)
|
|
1150
1132
|
const existing = this._emailTracking.get(requestId);
|
|
1151
1133
|
if (existing) {
|
|
1152
1134
|
clearTimeout(existing.timerId);
|
|
@@ -5,43 +5,15 @@ export interface EmailResult {
|
|
|
5
5
|
status: EmailResultStatus;
|
|
6
6
|
emailType?: EmailType;
|
|
7
7
|
recipient?: string;
|
|
8
|
-
/**
|
|
9
|
-
* Bill GUID when the event came from a bill email — single or bulk.
|
|
10
|
-
* Use this to correlate per-bill outcomes when one `requestId`
|
|
11
|
-
* covers many bills (`POST /company/<id>/bills/bulk`).
|
|
12
|
-
*/
|
|
13
8
|
billGuid?: string;
|
|
14
9
|
error?: EmailFailedPayload['error'];
|
|
15
10
|
}
|
|
16
11
|
export interface UseEmailStatusOptions {
|
|
17
|
-
/** How long to wait for a socket event before firing `onTimeout` (ms). Default 30 000. */
|
|
18
12
|
timeout?: number;
|
|
19
|
-
/** Called when the backend reports success. */
|
|
20
13
|
onSuccess?: (result: EmailResult) => void;
|
|
21
|
-
/** Called when the backend reports failure. */
|
|
22
14
|
onFailure?: (result: EmailResult) => void;
|
|
23
|
-
/** Called when neither success nor failure arrives within the timeout window. */
|
|
24
15
|
onTimeout?: (requestId: string) => void;
|
|
25
16
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Subscribe to backend email delivery notifications via Socket.IO.
|
|
28
|
-
*
|
|
29
|
-
* Delegates to `AvroQueryClient.trackEmail()` which registers socket
|
|
30
|
-
* listeners on the client itself (not in a React effect), so
|
|
31
|
-
* notifications survive component unmounts.
|
|
32
|
-
*
|
|
33
|
-
* Usage:
|
|
34
|
-
* ```ts
|
|
35
|
-
* const { trackEmail, pending, results } = useEmailStatus({
|
|
36
|
-
* onSuccess: (r) => toast.success(`Email sent to ${r.recipient}`),
|
|
37
|
-
* onFailure: (r) => toast.error(`Email failed: ${r.error?.message}`),
|
|
38
|
-
* onTimeout: (id) => toast.warn("Email status unknown"),
|
|
39
|
-
* });
|
|
40
|
-
*
|
|
41
|
-
* const requestId = trackEmail("bill");
|
|
42
|
-
* await avroQueryClient.sendBillingEmail({ billId, request_id: requestId });
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
17
|
export declare function useEmailStatus(options?: UseEmailStatusOptions): {
|
|
46
18
|
readonly trackEmail: (emailType?: EmailType, opts?: {
|
|
47
19
|
requestId?: string;
|
|
@@ -1,41 +1,16 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from 'react';
|
|
2
2
|
import { useAvroQueryClient } from '../../client/AvroQueryClientProvider';
|
|
3
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
4
|
-
/* Hook */
|
|
5
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
6
|
-
/**
|
|
7
|
-
* Subscribe to backend email delivery notifications via Socket.IO.
|
|
8
|
-
*
|
|
9
|
-
* Delegates to `AvroQueryClient.trackEmail()` which registers socket
|
|
10
|
-
* listeners on the client itself (not in a React effect), so
|
|
11
|
-
* notifications survive component unmounts.
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* ```ts
|
|
15
|
-
* const { trackEmail, pending, results } = useEmailStatus({
|
|
16
|
-
* onSuccess: (r) => toast.success(`Email sent to ${r.recipient}`),
|
|
17
|
-
* onFailure: (r) => toast.error(`Email failed: ${r.error?.message}`),
|
|
18
|
-
* onTimeout: (id) => toast.warn("Email status unknown"),
|
|
19
|
-
* });
|
|
20
|
-
*
|
|
21
|
-
* const requestId = trackEmail("bill");
|
|
22
|
-
* await avroQueryClient.sendBillingEmail({ billId, request_id: requestId });
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
3
|
export function useEmailStatus(options = {}) {
|
|
26
4
|
const { timeout = 30000 } = options;
|
|
27
5
|
const client = useAvroQueryClient();
|
|
28
|
-
// Use refs for callbacks so the client-level handlers always see the latest
|
|
29
6
|
const onSuccessRef = useRef(options.onSuccess);
|
|
30
7
|
const onFailureRef = useRef(options.onFailure);
|
|
31
8
|
const onTimeoutRef = useRef(options.onTimeout);
|
|
32
9
|
onSuccessRef.current = options.onSuccess;
|
|
33
10
|
onFailureRef.current = options.onFailure;
|
|
34
11
|
onTimeoutRef.current = options.onTimeout;
|
|
35
|
-
// Expose reactive state so the UI can render pending / completed
|
|
36
12
|
const [pending, setPending] = useState(new Map());
|
|
37
13
|
const [results, setResults] = useState(new Map());
|
|
38
|
-
/* ── trackEmail — delegates to client.trackEmail() ──────────────── */
|
|
39
14
|
const trackEmail = useCallback((emailType, opts = {}) => {
|
|
40
15
|
const requestId = client.trackEmail({
|
|
41
16
|
emailType,
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ import './client/hooks/proposal';
|
|
|
32
32
|
import './client/hooks/timecards';
|
|
33
33
|
import './client/hooks/waivers';
|
|
34
34
|
import './client/hooks/email';
|
|
35
|
+
export { computePassOnFee, computeStripeFee, computeAvroFee, defaultPassOnFeeMethod, } from './billing/fees';
|
|
36
|
+
export type { PassOnFeeBreakdown } from './billing/fees';
|
|
35
37
|
export * from './types/api';
|
|
36
38
|
export * from './types/auth';
|
|
37
39
|
export * from './types/cache';
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import './client/hooks/proposal';
|
|
|
30
30
|
import './client/hooks/timecards';
|
|
31
31
|
import './client/hooks/waivers';
|
|
32
32
|
import './client/hooks/email';
|
|
33
|
+
export { computePassOnFee, computeStripeFee, computeAvroFee, defaultPassOnFeeMethod, } from './billing/fees';
|
|
33
34
|
export * from './types/api';
|
|
34
35
|
export * from './types/auth';
|
|
35
36
|
export * from './types/cache';
|
package/dist/types/api/Bill.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { BillPayment } from '../../types/api/BillPayment';
|
|
|
2
2
|
import { BillUser } from '../../types/api/BillUser';
|
|
3
3
|
import { CustomLineItem } from '../../types/api/CustomLineItem';
|
|
4
4
|
import { PaymentType } from '../../types/api/PaymentType';
|
|
5
|
+
import { ProcessingFeeLineItem } from '../../types/api/ProcessingFeeLineItem';
|
|
6
|
+
export type BillLineItem = CustomLineItem | ProcessingFeeLineItem;
|
|
5
7
|
export declare const BillStatus: {
|
|
6
8
|
readonly SENT: "SENT";
|
|
7
9
|
readonly PAID: "PAID";
|
|
@@ -27,8 +29,9 @@ export interface Bill {
|
|
|
27
29
|
intent_created_at: number;
|
|
28
30
|
intent_last_created_at: number;
|
|
29
31
|
payments: BillPayment[];
|
|
30
|
-
line_items:
|
|
32
|
+
line_items: BillLineItem[];
|
|
31
33
|
prepayments: string[];
|
|
32
34
|
months: string[];
|
|
33
35
|
due_date: number;
|
|
36
|
+
pass_on_fees: boolean;
|
|
34
37
|
}
|
|
@@ -14,6 +14,7 @@ export declare const LineItemType: {
|
|
|
14
14
|
readonly EVENT: "EVENT";
|
|
15
15
|
readonly SERVICE_MONTH: "SERVICE_MONTH";
|
|
16
16
|
readonly PREPAYMENT: "PREPAYMENT";
|
|
17
|
+
readonly PROCESSING_FEE: "PROCESSING_FEE";
|
|
17
18
|
};
|
|
18
19
|
export type LineItemType = (typeof LineItemType)[keyof typeof LineItemType];
|
|
19
20
|
declare module '../../types/api/LineItem' {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LineItem } from '../../types/api/LineItem';
|
|
2
|
+
import { PaymentType } from '../../types/api/PaymentType';
|
|
3
|
+
/**
|
|
4
|
+
* A read-only line item populated by the backend when the bill has
|
|
5
|
+
* `pass_on_fees` enabled. Represents the Stripe + Avro processing fees being
|
|
6
|
+
* passed on to the end customer.
|
|
7
|
+
*
|
|
8
|
+
* Merchants cannot edit, add, or delete these rows directly; they are managed
|
|
9
|
+
* by the server (created on bill save, recomputed when the payment method
|
|
10
|
+
* changes at checkout, and reconciled to the actual collected fees on
|
|
11
|
+
* `payment_intent.succeeded`).
|
|
12
|
+
*
|
|
13
|
+
* `payment_method_assumption` records which method was used to compute the
|
|
14
|
+
* current `cost`. UI surfaces (invoice details, email PDF, QB sync) display
|
|
15
|
+
* this value so the merchant knows whether the persisted figure is the card
|
|
16
|
+
* estimate, the ACH estimate, or the actual reconciled amount.
|
|
17
|
+
*/
|
|
18
|
+
export interface ProcessingFeeLineItem extends LineItem {
|
|
19
|
+
line_item_type: 'PROCESSING_FEE';
|
|
20
|
+
payment_method_assumption: PaymentType | null;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/api.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from '../types/api/PaymentType';
|
|
|
25
25
|
export * from '../types/api/Plan';
|
|
26
26
|
export * from '../types/api/PlanPayment';
|
|
27
27
|
export * from '../types/api/Prepayment';
|
|
28
|
+
export * from '../types/api/ProcessingFeeLineItem';
|
|
28
29
|
export * from '../types/api/Reaction';
|
|
29
30
|
export * from '../types/api/Route';
|
|
30
31
|
export * from '../types/api/RouteJob';
|
package/dist/types/api.js
CHANGED
|
@@ -24,6 +24,7 @@ export * from '../types/api/PaymentType';
|
|
|
24
24
|
export * from '../types/api/Plan';
|
|
25
25
|
export * from '../types/api/PlanPayment';
|
|
26
26
|
export * from '../types/api/Prepayment';
|
|
27
|
+
export * from '../types/api/ProcessingFeeLineItem';
|
|
27
28
|
export * from '../types/api/Reaction';
|
|
28
29
|
export * from '../types/api/Route';
|
|
29
30
|
export * from '../types/api/RouteJob';
|