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
|
@@ -127,6 +127,7 @@ export async function hydrateCart(items, opts = {}) {
|
|
|
127
127
|
variantId: ref.variantId,
|
|
128
128
|
qty: effectiveQty,
|
|
129
129
|
entryId: product.entryId,
|
|
130
|
+
productId: product.id,
|
|
130
131
|
variantName: variant.name ?? null,
|
|
131
132
|
variantSku: variant.sku,
|
|
132
133
|
productTitle: title,
|
|
@@ -205,6 +206,7 @@ export async function hydrateCart(items, opts = {}) {
|
|
|
205
206
|
variantId: ref.variantId,
|
|
206
207
|
qty: 0,
|
|
207
208
|
entryId: '',
|
|
209
|
+
productId: '',
|
|
208
210
|
variantName: null,
|
|
209
211
|
variantSku: null,
|
|
210
212
|
productTitle: null,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DatabaseAdapterWithDrizzle } from '../../db-postgres/index.js';
|
|
2
|
+
import type { ResolvedShopConfig } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Apply variant attribute GIN indexes for the given shop config.
|
|
5
|
+
* Idempotent (`CREATE INDEX IF NOT EXISTS`) and serialized — concurrent calls
|
|
6
|
+
* share the same in-flight promise, so a HMR re-init or fire-and-forget from
|
|
7
|
+
* `initCMS()` plus an explicit caller cannot race into a postgres
|
|
8
|
+
* `pg_class_relname_nsp_index` duplicate-key error.
|
|
9
|
+
*
|
|
10
|
+
* Both args are required to keep this module free of CMS singleton coupling
|
|
11
|
+
* (it stays importable from anywhere without circular deps).
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyVariantAttributeIndexes(shop: ResolvedShopConfig, db: DatabaseAdapterWithDrizzle['_drizzle']): Promise<void>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { createVariantAttributeIndexes } from '../../db-postgres/schema/shop/productVariant.js';
|
|
3
|
+
let inFlight = null;
|
|
4
|
+
/**
|
|
5
|
+
* Apply variant attribute GIN indexes for the given shop config.
|
|
6
|
+
* Idempotent (`CREATE INDEX IF NOT EXISTS`) and serialized — concurrent calls
|
|
7
|
+
* share the same in-flight promise, so a HMR re-init or fire-and-forget from
|
|
8
|
+
* `initCMS()` plus an explicit caller cannot race into a postgres
|
|
9
|
+
* `pg_class_relname_nsp_index` duplicate-key error.
|
|
10
|
+
*
|
|
11
|
+
* Both args are required to keep this module free of CMS singleton coupling
|
|
12
|
+
* (it stays importable from anywhere without circular deps).
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export function applyVariantAttributeIndexes(shop, db) {
|
|
16
|
+
if (inFlight)
|
|
17
|
+
return inFlight;
|
|
18
|
+
// Cleanup runs via `.finally()` (microtask) — NOT inside the async body's
|
|
19
|
+
// try/finally — so the outer `inFlight = promise` assignment lands before
|
|
20
|
+
// the reset, even when the body resolves synchronously (no stmts case).
|
|
21
|
+
const promise = (async () => {
|
|
22
|
+
const stmts = createVariantAttributeIndexes(shop.variantAttributes);
|
|
23
|
+
if (stmts.length === 0)
|
|
24
|
+
return;
|
|
25
|
+
for (const stmt of stmts) {
|
|
26
|
+
await db.execute(sql.raw(stmt));
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
inFlight = promise;
|
|
30
|
+
promise.finally(() => {
|
|
31
|
+
if (inFlight === promise)
|
|
32
|
+
inFlight = null;
|
|
33
|
+
});
|
|
34
|
+
return promise;
|
|
35
|
+
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { shopOrderItemsTable, shopOrderStatusHistoryTable, shopOrdersTable } from '../../db-postgres/schema/shop/index.js';
|
|
2
2
|
import type { CartItemRef } from '../cart/types.js';
|
|
3
3
|
import type { OrderStatus } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
* Thrown by `createOrderFromCart` when the cart contains items from multiple
|
|
7
|
+
* products and at least one of them has a deposit `paymentPolicy`. Deposit
|
|
8
|
+
* orders must span a single product (so the balance link/refund-per-kind flow
|
|
9
|
+
* stays unambiguous). Mixed-product carts are accepted only when every
|
|
10
|
+
* involved product has a `full` (or null) policy.
|
|
11
|
+
*/
|
|
12
|
+
export declare class MixedPaymentPolicyError extends Error {
|
|
13
|
+
readonly code = "MIXED_PAYMENT_POLICY";
|
|
14
|
+
constructor(message?: string);
|
|
15
|
+
}
|
|
4
16
|
export type OrderRow = typeof shopOrdersTable.$inferSelect;
|
|
5
17
|
export type OrderItemRow = typeof shopOrderItemsTable.$inferSelect;
|
|
6
18
|
export type OrderStatusHistoryRow = typeof shopOrderStatusHistoryTable.$inferSelect;
|
|
@@ -27,12 +39,34 @@ export interface CreateOrderResult {
|
|
|
27
39
|
items: OrderItemRow[];
|
|
28
40
|
requiresPaymentRedirect: boolean;
|
|
29
41
|
redirectUrl?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Minor-unit amount the customer should pay at checkout under
|
|
44
|
+
* `paymentPolicy`. Equals `order.totalGross` for full-payment orders;
|
|
45
|
+
* equals the resolved deposit + shipping for deposit orders.
|
|
46
|
+
*/
|
|
47
|
+
amountToPay: number;
|
|
48
|
+
/**
|
|
49
|
+
* `'full'` for orders paid in one shot; `'deposit'` when the customer
|
|
50
|
+
* pays only a deposit at checkout and owes a balance redeemable via
|
|
51
|
+
* the balance link flow.
|
|
52
|
+
*/
|
|
53
|
+
paymentKind: 'full' | 'deposit';
|
|
30
54
|
}
|
|
31
55
|
export declare function createOrderFromCart(input: CreateOrderInput): Promise<CreateOrderResult>;
|
|
32
56
|
export declare function updateOrderStatus(orderId: string, status: OrderStatus, opts?: {
|
|
33
57
|
note?: string;
|
|
34
58
|
changedBy?: string;
|
|
59
|
+
paymentKind?: 'full' | 'deposit' | 'balance';
|
|
35
60
|
}): Promise<OrderRow>;
|
|
61
|
+
/**
|
|
62
|
+
* @internal
|
|
63
|
+
* Mark the remaining balance on a deposit order as paid. Idempotent — no-op
|
|
64
|
+
* when `balanceOwed` is already false. Updates `partialPayment.paidAmount`
|
|
65
|
+
* to the full `order.totalGross` (deposit + balance), stamps `paidAt`, and
|
|
66
|
+
* clears `balanceOwed`. Stock decrement / order status are untouched — the
|
|
67
|
+
* order already transitioned to `paid` when the deposit cleared.
|
|
68
|
+
*/
|
|
69
|
+
export declare function markBalancePaid(orderId: string): Promise<OrderRow | null>;
|
|
36
70
|
export declare function setPaymentProviderRef(orderId: string, ref: string | null): Promise<void>;
|
|
37
71
|
export interface ShipmentInfoInput {
|
|
38
72
|
shipmentId: string;
|
|
@@ -54,3 +88,4 @@ export interface ListOrdersOptions {
|
|
|
54
88
|
offset?: number;
|
|
55
89
|
}
|
|
56
90
|
export declare function listOrders(opts?: ListOrdersOptions): Promise<OrderRow[]>;
|
|
91
|
+
export declare function countOrders(opts?: Omit<ListOrdersOptions, 'limit' | 'offset'>): Promise<number>;
|
|
@@ -7,7 +7,24 @@ import { resolveShippingPrice } from './shipping.js';
|
|
|
7
7
|
import { generateOrderNumber } from './order-number.js';
|
|
8
8
|
import { sendLowStockEmail, sendOrderStatusEmail } from './email.js';
|
|
9
9
|
import { isPaymentMethodAllowed } from './payment-compat.js';
|
|
10
|
+
import { isVariantExpired, VariantExpiredError } from '../expiry.js';
|
|
11
|
+
import { resolvePaymentAmount } from './payment-policy.js';
|
|
10
12
|
import { CouponError, recordCouponRedemption, releaseCouponSlot, reserveCouponSlot, validateCoupon } from './coupons.js';
|
|
13
|
+
/**
|
|
14
|
+
* @public
|
|
15
|
+
* Thrown by `createOrderFromCart` when the cart contains items from multiple
|
|
16
|
+
* products and at least one of them has a deposit `paymentPolicy`. Deposit
|
|
17
|
+
* orders must span a single product (so the balance link/refund-per-kind flow
|
|
18
|
+
* stays unambiguous). Mixed-product carts are accepted only when every
|
|
19
|
+
* involved product has a `full` (or null) policy.
|
|
20
|
+
*/
|
|
21
|
+
export class MixedPaymentPolicyError extends Error {
|
|
22
|
+
code = 'MIXED_PAYMENT_POLICY';
|
|
23
|
+
constructor(message = 'Cart mixes a deposit-policy product with other products.') {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'MixedPaymentPolicyError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
11
28
|
const STOCK_RESERVATION_TTL_MINUTES = 30;
|
|
12
29
|
async function purgeExpiredReservations() {
|
|
13
30
|
const db = getShopDb();
|
|
@@ -97,6 +114,43 @@ export async function createOrderFromCart(input) {
|
|
|
97
114
|
discountAmount: snapshot.subtotalGross - snapshot.totalGross
|
|
98
115
|
};
|
|
99
116
|
}
|
|
117
|
+
// Payment policy lookup + mixed-cart guard. Load each line's product
|
|
118
|
+
// paymentPolicy from shop_products. If any product has a deposit policy
|
|
119
|
+
// and the cart spans more than one product, refuse with
|
|
120
|
+
// MIXED_PAYMENT_POLICY (keeps balance link / refund-per-kind unambiguous).
|
|
121
|
+
const uniqueProductIds = Array.from(new Set(snapshot.items.map((l) => l.productId).filter(Boolean)));
|
|
122
|
+
const productPolicyRows = uniqueProductIds.length > 0
|
|
123
|
+
? await db
|
|
124
|
+
.select({
|
|
125
|
+
id: shopProductsTable.id,
|
|
126
|
+
paymentPolicy: shopProductsTable.paymentPolicy
|
|
127
|
+
})
|
|
128
|
+
.from(shopProductsTable)
|
|
129
|
+
.where(inArray(shopProductsTable.id, uniqueProductIds))
|
|
130
|
+
: [];
|
|
131
|
+
const policyByProductId = new Map(productPolicyRows.map((r) => [r.id, r.paymentPolicy]));
|
|
132
|
+
const hasDepositPolicy = productPolicyRows.some((r) => r.paymentPolicy?.type === 'deposit');
|
|
133
|
+
if (hasDepositPolicy && uniqueProductIds.length > 1) {
|
|
134
|
+
throw new MixedPaymentPolicyError();
|
|
135
|
+
}
|
|
136
|
+
// Variant expiry guard (opt-in via defineShop.variantExpiry). Run before
|
|
137
|
+
// stock reservation so expired variants surface a domain error instead of
|
|
138
|
+
// taking a reservation slot they will never use.
|
|
139
|
+
if (shop.variantExpiry) {
|
|
140
|
+
const variantIds = snapshot.items.map((l) => l.variantId);
|
|
141
|
+
const variantRows = await db
|
|
142
|
+
.select({
|
|
143
|
+
id: shopProductVariantsTable.id,
|
|
144
|
+
attributes: shopProductVariantsTable.attributes
|
|
145
|
+
})
|
|
146
|
+
.from(shopProductVariantsTable)
|
|
147
|
+
.where(inArray(shopProductVariantsTable.id, variantIds));
|
|
148
|
+
for (const v of variantRows) {
|
|
149
|
+
if (isVariantExpired({ attributes: v.attributes }, shop.variantExpiry)) {
|
|
150
|
+
throw new VariantExpiredError(v.id);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
100
154
|
// Stock availability under active reservations (when stock feature on)
|
|
101
155
|
if (shop.features.stock) {
|
|
102
156
|
await purgeExpiredReservations();
|
|
@@ -120,6 +174,30 @@ export async function createOrderFromCart(input) {
|
|
|
120
174
|
const totalNet = snapshot.totalNet + shippingResolved.net;
|
|
121
175
|
const totalGross = snapshot.totalGross + shippingResolved.gross;
|
|
122
176
|
const totalVat = snapshot.totalVat + shippingResolved.vat;
|
|
177
|
+
// Resolve per-line payment amounts under each product's paymentPolicy.
|
|
178
|
+
// `depositSum` is what we charge at checkout under deposit policy; the
|
|
179
|
+
// remainder + shipping rolls into `partialPayment.balanceAmount`.
|
|
180
|
+
let depositSum = 0;
|
|
181
|
+
let goodsBalance = 0;
|
|
182
|
+
let anyDepositLine = false;
|
|
183
|
+
for (const line of snapshot.items) {
|
|
184
|
+
const policy = policyByProductId.get(line.productId) ?? null;
|
|
185
|
+
const resolved = resolvePaymentAmount(policy, line.lineGross);
|
|
186
|
+
depositSum += resolved.amountToPay;
|
|
187
|
+
goodsBalance += resolved.balanceAmount;
|
|
188
|
+
if (resolved.kind === 'deposit')
|
|
189
|
+
anyDepositLine = true;
|
|
190
|
+
}
|
|
191
|
+
const paymentKind = anyDepositLine ? 'deposit' : 'full';
|
|
192
|
+
const amountToPay = anyDepositLine ? depositSum + shippingResolved.gross : totalGross;
|
|
193
|
+
const partialPayment = anyDepositLine
|
|
194
|
+
? {
|
|
195
|
+
kind: 'deposit',
|
|
196
|
+
paidAmount: 0,
|
|
197
|
+
balanceAmount: goodsBalance,
|
|
198
|
+
paidAt: null
|
|
199
|
+
}
|
|
200
|
+
: null;
|
|
123
201
|
// Consents snapshot
|
|
124
202
|
const consentSnapshot = shop.consents.map((c) => {
|
|
125
203
|
const accepted = input.consents?.find((x) => x.id === c.id)?.accepted ?? false;
|
|
@@ -171,7 +249,9 @@ export async function createOrderFromCart(input) {
|
|
|
171
249
|
paymentMethod: input.paymentMethod,
|
|
172
250
|
consents: consentSnapshot,
|
|
173
251
|
notes: input.notes ?? null,
|
|
174
|
-
language: input.language ?? cms.languages[0] ?? null
|
|
252
|
+
language: input.language ?? cms.languages[0] ?? null,
|
|
253
|
+
partialPayment,
|
|
254
|
+
balanceOwed: false
|
|
175
255
|
})
|
|
176
256
|
.returning();
|
|
177
257
|
order = inserted;
|
|
@@ -237,7 +317,9 @@ export async function createOrderFromCart(input) {
|
|
|
237
317
|
return {
|
|
238
318
|
order,
|
|
239
319
|
items,
|
|
240
|
-
requiresPaymentRedirect: false
|
|
320
|
+
requiresPaymentRedirect: false,
|
|
321
|
+
amountToPay,
|
|
322
|
+
paymentKind
|
|
241
323
|
};
|
|
242
324
|
}
|
|
243
325
|
export async function updateOrderStatus(orderId, status, opts = {}) {
|
|
@@ -248,6 +330,32 @@ export async function updateOrderStatus(orderId, status, opts = {}) {
|
|
|
248
330
|
if (order.status === status)
|
|
249
331
|
return order;
|
|
250
332
|
const shop = requireShopConfig();
|
|
333
|
+
// Deposit-kind transition to `paid`: bump partialPayment.paidAmount to the
|
|
334
|
+
// deposit amount, stamp paidAt, and flag balanceOwed = true. Stock is
|
|
335
|
+
// permanently confirmed by the existing `paid` branch below — no TTL
|
|
336
|
+
// release. Balance-kind transitions skip the order-level status change
|
|
337
|
+
// (handled separately by markBalancePaid) so we never reach this branch.
|
|
338
|
+
//
|
|
339
|
+
// Auto-detect deposit context when caller (e.g. admin manual mark-paid)
|
|
340
|
+
// didn't pass paymentKind: a non-balance-owed deposit order with paidAt
|
|
341
|
+
// still null = the initial deposit payment is the one being confirmed.
|
|
342
|
+
const partial = order.partialPayment;
|
|
343
|
+
const effectiveKind = opts.paymentKind ??
|
|
344
|
+
(partial?.kind === 'deposit' && !order.balanceOwed && partial.paidAt == null
|
|
345
|
+
? 'deposit'
|
|
346
|
+
: undefined);
|
|
347
|
+
if (status === 'paid' && effectiveKind === 'deposit' && order.partialPayment) {
|
|
348
|
+
const pp = order.partialPayment;
|
|
349
|
+
const paidAt = new Date().toISOString();
|
|
350
|
+
const paidAmount = order.totalGross - pp.balanceAmount;
|
|
351
|
+
await db
|
|
352
|
+
.update(shopOrdersTable)
|
|
353
|
+
.set({
|
|
354
|
+
partialPayment: { ...pp, paidAmount, paidAt },
|
|
355
|
+
balanceOwed: true
|
|
356
|
+
})
|
|
357
|
+
.where(eq(shopOrdersTable.id, orderId));
|
|
358
|
+
}
|
|
251
359
|
await db
|
|
252
360
|
.update(shopOrdersTable)
|
|
253
361
|
.set({ status, updatedAt: new Date() })
|
|
@@ -324,6 +432,37 @@ export async function updateOrderStatus(orderId, status, opts = {}) {
|
|
|
324
432
|
void sendOrderStatusEmail(orderId, status);
|
|
325
433
|
return updated;
|
|
326
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* @internal
|
|
437
|
+
* Mark the remaining balance on a deposit order as paid. Idempotent — no-op
|
|
438
|
+
* when `balanceOwed` is already false. Updates `partialPayment.paidAmount`
|
|
439
|
+
* to the full `order.totalGross` (deposit + balance), stamps `paidAt`, and
|
|
440
|
+
* clears `balanceOwed`. Stock decrement / order status are untouched — the
|
|
441
|
+
* order already transitioned to `paid` when the deposit cleared.
|
|
442
|
+
*/
|
|
443
|
+
export async function markBalancePaid(orderId) {
|
|
444
|
+
const db = getShopDb();
|
|
445
|
+
const [order] = await db.select().from(shopOrdersTable).where(eq(shopOrdersTable.id, orderId));
|
|
446
|
+
if (!order)
|
|
447
|
+
return null;
|
|
448
|
+
if (!order.balanceOwed || !order.partialPayment)
|
|
449
|
+
return order;
|
|
450
|
+
const pp = order.partialPayment;
|
|
451
|
+
await db
|
|
452
|
+
.update(shopOrdersTable)
|
|
453
|
+
.set({
|
|
454
|
+
partialPayment: {
|
|
455
|
+
...pp,
|
|
456
|
+
paidAmount: order.totalGross,
|
|
457
|
+
paidAt: new Date().toISOString()
|
|
458
|
+
},
|
|
459
|
+
balanceOwed: false,
|
|
460
|
+
updatedAt: new Date()
|
|
461
|
+
})
|
|
462
|
+
.where(eq(shopOrdersTable.id, orderId));
|
|
463
|
+
const [updated] = await db.select().from(shopOrdersTable).where(eq(shopOrdersTable.id, orderId));
|
|
464
|
+
return updated ?? null;
|
|
465
|
+
}
|
|
327
466
|
export async function setPaymentProviderRef(orderId, ref) {
|
|
328
467
|
const db = getShopDb();
|
|
329
468
|
await db
|
|
@@ -417,3 +556,17 @@ export async function listOrders(opts = {}) {
|
|
|
417
556
|
.limit(opts.limit ?? 100)
|
|
418
557
|
.offset(opts.offset ?? 0);
|
|
419
558
|
}
|
|
559
|
+
export async function countOrders(opts = {}) {
|
|
560
|
+
const db = getShopDb();
|
|
561
|
+
const conditions = [];
|
|
562
|
+
if (opts.status)
|
|
563
|
+
conditions.push(eq(shopOrdersTable.status, opts.status));
|
|
564
|
+
if (opts.email)
|
|
565
|
+
conditions.push(eq(shopOrdersTable.customerEmail, opts.email));
|
|
566
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
567
|
+
const [row] = await db
|
|
568
|
+
.select({ count: sql `count(*)::int` })
|
|
569
|
+
.from(shopOrdersTable)
|
|
570
|
+
.where(where);
|
|
571
|
+
return row?.count ?? 0;
|
|
572
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DepositAmount, PaymentPolicy } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* @public
|
|
4
|
+
* Result of resolving a per-line payment amount under a `PaymentPolicy`.
|
|
5
|
+
* `amountToPay` is the minor-unit amount to charge now; `balanceAmount` is
|
|
6
|
+
* what will still be owed under deposit policy (0 for full / clamped deposit).
|
|
7
|
+
*/
|
|
8
|
+
export interface ResolvedPaymentAmount {
|
|
9
|
+
amountToPay: number;
|
|
10
|
+
kind: 'full' | 'deposit';
|
|
11
|
+
balanceAmount: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
* Compute the deposit minor-unit amount from a `DepositAmount` spec. Percent
|
|
16
|
+
* floors `(base * value / 100)`; fixed amount is clamped to `base` to prevent
|
|
17
|
+
* collecting more than the line total. Caller is responsible for validating
|
|
18
|
+
* the spec (see {@link validatePaymentPolicy}).
|
|
19
|
+
*/
|
|
20
|
+
export declare function computeDepositAmount(spec: DepositAmount, base: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* @public
|
|
23
|
+
* Resolve a `PaymentPolicy` against a line gross total. Returns the immediate
|
|
24
|
+
* `amountToPay` (deposit when applicable), the `kind` for payment row tagging
|
|
25
|
+
* (`'full' | 'deposit'`), and the `balanceAmount` still owed. Null / `full`
|
|
26
|
+
* policies short-circuit to a normal full charge with `balanceAmount = 0`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolvePaymentAmount(policy: PaymentPolicy | null, lineGross: number): ResolvedPaymentAmount;
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
* Validate a `PaymentPolicy` configuration. Throws a `RangeError` when the
|
|
32
|
+
* spec is structurally invalid (percent out of (0, 100], amount ≤ 0, etc).
|
|
33
|
+
* Called from `upsertShopData` before persisting the policy on the product.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validatePaymentPolicy(policy: PaymentPolicy): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
* Compute the deposit minor-unit amount from a `DepositAmount` spec. Percent
|
|
4
|
+
* floors `(base * value / 100)`; fixed amount is clamped to `base` to prevent
|
|
5
|
+
* collecting more than the line total. Caller is responsible for validating
|
|
6
|
+
* the spec (see {@link validatePaymentPolicy}).
|
|
7
|
+
*/
|
|
8
|
+
export function computeDepositAmount(spec, base) {
|
|
9
|
+
if (spec.type === 'percent') {
|
|
10
|
+
return Math.floor((base * spec.value) / 100);
|
|
11
|
+
}
|
|
12
|
+
return Math.min(spec.value, base);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @public
|
|
16
|
+
* Resolve a `PaymentPolicy` against a line gross total. Returns the immediate
|
|
17
|
+
* `amountToPay` (deposit when applicable), the `kind` for payment row tagging
|
|
18
|
+
* (`'full' | 'deposit'`), and the `balanceAmount` still owed. Null / `full`
|
|
19
|
+
* policies short-circuit to a normal full charge with `balanceAmount = 0`.
|
|
20
|
+
*/
|
|
21
|
+
export function resolvePaymentAmount(policy, lineGross) {
|
|
22
|
+
if (!policy || policy.type === 'full') {
|
|
23
|
+
return { amountToPay: lineGross, kind: 'full', balanceAmount: 0 };
|
|
24
|
+
}
|
|
25
|
+
const amountToPay = computeDepositAmount(policy.depositAmount, lineGross);
|
|
26
|
+
return {
|
|
27
|
+
amountToPay,
|
|
28
|
+
kind: 'deposit',
|
|
29
|
+
balanceAmount: Math.max(0, lineGross - amountToPay)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @internal
|
|
34
|
+
* Validate a `PaymentPolicy` configuration. Throws a `RangeError` when the
|
|
35
|
+
* spec is structurally invalid (percent out of (0, 100], amount ≤ 0, etc).
|
|
36
|
+
* Called from `upsertShopData` before persisting the policy on the product.
|
|
37
|
+
*/
|
|
38
|
+
export function validatePaymentPolicy(policy) {
|
|
39
|
+
if (policy.type === 'full')
|
|
40
|
+
return;
|
|
41
|
+
const d = policy.depositAmount;
|
|
42
|
+
if (d.type === 'percent') {
|
|
43
|
+
if (!Number.isFinite(d.value) || d.value <= 0 || d.value > 100) {
|
|
44
|
+
throw new RangeError('Deposit percent must be in (0, 100].');
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (d.type === 'amount') {
|
|
49
|
+
if (!Number.isInteger(d.value) || d.value <= 0) {
|
|
50
|
+
throw new RangeError('Deposit amount must be a positive integer (minor units).');
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
throw new RangeError(`Unknown depositAmount.type: ${d.type}`);
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { shopPaymentsTable, type ShopPaymentKind } from '../../db-postgres/schema/shop/index.js';
|
|
2
|
+
export type ShopPaymentRow = typeof shopPaymentsTable.$inferSelect;
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
* Insert a new `shop_payments` row tagged with `kind` (`full | deposit |
|
|
6
|
+
* balance`). Called at checkout / balance link initiation, before the
|
|
7
|
+
* adapter takes the customer to the provider — `providerRef` may be null
|
|
8
|
+
* when the adapter is asynchronous about producing one.
|
|
9
|
+
*/
|
|
10
|
+
export declare function insertPaymentRow(input: {
|
|
11
|
+
orderId: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
providerRef?: string | null;
|
|
14
|
+
kind: ShopPaymentKind;
|
|
15
|
+
amount: number;
|
|
16
|
+
currency: string;
|
|
17
|
+
}): Promise<ShopPaymentRow>;
|
|
18
|
+
/** @internal Look up a payment row by provider + providerRef (webhook entry point). */
|
|
19
|
+
export declare function findPaymentByProviderRef(provider: string, providerRef: string): Promise<ShopPaymentRow | null>;
|
|
20
|
+
/** @internal Mark a payment row as paid (idempotent — no-op if already paid). */
|
|
21
|
+
export declare function markPaymentPaid(paymentId: string): Promise<void>;
|
|
22
|
+
/** @internal Mark a payment row as failed (rejected webhook). */
|
|
23
|
+
export declare function markPaymentFailed(paymentId: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
* List payment rows for an order, ordered by createdAt ascending. Used by
|
|
27
|
+
* refundOrder() when distinguishing which row to refund under per-kind refund.
|
|
28
|
+
*/
|
|
29
|
+
export declare function listOrderPayments(orderId: string): Promise<ShopPaymentRow[]>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { shopPaymentsTable } from '../../db-postgres/schema/shop/index.js';
|
|
3
|
+
import { getShopDb } from './db.js';
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
* Insert a new `shop_payments` row tagged with `kind` (`full | deposit |
|
|
7
|
+
* balance`). Called at checkout / balance link initiation, before the
|
|
8
|
+
* adapter takes the customer to the provider — `providerRef` may be null
|
|
9
|
+
* when the adapter is asynchronous about producing one.
|
|
10
|
+
*/
|
|
11
|
+
export async function insertPaymentRow(input) {
|
|
12
|
+
const db = getShopDb();
|
|
13
|
+
const [row] = await db
|
|
14
|
+
.insert(shopPaymentsTable)
|
|
15
|
+
.values({
|
|
16
|
+
orderId: input.orderId,
|
|
17
|
+
provider: input.provider,
|
|
18
|
+
providerRef: input.providerRef ?? null,
|
|
19
|
+
kind: input.kind,
|
|
20
|
+
amount: input.amount,
|
|
21
|
+
currency: input.currency,
|
|
22
|
+
status: 'pending'
|
|
23
|
+
})
|
|
24
|
+
.returning();
|
|
25
|
+
return row;
|
|
26
|
+
}
|
|
27
|
+
/** @internal Look up a payment row by provider + providerRef (webhook entry point). */
|
|
28
|
+
export async function findPaymentByProviderRef(provider, providerRef) {
|
|
29
|
+
const db = getShopDb();
|
|
30
|
+
const [row] = await db
|
|
31
|
+
.select()
|
|
32
|
+
.from(shopPaymentsTable)
|
|
33
|
+
.where(and(eq(shopPaymentsTable.provider, provider), eq(shopPaymentsTable.providerRef, providerRef)));
|
|
34
|
+
return row ?? null;
|
|
35
|
+
}
|
|
36
|
+
/** @internal Mark a payment row as paid (idempotent — no-op if already paid). */
|
|
37
|
+
export async function markPaymentPaid(paymentId) {
|
|
38
|
+
const db = getShopDb();
|
|
39
|
+
await db
|
|
40
|
+
.update(shopPaymentsTable)
|
|
41
|
+
.set({ status: 'paid', updatedAt: new Date() })
|
|
42
|
+
.where(eq(shopPaymentsTable.id, paymentId));
|
|
43
|
+
}
|
|
44
|
+
/** @internal Mark a payment row as failed (rejected webhook). */
|
|
45
|
+
export async function markPaymentFailed(paymentId) {
|
|
46
|
+
const db = getShopDb();
|
|
47
|
+
await db
|
|
48
|
+
.update(shopPaymentsTable)
|
|
49
|
+
.set({ status: 'failed', updatedAt: new Date() })
|
|
50
|
+
.where(eq(shopPaymentsTable.id, paymentId));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @internal
|
|
54
|
+
* List payment rows for an order, ordered by createdAt ascending. Used by
|
|
55
|
+
* refundOrder() when distinguishing which row to refund under per-kind refund.
|
|
56
|
+
*/
|
|
57
|
+
export async function listOrderPayments(orderId) {
|
|
58
|
+
const db = getShopDb();
|
|
59
|
+
return db
|
|
60
|
+
.select()
|
|
61
|
+
.from(shopPaymentsTable)
|
|
62
|
+
.where(eq(shopPaymentsTable.orderId, orderId))
|
|
63
|
+
.orderBy(shopPaymentsTable.createdAt);
|
|
64
|
+
}
|
|
@@ -12,7 +12,7 @@ export interface PopulatedShopField {
|
|
|
12
12
|
/** Netto delta w PLN (number, ≤6dp). Od 0.15.2. */
|
|
13
13
|
priceDelta: number;
|
|
14
14
|
stock: number | null;
|
|
15
|
-
attributes: Record<string,
|
|
15
|
+
attributes: Record<string, unknown> | null;
|
|
16
16
|
}>;
|
|
17
17
|
}
|
|
18
18
|
export declare function resolveShopFields(data: Record<string, unknown>, fields: Field[], ctx: PopulateCtx): Promise<Record<string, unknown>>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { shopRefundsTable } from '../../db-postgres/schema/shop/index.js';
|
|
1
|
+
import { shopRefundsTable, type ShopPaymentKind } from '../../db-postgres/schema/shop/index.js';
|
|
2
2
|
export type ShopRefundRow = typeof shopRefundsTable.$inferSelect;
|
|
3
3
|
export declare class RefundError extends Error {
|
|
4
|
-
readonly code: 'order_not_found' | 'order_not_paid' | 'no_provider_ref' | 'unknown_provider' | 'refund_unsupported' | 'invalid_amount' | 'amount_exceeds_remaining' | 'provider_error';
|
|
4
|
+
readonly code: 'order_not_found' | 'order_not_paid' | 'no_provider_ref' | 'unknown_provider' | 'refund_unsupported' | 'invalid_amount' | 'amount_exceeds_remaining' | 'provider_error' | 'no_payment_kind';
|
|
5
5
|
readonly cause?: unknown | undefined;
|
|
6
|
-
constructor(code: 'order_not_found' | 'order_not_paid' | 'no_provider_ref' | 'unknown_provider' | 'refund_unsupported' | 'invalid_amount' | 'amount_exceeds_remaining' | 'provider_error', message: string, cause?: unknown | undefined);
|
|
6
|
+
constructor(code: 'order_not_found' | 'order_not_paid' | 'no_provider_ref' | 'unknown_provider' | 'refund_unsupported' | 'invalid_amount' | 'amount_exceeds_remaining' | 'provider_error' | 'no_payment_kind', message: string, cause?: unknown | undefined);
|
|
7
7
|
}
|
|
8
8
|
export interface RefundOrderInput {
|
|
9
9
|
orderId: string;
|
|
@@ -11,6 +11,20 @@ export interface RefundOrderInput {
|
|
|
11
11
|
amount?: number;
|
|
12
12
|
reason?: string;
|
|
13
13
|
createdBy?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Which payment row to refund under deposit `paymentPolicy`. Defaults to
|
|
16
|
+
* `'full'`. When the order has only a `deposit` or `balance` row, that
|
|
17
|
+
* row is used regardless of this hint (back-compat). Per-kind refunds
|
|
18
|
+
* cap `amount` to the matched payment row's amount, never the full
|
|
19
|
+
* order total.
|
|
20
|
+
*/
|
|
21
|
+
kind?: ShopPaymentKind;
|
|
22
|
+
/**
|
|
23
|
+
* Re-increment variant stock for each order item when the refund
|
|
24
|
+
* succeeds. Default `false` to preserve legacy refund behavior; set
|
|
25
|
+
* `true` from the admin UI when the goods are not being delivered.
|
|
26
|
+
*/
|
|
27
|
+
releaseStock?: boolean;
|
|
14
28
|
}
|
|
15
29
|
export interface RefundOrderResult {
|
|
16
30
|
refund: ShopRefundRow;
|
|
@@ -20,13 +34,4 @@ export interface RefundOrderResult {
|
|
|
20
34
|
/** Sum of succeeded refunds for the given order, in minor units. */
|
|
21
35
|
export declare function getRefundedAmount(orderId: string): Promise<number>;
|
|
22
36
|
export declare function listRefunds(orderId: string): Promise<ShopRefundRow[]>;
|
|
23
|
-
/**
|
|
24
|
-
* Refund an order — full or partial. Validates eligibility, records a pending
|
|
25
|
-
* row, calls the adapter, transitions the row to succeeded/failed and the
|
|
26
|
-
* order status to `refunded` when the full captured amount has been refunded.
|
|
27
|
-
*
|
|
28
|
-
* Throws `RefundError` on validation failures and provider errors. The
|
|
29
|
-
* `pending` refund row is marked `failed` on provider error so admin can see
|
|
30
|
-
* what was attempted.
|
|
31
|
-
*/
|
|
32
37
|
export declare function refundOrder(input: RefundOrderInput): Promise<RefundOrderResult>;
|