@happyvertical/accounting 0.91.0 → 0.93.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/README.md CHANGED
@@ -37,11 +37,159 @@ complete billing address before creating the invoice so Stripe can calculate
37
37
  tax from that customer tax location. Read the invoice back to persist Stripe's
38
38
  calculated `taxAmount`; callers do not supply local tax tables or rates.
39
39
 
40
+ Invoice lines carry more than an amount:
41
+
42
+ - `periodStart` / `periodEnd` (set both) become the Stripe invoice item's
43
+ service `period`, so the invoice PDF, portal, and revenue reports show the
44
+ billed window. `periodEnd` is exclusive, like Stripe's own subscription
45
+ periods, and must not be before `periodStart`.
46
+ - `discount` is the line's total discount in major units. Stripe receives it
47
+ as an amount-off coupon on the invoice item (one reusable coupon per
48
+ currency and amount, id `hv_amount_off_<currency>_<stripe amount>`), so the
49
+ discount shows on the invoice and Stripe Tax taxes the discounted amount.
50
+
51
+ `collectionMethod: 'charge_automatically'` bills the customer's default
52
+ payment method instead of emailing a payable invoice (the default,
53
+ `send_invoice`). `invoices.send()` then finalizes the invoice with automatic
54
+ collection on: Stripe charges the card on its own collection schedule (not
55
+ necessarily at once, so activate service on `invoice.paid` rather than on
56
+ `send()`), retries failures under your Stripe retry settings, and reports the
57
+ result as `invoice.paid` or `invoice.payment_failed`. Automatically charged invoices carry no due date.
58
+ `invoices.markUncollectible()` writes an open invoice off, and invoice reads
59
+ report that state as `status: 'uncollectible'`.
60
+
61
+ ## Stripe customers
62
+
63
+ Pass a stable `idempotencyKey` when a retry might create the customer again
64
+ (for example a worker that crashed before storing the returned id). A retry
65
+ with the same key returns the first customer: Stripe replays the create
66
+ inside its idempotency window, and after it the adapter finds the customer by
67
+ the `local_id` metadata it writes on every customer (the oldest one wins if
68
+ earlier retries left duplicates). Reuse the key only for the same customer
69
+ contents.
70
+
71
+ ```ts
72
+ const { externalId } = await stripe.customers.sync({
73
+ id: account.id,
74
+ name: account.name,
75
+ billingAddress: account.address,
76
+ idempotencyKey: `billing-account:${account.id}`,
77
+ });
78
+ ```
79
+
40
80
  ## Stripe Checkout and webhooks
41
81
 
42
82
  `billing.createCheckoutSession` accepts `idempotencyKey`; reuse it when
