includio-cms 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +97 -4
- package/CHANGELOG.md +118 -0
- package/DOCS.md +1 -1
- package/README.md +2 -0
- package/ROADMAP.md +14 -0
- package/dist/admin/auth-client.d.ts +42 -42
- package/dist/admin/client/account/lang.d.ts +1 -0
- package/dist/admin/client/account/lang.js +4 -2
- package/dist/admin/client/account/profile-section.svelte +2 -2
- package/dist/admin/client/account/security-section.svelte +27 -4
- package/dist/admin/client/account/sessions-section.svelte +1 -1
- package/dist/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
- package/dist/admin/client/admin/admin-layout.svelte +12 -2
- package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
- package/dist/admin/client/admin/dashboard-page.svelte +34 -10
- package/dist/admin/client/collection/bulk-actions-bar.svelte +86 -44
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +3 -1
- package/dist/admin/client/collection/collection-entries.svelte +52 -36
- package/dist/admin/client/collection/collection-entries.svelte.d.ts +3 -0
- package/dist/admin/client/collection/collection.svelte +28 -14
- package/dist/admin/client/collection/collection.svelte.d.ts +3 -0
- package/dist/admin/client/collection/data-table.svelte +240 -130
- package/dist/admin/client/collection/data-table.svelte.d.ts +9 -0
- package/dist/admin/client/collection/date-cell.svelte +4 -4
- package/dist/admin/client/collection/row-actions.svelte +2 -1
- package/dist/admin/client/collection/sortable-header.svelte +33 -9
- package/dist/admin/client/collection/state-display.svelte +102 -0
- package/dist/admin/client/collection/state-display.svelte.d.ts +12 -0
- package/dist/admin/client/collection/status-badge.svelte +99 -11
- package/dist/admin/client/collection/status-badge.svelte.d.ts +15 -1
- package/dist/admin/client/collection/table-pagination.svelte +21 -6
- package/dist/admin/client/collection/table-toolbar.svelte +105 -80
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -8
- package/dist/admin/client/entry/entry-form.svelte +36 -11
- package/dist/admin/client/entry/entry-form.svelte.d.ts +1 -0
- package/dist/admin/client/entry/entry-header.svelte +22 -15
- package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -0
- package/dist/admin/client/entry/entry.svelte +269 -165
- package/dist/admin/client/entry/header/a11y-header-badge.svelte +47 -0
- package/dist/admin/client/entry/header/a11y-header-badge.svelte.d.ts +8 -0
- package/dist/admin/client/entry/header/publish-panel.svelte +69 -13
- package/dist/admin/client/entry/header/save-indicator.svelte +57 -28
- package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +1 -0
- package/dist/admin/client/entry/header/status-badge.svelte +60 -15
- package/dist/admin/client/entry/header/status-badge.svelte.d.ts +1 -2
- package/dist/admin/client/entry/header/version-history-sheet.svelte +1 -1
- package/dist/admin/client/entry/hybrid/hybrid-layout.svelte +74 -23
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +1 -1
- package/dist/admin/client/entry/utils.d.ts +14 -0
- package/dist/admin/client/entry/utils.js +28 -0
- package/dist/admin/client/form/form-submission/form-submission.svelte +2 -2
- package/dist/admin/client/form/form-submissions.svelte +143 -194
- package/dist/admin/client/form/form-submissions.svelte.d.ts +2 -0
- package/dist/admin/client/login/lang.d.ts +3 -0
- package/dist/admin/client/login/lang.js +10 -4
- package/dist/admin/client/login/login-form.svelte +8 -1
- package/dist/admin/client/login/reset-password-page.svelte +24 -3
- package/dist/admin/client/login/schema.d.ts +14 -2
- package/dist/admin/client/login/schema.js +19 -8
- package/dist/admin/client/maintenance/maintenance-page.svelte +16 -17
- package/dist/admin/client/media/media-page.svelte +1 -1
- package/dist/admin/client/shop/coupon-edit-page.svelte +117 -13
- package/dist/admin/client/shop/coupon-form.svelte +282 -138
- package/dist/admin/client/shop/coupon-form.svelte.d.ts +1 -9
- package/dist/admin/client/shop/coupon-new-page.svelte +40 -10
- package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +2 -17
- package/dist/admin/client/shop/coupon-schema.d.ts +28 -0
- package/dist/admin/client/shop/coupon-schema.js +53 -0
- package/dist/admin/client/shop/coupons-list-page.svelte +262 -118
- package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +16 -1
- package/dist/admin/client/shop/refund-dialog.svelte +37 -1
- package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shipping-method-edit-page.svelte +108 -59
- package/dist/admin/client/shop/shipping-method-form.svelte +36 -9
- package/dist/admin/client/shop/shipping-method-new-page.svelte +44 -13
- package/dist/admin/client/shop/shipping-methods-list-page.svelte +101 -59
- package/dist/admin/client/shop/shop-order-detail-page.svelte +220 -84
- package/dist/admin/client/shop/shop-orders-list-page.svelte +302 -152
- package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +18 -1
- package/dist/admin/client/shop/shop-products-list-page.svelte +355 -118
- package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +19 -1
- package/dist/admin/client/users/accept-invite-page.svelte +24 -3
- package/dist/admin/client/users/create-user-dialog.svelte +3 -8
- package/dist/admin/client/users/lang.d.ts +2 -0
- package/dist/admin/client/users/lang.js +4 -0
- package/dist/admin/client/users/pending-invitations.svelte +2 -9
- package/dist/admin/client/users/user-name-cell.svelte +20 -0
- package/dist/admin/client/users/user-name-cell.svelte.d.ts +9 -0
- package/dist/admin/client/users/user-role-badge.svelte +16 -0
- package/dist/admin/client/users/user-role-badge.svelte.d.ts +7 -0
- package/dist/admin/client/users/user-row-actions.svelte +72 -0
- package/dist/admin/client/users/user-row-actions.svelte.d.ts +20 -0
- package/dist/admin/client/users/user-sessions-sheet.svelte +2 -11
- package/dist/admin/client/users/users-page.svelte +283 -497
- package/dist/admin/client/users/users-page.svelte.d.ts +12 -1
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +59 -74
- package/dist/admin/components/dashboard/recent-activity.svelte +17 -5
- package/dist/admin/components/dashboard/recent-entries.svelte +19 -7
- package/dist/admin/components/dialogs/confirmation-dialog.svelte +105 -0
- package/dist/admin/components/dialogs/confirmation-dialog.svelte.d.ts +13 -0
- package/dist/admin/components/fields/block-picker-modal.svelte +6 -0
- package/dist/admin/components/fields/blocks-field.svelte +46 -1
- package/dist/admin/components/fields/boolean-field.svelte +1 -1
- package/dist/admin/components/fields/field-renderer.svelte +29 -22
- package/dist/admin/components/fields/file-field.svelte +344 -30
- package/dist/admin/components/fields/icon-field.svelte +86 -0
- package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
- package/dist/admin/components/fields/media-field.svelte +16 -2
- package/dist/admin/components/fields/object-field.svelte +27 -7
- package/dist/admin/components/fields/radio-field.svelte +22 -0
- package/dist/admin/components/fields/relation-field.svelte +123 -97
- package/dist/admin/components/fields/relation-picker-dialog.svelte +2 -2
- package/dist/admin/components/fields/seo-field.svelte +60 -30
- package/dist/admin/components/fields/shop-field.svelte +219 -24
- package/dist/admin/components/fields/simple-array-field.svelte +321 -151
- package/dist/admin/components/fields/simple-array-field.svelte.d.ts +3 -0
- package/dist/admin/components/fields/slug-field.svelte +146 -21
- package/dist/admin/components/fields/text-field-wrapper.svelte +37 -20
- package/dist/admin/components/fields/text-field.svelte +7 -2
- package/dist/admin/components/fields/url-field-wrapper.svelte +10 -0
- package/dist/admin/components/fields/url-field.svelte +36 -23
- package/dist/admin/components/forms/form-error-summary.svelte +143 -0
- package/dist/admin/components/forms/form-error-summary.svelte.d.ts +27 -0
- package/dist/admin/components/layout/app-sidebar.svelte +7 -2
- package/dist/admin/components/layout/detail-page-shell.svelte +71 -0
- package/dist/admin/components/layout/detail-page-shell.svelte.d.ts +24 -0
- package/dist/admin/components/layout/lang.d.ts +5 -0
- package/dist/admin/components/layout/lang.js +10 -0
- package/dist/admin/components/layout/layout-renderer.svelte +71 -2
- package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/layout/layout-tabs.svelte +173 -0
- package/dist/admin/components/layout/layout-tabs.svelte.d.ts +24 -0
- package/dist/admin/components/layout/nav-breadcrumbs.svelte +25 -7
- package/dist/admin/components/layout/nav-collections.svelte +23 -36
- package/dist/admin/components/layout/nav-forms.svelte +19 -35
- package/dist/admin/components/layout/nav-main.svelte +3 -28
- package/dist/admin/components/layout/nav-search.svelte +70 -2
- package/dist/admin/components/layout/nav-section.svelte +77 -0
- package/dist/admin/components/layout/nav-section.svelte.d.ts +22 -0
- package/dist/admin/components/layout/nav-shop.svelte +3 -27
- package/dist/admin/components/layout/nav-singletons.svelte +16 -28
- package/dist/admin/components/layout/page-header.stories.svelte +93 -0
- package/dist/admin/components/layout/page-header.stories.svelte.d.ts +27 -0
- package/dist/admin/components/layout/page-header.svelte +68 -0
- package/dist/admin/components/layout/page-header.svelte.d.ts +17 -0
- package/dist/admin/components/layout/site-header.svelte +9 -0
- package/dist/admin/components/layout/site-header.svelte.d.ts +2 -17
- package/dist/admin/components/media/file/file-name-input.svelte +6 -2
- package/dist/admin/components/media/file/file-preview.svelte +130 -17
- package/dist/admin/components/media/file-upload.svelte +16 -7
- package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
- package/dist/admin/components/media/files-list.svelte +153 -53
- package/dist/admin/components/media/files-list.svelte.d.ts +1 -0
- package/dist/admin/components/media/media-library.svelte +577 -198
- package/dist/admin/components/media/media-library.svelte.d.ts +4 -0
- package/dist/admin/components/media/media-selector.svelte +4 -2
- package/dist/admin/components/media/media-selector.svelte.d.ts +1 -0
- package/dist/admin/components/media/tag-sidebar.svelte +4 -4
- package/dist/admin/components/tiptap/FigureNodeView.svelte +10 -0
- package/dist/admin/components/tiptap/bubble-menu.svelte +104 -0
- package/dist/admin/components/tiptap/bubble-menu.svelte.d.ts +19 -0
- package/dist/admin/components/tiptap/content-editor.svelte +28 -24
- package/dist/admin/components/tiptap/editor-toolbar.svelte +7 -7
- package/dist/admin/components/tiptap/extensions.js +5 -1
- package/dist/admin/components/tiptap/image-dialog.svelte +5 -1
- package/dist/admin/components/tiptap/link-dialog.svelte +2 -0
- package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -20
- package/dist/admin/components/tiptap/video-dialog.svelte +1 -1
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
- package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
- package/dist/admin/helpers/build-icon-set-map.js +16 -0
- package/dist/admin/helpers/index.d.ts +2 -0
- package/dist/admin/helpers/index.js +2 -0
- package/dist/admin/i18n/errors.d.ts +140 -0
- package/dist/admin/i18n/errors.js +151 -0
- package/dist/admin/remote/entry.remote.d.ts +59 -4
- package/dist/admin/remote/entry.remote.js +239 -62
- package/dist/admin/remote/shop.remote.d.ts +87 -48
- package/dist/admin/remote/shop.remote.js +70 -8
- package/dist/admin/shared/password-generate.d.ts +6 -0
- package/dist/admin/shared/password-generate.js +40 -0
- package/dist/admin/shared/password-schema.d.ts +6 -0
- package/dist/admin/shared/password-schema.js +10 -3
- package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
- package/dist/admin/state/icon-sets.svelte.js +20 -0
- package/dist/admin/styles/admin.css +23 -6
- package/dist/admin/styles/tokens.md +244 -0
- package/dist/admin/utils/accordionActivation.d.ts +13 -0
- package/dist/admin/utils/accordionActivation.js +35 -0
- package/dist/admin/utils/entryLabel.d.ts +23 -0
- package/dist/admin/utils/entryLabel.js +51 -12
- package/dist/admin/utils/field-a11y.d.ts +29 -0
- package/dist/admin/utils/field-a11y.js +23 -0
- package/dist/admin/utils/fieldPathElement.d.ts +9 -0
- package/dist/admin/utils/fieldPathElement.js +18 -0
- package/dist/admin/utils/fileDisplay.d.ts +10 -0
- package/dist/admin/utils/fileDisplay.js +26 -0
- package/dist/admin/utils/flattenFormErrors.d.ts +19 -0
- package/dist/admin/utils/flattenFormErrors.js +102 -0
- package/dist/admin/utils/formatters.d.ts +12 -0
- package/dist/admin/utils/{formatDate.js → formatters.js} +23 -2
- package/dist/admin/utils/scrollWithin.d.ts +9 -0
- package/dist/admin/utils/scrollWithin.js +32 -0
- package/dist/admin/utils/tabActivation.d.ts +12 -0
- package/dist/admin/utils/tabActivation.js +24 -0
- package/dist/cli/scaffold/admin.js +2 -2
- package/dist/cms/runtime/schema.d.ts +1 -0
- package/dist/cms/runtime/schema.js +1 -0
- package/dist/cms/runtime/types.d.ts +80 -7
- package/dist/components/ui/accordion/accordion-content.svelte +17 -3
- package/dist/components/ui/accordion/accordion.stories.svelte +21 -1
- package/dist/components/ui/alert/alert.stories.svelte +14 -0
- package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte +45 -0
- package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte.d.ts +27 -0
- package/dist/components/ui/avatar/avatar.stories.svelte +27 -0
- package/dist/components/ui/badge/badge.stories.svelte +15 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +47 -0
- package/dist/components/ui/breadcrumb/breadcrumb.svelte +1 -1
- package/dist/components/ui/button/button.stories.svelte +53 -6
- package/dist/components/ui/button/button.svelte +39 -5
- package/dist/components/ui/button/button.svelte.d.ts +4 -0
- package/dist/components/ui/button-group/button-group.stories.svelte +44 -0
- package/dist/components/ui/button-group/button-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/calendar/calendar.stories.svelte +36 -0
- package/dist/components/ui/calendar/calendar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/card/card.stories.svelte +7 -0
- package/dist/components/ui/carousel/carousel.stories.svelte +43 -0
- package/dist/components/ui/carousel/carousel.stories.svelte.d.ts +27 -0
- package/dist/components/ui/checkbox/checkbox.stories.svelte +67 -0
- package/dist/components/ui/checkbox/checkbox.stories.svelte.d.ts +27 -0
- package/dist/components/ui/checkbox/checkbox.svelte +1 -1
- package/dist/components/ui/command/command.stories.svelte +18 -0
- package/dist/components/ui/data-table/data-table.stories.svelte +61 -0
- package/dist/components/ui/data-table/data-table.stories.svelte.d.ts +18 -0
- package/dist/components/ui/dialog/dialog-content.svelte +5 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +2 -0
- package/dist/components/ui/dialog/dialog.stories.svelte +35 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte +74 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte.d.ts +27 -0
- package/dist/components/ui/field/field-context.svelte.d.ts +22 -0
- package/dist/components/ui/field/field-context.svelte.js +9 -0
- package/dist/components/ui/field/field-control.svelte +18 -0
- package/dist/components/ui/field/field-control.svelte.d.ts +8 -0
- package/dist/components/ui/field/field-description.svelte +12 -0
- package/dist/components/ui/field/field-error.svelte +14 -6
- package/dist/components/ui/field/field-label.svelte +10 -0
- package/dist/components/ui/field/field.stories.svelte +95 -9
- package/dist/components/ui/field/field.svelte +57 -0
- package/dist/components/ui/field/field.svelte.d.ts +2 -0
- package/dist/components/ui/field/index.d.ts +3 -1
- package/dist/components/ui/field/index.js +4 -2
- package/dist/components/ui/form/form-field-errors.svelte +1 -1
- package/dist/components/ui/form/form.stories.svelte +25 -0
- package/dist/components/ui/form/form.stories.svelte.d.ts +26 -0
- package/dist/components/ui/input/input.stories.svelte +26 -0
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group.stories.svelte +43 -0
- package/dist/components/ui/input-group/input-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/item/item.stories.svelte +61 -0
- package/dist/components/ui/item/item.stories.svelte.d.ts +27 -0
- package/dist/components/ui/label/label.stories.svelte +7 -0
- package/dist/components/ui/live-region/index.d.ts +1 -0
- package/dist/components/ui/live-region/index.js +1 -0
- package/dist/components/ui/live-region/live-region-demo.svelte +32 -0
- package/dist/components/ui/live-region/live-region-demo.svelte.d.ts +7 -0
- package/dist/components/ui/live-region/live-region.stories.svelte +23 -0
- package/dist/components/ui/live-region/live-region.stories.svelte.d.ts +26 -0
- package/dist/components/ui/live-region/live-region.svelte +12 -0
- package/dist/components/ui/live-region/live-region.svelte.d.ts +8 -0
- package/dist/components/ui/popover/popover.stories.svelte +34 -0
- package/dist/components/ui/radio-group/radio-group.stories.svelte +58 -0
- package/dist/components/ui/radio-group/radio-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/resizable/resizable.stories.svelte +56 -0
- package/dist/components/ui/resizable/resizable.stories.svelte.d.ts +27 -0
- package/dist/components/ui/select/select.stories.svelte +49 -0
- package/dist/components/ui/separator/separator.stories.svelte +18 -0
- package/dist/components/ui/sheet/sheet.stories.svelte +34 -0
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte +1 -0
- package/dist/components/ui/sidebar/sidebar-trigger.svelte +1 -1
- package/dist/components/ui/sidebar/sidebar.stories.svelte +72 -0
- package/dist/components/ui/sidebar/sidebar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/skeleton/skeleton.stories.svelte +39 -0
- package/dist/components/ui/skeleton/skeleton.stories.svelte.d.ts +27 -0
- package/dist/components/ui/skeleton/skeleton.svelte +6 -0
- package/dist/components/ui/sonner/index.d.ts +1 -1
- package/dist/components/ui/sonner/index.js +1 -1
- package/dist/components/ui/sonner/sonner.stories.svelte +7 -0
- package/dist/components/ui/sonner/sonner.svelte +17 -1
- package/dist/components/ui/sonner/sonner.svelte.d.ts +6 -0
- package/dist/components/ui/spinner/spinner.stories.svelte +30 -0
- package/dist/components/ui/spinner/spinner.stories.svelte.d.ts +27 -0
- package/dist/components/ui/switch/switch.stories.svelte +56 -0
- package/dist/components/ui/switch/switch.stories.svelte.d.ts +27 -0
- package/dist/components/ui/table/table-cell.svelte +1 -1
- package/dist/components/ui/table/table-head.svelte +1 -1
- package/dist/components/ui/table/table.stories.svelte +68 -0
- package/dist/components/ui/table/table.stories.svelte.d.ts +27 -0
- package/dist/components/ui/table/table.svelte +1 -1
- package/dist/components/ui/tabs/tabs.stories.svelte +48 -0
- package/dist/components/ui/tabs/tabs.stories.svelte.d.ts +27 -0
- package/dist/components/ui/textarea/textarea.stories.svelte +21 -0
- package/dist/components/ui/toggle/toggle.stories.svelte +23 -0
- package/dist/components/ui/toggle-group/toggle-group.stories.svelte +43 -0
- package/dist/components/ui/tooltip/tooltip.stories.svelte +46 -6
- package/dist/core/cms.d.ts +11 -2
- package/dist/core/cms.js +29 -0
- package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
- package/dist/core/fields/fieldSchemaToTs.js +241 -90
- package/dist/core/fields/layoutUtils.d.ts +4 -1
- package/dist/core/fields/layoutUtils.js +41 -4
- package/dist/core/fields/resolveSeo.d.ts +70 -0
- package/dist/core/fields/resolveSeo.js +88 -0
- package/dist/core/fields/seoFieldDescriptor.d.ts +43 -0
- package/dist/core/fields/seoFieldDescriptor.js +74 -0
- package/dist/core/fields/slugPath.d.ts +13 -0
- package/dist/core/fields/slugPath.js +32 -0
- package/dist/core/fields/urlUtils.d.ts +8 -0
- package/dist/core/fields/urlUtils.js +27 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/server/entries/operations/create.js +13 -0
- package/dist/core/server/entries/operations/get.d.ts +7 -0
- package/dist/core/server/entries/operations/get.js +10 -6
- package/dist/core/server/entries/operations/slugUniqueness.d.ts +37 -0
- package/dist/core/server/entries/operations/slugUniqueness.js +116 -0
- package/dist/core/server/entries/operations/update.d.ts +6 -1
- package/dist/core/server/entries/operations/update.js +24 -1
- package/dist/core/server/fields/slugResolver.d.ts +3 -13
- package/dist/core/server/fields/slugResolver.js +8 -37
- package/dist/core/server/generator/fields.d.ts +2 -0
- package/dist/core/server/generator/fields.js +44 -18
- package/dist/core/server/generator/formFields.js +2 -1
- package/dist/core/server/generator/generator.js +6 -5
- package/dist/core/server/generator/utils.d.ts +1 -0
- package/dist/core/server/generator/utils.js +4 -0
- package/dist/db-postgres/schema/shop/order.d.ts +37 -1
- package/dist/db-postgres/schema/shop/order.js +3 -1
- package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
- package/dist/db-postgres/schema/shop/payment.js +4 -1
- package/dist/db-postgres/schema/shop/product.d.ts +20 -0
- package/dist/db-postgres/schema/shop/product.js +3 -1
- package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
- package/dist/db-postgres/schema/shop/productVariant.js +22 -0
- package/dist/shop/cart/types.d.ts +1 -0
- package/dist/shop/client/index.d.ts +54 -0
- package/dist/shop/client/index.js +5 -1
- package/dist/shop/expiry.d.ts +35 -0
- package/dist/shop/expiry.js +68 -0
- package/dist/shop/http/balance-handler.d.ts +20 -0
- package/dist/shop/http/balance-handler.js +91 -0
- package/dist/shop/http/cart-handler.js +19 -0
- package/dist/shop/http/checkout-handler.js +19 -1
- package/dist/shop/http/index.d.ts +2 -0
- package/dist/shop/http/index.js +2 -0
- package/dist/shop/http/upcoming-handler.d.ts +16 -0
- package/dist/shop/http/upcoming-handler.js +65 -0
- package/dist/shop/http/webhook-handler.js +46 -9
- package/dist/shop/index.d.ts +4 -1
- package/dist/shop/index.js +7 -1
- package/dist/shop/server/balance-payment.d.ts +40 -0
- package/dist/shop/server/balance-payment.js +140 -0
- package/dist/shop/server/cart-hydrate.js +2 -0
- package/dist/shop/server/init.d.ts +14 -0
- package/dist/shop/server/init.js +35 -0
- package/dist/shop/server/orders.d.ts +35 -0
- package/dist/shop/server/orders.js +155 -2
- package/dist/shop/server/payment-policy.d.ts +35 -0
- package/dist/shop/server/payment-policy.js +55 -0
- package/dist/shop/server/payments.d.ts +29 -0
- package/dist/shop/server/payments.js +64 -0
- package/dist/shop/server/populate.d.ts +1 -1
- package/dist/shop/server/refund.d.ts +17 -12
- package/dist/shop/server/refund.js +96 -13
- package/dist/shop/server/shop-data.d.ts +6 -1
- package/dist/shop/server/shop-data.js +44 -7
- package/dist/shop/template.d.ts +13 -0
- package/dist/shop/template.js +98 -0
- package/dist/shop/types.d.ts +142 -1
- package/dist/shop/variant-attributes.d.ts +28 -0
- package/dist/shop/variant-attributes.js +69 -0
- package/dist/sveltekit/server/handle.js +17 -0
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +2 -0
- package/dist/types/cms.d.ts +4 -3
- package/dist/types/cms.schema.d.ts +1 -1
- package/dist/types/cms.schema.js +13 -2
- package/dist/types/fields.d.ts +56 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -1
- package/dist/types/layout.d.ts +35 -2
- package/dist/types/plugins.d.ts +40 -0
- package/dist/types/plugins.js +4 -1
- package/dist/updates/0.26.0/index.d.ts +2 -0
- package/dist/updates/0.26.0/index.js +51 -0
- package/dist/updates/0.26.1/index.d.ts +2 -0
- package/dist/updates/0.26.1/index.js +19 -0
- package/dist/updates/0.27.0/index.d.ts +2 -0
- package/dist/updates/0.27.0/index.js +50 -0
- package/dist/updates/index.js +7 -1
- package/package.json +29 -7
- package/dist/admin/client/collection/empty-state.svelte +0 -28
- package/dist/admin/client/collection/empty-state.svelte.d.ts +0 -9
- package/dist/admin/client/form/submission-status-badge.svelte +0 -41
- package/dist/admin/client/form/submission-status-badge.svelte.d.ts +0 -7
- package/dist/admin/components/media/file-preview.svelte +0 -51
- package/dist/admin/components/media/file-preview.svelte.d.ts +0 -6
- package/dist/admin/utils/formatDate.d.ts +0 -5
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by `createOrderFromCart` / cart-add HTTP guard when a referenced
|
|
3
|
+
* variant has already expired under the configured `variantExpiry`. The
|
|
4
|
+
* `code` is `VARIANT_EXPIRED` and `variantId` identifies the offending row.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export class VariantExpiredError extends Error {
|
|
8
|
+
code = 'VARIANT_EXPIRED';
|
|
9
|
+
variantId;
|
|
10
|
+
constructor(variantId) {
|
|
11
|
+
super(`Variant ${variantId} has expired`);
|
|
12
|
+
this.name = 'VariantExpiredError';
|
|
13
|
+
this.variantId = variantId;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Add `offsetDays` to an ISO-8601 datetime and return the resulting `Date`.
|
|
18
|
+
* Returns `null` on malformed input (caller decides fail-open vs throw).
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
function addDaysIso(iso, offsetDays) {
|
|
22
|
+
const ts = Date.parse(iso);
|
|
23
|
+
if (Number.isNaN(ts))
|
|
24
|
+
return null;
|
|
25
|
+
return new Date(ts + offsetDays * 86_400_000);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check whether a variant has expired under the given config. Fail-open by
|
|
29
|
+
* design: `null` config, missing source attribute, non-string value, or
|
|
30
|
+
* malformed datetime all return `false` — only a parseable past datetime
|
|
31
|
+
* counts as expired. Malformed datetimes emit `console.warn` so authoring
|
|
32
|
+
* issues surface in dev without blocking checkout.
|
|
33
|
+
*
|
|
34
|
+
* @param variant - Object with an `attributes` map (e.g. `shop_product_variants.attributes`).
|
|
35
|
+
* @param config - Result of `defineShop({ variantExpiry })`, or `null` when not configured.
|
|
36
|
+
* @param now - Override the current time (test seam). Defaults to `Date.now()`.
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export function isVariantExpired(variant, config, now = new Date(Date.now())) {
|
|
40
|
+
if (!config)
|
|
41
|
+
return false;
|
|
42
|
+
const attrs = variant.attributes;
|
|
43
|
+
if (!attrs)
|
|
44
|
+
return false;
|
|
45
|
+
const raw = attrs[config.source];
|
|
46
|
+
if (raw === undefined || raw === null)
|
|
47
|
+
return false;
|
|
48
|
+
if (typeof raw !== 'string') {
|
|
49
|
+
console.warn(`[variantExpiry] attribute "${config.source}" is not a string (got ${typeof raw}); treating variant as upcoming`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const cutoff = addDaysIso(raw, config.offsetDays);
|
|
53
|
+
if (!cutoff) {
|
|
54
|
+
console.warn(`[variantExpiry] attribute "${config.source}" is not a parseable datetime ("${raw}"); treating variant as upcoming`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return cutoff.getTime() < now.getTime();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Return the subset of `variants` that have not yet expired. Order is
|
|
61
|
+
* preserved. `null` config = passthrough.
|
|
62
|
+
* @public
|
|
63
|
+
*/
|
|
64
|
+
export function filterUpcoming(variants, config, now) {
|
|
65
|
+
if (!config)
|
|
66
|
+
return variants;
|
|
67
|
+
return variants.filter((v) => !isVariantExpired(v, config, now));
|
|
68
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RequestHandler } from '@sveltejs/kit';
|
|
2
|
+
/**
|
|
3
|
+
* @experimental
|
|
4
|
+
* HTTP handlers for the balance-payment flow. Mount at
|
|
5
|
+
* `/api/shop/orders/[number]/balance` (or any URL containing `number` +
|
|
6
|
+
* `?token=...`).
|
|
7
|
+
*
|
|
8
|
+
* - `GET` returns the minimal public order view (amount due, currency)
|
|
9
|
+
* when the token + balanceOwed check passes.
|
|
10
|
+
* - `POST` initiates a payment session for the outstanding balance using
|
|
11
|
+
* the order's original payment adapter and returns `{ redirectUrl,
|
|
12
|
+
* status }`.
|
|
13
|
+
*
|
|
14
|
+
* Both verbs return 403 when the token is invalid OR `balanceOwed` is
|
|
15
|
+
* false (no oracle distinction — we don't tell the caller which gate failed).
|
|
16
|
+
*/
|
|
17
|
+
export declare function createBalanceHandler(): {
|
|
18
|
+
GET: RequestHandler;
|
|
19
|
+
POST: RequestHandler;
|
|
20
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { json } from '@sveltejs/kit';
|
|
2
|
+
import { getCMS } from '../../core/cms.js';
|
|
3
|
+
import { createBalanceSession, requireBalanceTokenSecret, verifyBalanceToken } from '../server/balance-payment.js';
|
|
4
|
+
import { getOrderByNumber } from '../server/orders.js';
|
|
5
|
+
function shopEnabled() {
|
|
6
|
+
try {
|
|
7
|
+
return getCMS().shopConfig !== null;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @experimental
|
|
15
|
+
* HTTP handlers for the balance-payment flow. Mount at
|
|
16
|
+
* `/api/shop/orders/[number]/balance` (or any URL containing `number` +
|
|
17
|
+
* `?token=...`).
|
|
18
|
+
*
|
|
19
|
+
* - `GET` returns the minimal public order view (amount due, currency)
|
|
20
|
+
* when the token + balanceOwed check passes.
|
|
21
|
+
* - `POST` initiates a payment session for the outstanding balance using
|
|
22
|
+
* the order's original payment adapter and returns `{ redirectUrl,
|
|
23
|
+
* status }`.
|
|
24
|
+
*
|
|
25
|
+
* Both verbs return 403 when the token is invalid OR `balanceOwed` is
|
|
26
|
+
* false (no oracle distinction — we don't tell the caller which gate failed).
|
|
27
|
+
*/
|
|
28
|
+
export function createBalanceHandler() {
|
|
29
|
+
return {
|
|
30
|
+
GET: async ({ params, url }) => {
|
|
31
|
+
if (!shopEnabled())
|
|
32
|
+
return json({ error: 'Shop not enabled' }, { status: 404 });
|
|
33
|
+
const orderNumber = params.number;
|
|
34
|
+
if (!orderNumber)
|
|
35
|
+
return json({ error: 'Order number required' }, { status: 400 });
|
|
36
|
+
const token = url.searchParams.get('token') ?? '';
|
|
37
|
+
const order = await getOrderByNumber(orderNumber);
|
|
38
|
+
if (!order)
|
|
39
|
+
return json({ error: 'Order not found' }, { status: 404 });
|
|
40
|
+
if (!order.balanceOwed || !order.partialPayment) {
|
|
41
|
+
return json({ error: 'No outstanding balance' }, { status: 403 });
|
|
42
|
+
}
|
|
43
|
+
const secret = requireBalanceTokenSecret();
|
|
44
|
+
if (!verifyBalanceToken(token, order.id, secret)) {
|
|
45
|
+
return json({ error: 'Invalid token' }, { status: 403 });
|
|
46
|
+
}
|
|
47
|
+
const pp = order.partialPayment;
|
|
48
|
+
return json({
|
|
49
|
+
orderNumber: order.number,
|
|
50
|
+
currency: order.currency,
|
|
51
|
+
totalGross: order.totalGross,
|
|
52
|
+
amountToPay: pp.balanceAmount,
|
|
53
|
+
paidAmount: pp.paidAmount,
|
|
54
|
+
paymentMethod: order.paymentMethod,
|
|
55
|
+
language: order.language
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
POST: async ({ params, url, request, getClientAddress }) => {
|
|
59
|
+
if (!shopEnabled())
|
|
60
|
+
return json({ error: 'Shop not enabled' }, { status: 404 });
|
|
61
|
+
const orderNumber = params.number;
|
|
62
|
+
if (!orderNumber)
|
|
63
|
+
return json({ error: 'Order number required' }, { status: 400 });
|
|
64
|
+
const token = url.searchParams.get('token') ?? '';
|
|
65
|
+
let customerIp;
|
|
66
|
+
try {
|
|
67
|
+
customerIp = getClientAddress();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
customerIp = undefined;
|
|
71
|
+
}
|
|
72
|
+
const body = (await request.json().catch(() => ({})));
|
|
73
|
+
const language = typeof body.language === 'string' ? body.language : undefined;
|
|
74
|
+
try {
|
|
75
|
+
const result = await createBalanceSession(orderNumber, token, { customerIp, language });
|
|
76
|
+
return json({
|
|
77
|
+
status: result.status,
|
|
78
|
+
redirectUrl: result.redirectUrl ?? null,
|
|
79
|
+
requiresPaymentRedirect: result.status === 'redirect'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const message = err instanceof Error ? err.message : 'Balance payment failed';
|
|
84
|
+
const code = message === 'Invalid balance token' || message === 'Order has no outstanding balance'
|
|
85
|
+
? 403
|
|
86
|
+
: 400;
|
|
87
|
+
return json({ error: message }, { status: code });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
1
2
|
import { json } from '@sveltejs/kit';
|
|
2
3
|
import { getCMS } from '../../core/cms.js';
|
|
4
|
+
import { shopProductVariantsTable } from '../../db-postgres/schema/shop/index.js';
|
|
3
5
|
import { addItem, readCartCookie, removeItem, setItemQty, writeCartCookie } from '../cart/cookie.js';
|
|
4
6
|
import { clearCouponCookie, isValidCouponCode, normalizeCouponCode, readCouponCookie, writeCouponCookie } from '../cart/coupon-cookie.js';
|
|
5
7
|
import { hydrateCart } from '../server/cart-hydrate.js';
|
|
6
8
|
import { CouponError, validateCoupon } from '../server/coupons.js';
|
|
9
|
+
import { getShopDb } from '../server/db.js';
|
|
7
10
|
import { checkRateLimit, clientKey } from '../rate-limit.js';
|
|
11
|
+
import { isVariantExpired } from '../expiry.js';
|
|
8
12
|
function shopEnabled() {
|
|
9
13
|
try {
|
|
10
14
|
return getCMS().shopConfig !== null;
|
|
@@ -54,6 +58,21 @@ export function createCartHandler() {
|
|
|
54
58
|
if (!Number.isFinite(qtyNum) || qtyNum <= 0) {
|
|
55
59
|
return json({ error: 'qty must be > 0' }, { status: 400 });
|
|
56
60
|
}
|
|
61
|
+
const expiryConfig = getCMS().shopConfig?.variantExpiry ?? null;
|
|
62
|
+
if (expiryConfig) {
|
|
63
|
+
const db = getShopDb();
|
|
64
|
+
const [row] = await db
|
|
65
|
+
.select({
|
|
66
|
+
id: shopProductVariantsTable.id,
|
|
67
|
+
attributes: shopProductVariantsTable.attributes
|
|
68
|
+
})
|
|
69
|
+
.from(shopProductVariantsTable)
|
|
70
|
+
.where(eq(shopProductVariantsTable.id, variantId));
|
|
71
|
+
if (row &&
|
|
72
|
+
isVariantExpired({ attributes: row.attributes }, expiryConfig)) {
|
|
73
|
+
return json({ error: 'VARIANT_EXPIRED', variantId }, { status: 400 });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
57
76
|
const items = readCartCookie(cookies);
|
|
58
77
|
const next = addItem(items, variantId, qtyNum);
|
|
59
78
|
writeCartCookie(cookies, next);
|
|
@@ -4,6 +4,7 @@ import { readCartCookie, writeCartCookie } from '../cart/cookie.js';
|
|
|
4
4
|
import { clearCouponCookie, readCouponCookie } from '../cart/coupon-cookie.js';
|
|
5
5
|
import { writeOrderTokenCookie } from '../cart/order-token-cookie.js';
|
|
6
6
|
import { createOrderFromCart, setPaymentProviderRef } from '../server/orders.js';
|
|
7
|
+
import { insertPaymentRow } from '../server/payments.js';
|
|
7
8
|
import { getShippingMethod } from '../server/shipping.js';
|
|
8
9
|
import { checkRateLimit, clientKey } from '../rate-limit.js';
|
|
9
10
|
import { requireShopConfig } from '../server/db.js';
|
|
@@ -119,10 +120,16 @@ export function createCheckoutHandler() {
|
|
|
119
120
|
const adapter = shop.payment.find((a) => a.id === paymentMethod);
|
|
120
121
|
let paymentResult = null;
|
|
121
122
|
if (adapter) {
|
|
123
|
+
// Under deposit policy, charge only the resolved deposit (line
|
|
124
|
+
// deposits + shipping) at checkout. The balance flow charges the
|
|
125
|
+
// remainder via a signed token later. We override totalGross on
|
|
126
|
+
// the OrderRef passed to the adapter so adapters that compute the
|
|
127
|
+
// charge from `orderRef.totalGross` Just Work — `order.totalGross`
|
|
128
|
+
// in the DB stays the source of truth for the full obligation.
|
|
122
129
|
const orderRef = {
|
|
123
130
|
id: result.order.id,
|
|
124
131
|
number: result.order.number,
|
|
125
|
-
totalGross: result.
|
|
132
|
+
totalGross: result.amountToPay,
|
|
126
133
|
currency: shop.currency,
|
|
127
134
|
customerEmail: result.order.customerEmail
|
|
128
135
|
};
|
|
@@ -141,6 +148,17 @@ export function createCheckoutHandler() {
|
|
|
141
148
|
if (paymentResult.providerRef) {
|
|
142
149
|
await setPaymentProviderRef(result.order.id, paymentResult.providerRef);
|
|
143
150
|
}
|
|
151
|
+
// Track this payment as its own row so the webhook can branch
|
|
152
|
+
// by `kind` (full / deposit / balance) and `refundOrder` can
|
|
153
|
+
// target deposit-vs-balance independently.
|
|
154
|
+
await insertPaymentRow({
|
|
155
|
+
orderId: result.order.id,
|
|
156
|
+
provider: adapter.id,
|
|
157
|
+
providerRef: paymentResult.providerRef ?? null,
|
|
158
|
+
kind: result.paymentKind,
|
|
159
|
+
amount: result.amountToPay,
|
|
160
|
+
currency: shop.currency
|
|
161
|
+
});
|
|
144
162
|
}
|
|
145
163
|
catch (err) {
|
|
146
164
|
console.error(`[shop] createPayment failed for ${result.order.number}:`, err);
|
|
@@ -8,3 +8,5 @@ export { createRetryPaymentHandler } from './retry-payment-handler.js';
|
|
|
8
8
|
export { createCarrierConfigHandler } from './carrier-handler.js';
|
|
9
9
|
export { createCarrierWebhookHandler } from './carrier-webhook-handler.js';
|
|
10
10
|
export { createShipmentLabelHandler } from './shipment-label-handler.js';
|
|
11
|
+
export { createUpcomingVariantsHandler } from './upcoming-handler.js';
|
|
12
|
+
export { createBalanceHandler } from './balance-handler.js';
|
package/dist/shop/http/index.js
CHANGED
|
@@ -8,3 +8,5 @@ export { createRetryPaymentHandler } from './retry-payment-handler.js';
|
|
|
8
8
|
export { createCarrierConfigHandler } from './carrier-handler.js';
|
|
9
9
|
export { createCarrierWebhookHandler } from './carrier-webhook-handler.js';
|
|
10
10
|
export { createShipmentLabelHandler } from './shipment-label-handler.js';
|
|
11
|
+
export { createUpcomingVariantsHandler } from './upcoming-handler.js';
|
|
12
|
+
export { createBalanceHandler } from './balance-handler.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RequestHandler } from '@sveltejs/kit';
|
|
2
|
+
/**
|
|
3
|
+
* Build a SvelteKit `GET` handler for upcoming variants of a single product.
|
|
4
|
+
* Mount at `/api/shop/products/[id]/variants/upcoming`.
|
|
5
|
+
*
|
|
6
|
+
* Returns `{ items: VariantRow[] }`. When `defineShop({ variantExpiry })` is
|
|
7
|
+
* configured, expired variants are filtered out; otherwise every variant for
|
|
8
|
+
* the product is returned. Order: `attributes.<source>` ascending if expiry
|
|
9
|
+
* config is set, otherwise insertion order.
|
|
10
|
+
*
|
|
11
|
+
* @experimental — HTTP shape is stable for envet pilot but may grow in 1.x
|
|
12
|
+
* (e.g. pagination, language filter) once a second consumer lands.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createUpcomingVariantsHandler(): {
|
|
15
|
+
GET: RequestHandler;
|
|
16
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { asc, eq } from 'drizzle-orm';
|
|
2
|
+
import { json } from '@sveltejs/kit';
|
|
3
|
+
import { getCMS } from '../../core/cms.js';
|
|
4
|
+
import { shopProductVariantsTable } from '../../db-postgres/schema/shop/index.js';
|
|
5
|
+
import { filterUpcoming } from '../expiry.js';
|
|
6
|
+
import { getShopDb } from '../server/db.js';
|
|
7
|
+
function shopEnabled() {
|
|
8
|
+
try {
|
|
9
|
+
return getCMS().shopConfig !== null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a SvelteKit `GET` handler for upcoming variants of a single product.
|
|
17
|
+
* Mount at `/api/shop/products/[id]/variants/upcoming`.
|
|
18
|
+
*
|
|
19
|
+
* Returns `{ items: VariantRow[] }`. When `defineShop({ variantExpiry })` is
|
|
20
|
+
* configured, expired variants are filtered out; otherwise every variant for
|
|
21
|
+
* the product is returned. Order: `attributes.<source>` ascending if expiry
|
|
22
|
+
* config is set, otherwise insertion order.
|
|
23
|
+
*
|
|
24
|
+
* @experimental — HTTP shape is stable for envet pilot but may grow in 1.x
|
|
25
|
+
* (e.g. pagination, language filter) once a second consumer lands.
|
|
26
|
+
*/
|
|
27
|
+
export function createUpcomingVariantsHandler() {
|
|
28
|
+
return {
|
|
29
|
+
GET: async ({ params }) => {
|
|
30
|
+
if (!shopEnabled())
|
|
31
|
+
return json({ error: 'Shop not enabled' }, { status: 404 });
|
|
32
|
+
const productId = params.id;
|
|
33
|
+
if (!productId)
|
|
34
|
+
return json({ error: 'Product id required' }, { status: 400 });
|
|
35
|
+
const db = getShopDb();
|
|
36
|
+
const rows = await db
|
|
37
|
+
.select()
|
|
38
|
+
.from(shopProductVariantsTable)
|
|
39
|
+
.where(eq(shopProductVariantsTable.productId, productId))
|
|
40
|
+
.orderBy(asc(shopProductVariantsTable.createdAt));
|
|
41
|
+
const expiryConfig = getCMS().shopConfig?.variantExpiry ?? null;
|
|
42
|
+
const variants = rows.map((r) => ({
|
|
43
|
+
...r,
|
|
44
|
+
attributes: r.attributes
|
|
45
|
+
}));
|
|
46
|
+
const upcoming = filterUpcoming(variants, expiryConfig);
|
|
47
|
+
if (expiryConfig) {
|
|
48
|
+
upcoming.sort((a, b) => {
|
|
49
|
+
const aVal = a.attributes?.[expiryConfig.source];
|
|
50
|
+
const bVal = b.attributes?.[expiryConfig.source];
|
|
51
|
+
const aTs = typeof aVal === 'string' ? Date.parse(aVal) : NaN;
|
|
52
|
+
const bTs = typeof bVal === 'string' ? Date.parse(bVal) : NaN;
|
|
53
|
+
if (Number.isNaN(aTs) && Number.isNaN(bTs))
|
|
54
|
+
return 0;
|
|
55
|
+
if (Number.isNaN(aTs))
|
|
56
|
+
return 1;
|
|
57
|
+
if (Number.isNaN(bTs))
|
|
58
|
+
return -1;
|
|
59
|
+
return aTs - bTs;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return json({ items: upcoming });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { json } from '@sveltejs/kit';
|
|
2
2
|
import { getCMS } from '../../core/cms.js';
|
|
3
3
|
import { requireShopConfig } from '../server/db.js';
|
|
4
|
-
import { getOrderByNumber, updateOrderStatus } from '../server/orders.js';
|
|
4
|
+
import { getOrderByNumber, markBalancePaid, updateOrderStatus } from '../server/orders.js';
|
|
5
|
+
import { findPaymentByProviderRef, markPaymentFailed, markPaymentPaid } from '../server/payments.js';
|
|
5
6
|
import { checkRateLimit, clientKey } from '../rate-limit.js';
|
|
6
7
|
import { isTerminalStatus, mapEventToStatus } from './webhook-logic.js';
|
|
7
8
|
import { markWebhookEventProcessed, reserveWebhookEvent } from './webhook-idempotency.js';
|
|
@@ -59,24 +60,60 @@ export function createPaymentWebhookHandler() {
|
|
|
59
60
|
await markWebhookEventProcessed(reservation.rowId, null);
|
|
60
61
|
return json({ received: true });
|
|
61
62
|
}
|
|
63
|
+
// Match payment row so we know whether the event targets a `full`,
|
|
64
|
+
// `deposit`, or `balance` payment row. Falls back to `'full'` when
|
|
65
|
+
// no row exists (back-compat with orders created before 0.27).
|
|
66
|
+
const paymentRow = await findPaymentByProviderRef(provider, event.providerRef);
|
|
67
|
+
const kind = paymentRow?.kind ?? 'full';
|
|
68
|
+
const targetStatus = mapEventToStatus(event);
|
|
69
|
+
if (!targetStatus) {
|
|
70
|
+
if (reservation.rowId)
|
|
71
|
+
await markWebhookEventProcessed(reservation.rowId, order.id);
|
|
72
|
+
return json({ received: true, noop: true });
|
|
73
|
+
}
|
|
74
|
+
// Balance events must NOT re-trigger updateOrderStatus(paid) — the
|
|
75
|
+
// order already transitioned when the deposit cleared. Use the
|
|
76
|
+
// markBalancePaid path which only updates partialPayment/balanceOwed.
|
|
77
|
+
if (kind === 'balance') {
|
|
78
|
+
try {
|
|
79
|
+
if (targetStatus === 'paid') {
|
|
80
|
+
await markBalancePaid(order.id);
|
|
81
|
+
if (paymentRow)
|
|
82
|
+
await markPaymentPaid(paymentRow.id);
|
|
83
|
+
}
|
|
84
|
+
else if (targetStatus === 'paymentRejected' && paymentRow) {
|
|
85
|
+
await markPaymentFailed(paymentRow.id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error(`[shop] balance webhook failed for ${order.number}:`, err);
|
|
90
|
+
return json({ error: 'Processing failed' }, { status: 500 });
|
|
91
|
+
}
|
|
92
|
+
if (reservation.rowId)
|
|
93
|
+
await markWebhookEventProcessed(reservation.rowId, order.id);
|
|
94
|
+
return json({ received: true, kind: 'balance' });
|
|
95
|
+
}
|
|
62
96
|
// Secondary idempotency — terminal states are no-ops (covers adapters
|
|
63
97
|
// that didn't surface eventId).
|
|
64
98
|
if (isTerminalStatus(order.status)) {
|
|
99
|
+
if (paymentRow && targetStatus === 'paid')
|
|
100
|
+
await markPaymentPaid(paymentRow.id);
|
|
65
101
|
if (reservation.rowId)
|
|
66
102
|
await markWebhookEventProcessed(reservation.rowId, order.id);
|
|
67
103
|
return json({ received: true, idempotent: true });
|
|
68
104
|
}
|
|
69
|
-
const targetStatus = mapEventToStatus(event);
|
|
70
|
-
if (!targetStatus) {
|
|
71
|
-
if (reservation.rowId)
|
|
72
|
-
await markWebhookEventProcessed(reservation.rowId, order.id);
|
|
73
|
-
return json({ received: true, noop: true });
|
|
74
|
-
}
|
|
75
105
|
try {
|
|
76
106
|
await updateOrderStatus(order.id, targetStatus, {
|
|
77
107
|
note: `Payment webhook (${provider})`,
|
|
78
|
-
changedBy: 'payment-webhook'
|
|
108
|
+
changedBy: 'payment-webhook',
|
|
109
|
+
paymentKind: kind
|
|
79
110
|
});
|
|
111
|
+
if (paymentRow) {
|
|
112
|
+
if (targetStatus === 'paid')
|
|
113
|
+
await markPaymentPaid(paymentRow.id);
|
|
114
|
+
else if (targetStatus === 'paymentRejected')
|
|
115
|
+
await markPaymentFailed(paymentRow.id);
|
|
116
|
+
}
|
|
80
117
|
}
|
|
81
118
|
catch (err) {
|
|
82
119
|
console.error(`[shop] updateOrderStatus failed for ${order.number}:`, err);
|
|
@@ -85,7 +122,7 @@ export function createPaymentWebhookHandler() {
|
|
|
85
122
|
}
|
|
86
123
|
if (reservation.rowId)
|
|
87
124
|
await markWebhookEventProcessed(reservation.rowId, order.id);
|
|
88
|
-
return json({ received: true });
|
|
125
|
+
return json({ received: true, kind });
|
|
89
126
|
}
|
|
90
127
|
};
|
|
91
128
|
}
|
package/dist/shop/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ShopConfig, ResolvedShopConfig } from './types.js';
|
|
2
2
|
export declare function defineShop(config: ShopConfig): ResolvedShopConfig;
|
|
3
|
+
export { InvalidVariantAttributesError } from './variant-attributes.js';
|
|
4
|
+
export { isVariantExpired, filterUpcoming, VariantExpiredError } from './expiry.js';
|
|
3
5
|
export { manualAdapter } from './adapters/manual/index.js';
|
|
4
6
|
export { payuAdapter } from './adapters/payu/index.js';
|
|
5
7
|
export type { PayuAdapterOptions } from './adapters/payu/index.js';
|
|
@@ -7,4 +9,5 @@ export { stripeAdapter } from './adapters/stripe/index.js';
|
|
|
7
9
|
export type { StripeAdapterOptions } from './adapters/stripe/index.js';
|
|
8
10
|
export { inpostAdapter } from './adapters/inpost/index.js';
|
|
9
11
|
export type { InpostAdapterOptions, InpostSenderAddress, GeowidgetConfigPreset, InpostEnvironment } from './adapters/inpost/index.js';
|
|
10
|
-
export type { ShopConfig, ResolvedShopConfig, Currency, OrderStatus, PaymentAdapter, PaymentCreateContext, PaymentRefundInput, PaymentRefundResult, CarrierAdapter, CarrierEvent, ShipmentCreateInput, ShipmentCreateResult, ShipmentLabel, ConsentConfig, ShopFeatures, PaymentCreateResult, PaymentEvent, OrderRef, CouponRef, I18nText } from './types.js';
|
|
12
|
+
export type { ShopConfig, ResolvedShopConfig, Currency, OrderStatus, PaymentAdapter, PaymentCreateContext, PaymentRefundInput, PaymentRefundResult, CarrierAdapter, CarrierEvent, ShipmentCreateInput, ShipmentCreateResult, ShipmentLabel, ConsentConfig, ShopFeatures, PaymentCreateResult, PaymentEvent, OrderRef, CouponRef, I18nText, VariantAttribute, VariantAttributeText, VariantAttributeNumber, VariantAttributeDatetime, VariantAttributeSelect, VariantAttributeBoolean, VariantAttributeImage, VariantAttributeEntry, VariantAttributeSlug, VariantLabelConfig, VariantExpiryConfig, PaymentPolicy, DepositAmount, PartialPayment } from './types.js';
|
|
13
|
+
export { interpolateTemplate } from './template.js';
|
package/dist/shop/index.js
CHANGED
|
@@ -13,10 +13,16 @@ export function defineShop(config) {
|
|
|
13
13
|
},
|
|
14
14
|
carriers: config.carriers ?? [],
|
|
15
15
|
consents: config.consents ?? [],
|
|
16
|
-
orderViewUrl: config.orderViewUrl ?? '/shop/order/{orderNumber}?token={accessToken}'
|
|
16
|
+
orderViewUrl: config.orderViewUrl ?? '/shop/order/{orderNumber}?token={accessToken}',
|
|
17
|
+
variantAttributes: config.variantAttributes ?? {},
|
|
18
|
+
variantLabel: config.variantLabel ?? null,
|
|
19
|
+
variantExpiry: config.variantExpiry ?? null
|
|
17
20
|
};
|
|
18
21
|
}
|
|
22
|
+
export { InvalidVariantAttributesError } from './variant-attributes.js';
|
|
23
|
+
export { isVariantExpired, filterUpcoming, VariantExpiredError } from './expiry.js';
|
|
19
24
|
export { manualAdapter } from './adapters/manual/index.js';
|
|
20
25
|
export { payuAdapter } from './adapters/payu/index.js';
|
|
21
26
|
export { stripeAdapter } from './adapters/stripe/index.js';
|
|
22
27
|
export { inpostAdapter } from './adapters/inpost/index.js';
|
|
28
|
+
export { interpolateTemplate } from './template.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PaymentCreateContext, PaymentCreateResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
* Generate a signed balance-payment token for `orderId`. Format:
|
|
5
|
+
* `base64url(payload).base64url(hmac-sha256(payload, secret))`. The token
|
|
6
|
+
* is deterministic for a given (orderId, secret) pair — server-side
|
|
7
|
+
* invalidation is by `order.balanceOwed` rather than rotation.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateBalanceToken(orderId: string, secret: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
* Verify a balance-payment token. Checks payload structure, `type` claim,
|
|
13
|
+
* orderId match, and constant-time HMAC compare. Does NOT consult the
|
|
14
|
+
* database — call `order.balanceOwed === true` separately as the
|
|
15
|
+
* server-side invalidation gate.
|
|
16
|
+
*/
|
|
17
|
+
export declare function verifyBalanceToken(token: string, orderId: string, secret: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
* Resolve the HMAC secret for balance tokens from the environment. Throws
|
|
21
|
+
* when missing — admin must set `INCLUDIO_BALANCE_TOKEN_SECRET` before
|
|
22
|
+
* enabling any deposit `paymentPolicy`. We refuse to silently downgrade
|
|
23
|
+
* because tokens without entropy would let arbitrary customers pay a
|
|
24
|
+
* balance for arbitrary orders.
|
|
25
|
+
*/
|
|
26
|
+
export declare function requireBalanceTokenSecret(): string;
|
|
27
|
+
export interface CreateBalanceSessionResult {
|
|
28
|
+
status: PaymentCreateResult['status'];
|
|
29
|
+
redirectUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @experimental
|
|
33
|
+
* Initiate a payment session for the outstanding balance on a deposit order.
|
|
34
|
+
* Verifies the token + `order.balanceOwed === true` + reuses the order's
|
|
35
|
+
* original payment adapter. Inserts a `shop_payments` row tagged
|
|
36
|
+
* `kind='balance'` so the webhook can route the eventual paid event to
|
|
37
|
+
* `markBalancePaid`. Errors when token invalid, order missing, balance
|
|
38
|
+
* already cleared, or adapter not configured.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createBalanceSession(orderNumber: string, token: string, ctx?: PaymentCreateContext): Promise<CreateBalanceSessionResult>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { shopOrdersTable } from '../../db-postgres/schema/shop/index.js';
|
|
4
|
+
import { getShopDb, requireShopConfig } from './db.js';
|
|
5
|
+
import { getOrderByNumber } from './orders.js';
|
|
6
|
+
import { insertPaymentRow } from './payments.js';
|
|
7
|
+
function base64url(buf) {
|
|
8
|
+
return buf.toString('base64').replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
9
|
+
}
|
|
10
|
+
function base64urlDecode(s) {
|
|
11
|
+
if (!s)
|
|
12
|
+
return null;
|
|
13
|
+
const padded = s.replace(/-/g, '+').replace(/_/g, '/');
|
|
14
|
+
const pad = padded.length % 4 === 0 ? '' : '='.repeat(4 - (padded.length % 4));
|
|
15
|
+
try {
|
|
16
|
+
return Buffer.from(padded + pad, 'base64');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function sign(payload, secret) {
|
|
23
|
+
return createHmac('sha256', secret).update(payload).digest();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @internal
|
|
27
|
+
* Generate a signed balance-payment token for `orderId`. Format:
|
|
28
|
+
* `base64url(payload).base64url(hmac-sha256(payload, secret))`. The token
|
|
29
|
+
* is deterministic for a given (orderId, secret) pair — server-side
|
|
30
|
+
* invalidation is by `order.balanceOwed` rather than rotation.
|
|
31
|
+
*/
|
|
32
|
+
export function generateBalanceToken(orderId, secret) {
|
|
33
|
+
const payload = { orderId, type: 'balance' };
|
|
34
|
+
const payloadBuf = Buffer.from(JSON.stringify(payload), 'utf8');
|
|
35
|
+
const sig = sign(payloadBuf, secret);
|
|
36
|
+
return `${base64url(payloadBuf)}.${base64url(sig)}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
* Verify a balance-payment token. Checks payload structure, `type` claim,
|
|
41
|
+
* orderId match, and constant-time HMAC compare. Does NOT consult the
|
|
42
|
+
* database — call `order.balanceOwed === true` separately as the
|
|
43
|
+
* server-side invalidation gate.
|
|
44
|
+
*/
|
|
45
|
+
export function verifyBalanceToken(token, orderId, secret) {
|
|
46
|
+
if (!token || typeof token !== 'string')
|
|
47
|
+
return false;
|
|
48
|
+
const dotIndex = token.indexOf('.');
|
|
49
|
+
if (dotIndex < 1 || dotIndex >= token.length - 1)
|
|
50
|
+
return false;
|
|
51
|
+
const payloadPart = token.slice(0, dotIndex);
|
|
52
|
+
const sigPart = token.slice(dotIndex + 1);
|
|
53
|
+
const payloadBuf = base64urlDecode(payloadPart);
|
|
54
|
+
const sigBuf = base64urlDecode(sigPart);
|
|
55
|
+
if (!payloadBuf || !sigBuf)
|
|
56
|
+
return false;
|
|
57
|
+
let payload;
|
|
58
|
+
try {
|
|
59
|
+
payload = JSON.parse(payloadBuf.toString('utf8'));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (!payload || payload.type !== 'balance' || payload.orderId !== orderId)
|
|
65
|
+
return false;
|
|
66
|
+
const expected = sign(payloadBuf, secret);
|
|
67
|
+
if (expected.length !== sigBuf.length)
|
|
68
|
+
return false;
|
|
69
|
+
return timingSafeEqual(new Uint8Array(expected), new Uint8Array(sigBuf));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* @internal
|
|
73
|
+
* Resolve the HMAC secret for balance tokens from the environment. Throws
|
|
74
|
+
* when missing — admin must set `INCLUDIO_BALANCE_TOKEN_SECRET` before
|
|
75
|
+
* enabling any deposit `paymentPolicy`. We refuse to silently downgrade
|
|
76
|
+
* because tokens without entropy would let arbitrary customers pay a
|
|
77
|
+
* balance for arbitrary orders.
|
|
78
|
+
*/
|
|
79
|
+
export function requireBalanceTokenSecret() {
|
|
80
|
+
const secret = process.env.INCLUDIO_BALANCE_TOKEN_SECRET;
|
|
81
|
+
if (!secret || secret.length < 16) {
|
|
82
|
+
throw new Error('INCLUDIO_BALANCE_TOKEN_SECRET env var required (≥16 chars) for balance link generation.');
|
|
83
|
+
}
|
|
84
|
+
return secret;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* @experimental
|
|
88
|
+
* Initiate a payment session for the outstanding balance on a deposit order.
|
|
89
|
+
* Verifies the token + `order.balanceOwed === true` + reuses the order's
|
|
90
|
+
* original payment adapter. Inserts a `shop_payments` row tagged
|
|
91
|
+
* `kind='balance'` so the webhook can route the eventual paid event to
|
|
92
|
+
* `markBalancePaid`. Errors when token invalid, order missing, balance
|
|
93
|
+
* already cleared, or adapter not configured.
|
|
94
|
+
*/
|
|
95
|
+
export async function createBalanceSession(orderNumber, token, ctx) {
|
|
96
|
+
const shop = requireShopConfig();
|
|
97
|
+
const order = await getOrderByNumber(orderNumber);
|
|
98
|
+
if (!order)
|
|
99
|
+
throw new Error('Order not found');
|
|
100
|
+
if (!order.balanceOwed || !order.partialPayment) {
|
|
101
|
+
throw new Error('Order has no outstanding balance');
|
|
102
|
+
}
|
|
103
|
+
if (!order.paymentMethod)
|
|
104
|
+
throw new Error('Order has no payment method');
|
|
105
|
+
const secret = requireBalanceTokenSecret();
|
|
106
|
+
if (!verifyBalanceToken(token, order.id, secret)) {
|
|
107
|
+
throw new Error('Invalid balance token');
|
|
108
|
+
}
|
|
109
|
+
const adapter = shop.payment.find((a) => a.id === order.paymentMethod);
|
|
110
|
+
if (!adapter)
|
|
111
|
+
throw new Error(`Payment provider "${order.paymentMethod}" is not configured`);
|
|
112
|
+
const balanceAmount = order.partialPayment.balanceAmount;
|
|
113
|
+
const orderRef = {
|
|
114
|
+
id: order.id,
|
|
115
|
+
number: order.number,
|
|
116
|
+
totalGross: balanceAmount,
|
|
117
|
+
currency: shop.currency,
|
|
118
|
+
customerEmail: order.customerEmail
|
|
119
|
+
};
|
|
120
|
+
const result = await adapter.createPayment(orderRef, ctx);
|
|
121
|
+
await insertPaymentRow({
|
|
122
|
+
orderId: order.id,
|
|
123
|
+
provider: adapter.id,
|
|
124
|
+
providerRef: result.providerRef ?? null,
|
|
125
|
+
kind: 'balance',
|
|
126
|
+
amount: balanceAmount,
|
|
127
|
+
currency: shop.currency
|
|
128
|
+
});
|
|
129
|
+
// Mirror the latest providerRef on the order so refunds / refresh-payment
|
|
130
|
+
// (which look at order.paymentProviderRef as a fallback) point to the
|
|
131
|
+
// balance row first. Per-kind refund still uses shop_payments.kind.
|
|
132
|
+
if (result.providerRef) {
|
|
133
|
+
const db = getShopDb();
|
|
134
|
+
await db
|
|
135
|
+
.update(shopOrdersTable)
|
|
136
|
+
.set({ paymentProviderRef: result.providerRef, updatedAt: new Date() })
|
|
137
|
+
.where(eq(shopOrdersTable.id, order.id));
|
|
138
|
+
}
|
|
139
|
+
return { status: result.status, redirectUrl: result.redirectUrl };
|
|
140
|
+
}
|