@owlmeans/web-payment 0.1.18-rc.11 → 0.1.18-rc.13
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 +1 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-payment/SKILL.md +70 -20
- package/build/@/components/ui/select.d.ts +8 -0
- package/build/@/components/ui/select.d.ts.map +1 -0
- package/build/@/components/ui/select.js +9 -0
- package/build/@/components/ui/select.js.map +1 -0
- package/build/dialog.d.ts +1 -1
- package/build/dialog.d.ts.map +1 -1
- package/build/dialog.js +5 -2
- package/build/dialog.js.map +1 -1
- package/build/estimate-cache.d.ts +14 -0
- package/build/estimate-cache.d.ts.map +1 -0
- package/build/estimate-cache.js +39 -0
- package/build/estimate-cache.js.map +1 -0
- package/build/estimate-summary.d.ts +56 -0
- package/build/estimate-summary.d.ts.map +1 -0
- package/build/estimate-summary.js +91 -0
- package/build/estimate-summary.js.map +1 -0
- package/build/estimate.d.ts +28 -0
- package/build/estimate.d.ts.map +1 -0
- package/build/estimate.js +67 -0
- package/build/estimate.js.map +1 -0
- package/build/i18n/be.json +1 -1
- package/build/i18n/de.json +1 -1
- package/build/i18n/en.json +1 -1
- package/build/i18n/es.json +1 -1
- package/build/i18n/pl.json +1 -1
- package/build/i18n/ru.json +1 -1
- package/build/i18n/uk.json +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/types.d.ts +23 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +9 -7
- package/src/@/components/ui/select.tsx +62 -0
- package/src/dialog.tsx +5 -2
- package/src/estimate-cache.ts +40 -0
- package/src/estimate-summary.tsx +162 -0
- package/src/estimate.ts +93 -0
- package/src/i18n/be.json +1 -1
- package/src/i18n/de.json +1 -1
- package/src/i18n/en.json +1 -1
- package/src/i18n/es.json +1 -1
- package/src/i18n/pl.json +1 -1
- package/src/i18n/ru.json +1 -1
- package/src/i18n/uk.json +1 -1
- package/src/index.ts +2 -0
- package/src/types.ts +27 -1
- package/tests/context.ts +2 -1
- package/tests/dialog.spec.ts +49 -0
- package/tests/estimate-cache.spec.ts +46 -0
- package/tests/harness/entitlement.tsx +43 -4
- package/tests/harness/estimate-fixtures.ts +48 -0
- package/tests/harness/mount.tsx +3 -0
- package/tests/pieces.spec.ts +22 -0
package/build/i18n/de.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "amount-checkout": { "title": "Guthaben aufladen", "description": "Wähle den Nettowert des Guthabens für deine Organisation.", "presets": "Vorgegebene Beträge", "custom": "Eigener Betrag", "invalid": "Gib einen gültigen Betrag mit höchstens zwei Dezimalstellen ein.", "below": "Der Mindestbetrag ist {{amount}}.", "above": "Der Höchstbetrag ist {{amount}}.", "bounds": "Wähle zwischen {{minimum}} und {{maximum}}.", "credits": "Guthabenwert", "adjustment": "Verarbeitungsaufschlag", "subtotal": "Zwischensumme vor Steuern", "tax-note": "Stripe berechnet anwendbare Steuern beim Bezahlen.", "cancel": "Abbrechen", "continue": "Weiter zu Stripe", "pending": "Zahlung wird geöffnet…", "close": "Schließen" }, "entitlement": { "plan": { "current": "Aktueller Tarif" }, "status": { "trial": "Testphase", "trial-on": "Testphase endet am {{date}}", "past-due": "Zahlung überfällig", "cancel-scheduled": "Kündigung vorgemerkt", "cancel-scheduled-on": "Endet am {{date}}", "paused": "Pausiert", "paused-on": "Pausiert seit {{date}}", "suspended": "Ausgesetzt", "free": "Kostenloser Tarif", "renews-on": "Verlängert sich am {{date}}", "active": "Aktiv", "created": "Wartet auf Aktivierung", "canceled": "Gekündigt", "expired": "Abgelaufen", "ended": "Beendet", "blocked": "Gesperrt" }, "limit": { "usage": "{{used}} von {{limit}}", "usage-unit": "{{used}} von {{limit}} {{unit}}", "not-included": "Nicht enthalten", "exhausted": "Limit erreicht", "resets-on": "Wird am {{date}} zurückgesetzt" }, "promo": { "free-until": "Kostenlos bis {{date}}", "grandfathered": "In deinem Tarif enthalten", "ended": "Aktion beendet" }, "capability": { "included": "Enthalten", "not-included": "Nicht enthalten" } } }
|
|
1
|
+
{ "amount-checkout": { "title": "Guthaben aufladen", "description": "Wähle den Nettowert des Guthabens für deine Organisation.", "presets": "Vorgegebene Beträge", "custom": "Eigener Betrag", "invalid": "Gib einen gültigen Betrag mit höchstens zwei Dezimalstellen ein.", "below": "Der Mindestbetrag ist {{amount}}.", "above": "Der Höchstbetrag ist {{amount}}.", "bounds": "Wähle zwischen {{minimum}} und {{maximum}}.", "credits": "Guthabenwert", "adjustment": "Verarbeitungsaufschlag", "subtotal": "Zwischensumme vor Steuern", "tax-note": "Stripe berechnet anwendbare Steuern beim Bezahlen.", "cancel": "Abbrechen", "continue": "Weiter zu Stripe", "pending": "Zahlung wird geöffnet…", "close": "Schließen" }, "entitlement": { "plan": { "current": "Aktueller Tarif" }, "status": { "trial": "Testphase", "trial-on": "Testphase endet am {{date}}", "past-due": "Zahlung überfällig", "cancel-scheduled": "Kündigung vorgemerkt", "cancel-scheduled-on": "Endet am {{date}}", "paused": "Pausiert", "paused-on": "Pausiert seit {{date}}", "suspended": "Ausgesetzt", "free": "Kostenloser Tarif", "renews-on": "Verlängert sich am {{date}}", "active": "Aktiv", "created": "Wartet auf Aktivierung", "canceled": "Gekündigt", "expired": "Abgelaufen", "ended": "Beendet", "blocked": "Gesperrt" }, "limit": { "usage": "{{used}} von {{limit}}", "usage-unit": "{{used}} von {{limit}} {{unit}}", "not-included": "Nicht enthalten", "exhausted": "Limit erreicht", "resets-on": "Wird am {{date}} zurückgesetzt" }, "promo": { "free-until": "Kostenlos bis {{date}}", "grandfathered": "In deinem Tarif enthalten", "ended": "Aktion beendet" }, "capability": { "included": "Enthalten", "not-included": "Nicht enthalten" } }, "estimate": { "country": "Rechnungsland", "country-placeholder": "Land wählen", "tax-type": { "vat": "USt.", "gst": "GST", "sales-tax": "Umsatzsteuer", "tax": "Steuer" }, "rate": "{{type}} ({{percentage}}%)", "total": "Geschätzter Gesamtbetrag", "converted-note": "Umgerechnet zum aktuellen Stripe-Wechselkurs; der genaue Betrag und eine mögliche Umrechnungsgebühr werden beim Bezahlen bestätigt.", "status": { "taxed": "Die Steuer ist bereits im geschätzten Gesamtbetrag unten enthalten.", "reverse-charge": "Reverse-Charge-Verfahren — mit deiner USt-IdNr. rechnest du diese Steuer selbst ab, daher berechnen wir nichts.", "none": "Für dieses Land fällt keine Steuer an.", "at-checkout": "Die genaue Steuer wird beim Bezahlen berechnet.", "location-required": "Wähle dein Rechnungsland, um eine Schätzung zu sehen." }, "loading": "Steuer wird geprüft…", "failed": "Die Steuer konnte gerade nicht geprüft werden." } }
|
package/build/i18n/en.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "amount-checkout": { "title": "Add credits", "description": "Choose the net credit value to add to your organization.", "presets": "Preset amounts", "custom": "Custom amount", "invalid": "Enter a valid amount with no more than two decimal places.", "below": "The minimum amount is {{amount}}.", "above": "The maximum amount is {{amount}}.", "bounds": "Choose between {{minimum}} and {{maximum}}.", "credits": "Credit value", "adjustment": "Processing adjustment", "subtotal": "Pre-tax subtotal", "tax-note": "Stripe calculates any applicable tax at checkout.", "cancel": "Cancel", "continue": "Continue to Stripe", "pending": "Opening checkout…", "close": "Close" }, "entitlement": { "plan": { "current": "Current plan" }, "status": { "trial": "Trial", "trial-on": "Trial ends on {{date}}", "past-due": "Payment past due", "cancel-scheduled": "Cancellation scheduled", "cancel-scheduled-on": "Cancels on {{date}}", "paused": "Paused", "paused-on": "Paused since {{date}}", "suspended": "Suspended", "free": "Free plan", "renews-on": "Renews on {{date}}", "active": "Active", "created": "Awaiting activation", "canceled": "Canceled", "expired": "Expired", "ended": "Ended", "blocked": "Blocked" }, "limit": { "usage": "{{used}} of {{limit}}", "usage-unit": "{{used}} of {{limit}} {{unit}}", "not-included": "Not included", "exhausted": "Limit reached", "resets-on": "Resets on {{date}}" }, "promo": { "free-until": "Free until {{date}}", "grandfathered": "Included for your plan", "ended": "Promotion ended" }, "capability": { "included": "Included", "not-included": "Not included" } } }
|
|
1
|
+
{ "amount-checkout": { "title": "Add credits", "description": "Choose the net credit value to add to your organization.", "presets": "Preset amounts", "custom": "Custom amount", "invalid": "Enter a valid amount with no more than two decimal places.", "below": "The minimum amount is {{amount}}.", "above": "The maximum amount is {{amount}}.", "bounds": "Choose between {{minimum}} and {{maximum}}.", "credits": "Credit value", "adjustment": "Processing adjustment", "subtotal": "Pre-tax subtotal", "tax-note": "Stripe calculates any applicable tax at checkout.", "cancel": "Cancel", "continue": "Continue to Stripe", "pending": "Opening checkout…", "close": "Close" }, "entitlement": { "plan": { "current": "Current plan" }, "status": { "trial": "Trial", "trial-on": "Trial ends on {{date}}", "past-due": "Payment past due", "cancel-scheduled": "Cancellation scheduled", "cancel-scheduled-on": "Cancels on {{date}}", "paused": "Paused", "paused-on": "Paused since {{date}}", "suspended": "Suspended", "free": "Free plan", "renews-on": "Renews on {{date}}", "active": "Active", "created": "Awaiting activation", "canceled": "Canceled", "expired": "Expired", "ended": "Ended", "blocked": "Blocked" }, "limit": { "usage": "{{used}} of {{limit}}", "usage-unit": "{{used}} of {{limit}} {{unit}}", "not-included": "Not included", "exhausted": "Limit reached", "resets-on": "Resets on {{date}}" }, "promo": { "free-until": "Free until {{date}}", "grandfathered": "Included for your plan", "ended": "Promotion ended" }, "capability": { "included": "Included", "not-included": "Not included" } }, "estimate": { "country": "Billing country", "country-placeholder": "Choose a country", "tax-type": { "vat": "VAT", "gst": "GST", "sales-tax": "Sales tax", "tax": "Tax" }, "rate": "{{type}} ({{percentage}}%)", "total": "Estimated total", "converted-note": "Converted at Stripe's current exchange rate; the exact amount and any conversion fee are confirmed at checkout.", "status": { "taxed": "Tax is included in the estimated total below.", "reverse-charge": "Reverse charge — your VAT ID means you account for this tax yourself, so nothing is added.", "none": "No tax applies for this country.", "at-checkout": "The exact tax is calculated at checkout.", "location-required": "Choose your billing country to see an estimate." }, "loading": "Checking tax…", "failed": "Tax could not be checked right now." } }
|
package/build/i18n/es.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "amount-checkout": { "title": "Añadir créditos", "description": "Elige el valor neto de créditos para tu organización.", "presets": "Importes predefinidos", "custom": "Importe personalizado", "invalid": "Introduce un importe válido con un máximo de dos decimales.", "below": "El importe mínimo es {{amount}}.", "above": "El importe máximo es {{amount}}.", "bounds": "Elige entre {{minimum}} y {{maximum}}.", "credits": "Valor de los créditos", "adjustment": "Ajuste de procesamiento", "subtotal": "Subtotal antes de impuestos", "tax-note": "Stripe calcula los impuestos aplicables al pagar.", "cancel": "Cancelar", "continue": "Continuar a Stripe", "pending": "Abriendo el pago…", "close": "Cerrar" }, "entitlement": { "plan": { "current": "Plan actual" }, "status": { "trial": "Prueba", "trial-on": "La prueba termina el {{date}}", "past-due": "Pago vencido", "cancel-scheduled": "Cancelación programada", "cancel-scheduled-on": "Se cancela el {{date}}", "paused": "En pausa", "paused-on": "En pausa desde el {{date}}", "suspended": "Suspendido", "free": "Plan gratuito", "renews-on": "Se renueva el {{date}}", "active": "Activo", "created": "Pendiente de activación", "canceled": "Cancelado", "expired": "Vencido", "ended": "Finalizado", "blocked": "Bloqueado" }, "limit": { "usage": "{{used}} de {{limit}}", "usage-unit": "{{used}} de {{limit}} {{unit}}", "not-included": "No incluido", "exhausted": "Límite alcanzado", "resets-on": "Se restablece el {{date}}" }, "promo": { "free-until": "Gratis hasta el {{date}}", "grandfathered": "Incluido en tu plan", "ended": "Promoción finalizada" }, "capability": { "included": "Incluido", "not-included": "No incluido" } } }
|
|
1
|
+
{ "amount-checkout": { "title": "Añadir créditos", "description": "Elige el valor neto de créditos para tu organización.", "presets": "Importes predefinidos", "custom": "Importe personalizado", "invalid": "Introduce un importe válido con un máximo de dos decimales.", "below": "El importe mínimo es {{amount}}.", "above": "El importe máximo es {{amount}}.", "bounds": "Elige entre {{minimum}} y {{maximum}}.", "credits": "Valor de los créditos", "adjustment": "Ajuste de procesamiento", "subtotal": "Subtotal antes de impuestos", "tax-note": "Stripe calcula los impuestos aplicables al pagar.", "cancel": "Cancelar", "continue": "Continuar a Stripe", "pending": "Abriendo el pago…", "close": "Cerrar" }, "entitlement": { "plan": { "current": "Plan actual" }, "status": { "trial": "Prueba", "trial-on": "La prueba termina el {{date}}", "past-due": "Pago vencido", "cancel-scheduled": "Cancelación programada", "cancel-scheduled-on": "Se cancela el {{date}}", "paused": "En pausa", "paused-on": "En pausa desde el {{date}}", "suspended": "Suspendido", "free": "Plan gratuito", "renews-on": "Se renueva el {{date}}", "active": "Activo", "created": "Pendiente de activación", "canceled": "Cancelado", "expired": "Vencido", "ended": "Finalizado", "blocked": "Bloqueado" }, "limit": { "usage": "{{used}} de {{limit}}", "usage-unit": "{{used}} de {{limit}} {{unit}}", "not-included": "No incluido", "exhausted": "Límite alcanzado", "resets-on": "Se restablece el {{date}}" }, "promo": { "free-until": "Gratis hasta el {{date}}", "grandfathered": "Incluido en tu plan", "ended": "Promoción finalizada" }, "capability": { "included": "Incluido", "not-included": "No incluido" } }, "estimate": { "country": "País de facturación", "country-placeholder": "Elige un país", "tax-type": { "vat": "IVA", "gst": "GST", "sales-tax": "Impuesto sobre ventas", "tax": "Impuesto" }, "rate": "{{type}} ({{percentage}}%)", "total": "Total estimado", "converted-note": "Convertido al tipo de cambio actual de Stripe; el importe exacto y cualquier comisión de conversión se confirman al pagar.", "status": { "taxed": "El impuesto ya está incluido en el total estimado de abajo.", "reverse-charge": "Inversión del sujeto pasivo — con tu NIF-IVA, tú mismo declaras este impuesto, así que no añadimos nada.", "none": "No se aplica ningún impuesto para este país.", "at-checkout": "El impuesto exacto se calcula al pagar.", "location-required": "Elige tu país de facturación para ver una estimación." }, "loading": "Comprobando el impuesto…", "failed": "No se ha podido comprobar el impuesto ahora." } }
|
package/build/i18n/pl.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "amount-checkout": { "title": "Dodaj kredyty", "description": "Wybierz wartość netto kredytów dla organizacji.", "presets": "Kwoty sugerowane", "custom": "Własna kwota", "invalid": "Wpisz prawidłową kwotę z maksymalnie dwoma miejscami dziesiętnymi.", "below": "Minimalna kwota to {{amount}}.", "above": "Maksymalna kwota to {{amount}}.", "bounds": "Wybierz od {{minimum}} do {{maximum}}.", "credits": "Wartość kredytów", "adjustment": "Korekta przetwarzania", "subtotal": "Suma przed podatkiem", "tax-note": "Stripe obliczy należny podatek podczas płatności.", "cancel": "Anuluj", "continue": "Przejdź do Stripe", "pending": "Otwieranie płatności…", "close": "Zamknij" }, "entitlement": { "plan": { "current": "Obecny plan" }, "status": { "trial": "Okres próbny", "trial-on": "Okres próbny kończy się {{date}}", "past-due": "Zaległa płatność", "cancel-scheduled": "Anulowanie zaplanowane", "cancel-scheduled-on": "Zostanie anulowany {{date}}", "paused": "Wstrzymany", "paused-on": "Wstrzymany od {{date}}", "suspended": "Zawieszony", "free": "Plan darmowy", "renews-on": "Odnawia się {{date}}", "active": "Aktywny", "created": "Oczekuje na aktywację", "canceled": "Anulowany", "expired": "Wygasł", "ended": "Zakończony", "blocked": "Zablokowany" }, "limit": { "usage": "{{used}} z {{limit}}", "usage-unit": "{{used}} z {{limit}} {{unit}}", "not-included": "Brak w planie", "exhausted": "Limit wyczerpany", "resets-on": "Odnowienie {{date}}" }, "promo": { "free-until": "Bezpłatnie do {{date}}", "grandfathered": "Zachowane w Twoim planie", "ended": "Promocja zakończona" }, "capability": { "included": "W planie", "not-included": "Brak w planie" } } }
|
|
1
|
+
{ "amount-checkout": { "title": "Dodaj kredyty", "description": "Wybierz wartość netto kredytów dla organizacji.", "presets": "Kwoty sugerowane", "custom": "Własna kwota", "invalid": "Wpisz prawidłową kwotę z maksymalnie dwoma miejscami dziesiętnymi.", "below": "Minimalna kwota to {{amount}}.", "above": "Maksymalna kwota to {{amount}}.", "bounds": "Wybierz od {{minimum}} do {{maximum}}.", "credits": "Wartość kredytów", "adjustment": "Korekta przetwarzania", "subtotal": "Suma przed podatkiem", "tax-note": "Stripe obliczy należny podatek podczas płatności.", "cancel": "Anuluj", "continue": "Przejdź do Stripe", "pending": "Otwieranie płatności…", "close": "Zamknij" }, "entitlement": { "plan": { "current": "Obecny plan" }, "status": { "trial": "Okres próbny", "trial-on": "Okres próbny kończy się {{date}}", "past-due": "Zaległa płatność", "cancel-scheduled": "Anulowanie zaplanowane", "cancel-scheduled-on": "Zostanie anulowany {{date}}", "paused": "Wstrzymany", "paused-on": "Wstrzymany od {{date}}", "suspended": "Zawieszony", "free": "Plan darmowy", "renews-on": "Odnawia się {{date}}", "active": "Aktywny", "created": "Oczekuje na aktywację", "canceled": "Anulowany", "expired": "Wygasł", "ended": "Zakończony", "blocked": "Zablokowany" }, "limit": { "usage": "{{used}} z {{limit}}", "usage-unit": "{{used}} z {{limit}} {{unit}}", "not-included": "Brak w planie", "exhausted": "Limit wyczerpany", "resets-on": "Odnowienie {{date}}" }, "promo": { "free-until": "Bezpłatnie do {{date}}", "grandfathered": "Zachowane w Twoim planie", "ended": "Promocja zakończona" }, "capability": { "included": "W planie", "not-included": "Brak w planie" } }, "estimate": { "country": "Kraj rozliczeniowy", "country-placeholder": "Wybierz kraj", "tax-type": { "vat": "VAT", "gst": "GST", "sales-tax": "Podatek od sprzedaży", "tax": "Podatek" }, "rate": "{{type}} ({{percentage}}%)", "total": "Szacowana suma", "converted-note": "Przeliczone po bieżącym kursie Stripe; dokładna kwota i ewentualna opłata za przewalutowanie zostaną potwierdzone podczas płatności.", "status": { "taxed": "Podatek jest wliczony w szacowaną sumę poniżej.", "reverse-charge": "Odwrotne obciążenie — Twój numer VAT oznacza, że sam rozliczasz ten podatek, więc nic nie doliczamy.", "none": "Dla tego kraju podatek nie ma zastosowania.", "at-checkout": "Dokładny podatek zostanie obliczony podczas płatności.", "location-required": "Wybierz kraj rozliczeniowy, aby zobaczyć szacunek." }, "loading": "Sprawdzanie podatku…", "failed": "Nie udało się teraz sprawdzić podatku." } }
|
package/build/i18n/ru.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "amount-checkout": { "title": "Пополнить кредиты", "description": "Выберите чистую стоимость кредитов для организации.", "presets": "Готовые суммы", "custom": "Своя сумма", "invalid": "Введите корректную сумму, не более двух знаков после запятой.", "below": "Минимальная сумма — {{amount}}.", "above": "Максимальная сумма — {{amount}}.", "bounds": "Выберите сумму от {{minimum}} до {{maximum}}.", "credits": "Стоимость кредитов", "adjustment": "Корректировка обработки", "subtotal": "Сумма до налога", "tax-note": "Stripe рассчитает применимый налог при оплате.", "cancel": "Отмена", "continue": "Перейти в Stripe", "pending": "Открываем оплату…", "close": "Закрыть" }, "entitlement": { "plan": { "current": "Текущий план" }, "status": { "trial": "Пробный период", "trial-on": "Пробный период до {{date}}", "past-due": "Платёж просрочен", "cancel-scheduled": "Отмена запланирована", "cancel-scheduled-on": "Отменяется {{date}}", "paused": "На паузе", "paused-on": "На паузе с {{date}}", "suspended": "Приостановлен", "free": "Бесплатный план", "renews-on": "Продлевается {{date}}", "active": "Активен", "created": "Ожидает активации", "canceled": "Отменён", "expired": "Истёк", "ended": "Завершён", "blocked": "Заблокирован" }, "limit": { "usage": "{{used}} из {{limit}}", "usage-unit": "{{used}} из {{limit}} {{unit}}", "not-included": "Не входит в план", "exhausted": "Лимит исчерпан", "resets-on": "Обновится {{date}}" }, "promo": { "free-until": "Бесплатно до {{date}}", "grandfathered": "Сохранено для вашего плана", "ended": "Акция завершена" }, "capability": { "included": "Входит в план", "not-included": "Не входит в план" } } }
|
|
1
|
+
{ "amount-checkout": { "title": "Пополнить кредиты", "description": "Выберите чистую стоимость кредитов для организации.", "presets": "Готовые суммы", "custom": "Своя сумма", "invalid": "Введите корректную сумму, не более двух знаков после запятой.", "below": "Минимальная сумма — {{amount}}.", "above": "Максимальная сумма — {{amount}}.", "bounds": "Выберите сумму от {{minimum}} до {{maximum}}.", "credits": "Стоимость кредитов", "adjustment": "Корректировка обработки", "subtotal": "Сумма до налога", "tax-note": "Stripe рассчитает применимый налог при оплате.", "cancel": "Отмена", "continue": "Перейти в Stripe", "pending": "Открываем оплату…", "close": "Закрыть" }, "entitlement": { "plan": { "current": "Текущий план" }, "status": { "trial": "Пробный период", "trial-on": "Пробный период до {{date}}", "past-due": "Платёж просрочен", "cancel-scheduled": "Отмена запланирована", "cancel-scheduled-on": "Отменяется {{date}}", "paused": "На паузе", "paused-on": "На паузе с {{date}}", "suspended": "Приостановлен", "free": "Бесплатный план", "renews-on": "Продлевается {{date}}", "active": "Активен", "created": "Ожидает активации", "canceled": "Отменён", "expired": "Истёк", "ended": "Завершён", "blocked": "Заблокирован" }, "limit": { "usage": "{{used}} из {{limit}}", "usage-unit": "{{used}} из {{limit}} {{unit}}", "not-included": "Не входит в план", "exhausted": "Лимит исчерпан", "resets-on": "Обновится {{date}}" }, "promo": { "free-until": "Бесплатно до {{date}}", "grandfathered": "Сохранено для вашего плана", "ended": "Акция завершена" }, "capability": { "included": "Входит в план", "not-included": "Не входит в план" } }, "estimate": { "country": "Страна плательщика", "country-placeholder": "Выберите страну", "tax-type": { "vat": "НДС", "gst": "GST", "sales-tax": "Налог с продаж", "tax": "Налог" }, "rate": "{{type}} ({{percentage}}%)", "total": "Примерная сумма", "converted-note": "Пересчитано по текущему курсу Stripe; точная сумма и возможная комиссия за конвертацию будут подтверждены при оплате.", "status": { "taxed": "Налог уже включён в примерную сумму ниже.", "reverse-charge": "Обратное начисление — при указанном номере НДС вы учитываете налог самостоятельно, поэтому мы ничего не добавляем.", "none": "Для этой страны налог не применяется.", "at-checkout": "Точный налог будет рассчитан при оплате.", "location-required": "Выберите страну плательщика, чтобы увидеть расчёт." }, "loading": "Проверяем налог…", "failed": "Сейчас не удалось проверить налог." } }
|
package/build/i18n/uk.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "amount-checkout": { "title": "Поповнити кредити", "description": "Виберіть чисту вартість кредитів для організації.", "presets": "Готові суми", "custom": "Власна сума", "invalid": "Введіть коректну суму, не більше двох знаків після коми.", "below": "Мінімальна сума — {{amount}}.", "above": "Максимальна сума — {{amount}}.", "bounds": "Виберіть суму від {{minimum}} до {{maximum}}.", "credits": "Вартість кредитів", "adjustment": "Коригування обробки", "subtotal": "Сума до податку", "tax-note": "Stripe розрахує застосовний податок під час оплати.", "cancel": "Скасувати", "continue": "Перейти до Stripe", "pending": "Відкриваємо оплату…", "close": "Закрити" }, "entitlement": { "plan": { "current": "Поточний план" }, "status": { "trial": "Пробний період", "trial-on": "Пробний період до {{date}}", "past-due": "Платіж прострочено", "cancel-scheduled": "Скасування заплановано", "cancel-scheduled-on": "Скасовується {{date}}", "paused": "На паузі", "paused-on": "На паузі з {{date}}", "suspended": "Призупинено", "free": "Безкоштовний план", "renews-on": "Подовжується {{date}}", "active": "Активний", "created": "Очікує активації", "canceled": "Скасовано", "expired": "Термін минув", "ended": "Завершено", "blocked": "Заблоковано" }, "limit": { "usage": "{{used}} з {{limit}}", "usage-unit": "{{used}} з {{limit}} {{unit}}", "not-included": "Не входить до плану", "exhausted": "Ліміт вичерпано", "resets-on": "Оновиться {{date}}" }, "promo": { "free-until": "Безкоштовно до {{date}}", "grandfathered": "Збережено для вашого плану", "ended": "Акцію завершено" }, "capability": { "included": "Входить до плану", "not-included": "Не входить до плану" } } }
|
|
1
|
+
{ "amount-checkout": { "title": "Поповнити кредити", "description": "Виберіть чисту вартість кредитів для організації.", "presets": "Готові суми", "custom": "Власна сума", "invalid": "Введіть коректну суму, не більше двох знаків після коми.", "below": "Мінімальна сума — {{amount}}.", "above": "Максимальна сума — {{amount}}.", "bounds": "Виберіть суму від {{minimum}} до {{maximum}}.", "credits": "Вартість кредитів", "adjustment": "Коригування обробки", "subtotal": "Сума до податку", "tax-note": "Stripe розрахує застосовний податок під час оплати.", "cancel": "Скасувати", "continue": "Перейти до Stripe", "pending": "Відкриваємо оплату…", "close": "Закрити" }, "entitlement": { "plan": { "current": "Поточний план" }, "status": { "trial": "Пробний період", "trial-on": "Пробний період до {{date}}", "past-due": "Платіж прострочено", "cancel-scheduled": "Скасування заплановано", "cancel-scheduled-on": "Скасовується {{date}}", "paused": "На паузі", "paused-on": "На паузі з {{date}}", "suspended": "Призупинено", "free": "Безкоштовний план", "renews-on": "Подовжується {{date}}", "active": "Активний", "created": "Очікує активації", "canceled": "Скасовано", "expired": "Термін минув", "ended": "Завершено", "blocked": "Заблоковано" }, "limit": { "usage": "{{used}} з {{limit}}", "usage-unit": "{{used}} з {{limit}} {{unit}}", "not-included": "Не входить до плану", "exhausted": "Ліміт вичерпано", "resets-on": "Оновиться {{date}}" }, "promo": { "free-until": "Безкоштовно до {{date}}", "grandfathered": "Збережено для вашого плану", "ended": "Акцію завершено" }, "capability": { "included": "Входить до плану", "not-included": "Не входить до плану" } }, "estimate": { "country": "Країна платника", "country-placeholder": "Виберіть країну", "tax-type": { "vat": "ПДВ", "gst": "GST", "sales-tax": "Податок з продажів", "tax": "Податок" }, "rate": "{{type}} ({{percentage}}%)", "total": "Орієнтовна сума", "converted-note": "Перераховано за поточним курсом Stripe; точна сума та можлива комісія за конвертацію будуть підтверджені під час оплати.", "status": { "taxed": "Податок уже включено до орієнтовної суми нижче.", "reverse-charge": "Зворотне нарахування — за вказаним номером ПДВ ви обліковуєте цей податок самостійно, тож ми нічого не додаємо.", "none": "Для цієї країни податок не застосовується.", "at-checkout": "Точний податок буде розраховано під час оплати.", "location-required": "Виберіть країну платника, щоб побачити розрахунок." }, "loading": "Перевіряємо податок…", "failed": "Зараз не вдалося перевірити податок." } }
|
package/build/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export * from './service.js';
|
|
|
4
4
|
export * from './hooks.js';
|
|
5
5
|
export * from './amount.js';
|
|
6
6
|
export * from './dialog.js';
|
|
7
|
+
export * from './estimate.js';
|
|
8
|
+
export * from './estimate-summary.js';
|
|
7
9
|
export * from './selectors.js';
|
|
8
10
|
export * from './entitlement.js';
|
|
9
11
|
export * from './plan-card.js';
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAClB,mBAAmB,YAAY,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAClB,mBAAmB,YAAY,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -3,6 +3,8 @@ export * from './service.js';
|
|
|
3
3
|
export * from './hooks.js';
|
|
4
4
|
export * from './amount.js';
|
|
5
5
|
export * from './dialog.js';
|
|
6
|
+
export * from './estimate.js';
|
|
7
|
+
export * from './estimate-summary.js';
|
|
6
8
|
export * from './selectors.js';
|
|
7
9
|
export * from './entitlement.js';
|
|
8
10
|
export * from './plan-card.js';
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAElB,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAElB,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA"}
|
package/build/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { CallOptions, RequestShape } from '@owlmeans/entrypoint';
|
|
3
|
-
import type { AmountCheckoutPolicy, CapabilityView, CreateCheckoutResponse, EntitlementPlanView, LimitView, PortalLinkBody } from '@owlmeans/payment';
|
|
3
|
+
import type { AmountCheckoutPolicy, CapabilityView, CreateCheckoutResponse, EntitlementPlanView, LimitView, PortalLinkBody, PriceEstimate, PriceEstimateBody } from '@owlmeans/payment';
|
|
4
4
|
export type CheckoutResult = CreateCheckoutResponse;
|
|
5
5
|
export interface AmountCheckoutDialogProps {
|
|
6
6
|
open: boolean;
|
|
@@ -8,7 +8,29 @@ export interface AmountCheckoutDialogProps {
|
|
|
8
8
|
policy: AmountCheckoutPolicy;
|
|
9
9
|
pending?: boolean;
|
|
10
10
|
onConfirm: (amountMinor: number) => Promise<void> | void;
|
|
11
|
+
/** A live tax/currency estimate for the credit line — absent: the plain "tax at checkout" note. */
|
|
12
|
+
estimate?: PriceEstimateControl;
|
|
11
13
|
}
|
|
14
|
+
/** What `usePriceEstimate` returns: the latest answer, the chosen country, and its lifecycle. */
|
|
15
|
+
export interface PriceEstimateControl {
|
|
16
|
+
estimate: PriceEstimate | null;
|
|
17
|
+
/** ISO 3166-1 alpha-2, or `''` before a country is known. */
|
|
18
|
+
country: string;
|
|
19
|
+
/** A request is in flight — including the first, silent one. */
|
|
20
|
+
loading: boolean;
|
|
21
|
+
/** The last request failed; `estimate` is the previous good answer, if any. */
|
|
22
|
+
failed: boolean;
|
|
23
|
+
onCountryChange: (country: string) => void;
|
|
24
|
+
}
|
|
25
|
+
/** The request of a price-estimate protocol: its body is a `PriceEstimateBody`. */
|
|
26
|
+
export type PriceEstimateRequest = RequestShape & {
|
|
27
|
+
body: PriceEstimateBody;
|
|
28
|
+
};
|
|
29
|
+
type PriceEstimateCall<Request extends PriceEstimateRequest> = Omit<Request, 'body'> & {
|
|
30
|
+
body?: Omit<Request['body'], 'country'>;
|
|
31
|
+
};
|
|
32
|
+
/** A price-estimate protocol's call arguments with `body.country` left to `usePriceEstimate`. */
|
|
33
|
+
export type PriceEstimateArguments<Request extends PriceEstimateRequest> = {} extends PriceEstimateCall<Request> ? [request?: PriceEstimateCall<Request> & CallOptions] : [request: PriceEstimateCall<Request> & CallOptions];
|
|
12
34
|
/** A limit row plus what a UI derives from it. */
|
|
13
35
|
export interface LimitStatus extends LimitView {
|
|
14
36
|
/** No room for one more unit — including a limit that is not included (`limit: 0`). */
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACrE,OAAO,KAAK,EACV,oBAAoB,EAAE,cAAc,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,SAAS,EAC5F,cAAc,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-payment",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Protocol-bound payment hooks and themed amount checkout UI for OwlMeans web applications.",
|
|
6
6
|
"type": "module",
|
|
@@ -23,16 +23,17 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@owlmeans/client-i18n": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/client-payment": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/i18n": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/payment": "^0.1.18-rc.
|
|
26
|
+
"@owlmeans/client-i18n": "^0.1.18-rc.32",
|
|
27
|
+
"@owlmeans/client-payment": "^0.1.18-rc.38",
|
|
28
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.29",
|
|
29
|
+
"@owlmeans/i18n": "^0.1.18-rc.29",
|
|
30
|
+
"@owlmeans/payment": "^0.1.18-rc.31"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@radix-ui/react-dialog": "*",
|
|
34
34
|
"@radix-ui/react-label": "*",
|
|
35
35
|
"@radix-ui/react-progress": "*",
|
|
36
|
+
"@radix-ui/react-select": "*",
|
|
36
37
|
"@radix-ui/react-slot": "*",
|
|
37
38
|
"class-variance-authority": "*",
|
|
38
39
|
"clsx": "*",
|
|
@@ -43,10 +44,11 @@
|
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@owlmeans/dep-config": "workspace:*",
|
|
46
|
-
"@owlmeans/test-ui": "^0.1.18-rc.
|
|
47
|
+
"@owlmeans/test-ui": "^0.1.18-rc.31",
|
|
47
48
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
48
49
|
"@radix-ui/react-label": "^2.1.0",
|
|
49
50
|
"@radix-ui/react-progress": "^1.1.0",
|
|
51
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
50
52
|
"@radix-ui/react-slot": "^1.1.0",
|
|
51
53
|
"@vitejs/plugin-react": "^6.0.3",
|
|
52
54
|
"@types/bun": "^1.4.0",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as SelectPrimitive from '@radix-ui/react-select'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
export const Select = (props: React.ComponentProps<typeof SelectPrimitive.Root>) => <SelectPrimitive.Root {...props} />
|
|
6
|
+
export const SelectValue = (props: React.ComponentProps<typeof SelectPrimitive.Value>) => <SelectPrimitive.Value {...props} />
|
|
7
|
+
|
|
8
|
+
export const SelectTrigger = ({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger>) => (
|
|
9
|
+
<SelectPrimitive.Trigger
|
|
10
|
+
className={cn(
|
|
11
|
+
'border-input data-[placeholder]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50',
|
|
12
|
+
'flex h-9 w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs',
|
|
13
|
+
'outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
<SelectPrimitive.Icon asChild>
|
|
20
|
+
<span aria-hidden="true" className="text-muted-foreground shrink-0">▾</span>
|
|
21
|
+
</SelectPrimitive.Icon>
|
|
22
|
+
</SelectPrimitive.Trigger>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
export const SelectContent = ({ className, children, position = 'popper', ...props }: React.ComponentProps<typeof SelectPrimitive.Content>) => (
|
|
26
|
+
<SelectPrimitive.Portal>
|
|
27
|
+
<SelectPrimitive.Content
|
|
28
|
+
position={position}
|
|
29
|
+
className={cn(
|
|
30
|
+
'bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-y-auto rounded-md border shadow-md',
|
|
31
|
+
position === 'popper' && 'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<SelectPrimitive.ScrollUpButton className="flex items-center justify-center py-1">
|
|
37
|
+
<span aria-hidden="true">▴</span>
|
|
38
|
+
</SelectPrimitive.ScrollUpButton>
|
|
39
|
+
<SelectPrimitive.Viewport className={cn('p-1', position === 'popper' && 'w-full min-w-[var(--radix-select-trigger-width)]')}>
|
|
40
|
+
{children}
|
|
41
|
+
</SelectPrimitive.Viewport>
|
|
42
|
+
<SelectPrimitive.ScrollDownButton className="flex items-center justify-center py-1">
|
|
43
|
+
<span aria-hidden="true">▾</span>
|
|
44
|
+
</SelectPrimitive.ScrollDownButton>
|
|
45
|
+
</SelectPrimitive.Content>
|
|
46
|
+
</SelectPrimitive.Portal>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
export const SelectItem = ({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) => (
|
|
50
|
+
<SelectPrimitive.Item
|
|
51
|
+
className={cn(
|
|
52
|
+
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
|
58
|
+
<SelectPrimitive.ItemIndicator aria-hidden="true">✓</SelectPrimitive.ItemIndicator>
|
|
59
|
+
</span>
|
|
60
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
61
|
+
</SelectPrimitive.Item>
|
|
62
|
+
)
|
package/src/dialog.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { Input } from '@/components/ui/input'
|
|
9
9
|
import { Label } from '@/components/ui/label'
|
|
10
10
|
import { inputAmount, parseAmountMinor } from './amount.js'
|
|
11
|
+
import { PriceEstimateSummary } from './estimate-summary.js'
|
|
11
12
|
import type { AmountCheckoutDialogProps } from './types.js'
|
|
12
13
|
|
|
13
14
|
const money = (minor: number, currency: string, locale: string): string => new Intl.NumberFormat(locale, {
|
|
@@ -15,7 +16,7 @@ const money = (minor: number, currency: string, locale: string): string => new I
|
|
|
15
16
|
}).format(minor / 100)
|
|
16
17
|
|
|
17
18
|
export const AmountCheckoutDialog = ({
|
|
18
|
-
open, onOpenChange, policy, pending = false, onConfirm,
|
|
19
|
+
open, onOpenChange, policy, pending = false, onConfirm, estimate,
|
|
19
20
|
}: AmountCheckoutDialogProps) => {
|
|
20
21
|
const t = useI18nLib('web-payment', 'amount-checkout')
|
|
21
22
|
const [locale] = useLanguage()
|
|
@@ -73,7 +74,9 @@ export const AmountCheckoutDialog = ({
|
|
|
73
74
|
<dt className="text-muted-foreground">{t('adjustment')}</dt><dd>{valid ? money(adjustmentMinor, policy.currency, locale) : '—'}</dd>
|
|
74
75
|
<dt className="font-medium">{t('subtotal')}</dt><dd className="font-medium">{valid ? money(chargeMinor, policy.currency, locale) : '—'}</dd>
|
|
75
76
|
</dl>
|
|
76
|
-
|
|
77
|
+
{estimate != null
|
|
78
|
+
? valid && <PriceEstimateSummary control={estimate} subtotalMinor={chargeMinor} currency={policy.currency} />
|
|
79
|
+
: <p className="text-muted-foreground text-xs">{t('tax-note')}</p>}
|
|
77
80
|
</div>
|
|
78
81
|
<DialogFooter>
|
|
79
82
|
<Button type="button" variant="outline" disabled={pending} onClick={() => onOpenChange(false)}>{t('cancel')}</Button>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small keyed TTL cache with in-flight de-duplication — the client-side twin of
|
|
3
|
+
* `@owlmeans/server-payment`'s `EstimateCache`, so opening the credit dialog twice in a row (or two
|
|
4
|
+
* mounts asking for the same plan/country) never issues a second request while the first is still
|
|
5
|
+
* on the wire, and a settled answer is reused for its TTL. A rejected fetch is never cached: the
|
|
6
|
+
* next call simply tries again.
|
|
7
|
+
*/
|
|
8
|
+
export class EstimateCache<T> {
|
|
9
|
+
private readonly store = new Map<string, { value: T, expiresAt: number }>()
|
|
10
|
+
private readonly inflight = new Map<string, Promise<T>>()
|
|
11
|
+
|
|
12
|
+
get(key: string): T | undefined {
|
|
13
|
+
const hit = this.store.get(key)
|
|
14
|
+
if (hit == null || hit.expiresAt <= Date.now()) {
|
|
15
|
+
return undefined
|
|
16
|
+
}
|
|
17
|
+
return hit.value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async load(key: string, ttlMs: number, factory: () => Promise<T>): Promise<T> {
|
|
21
|
+
const hit = this.get(key)
|
|
22
|
+
if (hit !== undefined) {
|
|
23
|
+
return hit
|
|
24
|
+
}
|
|
25
|
+
const pending = this.inflight.get(key)
|
|
26
|
+
if (pending != null) {
|
|
27
|
+
return pending
|
|
28
|
+
}
|
|
29
|
+
const promise = factory().then(value => {
|
|
30
|
+
this.store.set(key, { value, expiresAt: Date.now() + ttlMs })
|
|
31
|
+
this.inflight.delete(key)
|
|
32
|
+
return value
|
|
33
|
+
}).catch(error => {
|
|
34
|
+
this.inflight.delete(key)
|
|
35
|
+
throw error
|
|
36
|
+
})
|
|
37
|
+
this.inflight.set(key, promise)
|
|
38
|
+
return promise
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Fragment, useMemo } from 'react'
|
|
2
|
+
import { useI18nLib, useLanguage } from '@owlmeans/client-i18n'
|
|
3
|
+
import { COUNTRY_CODES, TaxEstimateStatus, estimateOf } from '@owlmeans/payment'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { Label } from '@/components/ui/label'
|
|
6
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
7
|
+
import type { PriceEstimateControl } from './types.js'
|
|
8
|
+
|
|
9
|
+
const money = (minor: number, currency: string, locale: string): string => new Intl.NumberFormat(locale, {
|
|
10
|
+
style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2,
|
|
11
|
+
}).format(minor / 100)
|
|
12
|
+
|
|
13
|
+
const localMoney = (amount: number, currency: string, locale: string): string => {
|
|
14
|
+
try {
|
|
15
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency: currency.toUpperCase() }).format(amount)
|
|
16
|
+
} catch {
|
|
17
|
+
return `${amount.toFixed(2)} ${currency.toUpperCase()}`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The country names a `<Select>` offers, in the viewer's own language and alphabetical order. */
|
|
22
|
+
const useCountryOptions = (locale: string): Array<{ code: string, name: string }> => useMemo(() => {
|
|
23
|
+
let names: Intl.DisplayNames | null = null
|
|
24
|
+
try { names = new Intl.DisplayNames([locale], { type: 'region' }) } catch { names = null }
|
|
25
|
+
const collator = new Intl.Collator(locale)
|
|
26
|
+
return COUNTRY_CODES
|
|
27
|
+
.map(code => ({ code, name: names?.of(code) ?? code }))
|
|
28
|
+
.sort((a, b) => collator.compare(a.name, b.name))
|
|
29
|
+
}, [locale])
|
|
30
|
+
|
|
31
|
+
export interface CountrySelectProps {
|
|
32
|
+
/** ISO 3166-1 alpha-2, or `''` for the placeholder. */
|
|
33
|
+
value: string
|
|
34
|
+
onChange: (country: string) => void
|
|
35
|
+
/** Defaults to the `estimate.country` string (also the field's own label). */
|
|
36
|
+
label?: string
|
|
37
|
+
id?: string
|
|
38
|
+
className?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The billing-country picker alone — the piece `PriceEstimateSummary` composes for a single,
|
|
43
|
+
* self-contained estimate, and what a shared picker (several estimates, one country — a plan
|
|
44
|
+
* comparison table) renders once and drives every `usePriceEstimate({ country, onCountryChange })`
|
|
45
|
+
* with. Country names are the viewer's own language, alphabetically ordered.
|
|
46
|
+
*/
|
|
47
|
+
export const CountrySelect = ({ value, onChange, label, id = 'price-estimate-country', className }: CountrySelectProps) => {
|
|
48
|
+
const t = useI18nLib('web-payment', 'estimate')
|
|
49
|
+
const [locale] = useLanguage()
|
|
50
|
+
const countries = useCountryOptions(locale)
|
|
51
|
+
const text = label ?? t('country')
|
|
52
|
+
|
|
53
|
+
return <div className={cn('grid gap-2', className)}>
|
|
54
|
+
<Label htmlFor={id}>{text}</Label>
|
|
55
|
+
{/* Always controlled, `''` included (no `SelectItem` ever has that value) — switching to
|
|
56
|
+
`undefined` once a country is chosen is what trips React's controlled/uncontrolled warning. */}
|
|
57
|
+
<Select value={value} onValueChange={onChange}>
|
|
58
|
+
<SelectTrigger id={id} aria-label={text}>
|
|
59
|
+
<SelectValue placeholder={t('country-placeholder')} />
|
|
60
|
+
</SelectTrigger>
|
|
61
|
+
<SelectContent>
|
|
62
|
+
{countries.map(({ code, name }) => <SelectItem key={code} value={code}>{name}</SelectItem>)}
|
|
63
|
+
</SelectContent>
|
|
64
|
+
</Select>
|
|
65
|
+
</div>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface PriceEstimateAmountProps {
|
|
69
|
+
control: Pick<PriceEstimateControl, 'estimate' | 'loading' | 'failed'>
|
|
70
|
+
/** The amount to show tax and a total for — may differ from the estimate's own reference amount. */
|
|
71
|
+
subtotalMinor: number
|
|
72
|
+
currency: string
|
|
73
|
+
/** Appended after the total row, e.g. `"/ month"`. */
|
|
74
|
+
suffix?: string
|
|
75
|
+
className?: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What Stripe Tax says at the estimate's country, with NO country picker of its own: the rate(s),
|
|
80
|
+
* an estimated total, and one sentence for whichever status leaves no number to trust (reverse
|
|
81
|
+
* charge, no tax, "at checkout", "choose a country"). Several of these may share one
|
|
82
|
+
* `CountrySelect` (a plan comparison table).
|
|
83
|
+
*
|
|
84
|
+
* A country whose currency differs from `currency` (Adaptive Pricing found a rate) shows the tax
|
|
85
|
+
* and the total in THAT currency instead, marked `≈` throughout — never both currencies at once,
|
|
86
|
+
* which would read as two different prices for the same line. The rate percentage itself is
|
|
87
|
+
* currency-agnostic and always shows as Stripe stated it.
|
|
88
|
+
*
|
|
89
|
+
* `data-price-estimate` / `data-status` are stable test hooks.
|
|
90
|
+
*/
|
|
91
|
+
export const PriceEstimateAmount = ({ control, subtotalMinor, currency, suffix, className }: PriceEstimateAmountProps) => {
|
|
92
|
+
const t = useI18nLib('web-payment', 'estimate')
|
|
93
|
+
const [locale] = useLanguage()
|
|
94
|
+
const { estimate, loading, failed } = control
|
|
95
|
+
|
|
96
|
+
const derived = estimate != null ? estimateOf(subtotalMinor, estimate) : null
|
|
97
|
+
const status = estimate?.tax.status
|
|
98
|
+
const numeric = status === TaxEstimateStatus.Taxed || status === TaxEstimateStatus.ReverseCharge
|
|
99
|
+
|| status === TaxEstimateStatus.None
|
|
100
|
+
const rateLabel = estimate != null && estimate.tax.rates.length > 0
|
|
101
|
+
? estimate.tax.rates
|
|
102
|
+
.map(rate => t('rate', { type: t(`tax-type.${rate.type}`), percentage: rate.percentage }))
|
|
103
|
+
.join(' + ')
|
|
104
|
+
: null
|
|
105
|
+
|
|
106
|
+
const local = derived?.local
|
|
107
|
+
const taxText = local != null
|
|
108
|
+
? (derived?.taxMinor != null ? localMoney(local.taxAmount, local.currency, locale) : null)
|
|
109
|
+
: (derived?.taxMinor != null ? money(derived.taxMinor, currency, locale) : null)
|
|
110
|
+
const totalText = local != null
|
|
111
|
+
? (derived?.totalMinor != null ? localMoney(local.totalAmount, local.currency, locale) : null)
|
|
112
|
+
: (derived?.totalMinor != null ? money(derived.totalMinor, currency, locale) : null)
|
|
113
|
+
const approx = local != null ? '≈ ' : ''
|
|
114
|
+
|
|
115
|
+
return <div data-price-estimate="" data-status={status ?? 'pending'} className={cn('grid gap-1.5', className)}>
|
|
116
|
+
{estimate == null && loading && <p className="text-muted-foreground text-xs">{t('loading')}</p>}
|
|
117
|
+
{estimate == null && !loading && failed && <p className="text-destructive text-xs">{t('failed')}</p>}
|
|
118
|
+
|
|
119
|
+
{estimate != null && <>
|
|
120
|
+
{numeric && taxText != null && totalText != null && <dl className="grid grid-cols-[1fr_auto] gap-x-4 gap-y-1 text-sm" data-estimate-rows="">
|
|
121
|
+
{status === TaxEstimateStatus.Taxed && rateLabel != null && <Fragment>
|
|
122
|
+
<dt className="text-muted-foreground" data-estimate-rate="">{rateLabel}</dt>
|
|
123
|
+
<dd data-estimate-tax="">{approx}{taxText}</dd>
|
|
124
|
+
</Fragment>}
|
|
125
|
+
<dt className="font-medium">{t('total')}</dt>
|
|
126
|
+
<dd className="font-medium" data-estimate-total="">
|
|
127
|
+
{approx}{totalText}{suffix != null ? ` ${suffix}` : ''}
|
|
128
|
+
</dd>
|
|
129
|
+
</dl>}
|
|
130
|
+
{numeric && local != null && <p className="text-muted-foreground text-xs" data-estimate-converted="">
|
|
131
|
+
{t('converted-note')}
|
|
132
|
+
</p>}
|
|
133
|
+
<p className="text-muted-foreground text-xs" data-estimate-status="">
|
|
134
|
+
{t(`status.${status}`)}
|
|
135
|
+
</p>
|
|
136
|
+
</>}
|
|
137
|
+
</div>
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface PriceEstimateSummaryProps {
|
|
141
|
+
control: PriceEstimateControl
|
|
142
|
+
/** The amount the summary shows tax and a total for — may differ from the estimate's own reference amount. */
|
|
143
|
+
subtotalMinor: number
|
|
144
|
+
currency: string
|
|
145
|
+
/** Appended after the total row, e.g. `"/ month"`. */
|
|
146
|
+
suffix?: string
|
|
147
|
+
className?: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* A self-contained estimate: `CountrySelect` bound to the control's own `country`/`onCountryChange`,
|
|
152
|
+
* plus `PriceEstimateAmount` beneath it. What a single estimate (the credit dialog) wants; several
|
|
153
|
+
* estimates sharing one picker (a plan comparison table) compose the two pieces separately instead.
|
|
154
|
+
*/
|
|
155
|
+
export const PriceEstimateSummary = ({ control, subtotalMinor, currency, suffix, className }: PriceEstimateSummaryProps) => {
|
|
156
|
+
const { country, onCountryChange } = control
|
|
157
|
+
|
|
158
|
+
return <div className={cn('grid gap-3', className)}>
|
|
159
|
+
<CountrySelect value={country} onChange={onCountryChange} />
|
|
160
|
+
<PriceEstimateAmount control={control} subtotalMinor={subtotalMinor} currency={currency} suffix={suffix} />
|
|
161
|
+
</div>
|
|
162
|
+
}
|
package/src/estimate.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import type { RegisteredEntrypoint } from '@owlmeans/entrypoint'
|
|
3
|
+
import type { PriceEstimate } from '@owlmeans/payment'
|
|
4
|
+
import { EstimateCache } from './estimate-cache.js'
|
|
5
|
+
import type { PriceEstimateArguments, PriceEstimateControl, PriceEstimateRequest } from './types.js'
|
|
6
|
+
|
|
7
|
+
const DEFAULT_TTL_MS = 5 * 60_000
|
|
8
|
+
|
|
9
|
+
/** Shared across every mounted dialog/estimate card in the tab — the server already caches per TTL too. */
|
|
10
|
+
const cache = new EstimateCache<PriceEstimate>()
|
|
11
|
+
|
|
12
|
+
export interface UsePriceEstimateOptions {
|
|
13
|
+
/** Fetch (and refetch on a country change) only while `true` — a closed dialog costs nothing. */
|
|
14
|
+
enabled: boolean
|
|
15
|
+
ttlMs?: number
|
|
16
|
+
/**
|
|
17
|
+
* Controls the country from outside (several estimates sharing one picker — a plan comparison
|
|
18
|
+
* table). Given: the hook never owns `useState` for it, never preselects from `source:
|
|
19
|
+
* 'customer'` (the owner does, once, for whichever estimate answers first), and the returned
|
|
20
|
+
* control's `onCountryChange` calls this back instead of an internal setter. Absent (the
|
|
21
|
+
* default): the hook is self-contained, exactly as a standalone dialog needs.
|
|
22
|
+
*/
|
|
23
|
+
country?: string
|
|
24
|
+
onCountryChange?: (country: string) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A live `PriceEstimate` for one protocol/request, with a country the caller may change.
|
|
29
|
+
*
|
|
30
|
+
* Fetches once `enabled` and again on every `onCountryChange`, from a client-side cache keyed by
|
|
31
|
+
* the entry's alias, the request body and the country — a TTL match answers instantly, an identical
|
|
32
|
+
* in-flight request is shared. Uncontrolled (no `opts.country`): the first answer whose `source` is
|
|
33
|
+
* `'customer'` preselects its `country`, once, so a returning buyer sees their own country without
|
|
34
|
+
* picking it. Controlled (`opts.country` given): the country and its preselection are the caller's.
|
|
35
|
+
*/
|
|
36
|
+
export const usePriceEstimate = <Request extends PriceEstimateRequest>(
|
|
37
|
+
entry: RegisteredEntrypoint<Request, PriceEstimate>, opts: UsePriceEstimateOptions,
|
|
38
|
+
...request: PriceEstimateArguments<Request>
|
|
39
|
+
): PriceEstimateControl => {
|
|
40
|
+
const entryRef = useRef(entry)
|
|
41
|
+
entryRef.current = entry
|
|
42
|
+
const base = (request[0] ?? {}) as { body?: Omit<Request['body'], 'country'> } & Record<string, unknown>
|
|
43
|
+
const bodyKey = JSON.stringify(base.body ?? {})
|
|
44
|
+
const controlled = opts.country !== undefined
|
|
45
|
+
|
|
46
|
+
const [internalCountry, setInternalCountry] = useState('')
|
|
47
|
+
const country = controlled ? opts.country! : internalCountry
|
|
48
|
+
const [estimate, setEstimate] = useState<PriceEstimate | null>(null)
|
|
49
|
+
const [loading, setLoading] = useState(false)
|
|
50
|
+
const [failed, setFailed] = useState(false)
|
|
51
|
+
const preselectedRef = useRef(false)
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!opts.enabled) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
let active = true
|
|
58
|
+
const key = `${entryRef.current.alias}:${bodyKey}:${country}`
|
|
59
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS
|
|
60
|
+
setLoading(true)
|
|
61
|
+
setFailed(false)
|
|
62
|
+
cache.load(key, ttlMs, async () => await entryRef.current.call({
|
|
63
|
+
...base, body: { ...(base.body ?? {}), ...(country !== '' ? { country } : {}) },
|
|
64
|
+
} as never)).then(result => {
|
|
65
|
+
if (!active) return
|
|
66
|
+
setEstimate(result)
|
|
67
|
+
setLoading(false)
|
|
68
|
+
if (!controlled && !preselectedRef.current && country === '' && result.source === 'customer' && result.country != null) {
|
|
69
|
+
preselectedRef.current = true
|
|
70
|
+
setInternalCountry(result.country)
|
|
71
|
+
}
|
|
72
|
+
}).catch(() => {
|
|
73
|
+
if (active) {
|
|
74
|
+
setFailed(true)
|
|
75
|
+
setLoading(false)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
return () => { active = false }
|
|
79
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
80
|
+
}, [opts.enabled, opts.ttlMs, bodyKey, country])
|
|
81
|
+
|
|
82
|
+
const onCountryChange = useCallback((next: string) => {
|
|
83
|
+
if (controlled) {
|
|
84
|
+
opts.onCountryChange?.(next)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
preselectedRef.current = true
|
|
88
|
+
setInternalCountry(next)
|
|
89
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
|
+
}, [controlled, opts.onCountryChange])
|
|
91
|
+
|
|
92
|
+
return { estimate, country, loading, failed, onCountryChange }
|
|
93
|
+
}
|