@softure-ai/billing 0.0.0-stage → 0.1.5
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/LICENSE +21 -0
- package/README.md +699 -2
- package/dist/calendar.d.ts +26 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +81 -0
- package/dist/calendar.js.map +1 -0
- package/dist/contract.d.ts +187 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +6 -0
- package/dist/contract.js.map +1 -0
- package/dist/currency-digits.d.ts +3 -0
- package/dist/currency-digits.d.ts.map +1 -0
- package/dist/currency-digits.js +32 -0
- package/dist/currency-digits.js.map +1 -0
- package/dist/entitlement.d.ts +25 -0
- package/dist/entitlement.d.ts.map +1 -0
- package/dist/entitlement.js +75 -0
- package/dist/entitlement.js.map +1 -0
- package/dist/fields.d.ts +25 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +27 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +274 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/invoice.d.ts +29 -0
- package/dist/invoice.d.ts.map +1 -0
- package/dist/invoice.js +35 -0
- package/dist/invoice.js.map +1 -0
- package/dist/mailing/index.d.ts +2 -0
- package/dist/mailing/index.d.ts.map +1 -0
- package/dist/mailing/index.js +4 -0
- package/dist/mailing/index.js.map +1 -0
- package/dist/mailing/reminder-mail.d.ts +50 -0
- package/dist/mailing/reminder-mail.d.ts.map +1 -0
- package/dist/mailing/reminder-mail.js +71 -0
- package/dist/mailing/reminder-mail.js.map +1 -0
- package/dist/manual.d.ts +12 -0
- package/dist/manual.d.ts.map +1 -0
- package/dist/manual.js +22 -0
- package/dist/manual.js.map +1 -0
- package/dist/messages/en.d.ts +176 -0
- package/dist/messages/en.d.ts.map +1 -0
- package/dist/messages/en.js +151 -0
- package/dist/messages/en.js.map +1 -0
- package/dist/messages/index.d.ts +359 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +14 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/pl.d.ts +3 -0
- package/dist/messages/pl.d.ts.map +1 -0
- package/dist/messages/pl.js +151 -0
- package/dist/messages/pl.js.map +1 -0
- package/dist/next/access.d.ts +16 -0
- package/dist/next/access.d.ts.map +1 -0
- package/dist/next/access.js +24 -0
- package/dist/next/access.js.map +1 -0
- package/dist/next/actions.d.ts +24 -0
- package/dist/next/actions.d.ts.map +1 -0
- package/dist/next/actions.js +158 -0
- package/dist/next/actions.js.map +1 -0
- package/dist/next/context.d.ts +4 -0
- package/dist/next/context.d.ts.map +1 -0
- package/dist/next/context.js +17 -0
- package/dist/next/context.js.map +1 -0
- package/dist/next/current-entitlement.d.ts +18 -0
- package/dist/next/current-entitlement.d.ts.map +1 -0
- package/dist/next/current-entitlement.js +26 -0
- package/dist/next/current-entitlement.js.map +1 -0
- package/dist/next/index.d.ts +8 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +12 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/pages.d.ts +24 -0
- package/dist/next/pages.d.ts.map +1 -0
- package/dist/next/pages.js +166 -0
- package/dist/next/pages.js.map +1 -0
- package/dist/next/pricing.d.ts +12 -0
- package/dist/next/pricing.d.ts.map +1 -0
- package/dist/next/pricing.js +20 -0
- package/dist/next/pricing.js.map +1 -0
- package/dist/next/route.d.ts +11 -0
- package/dist/next/route.d.ts.map +1 -0
- package/dist/next/route.js +56 -0
- package/dist/next/route.js.map +1 -0
- package/dist/options.d.ts +85 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +121 -0
- package/dist/options.js.map +1 -0
- package/dist/payment.d.ts +61 -0
- package/dist/payment.d.ts.map +1 -0
- package/dist/payment.js +12 -0
- package/dist/payment.js.map +1 -0
- package/dist/plans.d.ts +20 -0
- package/dist/plans.d.ts.map +1 -0
- package/dist/plans.js +53 -0
- package/dist/plans.js.map +1 -0
- package/dist/price.d.ts +12 -0
- package/dist/price.d.ts.map +1 -0
- package/dist/price.js +37 -0
- package/dist/price.js.map +1 -0
- package/dist/refund.d.ts +70 -0
- package/dist/refund.d.ts.map +1 -0
- package/dist/refund.js +109 -0
- package/dist/refund.js.map +1 -0
- package/dist/reminder.d.ts +30 -0
- package/dist/reminder.d.ts.map +1 -0
- package/dist/reminder.js +34 -0
- package/dist/reminder.js.map +1 -0
- package/dist/schema.d.ts +1023 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +77 -0
- package/dist/schema.js.map +1 -0
- package/dist/scripts/entitlement-scripts.d.ts +17 -0
- package/dist/scripts/entitlement-scripts.d.ts.map +1 -0
- package/dist/scripts/entitlement-scripts.js +167 -0
- package/dist/scripts/entitlement-scripts.js.map +1 -0
- package/dist/scripts/index.d.ts +3 -0
- package/dist/scripts/index.d.ts.map +1 -0
- package/dist/scripts/index.js +5 -0
- package/dist/scripts/index.js.map +1 -0
- package/dist/scripts/plan-scripts.d.ts +19 -0
- package/dist/scripts/plan-scripts.d.ts.map +1 -0
- package/dist/scripts/plan-scripts.js +106 -0
- package/dist/scripts/plan-scripts.js.map +1 -0
- package/dist/server/entitlements.d.ts +69 -0
- package/dist/server/entitlements.d.ts.map +1 -0
- package/dist/server/entitlements.js +202 -0
- package/dist/server/entitlements.js.map +1 -0
- package/dist/server/grants.d.ts +74 -0
- package/dist/server/grants.d.ts.map +1 -0
- package/dist/server/grants.js +174 -0
- package/dist/server/grants.js.map +1 -0
- package/dist/server/health.d.ts +3 -0
- package/dist/server/health.d.ts.map +1 -0
- package/dist/server/health.js +17 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +14 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/options.d.ts +20 -0
- package/dist/server/options.d.ts.map +1 -0
- package/dist/server/options.js +38 -0
- package/dist/server/options.js.map +1 -0
- package/dist/server/payments.d.ts +140 -0
- package/dist/server/payments.d.ts.map +1 -0
- package/dist/server/payments.js +339 -0
- package/dist/server/payments.js.map +1 -0
- package/dist/server/plans.d.ts +50 -0
- package/dist/server/plans.d.ts.map +1 -0
- package/dist/server/plans.js +129 -0
- package/dist/server/plans.js.map +1 -0
- package/dist/server/privacy.d.ts +77 -0
- package/dist/server/privacy.d.ts.map +1 -0
- package/dist/server/privacy.js +110 -0
- package/dist/server/privacy.js.map +1 -0
- package/dist/server/reminders.d.ts +20 -0
- package/dist/server/reminders.d.ts.map +1 -0
- package/dist/server/reminders.js +85 -0
- package/dist/server/reminders.js.map +1 -0
- package/dist/server/requests.d.ts +80 -0
- package/dist/server/requests.d.ts.map +1 -0
- package/dist/server/requests.js +155 -0
- package/dist/server/requests.js.map +1 -0
- package/dist/server/setup.d.ts +9 -0
- package/dist/server/setup.d.ts.map +1 -0
- package/dist/server/setup.js +34 -0
- package/dist/server/setup.js.map +1 -0
- package/dist/server/take-back.d.ts +80 -0
- package/dist/server/take-back.d.ts.map +1 -0
- package/dist/server/take-back.js +138 -0
- package/dist/server/take-back.js.map +1 -0
- package/dist/server/user-id.d.ts +4 -0
- package/dist/server/user-id.d.ts.map +1 -0
- package/dist/server/user-id.js +11 -0
- package/dist/server/user-id.js.map +1 -0
- package/dist/stripe-currency.d.ts +27 -0
- package/dist/stripe-currency.d.ts.map +1 -0
- package/dist/stripe-currency.js +57 -0
- package/dist/stripe-currency.js.map +1 -0
- package/dist/stripe-webhook.d.ts +101 -0
- package/dist/stripe-webhook.d.ts.map +1 -0
- package/dist/stripe-webhook.js +209 -0
- package/dist/stripe-webhook.js.map +1 -0
- package/dist/stripe.d.ts +25 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +116 -0
- package/dist/stripe.js.map +1 -0
- package/dist/ui/access-badge.d.ts +16 -0
- package/dist/ui/access-badge.d.ts.map +1 -0
- package/dist/ui/access-badge.js +43 -0
- package/dist/ui/access-badge.js.map +1 -0
- package/dist/ui/access-notice.d.ts +20 -0
- package/dist/ui/access-notice.d.ts.map +1 -0
- package/dist/ui/access-notice.js +41 -0
- package/dist/ui/access-notice.js.map +1 -0
- package/dist/ui/format.d.ts +11 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +21 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/grant-form.d.ts +18 -0
- package/dist/ui/grant-form.d.ts.map +1 -0
- package/dist/ui/grant-form.js +23 -0
- package/dist/ui/grant-form.js.map +1 -0
- package/dist/ui/grant-history.d.ts +39 -0
- package/dist/ui/grant-history.d.ts.map +1 -0
- package/dist/ui/grant-history.js +36 -0
- package/dist/ui/grant-history.js.map +1 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +12 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/payment-form.d.ts +23 -0
- package/dist/ui/payment-form.d.ts.map +1 -0
- package/dist/ui/payment-form.js +34 -0
- package/dist/ui/payment-form.js.map +1 -0
- package/dist/ui/payment-requests.d.ts +30 -0
- package/dist/ui/payment-requests.d.ts.map +1 -0
- package/dist/ui/payment-requests.js +31 -0
- package/dist/ui/payment-requests.js.map +1 -0
- package/dist/ui/pricing-tiles.d.ts +22 -0
- package/dist/ui/pricing-tiles.d.ts.map +1 -0
- package/dist/ui/pricing-tiles.js +40 -0
- package/dist/ui/pricing-tiles.js.map +1 -0
- package/migrations/0001_create_entitlements.sql +16 -0
- package/migrations/0002_create_payments.sql +30 -0
- package/migrations/0003_record_payment_grants.sql +25 -0
- package/migrations/0004_create_requests_and_grants.sql +58 -0
- package/migrations/0005_record_refunded_amounts.sql +18 -0
- package/migrations/0006_record_request_handover_and_prices.sql +38 -0
- package/migrations/0007_record_failed_refunds.sql +27 -0
- package/migrations/0008_record_request_handover_claims.sql +9 -0
- package/migrations/0009_record_pending_charge_states.sql +16 -0
- package/module.json +23 -0
- package/package.json +81 -4
- package/src/calendar.ts +90 -0
- package/src/contract.ts +181 -0
- package/src/currency-digits.ts +37 -0
- package/src/entitlement.ts +84 -0
- package/src/fields.ts +37 -0
- package/src/index.ts +163 -0
- package/src/invoice.ts +58 -0
- package/src/mailing/index.ts +11 -0
- package/src/mailing/reminder-mail.ts +108 -0
- package/src/manual.ts +31 -0
- package/src/messages/en.ts +150 -0
- package/src/messages/index.ts +18 -0
- package/src/messages/pl.ts +152 -0
- package/src/next/access.tsx +55 -0
- package/src/next/actions.ts +176 -0
- package/src/next/context.ts +18 -0
- package/src/next/current-entitlement.ts +36 -0
- package/src/next/index.ts +11 -0
- package/src/next/next-modules.d.ts +21 -0
- package/src/next/pages.tsx +267 -0
- package/src/next/pricing.tsx +39 -0
- package/src/next/route.ts +57 -0
- package/src/options.ts +128 -0
- package/src/payment.ts +77 -0
- package/src/plans.ts +63 -0
- package/src/price.ts +50 -0
- package/src/refund.ts +135 -0
- package/src/reminder.ts +52 -0
- package/src/schema.ts +86 -0
- package/src/scripts/entitlement-scripts.ts +188 -0
- package/src/scripts/index.ts +11 -0
- package/src/scripts/plan-scripts.ts +143 -0
- package/src/server/entitlements.ts +227 -0
- package/src/server/grants.ts +227 -0
- package/src/server/health.ts +18 -0
- package/src/server/index.ts +83 -0
- package/src/server/options.ts +52 -0
- package/src/server/payments.ts +434 -0
- package/src/server/plans.ts +143 -0
- package/src/server/privacy.ts +189 -0
- package/src/server/reminders.ts +113 -0
- package/src/server/requests.ts +201 -0
- package/src/server/setup.ts +37 -0
- package/src/server/take-back.ts +190 -0
- package/src/server/user-id.ts +12 -0
- package/src/stripe-currency.ts +65 -0
- package/src/stripe-webhook.ts +278 -0
- package/src/stripe.ts +130 -0
- package/src/ui/access-badge.tsx +64 -0
- package/src/ui/access-notice.tsx +73 -0
- package/src/ui/format.ts +25 -0
- package/src/ui/grant-form.tsx +69 -0
- package/src/ui/grant-history.tsx +127 -0
- package/src/ui/index.ts +25 -0
- package/src/ui/payment-form.tsx +111 -0
- package/src/ui/payment-requests.tsx +126 -0
- package/src/ui/pricing-tiles.tsx +105 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { en } from "./en.js";
|
|
2
|
+
|
|
3
|
+
export const pl: typeof en = {
|
|
4
|
+
badge: {
|
|
5
|
+
trial: "Okres próbny",
|
|
6
|
+
paid: "Opłacone",
|
|
7
|
+
lifetime: "Dostęp dożywotni",
|
|
8
|
+
readOnly: "Tylko odczyt",
|
|
9
|
+
daysLeft: { one: "został {count} dzień", few: "zostały {count} dni", many: "zostało {count} dni", other: "zostało {count} dnia" },
|
|
10
|
+
until: "do {date}",
|
|
11
|
+
},
|
|
12
|
+
notice: {
|
|
13
|
+
trialEnding: "Okres próbny kończy się {date}. Wybierz plan, żeby potem dalej wprowadzać zmiany.",
|
|
14
|
+
paidEnding: "Dostęp kończy się {date}. Przedłuż go, żeby potem dalej wprowadzać zmiany.",
|
|
15
|
+
trialEnded: "Okres próbny się skończył. Twoje dane są bezpieczne i nadal możesz je przeglądać; wybierz plan, żeby znów wprowadzać zmiany.",
|
|
16
|
+
paidEnded: "Dostęp się skończył. Twoje dane są bezpieczne i nadal możesz je przeglądać; przedłuż go, żeby znów wprowadzać zmiany.",
|
|
17
|
+
choosePlan: "Wybierz plan",
|
|
18
|
+
renew: "Przedłuż dostęp",
|
|
19
|
+
},
|
|
20
|
+
reminderMail: {
|
|
21
|
+
trialEnding: {
|
|
22
|
+
subject: "Okres próbny kończy się {date}",
|
|
23
|
+
body: "Okres próbny kończy się {date}. Wybierz plan, żeby potem dalej wprowadzać zmiany; Twoje dane i tak pozostaną bezpieczne.",
|
|
24
|
+
},
|
|
25
|
+
paidEnding: {
|
|
26
|
+
subject: "Dostęp kończy się {date}",
|
|
27
|
+
body: "Dostęp kończy się {date}. Przedłuż go, żeby potem dalej wprowadzać zmiany; Twoje dane i tak pozostaną bezpieczne.",
|
|
28
|
+
},
|
|
29
|
+
trialEnded: {
|
|
30
|
+
subject: "Okres próbny się skończył",
|
|
31
|
+
body: "Okres próbny się skończył. Twoje dane są bezpieczne i nadal możesz je przeglądać; wybierz plan, żeby znów wprowadzać zmiany.",
|
|
32
|
+
},
|
|
33
|
+
paidEnded: {
|
|
34
|
+
subject: "Dostęp się skończył",
|
|
35
|
+
body: "Dostęp się skończył. Twoje dane są bezpieczne i nadal możesz je przeglądać; przedłuż go, żeby znów wprowadzać zmiany.",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
pricing: {
|
|
39
|
+
period: {
|
|
40
|
+
day: { one: "za dzień", few: "za {count} dni", many: "za {count} dni", other: "za {count} dnia" },
|
|
41
|
+
week: { one: "za tydzień", few: "za {count} tygodnie", many: "za {count} tygodni", other: "za {count} tygodnia" },
|
|
42
|
+
month: { one: "za miesiąc", few: "za {count} miesiące", many: "za {count} miesięcy", other: "za {count} miesiąca" },
|
|
43
|
+
year: { one: "za rok", few: "za {count} lata", many: "za {count} lat", other: "za {count} roku" },
|
|
44
|
+
lifetime: "płatność jednorazowa",
|
|
45
|
+
},
|
|
46
|
+
featured: "Polecany",
|
|
47
|
+
choose: "Wybierz {plan}",
|
|
48
|
+
empty: "Nie ma jeszcze dostępnych planów.",
|
|
49
|
+
},
|
|
50
|
+
payment: {
|
|
51
|
+
title: "Wybierz plan",
|
|
52
|
+
lead: "Wybierz plan dla siebie. Płatny dostęp zaczyna się, gdy skończy się obecny.",
|
|
53
|
+
plansLabel: "Plany",
|
|
54
|
+
orderTitle: "Twoje zamówienie",
|
|
55
|
+
orderLead: "{plan}, {price} {period}",
|
|
56
|
+
changePlan: "Wybierz inny plan",
|
|
57
|
+
invoiceLead: "Fakturę wyślemy na adres {email}. Dostęp zacznie się po jej opłaceniu.",
|
|
58
|
+
fields: {
|
|
59
|
+
name: "Imię i nazwisko lub firma",
|
|
60
|
+
taxId: "NIP (opcjonalnie)",
|
|
61
|
+
address: "Adres do faktury",
|
|
62
|
+
},
|
|
63
|
+
requestInvoice: "Poproś o fakturę",
|
|
64
|
+
checkout: "Przejdź do płatności",
|
|
65
|
+
pending: "Wysyłanie…",
|
|
66
|
+
requested: "Dziękujemy! Prośba o fakturę za {plan} została wysłana. Fakturę wyślemy na adres {email}; dostęp zacznie się po jej opłaceniu.",
|
|
67
|
+
checkoutSuccess: "Dziękujemy za płatność! Dostęp zaktualizujemy, gdy płatność zostanie potwierdzona, zwykle w ciągu minuty.",
|
|
68
|
+
checkoutCancelled: "Płatność została anulowana i nic nie pobrano. Możesz spróbować ponownie albo wybrać inny plan.",
|
|
69
|
+
lifetime: "Masz dostęp dożywotni: nie ma już za co płacić.",
|
|
70
|
+
},
|
|
71
|
+
admin: {
|
|
72
|
+
title: "Nadaj dostęp",
|
|
73
|
+
lead: "Nadaj plan kontu, gdy wpłynie płatność. Płatny okres zaczyna się, gdy skończy się obecny dostęp konta.",
|
|
74
|
+
email: "E-mail konta",
|
|
75
|
+
plan: "Plan",
|
|
76
|
+
submit: "Nadaj plan",
|
|
77
|
+
pending: "Nadawanie…",
|
|
78
|
+
granted: "{email} ma teraz {plan}.",
|
|
79
|
+
grantedUntil: "{email} ma teraz {plan}, z dostępem do {date}.",
|
|
80
|
+
noPlans: "Nie ma planów do nadania: dodaj je w billing({ plans }).",
|
|
81
|
+
requests: {
|
|
82
|
+
title: "Prośby o fakturę",
|
|
83
|
+
lead: "Prośby wysłane ze strony płatności. Nadaj plan, gdy faktura zostanie opłacona, albo odrzuć prośbę.",
|
|
84
|
+
empty: "Brak otwartych próśb.",
|
|
85
|
+
line: "{email} – {plan}",
|
|
86
|
+
requestedOn: "Wysłana {date}",
|
|
87
|
+
price: "Cena: {price}",
|
|
88
|
+
invoice: "Faktura na: {name}, {address}",
|
|
89
|
+
taxId: "NIP: {taxId}",
|
|
90
|
+
noInvoice: "Bez danych do faktury",
|
|
91
|
+
grant: "Nadaj",
|
|
92
|
+
granting: "Nadawanie…",
|
|
93
|
+
dismiss: "Odrzuć",
|
|
94
|
+
dismissing: "Odrzucanie…",
|
|
95
|
+
history: "Historia",
|
|
96
|
+
grantLabel: "Nadaj {plan} dla {email}",
|
|
97
|
+
dismissLabel: "Odrzuć prośbę {email} o {plan}",
|
|
98
|
+
},
|
|
99
|
+
history: {
|
|
100
|
+
title: "Historia konta",
|
|
101
|
+
lead: "Znajdź konto, żeby zobaczyć jego dostęp, plany nadane ręcznie i płatności.",
|
|
102
|
+
email: "Znajdź konto po e-mailu",
|
|
103
|
+
submit: "Pokaż historię",
|
|
104
|
+
pending: "Szukanie…",
|
|
105
|
+
accountTitle: "Historia konta {email}",
|
|
106
|
+
empty: "Brak nadań i płatności.",
|
|
107
|
+
manual: "{plan}, nadany ręcznie",
|
|
108
|
+
fromRequest: "{plan}, nadany z prośby",
|
|
109
|
+
provider: "{plan}, opłacony przez {provider}",
|
|
110
|
+
grantedOn: "Nadany {date}",
|
|
111
|
+
grantedFor: "Nadany {date} za {amount}",
|
|
112
|
+
paidOn: "Zapłacono {amount}, {date}",
|
|
113
|
+
period: "Dostęp od {from} do {to}",
|
|
114
|
+
lifetime: "Dostęp dożywotni",
|
|
115
|
+
noGrant: "Zapisana, zanim zaczęliśmy zapisywać nadania",
|
|
116
|
+
active: "Aktywny",
|
|
117
|
+
revokedOn: "Cofnięty {date}",
|
|
118
|
+
paid: "Opłacona",
|
|
119
|
+
refundedOn: "Zwrócona {date}",
|
|
120
|
+
partlyRefunded: "Opłacona, zwrócono {amount}",
|
|
121
|
+
revoke: "Cofnij",
|
|
122
|
+
revoking: "Cofanie…",
|
|
123
|
+
revokeLabel: "Cofnij {plan} nadany {date}",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
errors: {
|
|
127
|
+
billing: {
|
|
128
|
+
read_only: "Konto jest w trybie tylko do odczytu. Wybierz plan, żeby wprowadzać zmiany.",
|
|
129
|
+
account_unknown: "Żadne konto nie ma tego adresu e-mail.",
|
|
130
|
+
end_not_in_future: "Data końca musi być w przyszłości.",
|
|
131
|
+
plan_unknown: "Ten plan nie jest już dostępny. Wybierz inny.",
|
|
132
|
+
invoice_details_invalid: "Sprawdź dane do faktury poniżej.",
|
|
133
|
+
invoice_field_required: "Wypełnij to pole.",
|
|
134
|
+
invoice_field_too_long: "Użyj najwyżej {max} znaków.",
|
|
135
|
+
invoice_field_control_characters: "Usuń znaki nowej linii, tabulatory i inne znaki sterujące.",
|
|
136
|
+
payment_failed: "Nie udało się rozpocząć płatności. Spróbuj za chwilę.",
|
|
137
|
+
lifetime_active: "To konto ma już dostęp dożywotni: nie ma za co płacić ani czego nadawać.",
|
|
138
|
+
request_closed: "Ta prośba została już obsłużona lub odrzucona.",
|
|
139
|
+
grant_revoked: "Ten dostęp został już cofnięty.",
|
|
140
|
+
},
|
|
141
|
+
security: {
|
|
142
|
+
rate_limited: "Zbyt wiele prób. Spróbuj później.",
|
|
143
|
+
},
|
|
144
|
+
auth: {
|
|
145
|
+
forbidden: "Nie masz uprawnień do tej operacji.",
|
|
146
|
+
},
|
|
147
|
+
core: {
|
|
148
|
+
database_failed: "Coś poszło nie tak po naszej stronie. Spróbuj za chwilę.",
|
|
149
|
+
unexpected: "Coś poszło nie tak po naszej stronie. Spróbuj za chwilę.",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The badge and the notice for the signed-in account, wired to the registered config: one line in
|
|
2
|
+
// any server component (`<CurrentAccessBadge />`, `<CurrentAccessNotice />`). Without a session
|
|
3
|
+
// they render nothing.
|
|
4
|
+
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
5
|
+
import type { ClassNames, LinkComponentType } from "@softure-ai/ui";
|
|
6
|
+
import { getBillingMessages, getBillingRoutes } from "../server/options.js";
|
|
7
|
+
import { AccessBadge, type AccessBadgeSlot } from "../ui/access-badge.js";
|
|
8
|
+
import { AccessNotice, type AccessNoticeSlot } from "../ui/access-notice.js";
|
|
9
|
+
import { getCurrentEntitlement } from "./current-entitlement.js";
|
|
10
|
+
|
|
11
|
+
export interface CurrentAccessBadgeProps {
|
|
12
|
+
readonly classNames?: ClassNames<AccessBadgeSlot>;
|
|
13
|
+
readonly unstyled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function CurrentAccessBadge({ classNames, unstyled }: CurrentAccessBadgeProps) {
|
|
17
|
+
const entitlement = await getCurrentEntitlement();
|
|
18
|
+
if (entitlement === null) return null;
|
|
19
|
+
const config = getSoftureConfig();
|
|
20
|
+
return (
|
|
21
|
+
<AccessBadge
|
|
22
|
+
entitlement={entitlement}
|
|
23
|
+
messages={getBillingMessages(config)}
|
|
24
|
+
locale={config.locale}
|
|
25
|
+
timezone={config.timezone}
|
|
26
|
+
classNames={classNames}
|
|
27
|
+
unstyled={unstyled}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CurrentAccessNoticeProps {
|
|
33
|
+
/** The app's link component, e.g. Next's `Link`; a plain `<a>` when omitted. */
|
|
34
|
+
readonly LinkComponent?: LinkComponentType;
|
|
35
|
+
readonly classNames?: ClassNames<AccessNoticeSlot>;
|
|
36
|
+
readonly unstyled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function CurrentAccessNotice({ LinkComponent, classNames, unstyled }: CurrentAccessNoticeProps) {
|
|
40
|
+
const entitlement = await getCurrentEntitlement();
|
|
41
|
+
if (entitlement === null) return null;
|
|
42
|
+
const config = getSoftureConfig();
|
|
43
|
+
return (
|
|
44
|
+
<AccessNotice
|
|
45
|
+
entitlement={entitlement}
|
|
46
|
+
messages={getBillingMessages(config)}
|
|
47
|
+
locale={config.locale}
|
|
48
|
+
timezone={config.timezone}
|
|
49
|
+
paymentHref={getBillingRoutes(config).payment}
|
|
50
|
+
LinkComponent={LinkComponent}
|
|
51
|
+
classNames={classNames}
|
|
52
|
+
unstyled={unstyled}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
// The billing server actions. Each reads the account from the session (never from a bound
|
|
4
|
+
// argument, which the client controls, docs/02 §8) before it reads the form: the payment action
|
|
5
|
+
// needs a signed-in account, the admin actions the admin role. Unexpected failures become
|
|
6
|
+
// `safeError` codes. Next refuses an action whose Origin does not match the host. An admin action
|
|
7
|
+
// that changed something revalidates the admin page, so its lists show the change.
|
|
8
|
+
import type { AuthUser } from "@softure-ai/auth";
|
|
9
|
+
import { authorizeRole, requireUser } from "@softure-ai/auth/next";
|
|
10
|
+
import { errorLogLabel, formatMessage, safeError, type CoreErrorCode, type Err, type Ok, type SoftureConfig } from "@softure-ai/core";
|
|
11
|
+
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
12
|
+
import { revalidatePath } from "next/cache";
|
|
13
|
+
import { redirect } from "next/navigation";
|
|
14
|
+
import type { AdminActionErrorCode, AdminActionState, GrantFormState, PaymentFormState } from "../contract.js";
|
|
15
|
+
import { ACCOUNT_PARAM, EMAIL_FIELD, GRANT_FIELD, INVOICE_FIELDS, PLAN_FIELD, REQUEST_FIELD } from "../fields.js";
|
|
16
|
+
import { findPlan, getLocalizedText } from "../plans.js";
|
|
17
|
+
import { grantPaymentRequest, grantPlanManually, revokeManualGrant } from "../server/grants.js";
|
|
18
|
+
import { getBillingMessages, getBillingOptions, getBillingRoutes } from "../server/options.js";
|
|
19
|
+
import { findAccountByEmail, getBillingPlans, startPayment } from "../server/plans.js";
|
|
20
|
+
import type { BillingContext } from "../server/entitlements.js";
|
|
21
|
+
import { dismissPaymentRequest } from "../server/requests.js";
|
|
22
|
+
import { formatLastDay } from "../ui/format.js";
|
|
23
|
+
import { getBillingContext } from "./context.js";
|
|
24
|
+
|
|
25
|
+
/** Longer values are cut: the server functions refuse them anyway, and nothing huge is echoed back. */
|
|
26
|
+
const MAX_FIELD_LENGTH = 1024;
|
|
27
|
+
|
|
28
|
+
function readText(formData: FormData, name: string): string {
|
|
29
|
+
const value = formData.get(name);
|
|
30
|
+
return typeof value === "string" ? value.slice(0, MAX_FIELD_LENGTH) : "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function reportFailure(operation: string, error: unknown): CoreErrorCode {
|
|
34
|
+
console.error(`@softure-ai/billing: ${operation} failed: ${errorLogLabel(error)}`);
|
|
35
|
+
return safeError(error).error;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Starts paying for the plan of the form. Without a session it redirects to the login page and
|
|
40
|
+
* back to the payment page. A hosted checkout is a redirect; a manual request answers `requested`.
|
|
41
|
+
*/
|
|
42
|
+
export async function startPaymentAction(_previous: PaymentFormState, formData: FormData): Promise<PaymentFormState> {
|
|
43
|
+
const config = getSoftureConfig();
|
|
44
|
+
const user = await requireUser({ next: getBillingRoutes(config).payment });
|
|
45
|
+
const planId = readText(formData, PLAN_FIELD);
|
|
46
|
+
const invoice = { name: readText(formData, INVOICE_FIELDS.name), taxId: readText(formData, INVOICE_FIELDS.taxId), address: readText(formData, INVOICE_FIELDS.address) };
|
|
47
|
+
const values = { [INVOICE_FIELDS.name]: invoice.name, [INVOICE_FIELDS.taxId]: invoice.taxId, [INVOICE_FIELDS.address]: invoice.address };
|
|
48
|
+
|
|
49
|
+
let result;
|
|
50
|
+
try {
|
|
51
|
+
result = await startPayment(await getBillingContext(config), { account: { id: user.id, email: user.email }, planId, invoice });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return { status: "error", error: reportFailure("starting a payment", error), values };
|
|
54
|
+
}
|
|
55
|
+
if (!result.ok) return { status: "error", error: result.error, ...("fieldErrors" in result ? { fieldErrors: result.fieldErrors } : {}), values };
|
|
56
|
+
// Outside the try: Next's redirect works by throwing.
|
|
57
|
+
if (result.value.type === "redirect") redirect(result.value.url);
|
|
58
|
+
return { status: "requested" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function refreshAdminPage(config: SoftureConfig): void {
|
|
62
|
+
revalidatePath(getBillingRoutes(config).admin);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The signed-in admin, or the refusal: the role of `billing({ adminRole })`, from the session. */
|
|
66
|
+
async function authorizeAdmin(config: SoftureConfig): Promise<Ok<AuthUser> | Err<"auth.forbidden">> {
|
|
67
|
+
return authorizeRole(getBillingOptions(config).adminRole);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Grants one payment of a plan to the account with the form's email and records it in the
|
|
72
|
+
* account's history. Only for the role of `billing({ adminRole })`, checked from the session
|
|
73
|
+
* before the form is read. An account with lifetime access is refused.
|
|
74
|
+
*/
|
|
75
|
+
export async function grantPlanAction(_previous: GrantFormState, formData: FormData): Promise<GrantFormState> {
|
|
76
|
+
const config = getSoftureConfig();
|
|
77
|
+
const admin = await authorizeAdmin(config);
|
|
78
|
+
if (!admin.ok) return { status: "error", error: admin.error };
|
|
79
|
+
const email = readText(formData, EMAIL_FIELD).trim();
|
|
80
|
+
const planId = readText(formData, PLAN_FIELD);
|
|
81
|
+
const echo = { email, planId };
|
|
82
|
+
|
|
83
|
+
const plan = findPlan(getBillingPlans(config), planId);
|
|
84
|
+
if (plan === undefined) return { status: "error", error: "billing.plan_unknown", ...echo };
|
|
85
|
+
let result;
|
|
86
|
+
try {
|
|
87
|
+
const ctx = await getBillingContext(config);
|
|
88
|
+
const account = await findAccountByEmail(ctx, email);
|
|
89
|
+
if (account === null) return { status: "error", error: "billing.account_unknown", ...echo };
|
|
90
|
+
result = await grantPlanManually(ctx, { userId: account.id, planId: plan.id, adminId: admin.value.id });
|
|
91
|
+
if (result.ok) {
|
|
92
|
+
refreshAdminPage(config);
|
|
93
|
+
const messages = getBillingMessages(config).admin;
|
|
94
|
+
const { entitlement } = result.value;
|
|
95
|
+
const values = { email: account.email, plan: getLocalizedText(plan.name, config.locale) };
|
|
96
|
+
const notice =
|
|
97
|
+
entitlement.status === "paid" && entitlement.endsAt !== null
|
|
98
|
+
? formatMessage(messages.grantedUntil, { ...values, date: formatLastDay(entitlement.endsAt, config.locale, config.timezone) })
|
|
99
|
+
: formatMessage(messages.granted, values);
|
|
100
|
+
return { status: "granted", notice, planId };
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return { status: "error", error: reportFailure("granting a plan", error), ...echo };
|
|
104
|
+
}
|
|
105
|
+
// No request was given, so it is never closed; an account deleted meanwhile is unknown.
|
|
106
|
+
const error = result.error === "billing.request_closed" ? "billing.account_unknown" : result.error;
|
|
107
|
+
return { status: "error", error, ...echo };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface AdminChange {
|
|
111
|
+
readonly operation: string;
|
|
112
|
+
/** The form field that names the row. */
|
|
113
|
+
readonly field: string;
|
|
114
|
+
readonly change: (ctx: BillingContext, id: string, adminId: string) => Promise<{ readonly ok: true } | Err<AdminActionErrorCode>>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Runs one admin change on the row the form names: role first, then the change, then a refresh. */
|
|
118
|
+
async function runAdminChange(formData: FormData, { operation, field, change }: AdminChange): Promise<AdminActionState> {
|
|
119
|
+
const config = getSoftureConfig();
|
|
120
|
+
const admin = await authorizeAdmin(config);
|
|
121
|
+
if (!admin.ok) return { status: "error", error: admin.error };
|
|
122
|
+
const id = readText(formData, field);
|
|
123
|
+
try {
|
|
124
|
+
const result = await change(await getBillingContext(config), id, admin.value.id);
|
|
125
|
+
if (!result.ok) return { status: "error", error: result.error };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return { status: "error", error: reportFailure(operation, error) };
|
|
128
|
+
}
|
|
129
|
+
refreshAdminPage(config);
|
|
130
|
+
return { status: "done" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Grants the plan of the form's open request and closes it (admin role). */
|
|
134
|
+
export async function grantRequestAction(_previous: AdminActionState, formData: FormData): Promise<AdminActionState> {
|
|
135
|
+
return runAdminChange(formData, {
|
|
136
|
+
operation: "granting a payment request",
|
|
137
|
+
field: REQUEST_FIELD,
|
|
138
|
+
change: (ctx, requestId, adminId) => grantPaymentRequest(ctx, { requestId, adminId }),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Closes the form's open request without a grant (admin role). */
|
|
143
|
+
export async function dismissRequestAction(_previous: AdminActionState, formData: FormData): Promise<AdminActionState> {
|
|
144
|
+
return runAdminChange(formData, { operation: "dismissing a payment request", field: REQUEST_FIELD, change: (ctx, requestId) => dismissPaymentRequest(ctx, requestId) });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Revokes the form's manual grant, taking back only what it added (admin role). */
|
|
148
|
+
export async function revokeGrantAction(_previous: AdminActionState, formData: FormData): Promise<AdminActionState> {
|
|
149
|
+
return runAdminChange(formData, {
|
|
150
|
+
operation: "revoking a manual grant",
|
|
151
|
+
field: GRANT_FIELD,
|
|
152
|
+
change: (ctx, grantId, adminId) => revokeManualGrant(ctx, { grantId, adminId }),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Finds the account with the form's email and sends the admin to its history on the admin page,
|
|
158
|
+
* by the account's id: an email address never lands in a URL (admin role).
|
|
159
|
+
*/
|
|
160
|
+
export async function findAccountAction(_previous: AdminActionState, formData: FormData): Promise<AdminActionState> {
|
|
161
|
+
const config = getSoftureConfig();
|
|
162
|
+
const admin = await authorizeAdmin(config);
|
|
163
|
+
if (!admin.ok) return { status: "error", error: admin.error };
|
|
164
|
+
const email = readText(formData, EMAIL_FIELD).trim();
|
|
165
|
+
let accountId;
|
|
166
|
+
try {
|
|
167
|
+
const account = await findAccountByEmail(await getBillingContext(config), email);
|
|
168
|
+
if (account === null) return { status: "error", error: "billing.account_unknown", email };
|
|
169
|
+
accountId = account.id;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return { status: "error", error: reportFailure("finding an account", error), email };
|
|
172
|
+
}
|
|
173
|
+
// Outside the try: Next's redirect works by throwing.
|
|
174
|
+
redirect(`${getBillingRoutes(config).admin}?${new URLSearchParams({ [ACCOUNT_PARAM]: accountId }).toString()}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// What the adapter hands the server functions: the registered config, the process-wide database
|
|
2
|
+
// handle and the wall clock. Every billing request comes through here, so a broken setup (an
|
|
3
|
+
// undeclared `adminRole`) fails on the first one.
|
|
4
|
+
import { systemClock, type SoftureConfig } from "@softure-ai/core";
|
|
5
|
+
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
6
|
+
import { getSharedDatabase } from "@softure-ai/db";
|
|
7
|
+
import type { BillingContext } from "../server/entitlements.js";
|
|
8
|
+
import { assertAdminRoleDeclared } from "../server/setup.js";
|
|
9
|
+
|
|
10
|
+
export async function getBillingContext(config: SoftureConfig = getSoftureConfig()): Promise<BillingContext> {
|
|
11
|
+
assertAdminRoleDeclared(config);
|
|
12
|
+
if (config.database === null) {
|
|
13
|
+
// Unreachable for a validated config: the module has a database schema.
|
|
14
|
+
throw new Error("@softure-ai/billing: softure.config.ts has no database; billing needs one");
|
|
15
|
+
}
|
|
16
|
+
const { db } = await getSharedDatabase(config.database.url);
|
|
17
|
+
return { db, clock: systemClock, config };
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The signed-in account's entitlement and the write guard, for server components, actions and route
|
|
2
|
+
// handlers. React's `cache` makes the lookup once per request; nothing is cached across requests,
|
|
3
|
+
// so a grant shows on the next one.
|
|
4
|
+
import type { AuthUser } from "@softure-ai/auth";
|
|
5
|
+
import { getCurrentUser, requireUser, type RequireUserOptions } from "@softure-ai/auth/next";
|
|
6
|
+
import { err, ok, type Err, type Ok } from "@softure-ai/core";
|
|
7
|
+
import { cache } from "react";
|
|
8
|
+
import type { Entitlement } from "../contract.js";
|
|
9
|
+
import { getEntitlement } from "../server/entitlements.js";
|
|
10
|
+
import { getBillingContext } from "./context.js";
|
|
11
|
+
|
|
12
|
+
/** The entitlement of the request's account, or null without a session. Database failures propagate. */
|
|
13
|
+
export const getCurrentEntitlement = cache(async (): Promise<Entitlement | null> => {
|
|
14
|
+
const user = await getCurrentUser();
|
|
15
|
+
if (user === null) return null;
|
|
16
|
+
return getEntitlement(await getBillingContext(), user.id);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/** What a write may go ahead with. */
|
|
20
|
+
export interface WriteAccess {
|
|
21
|
+
readonly user: AuthUser;
|
|
22
|
+
readonly entitlement: Entitlement;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The guard for an app's write actions; call it before reading any input. Without a session it
|
|
27
|
+
* redirects to the login page (as `requireUser`); a read-only account gets `billing.read_only` for
|
|
28
|
+
* the action to return to its form. Never a reason to skip the action's own authorization.
|
|
29
|
+
*/
|
|
30
|
+
export async function requireWriteAccess(options: RequireUserOptions = {}): Promise<Ok<WriteAccess> | Err<"billing.read_only">> {
|
|
31
|
+
const user = await requireUser(options);
|
|
32
|
+
const entitlement = await getCurrentEntitlement();
|
|
33
|
+
// The session's account exists, so it has an entitlement; null would mean it vanished mid-request.
|
|
34
|
+
if (entitlement === null || entitlement.status === "read_only") return err("billing.read_only");
|
|
35
|
+
return ok({ user, entitlement });
|
|
36
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// The Next.js adapter of @softure-ai/billing: the signed-in account's entitlement, the write guard
|
|
2
|
+
// for server actions, the badge and notice, the pricing tiles, the payment and admin pages and
|
|
3
|
+
// their actions, and the Stripe webhook route, wired to the registered configuration
|
|
4
|
+
// (docs/02-module-standard.md §8).
|
|
5
|
+
export { CurrentAccessBadge, type CurrentAccessBadgeProps, CurrentAccessNotice, type CurrentAccessNoticeProps } from "./access.js";
|
|
6
|
+
export { dismissRequestAction, findAccountAction, grantPlanAction, grantRequestAction, revokeGrantAction, startPaymentAction } from "./actions.js";
|
|
7
|
+
export { getBillingContext } from "./context.js";
|
|
8
|
+
export { getCurrentEntitlement, requireWriteAccess, type WriteAccess } from "./current-entitlement.js";
|
|
9
|
+
export { BillingAdminPage, type BillingAdminPageProps, PaymentPage, type PaymentPageProps } from "./pages.js";
|
|
10
|
+
export { getPlanPaymentHref, Pricing, type PricingProps } from "./pricing.js";
|
|
11
|
+
export { STRIPE_WEBHOOK_MAX_BYTES, STRIPE_WEBHOOK_SECRET_ENV, stripeWebhookRoute } from "./route.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Billing's adapter imports `@softure-ai/auth/next`, whose sources import these specifiers.
|
|
2
|
+
// Next.js has no `exports` map, so NodeNext resolution (tsconfig.base.json) only finds
|
|
3
|
+
// `next/navigation.js` and `next/headers.js`. The code must still import the bare specifiers:
|
|
4
|
+
// Next's bundler aliases them per runtime, and the `.js` form bypasses the alias (measured:
|
|
5
|
+
// `next build` failed to collect a route handler importing `next/navigation.js`, MODULE_UNPARSABLE
|
|
6
|
+
// on the vendored app-router context). These declarations give the bare specifiers their types.
|
|
7
|
+
declare module "next/navigation" {
|
|
8
|
+
export * from "next/navigation.js";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module "next/headers" {
|
|
12
|
+
export * from "next/headers.js";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module "next/server" {
|
|
16
|
+
export * from "next/server.js";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare module "next/cache" {
|
|
20
|
+
export * from "next/cache.js";
|
|
21
|
+
}
|