43
- retrying the same Checkout Session creation. After `webhooks.verify` succeeds,
44
- the parsed Stripe webhook exposes its provider event as `WebhookEvent.id`.
83
+ retrying the same Checkout Session creation.
84
+
85
+ Price ad-hoc Checkout lines with `priceData.unitAmountMinor`, integer ISO 4217
86
+ minor units that the adapter converts to Stripe's unit (ISK and UGX are
87
+ two-decimal at Stripe). `priceData.unitAmount` is still accepted and passed
88
+ through unconverted in Stripe's unit. Set `automaticTax: true` to charge tax
89
+ with Stripe Tax; Stripe needs the buyer's location, so collect it with
90
+ `billingAddressCollection` or, for an existing customer, save it with
91
+ `customerUpdate: { address: 'auto' }`. Ad-hoc prices default to
92
+ `taxBehavior: 'exclusive'` when tax is on.
93
+
94
+ ```ts
95
+ const session = await stripe.billing.createCheckoutSession({
96
+ mode: 'payment',
97
+ customerExternalId: 'cus_123',
98
+ lineItems: [
99
+ {
100
+ priceData: { currency: 'CAD', unitAmountMinor: 5000, productName: 'Credit' },
101
+ },
102
+ ],
103
+ automaticTax: true,
104
+ customerUpdate: { address: 'auto' },
105
+ setupFutureUsage: 'off_session', // also keep the card for top-ups
106
+ successUrl,
107
+ cancelUrl,
108
+ idempotencyKey: `credit-purchase:${cartId}`,
109
+ });
110
+ ```
111
+
112
+ ### Card on file
113
+
114
+ A `setup` mode session saves a payment method without charging. It takes no
115
+ line items and needs `currency` (or `paymentMethodTypes`). After it completes,
116
+ read the saved method and make it the customer's default so automatically
117
+ charged invoices and off-session charges can use it:
118
+
119
+ ```ts
120
+ const setup = await stripe.billing.createCheckoutSession({
121
+ mode: 'setup',
122
+ customerExternalId: 'cus_123',
123
+ currency: 'CAD',
124
+ billingAddressCollection: 'required',
125
+ customerUpdate: { address: 'auto', name: 'auto' },
126
+ successUrl,
127
+ cancelUrl,
128
+ idempotencyKey: `card-setup:${accountId}:${attempt}`,
129
+ });
130
+ // On checkout.session.completed:
131
+ // Both methods are optional on StripeBillingOperations; StripeProvider has them.
132
+ const done = await stripe.billing.retrieveCheckoutSession?.(setup.externalId);
133
+ if (done?.status === 'complete' && done.paymentMethodExternalId) {
134
+ await stripe.billing.setDefaultPaymentMethod?.(
135
+ 'cus_123',
136
+ done.paymentMethodExternalId,
137
+ );
138
+ }
139
+ ```
140
+
141
+ ## Off-session charges
142
+
143
+ `payments.chargeSavedPaymentMethod()` charges a saved payment method without
144
+ the customer present, for example an automatic prepaid-credit top-up. It is
145
+ provider-neutral and optional on `PaymentOperations`: a provider that cannot
146
+ pull funds from a stored method (such as a push-payment BTC rail) does not
147
+ implement it, so check for it before calling. Amounts are integer minor units
148
+ (`amountMinor`), and `idempotencyKey` is required.
149
+
150
+ ```ts
151
+ const charge = await stripe.payments.chargeSavedPaymentMethod?.({
152
+ customerExternalId: 'cus_123', // default payment method unless one is given
153
+ amountMinor: 2500,
154
+ currency: 'USD',
155
+ idempotencyKey: `topup:${policyId}:${sequence}`,
156
+ metadata: { policy: policyId },
157
+ });
158
+ switch (charge?.status) {
159
+ case 'succeeded': // funds secured: grant the credit
160
+ case 'processing': // wait for the payment webhook
161
+ case 'requires_action': // issuer wants authentication; re-save the card on-session
162
+ case 'failed': // declined or no payment method; see failureCode
163
+ }
164
+ ```
165
+
166
+ A decline (`failureCode: 'card_declined'`) or an authentication requirement
167
+ (`requires_action`, `failureCode: 'authentication_required'`) is returned, not
168
+ thrown. Results never include Stripe's client secret, so a `requires_action`
169
+ attempt is not completed later: when the customer is next present, collect
170
+ authentication with a `setup` mode Checkout session and
171
+ `setDefaultPaymentMethod`, then charge again with a new idempotency key (the
172
+ old key returns the original, unauthenticated attempt). Other Stripe API
173
+ errors throw a `StripeApiError` carrying `status`, `type`, and `code`;
174
+ invalid input and a reused-key refusal throw a plain `Error` before any
175
+ provider request. Every retry with the same key returns the original
176
+ charge: Stripe replays it inside its idempotency window, and after it the
177
+ adapter finds the PaymentIntent by its `hv_charge_key` metadata. Scope keys
178
+ to the payer and use a new key for a new attempt: a key reused for a
179
+ different amount, currency, customer, or explicit payment method is refused
180
+ (Stripe `idempotency_error`, or an adapter error after the window), never
181
+ charged. Pass `paymentMethodExternalId` to keep retries identical when the
182
+ customer's default card may change in between. Payment webhooks (`payment_intent.*`) carry a normalized `WebhookEvent.payment`
183
+ summary with the status, minor-unit amount, and `chargeKey`.
184
+
185
+ Minor-unit amounts (`amountMinor`, `unitAmountMinor`, and the `*Minor` read
186
+ fields) use ISO 4217 exponents from an explicit table, not the runtime's
187
+ `Intl` display digits, which differ for some currencies (for example RSD,
188
+ IQD, and MGA). Derive minor units the same way before calling.
189
+
190
+ ## Stripe webhooks
191
+
192
+ After `webhooks.verify` succeeds, the parsed Stripe webhook exposes its provider event as `WebhookEvent.id`.
45
193
  Persist that identifier in a durable inbox before applying side effects; the
46
194
  in-process parser alone cannot deduplicate deliveries across restarts or
47
195
  replicas.
@@ -1,4 +1,4 @@
1
- import { a as __toCommonJS, i as __require, n as __esmMin, r as __exportAll, t as __commonJSMin } from "./quickbooks-BF3GrJBw.js";
1
+ import { a as __toCommonJS, i as __require, n as __esmMin, r as __exportAll, t as __commonJSMin } from "./quickbooks-DujZt_Gx.js";
2
2
  //#region ../../node_modules/.pnpm/atob@2.1.2/node_modules/atob/browser-atob.js
3
3
  var require_browser_atob = /* @__PURE__ */ __commonJSMin(((exports, module) => {
4
4
  (function(w) {
@@ -18261,4 +18261,4 @@ var require_OAuthClient = /* @__PURE__ */ __commonJSMin(((exports, module) => {
18261
18261
  //#endregion
18262
18262
  export default require_OAuthClient();
18263
18263
 
18264
- //# sourceMappingURL=OAuthClient-Bv-lzpx8.js.map
18264
+ //# sourceMappingURL=OAuthClient-DgRaQp_Y.js.map