@funnelfox/billing 0.9.0 → 0.9.3-ffb-apple-pay-continue-button.33

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.
@@ -6,70 +6,9 @@
6
6
  * @license MIT
7
7
  */
8
8
  import { g as getStripe } from './chunk-stripe-loader.es.js';
9
- import { f as formatCurrencyAmount } from './chunk-index.es.js';
9
+ import './chunk-index.es.js';
10
10
 
11
- const TAX_STYLE_ID = 'ff-stripe-tax-styles';
12
11
  const TAX_RECALC_DEBOUNCE_MS = 600;
13
- // The Stripe card form renders Stripe Elements (not the card skin), so it owns its own subtotal/tax/
14
- // total summary here — mirroring the hosted card skin so every integration shows tax consistently
15
- // without the host page reimplementing it.
16
- function injectTaxStyles() {
17
- if (typeof document === 'undefined')
18
- return;
19
- if (document.getElementById(TAX_STYLE_ID))
20
- return;
21
- const style = document.createElement('style');
22
- style.id = TAX_STYLE_ID;
23
- style.textContent = [
24
- '.ff-tax-summary{margin-top:12px;padding-top:12px;border-top:1px solid rgb(0 0 0 / 10%);font-size:16px;transition:opacity .2s ease}',
25
- '.ff-tax-summary[hidden]{display:none}',
26
- '.ff-tax-summary--updating{opacity:.55}',
27
- '.ff-tax-row{display:flex;justify-content:space-between;gap:10px;margin-bottom:6px}',
28
- '.ff-tax-row.ff-tax-total{margin-top:8px;margin-bottom:0;font-weight:600}',
29
- ].join('\n');
30
- document.head.appendChild(style);
31
- }
32
- function createTaxSummary() {
33
- const root = document.createElement('div');
34
- root.className = 'ff-tax-summary';
35
- root.hidden = true;
36
- const makeRow = (label, isTotal = false) => {
37
- const rowEl = document.createElement('div');
38
- rowEl.className = isTotal ? 'ff-tax-row ff-tax-total' : 'ff-tax-row';
39
- const labelEl = document.createElement('span');
40
- labelEl.textContent = label;
41
- const valueEl = document.createElement('span');
42
- rowEl.append(labelEl, valueEl);
43
- root.appendChild(rowEl);
44
- return valueEl;
45
- };
46
- const subtotalEl = makeRow('Subtotal');
47
- const taxEl = makeRow('Tax');
48
- const totalEl = makeRow('Total', true);
49
- return {
50
- root,
51
- // taxAmount is the tax added on top of the price; it is 0 for tax-inclusive pricing and for
52
- // non-taxed locations — in both cases there is nothing to itemise, so the summary stays hidden.
53
- render(info) {
54
- root.classList.remove('ff-tax-summary--updating');
55
- if (!info || !(info.taxAmount > 0)) {
56
- root.hidden = true;
57
- return;
58
- }
59
- subtotalEl.textContent = formatCurrencyAmount(info.amountTotal - info.taxAmount, info.currency);
60
- taxEl.textContent = formatCurrencyAmount(info.taxAmount, info.currency);
61
- totalEl.textContent = formatCurrencyAmount(info.amountTotal, info.currency);
62
- root.hidden = false;
63
- },
64
- setPending() {
65
- if (!root.hidden)
66
- root.classList.add('ff-tax-summary--updating');
67
- },
68
- setIdle() {
69
- root.classList.remove('ff-tax-summary--updating');
70
- },
71
- };
72
- }
73
12
  async function mountStripeCardForm(element, session, params) {
74
13
  const { stripe_public_key, amount, currency, order_id, is_link_enabled, tax_amount, amount_total, } = session.data;
75
14
  // Tax flow is driven by the tenant setting (session.tax_enabled); enableTax is a legacy override.
@@ -79,8 +18,6 @@ async function mountStripeCardForm(element, session, params) {
79
18
  throw new Error('Failed to load Stripe');
80
19
  // Remounting (e.g. picking another price) must replace the form, not stack a second one.
81
20
  element.replaceChildren();
82
- injectTaxStyles();
83
- const taxSummary = createTaxSummary();
84
21
  const stripeElements = stripe.elements({
85
22
  mode: 'subscription',
86
23
  amount,
@@ -103,21 +40,22 @@ async function mountStripeCardForm(element, session, params) {
103
40
  terms: { card: 'never' },
104
41
  fields: {
105
42
  billingDetails: {
106
- // In tax mode collect only what the org configured (country and/or postal) and hide the rest
107
- // of the address; otherwise use Stripe's default card billing (country + postal).
108
- address: taxEnabled
109
- ? {
110
- line1: 'never',
111
- line2: 'never',
112
- city: 'never',
113
- state: 'never',
114
- country: showCountry ? 'auto' : 'never',
115
- postalCode: collectLocation ? 'auto' : 'never',
116
- }
117
- : 'auto',
43
+ // Field visibility is owned by the org settings (Taxes → Settings) and is independent of
44
+ // whether tax is enabled: show the country selector and/or postal code only when configured,
45
+ // and always hide the rest of the address (supplied to createPaymentMethod below). Previously
46
+ // the untaxed path fell back to Stripe's default billing (country + postal), showing those
47
+ // fields even when the org had turned them off.
48
+ address: {
49
+ line1: 'never',
50
+ line2: 'never',
51
+ city: 'never',
52
+ state: 'never',
53
+ country: showCountry ? 'auto' : 'never',
54
+ postalCode: showPostal ? 'auto' : 'never',
55
+ },
118
56
  },
119
57
  },
120
- ...(session.data.detected_country_code && (!taxEnabled || showCountry)
58
+ ...(session.data.detected_country_code && showCountry
121
59
  ? {
122
60
  defaultValues: {
123
61
  billingDetails: {
@@ -128,16 +66,16 @@ async function mountStripeCardForm(element, session, params) {
128
66
  : {}),
129
67
  });
130
68
  // Address fields we hid (fields=never) must still be supplied to createPaymentMethod. Only country +
131
- // postal matter for tax; country is hidden only when the selector is off, postal only when we collect
132
- // no location at all.
69
+ // postal matter for tax; country is supplied when its selector is off, postal when its field is off.
133
70
  const hiddenBillingAddress = { line1: '', line2: '', city: '', state: '' };
134
71
  if (!showCountry)
135
72
  hiddenBillingAddress.country = session.data.detected_country_code ?? '';
136
- if (!collectLocation)
73
+ if (!showPostal)
137
74
  hiddenBillingAddress.postal_code = '';
138
- // Tax needs only the country + postal code. When the org collects location on the form we read it from
139
- // the Payment Element's change events as the buyer types and recalculate tax live, then attach the
140
- // calculation id to the charge. When it does not, tax follows the IP-derived session estimate.
75
+ // Tax needs only the country + postal code. When the org collects location on the form we read it
76
+ // from the Payment Element's change events as the buyer types, recalculate tax live (reported via
77
+ // onTaxChange), and attach the calculation id to the charge. When it does not, tax follows the
78
+ // IP-derived session estimate.
141
79
  let taxCalc;
142
80
  let recalcSeq = 0;
143
81
  if (taxEnabled && session.data.tax_calculation_id) {
@@ -153,7 +91,6 @@ async function mountStripeCardForm(element, session, params) {
153
91
  const country = taxAddress.country;
154
92
  const postal = taxAddress.postalCode;
155
93
  if (!country) {
156
- taxSummary.setIdle();
157
94
  return;
158
95
  }
159
96
  const seq = ++recalcSeq;
@@ -167,13 +104,11 @@ async function mountStripeCardForm(element, session, params) {
167
104
  if (seq !== recalcSeq)
168
105
  return; // a newer recalc superseded this one
169
106
  taxCalc = { id: tax.tax_calculation_id, country, postal };
170
- const info = {
107
+ params.onTaxChange?.({
171
108
  amountTotal: tax.amount_total,
172
109
  taxAmount: tax.tax_amount,
173
110
  currency: tax.currency,
174
- };
175
- taxSummary.render(info);
176
- params.onTaxChange?.(info);
111
+ });
177
112
  }
178
113
  catch (err) {
179
114
  if (seq !== recalcSeq)
@@ -182,7 +117,6 @@ async function mountStripeCardForm(element, session, params) {
182
117
  taxCalc = undefined;
183
118
  params.onTaxError?.(err);
184
119
  }
185
- taxSummary.setIdle();
186
120
  }
187
121
  };
188
122
  paymentElement.mount(element);
@@ -207,19 +141,21 @@ async function mountStripeCardForm(element, session, params) {
207
141
  };
208
142
  if (!taxAddress.country)
209
143
  return;
210
- taxSummary.setPending();
211
144
  if (debounce)
212
145
  clearTimeout(debounce);
213
146
  debounce = setTimeout(runRecalc, TAX_RECALC_DEBOUNCE_MS);
214
147
  });
215
148
  }
216
- // SDK-owned summary lives below the form and reflects the session's tax estimate immediately; when the
217
- // org collects location, the Payment Element's fields then keep it in sync as the buyer edits them.
218
- element.appendChild(taxSummary.root);
219
- taxSummary.render(amount_total != null
220
- ? { amountTotal: amount_total, taxAmount: tax_amount ?? 0, currency }
221
- : null);
222
- // Refine the initial estimate against the pre-filled country right away.
149
+ // The form renders no tax lines; the host re-renders its own prices off onTaxChange. Report the
150
+ // session estimate as the first calculation, then (when the org collects location) refine it
151
+ // against the pre-filled country right away — later address edits keep firing it via the recalc.
152
+ if (taxEnabled && amount_total != null) {
153
+ params.onTaxChange?.({
154
+ amountTotal: amount_total,
155
+ taxAmount: tax_amount ?? 0,
156
+ currency,
157
+ });
158
+ }
223
159
  if (taxEnabled && collectLocation)
224
160
  void runRecalc();
225
161
  await new Promise((resolve, reject) => {
@@ -241,10 +177,10 @@ async function mountStripeCardForm(element, session, params) {
241
177
  const { error, paymentMethod } = await stripe.createPaymentMethod({
242
178
  elements: stripeElements,
243
179
  // Supply the address fields we hid on the Payment Element (fields=never); Stripe requires
244
- // them here. Country/postal collected by the element are filled by Stripe automatically.
245
- ...(taxEnabled
246
- ? { params: { billing_details: { address: hiddenBillingAddress } } }
247
- : {}),
180
+ // them here. We always hide line1/line2/city/state (and country/postal per the org
181
+ // settings), so these must be supplied regardless of tax mode. Country/postal shown on the
182
+ // element are filled by Stripe automatically.
183
+ params: { billing_details: { address: hiddenBillingAddress } },
248
184
  });
249
185
  if (error)
250
186
  throw error;
@@ -5,4 +5,4 @@
5
5
  * @author Funnelfox
6
6
  * @license MIT
7
7
  */
8
- export { A as APIError, B as Billing, d as CHECKOUT_STATES, C as CheckoutError, b as ConfigurationError, c as DEFAULTS, e as ERROR_CODES, E as EVENTS, F as FunnefoxSDKError, N as NetworkError, P as PaymentMethod, a as PrimerError, S as SDK_VERSION, V as ValidationError, g as configure, h as createCheckout, i as createClientSession, B as default, j as getAvailablePaymentMethods } from './chunk-index.es.js';
8
+ export { A as APIError, B as Billing, d as CHECKOUT_STATES, C as CheckoutError, b as ConfigurationError, c as DEFAULTS, e as ERROR_CODES, E as EVENTS, F as FunnefoxSDKError, N as NetworkError, P as PaymentMethod, a as PrimerError, S as SDK_VERSION, V as ValidationError, f as configure, g as createCheckout, h as createClientSession, B as default, i as getAvailablePaymentMethods } from './chunk-index.es.js';