@open-mercato/core 0.6.6 → 0.6.7-develop.6553.1.2ec7246e04
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/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
- package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +12 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_keys/api/keys/route.js +30 -13
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/cli.js +2 -1
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +9 -7
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +21 -17
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
- package/dist/modules/attachments/lib/requestScope.js +10 -0
- package/dist/modules/attachments/lib/requestScope.js.map +7 -0
- package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/auth/api/autologin.js +85 -0
- package/dist/modules/auth/api/autologin.js.map +7 -0
- package/dist/modules/auth/api/users/route.js +6 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +3 -2
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/autologin.js +19 -0
- package/dist/modules/auth/lib/autologin.js.map +7 -0
- package/dist/modules/configs/lib/upgrade-actions.js +22 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
- package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
- package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
- package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
- package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/cli.js +4 -3
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
- package/dist/modules/dictionaries/commands/entries.js +7 -14
- package/dist/modules/dictionaries/commands/entries.js.map +2 -2
- package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
- package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
- package/dist/modules/dictionaries/commands/factory.js +9 -3
- package/dist/modules/dictionaries/commands/factory.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +56 -41
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +10 -3
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +5 -4
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/cli.js +2 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
- package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
- package/dist/modules/feature_toggles/defaults.json +8 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/data/entities.js +45 -0
- package/dist/modules/payment_gateways/data/entities.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
- package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
- package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/status.js +2 -8
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +44 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +2 -27
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
- package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
- package/dist/modules/sales/lib/calculations.js +6 -0
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
- package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
- package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
- package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/sync_excel/lib/parser.js +185 -0
- package/dist/modules/sync_excel/lib/parser.js.map +2 -2
- package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
- package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
- package/generated/entities/gateway_session_initialization/index.ts +10 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +12 -0
- package/package.json +9 -8
- package/src/modules/api_keys/api/keys/route.ts +36 -13
- package/src/modules/api_keys/cli.ts +2 -3
- package/src/modules/attachments/api/file/[id]/route.ts +11 -7
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
- package/src/modules/attachments/api/route.ts +26 -21
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
- package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
- package/src/modules/attachments/lib/requestScope.ts +27 -0
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
- package/src/modules/auth/api/autologin.ts +100 -0
- package/src/modules/auth/api/users/route.ts +11 -2
- package/src/modules/auth/cli.ts +3 -2
- package/src/modules/auth/lib/autologin.ts +36 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +24 -0
- package/src/modules/customer_accounts/AGENTS.md +8 -6
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
- package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
- package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
- package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
- package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
- package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
- package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
- package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
- package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
- package/src/modules/dashboards/cli.ts +4 -9
- package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
- package/src/modules/dictionaries/commands/entries.ts +7 -16
- package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
- package/src/modules/dictionaries/commands/factory.ts +8 -2
- package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
- package/src/modules/dictionaries/i18n/de.json +2 -0
- package/src/modules/dictionaries/i18n/en.json +2 -0
- package/src/modules/dictionaries/i18n/es.json +2 -0
- package/src/modules/dictionaries/i18n/pl.json +2 -0
- package/src/modules/directory/utils/organizationScope.ts +36 -2
- package/src/modules/entities/api/records.ts +5 -7
- package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
- package/src/modules/entities/cli.ts +2 -1
- package/src/modules/entities/i18n/de.json +34 -0
- package/src/modules/entities/i18n/en.json +34 -0
- package/src/modules/entities/i18n/es.json +34 -0
- package/src/modules/entities/i18n/pl.json +34 -0
- package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
- package/src/modules/feature_toggles/defaults.json +8 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
- package/src/modules/payment_gateways/data/entities.ts +39 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
- package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
- package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
- package/src/modules/query_index/api/status.ts +2 -8
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
- package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/returns.ts +2 -32
- package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
- package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
- package/src/modules/sales/i18n/de.json +5 -0
- package/src/modules/sales/i18n/en.json +5 -0
- package/src/modules/sales/i18n/es.json +5 -0
- package/src/modules/sales/i18n/pl.json +5 -0
- package/src/modules/sales/lib/calculations.ts +15 -0
- package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
- package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
- package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
- package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
- package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
- package/src/modules/sync_excel/lib/parser.ts +245 -0
- package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
|
@@ -1199,84 +1199,41 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1199
1199
|
</section>
|
|
1200
1200
|
) : null}
|
|
1201
1201
|
|
|
1202
|
-
<Tabs value={activeTab} onValueChange={handleTabChange} className="space-y-5">
|
|
1203
|
-
<TabsList className="
|
|
1202
|
+
<Tabs value={activeTab} onValueChange={handleTabChange} variant="underline" className="space-y-5">
|
|
1203
|
+
<TabsList className="w-full overflow-x-auto">
|
|
1204
1204
|
{showCredentialsTab ? (
|
|
1205
|
-
<TabsTrigger
|
|
1206
|
-
|
|
1207
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1208
|
-
>
|
|
1209
|
-
<span className="inline-flex items-center gap-2">
|
|
1210
|
-
<Key className="h-4 w-4" />
|
|
1211
|
-
<span>{t('integrations.detail.tabs.credentials')}</span>
|
|
1212
|
-
</span>
|
|
1205
|
+
<TabsTrigger value="credentials" leading={<Key className="h-4 w-4" />}>
|
|
1206
|
+
{t('integrations.detail.tabs.credentials')}
|
|
1213
1207
|
</TabsTrigger>
|
|
1214
1208
|
) : null}
|
|
1215
1209
|
{leadingInjectedTab ? (
|
|
1216
|
-
<TabsTrigger
|
|
1217
|
-
|
|
1218
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1219
|
-
>
|
|
1220
|
-
<span className="inline-flex items-center gap-2">
|
|
1221
|
-
<Settings className="h-4 w-4" />
|
|
1222
|
-
<span>{leadingInjectedTab.label}</span>
|
|
1223
|
-
</span>
|
|
1210
|
+
<TabsTrigger value={leadingInjectedTab.id} leading={<Settings className="h-4 w-4" />}>
|
|
1211
|
+
{leadingInjectedTab.label}
|
|
1224
1212
|
</TabsTrigger>
|
|
1225
1213
|
) : null}
|
|
1226
1214
|
{showVersionTab ? (
|
|
1227
|
-
<TabsTrigger
|
|
1228
|
-
|
|
1229
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1230
|
-
>
|
|
1231
|
-
<span className="inline-flex items-center gap-2">
|
|
1232
|
-
<RefreshCw className="h-4 w-4" />
|
|
1233
|
-
<span>{t('integrations.detail.tabs.version')}</span>
|
|
1234
|
-
</span>
|
|
1215
|
+
<TabsTrigger value="version" leading={<RefreshCw className="h-4 w-4" />}>
|
|
1216
|
+
{t('integrations.detail.tabs.version')}
|
|
1235
1217
|
</TabsTrigger>
|
|
1236
1218
|
) : null}
|
|
1237
1219
|
{showDataSyncScheduleTab ? (
|
|
1238
|
-
<TabsTrigger
|
|
1239
|
-
|
|
1240
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1241
|
-
>
|
|
1242
|
-
<span className="inline-flex items-center gap-2">
|
|
1243
|
-
<Calendar className="h-4 w-4" />
|
|
1244
|
-
<span>{t('data_sync.integrationTab.title', 'Sync schedules')}</span>
|
|
1245
|
-
</span>
|
|
1220
|
+
<TabsTrigger value="data-sync-schedule" leading={<Calendar className="h-4 w-4" />}>
|
|
1221
|
+
{t('data_sync.integrationTab.title', 'Sync schedules')}
|
|
1246
1222
|
</TabsTrigger>
|
|
1247
1223
|
) : null}
|
|
1248
1224
|
{showHealthTab ? (
|
|
1249
|
-
<TabsTrigger
|
|
1250
|
-
|
|
1251
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1252
|
-
>
|
|
1253
|
-
<span className="inline-flex items-center gap-2">
|
|
1254
|
-
<Activity className="h-4 w-4" />
|
|
1255
|
-
<span>{t('integrations.detail.tabs.health')}</span>
|
|
1256
|
-
</span>
|
|
1225
|
+
<TabsTrigger value="health" leading={<Activity className="h-4 w-4" />}>
|
|
1226
|
+
{t('integrations.detail.tabs.health')}
|
|
1257
1227
|
</TabsTrigger>
|
|
1258
1228
|
) : null}
|
|
1259
1229
|
{showLogsTab ? (
|
|
1260
|
-
<TabsTrigger
|
|
1261
|
-
|
|
1262
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1263
|
-
>
|
|
1264
|
-
<span className="inline-flex items-center gap-2">
|
|
1265
|
-
<FileText className="h-4 w-4" />
|
|
1266
|
-
<span>{t('integrations.detail.tabs.logs')}</span>
|
|
1267
|
-
</span>
|
|
1230
|
+
<TabsTrigger value="logs" leading={<FileText className="h-4 w-4" />}>
|
|
1231
|
+
{t('integrations.detail.tabs.logs')}
|
|
1268
1232
|
</TabsTrigger>
|
|
1269
1233
|
) : null}
|
|
1270
1234
|
{trailingInjectedTabs.map((tab) => (
|
|
1271
|
-
<TabsTrigger
|
|
1272
|
-
|
|
1273
|
-
value={tab.id}
|
|
1274
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1275
|
-
>
|
|
1276
|
-
<span className="inline-flex items-center gap-2">
|
|
1277
|
-
<Settings className="h-4 w-4" />
|
|
1278
|
-
<span>{tab.label}</span>
|
|
1279
|
-
</span>
|
|
1235
|
+
<TabsTrigger key={tab.id} value={tab.id} leading={<Settings className="h-4 w-4" />}>
|
|
1236
|
+
{tab.label}
|
|
1280
1237
|
</TabsTrigger>
|
|
1281
1238
|
))}
|
|
1282
1239
|
</TabsList>
|
|
@@ -75,6 +75,45 @@ export class GatewayTransaction {
|
|
|
75
75
|
deletedAt?: Date | null
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
@Entity({ tableName: 'gateway_session_initializations' })
|
|
79
|
+
@Unique({
|
|
80
|
+
name: 'gateway_session_initializations_scope_operation_unique',
|
|
81
|
+
properties: ['operationKey', 'providerKey', 'organizationId', 'tenantId'],
|
|
82
|
+
})
|
|
83
|
+
export class GatewaySessionInitialization {
|
|
84
|
+
[OptionalProps]?: 'claimToken' | 'claimedAt' | 'gatewayTransactionId' | 'createdAt' | 'updatedAt'
|
|
85
|
+
|
|
86
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
87
|
+
id!: string
|
|
88
|
+
|
|
89
|
+
@Property({ name: 'operation_key', type: 'text' })
|
|
90
|
+
operationKey!: string
|
|
91
|
+
|
|
92
|
+
@Property({ name: 'provider_key', type: 'text' })
|
|
93
|
+
providerKey!: string
|
|
94
|
+
|
|
95
|
+
@Property({ name: 'claim_token', type: 'uuid', nullable: true })
|
|
96
|
+
claimToken?: string | null
|
|
97
|
+
|
|
98
|
+
@Property({ name: 'claimed_at', type: Date, nullable: true })
|
|
99
|
+
claimedAt?: Date | null
|
|
100
|
+
|
|
101
|
+
@Property({ name: 'gateway_transaction_id', type: 'uuid', nullable: true })
|
|
102
|
+
gatewayTransactionId?: string | null
|
|
103
|
+
|
|
104
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
105
|
+
organizationId!: string
|
|
106
|
+
|
|
107
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
108
|
+
tenantId!: string
|
|
109
|
+
|
|
110
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
111
|
+
createdAt: Date = new Date()
|
|
112
|
+
|
|
113
|
+
@Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })
|
|
114
|
+
updatedAt: Date = new Date()
|
|
115
|
+
}
|
|
116
|
+
|
|
78
117
|
@Entity({ tableName: 'gateway_webhook_events' })
|
|
79
118
|
@Unique({
|
|
80
119
|
name: 'gateway_webhook_events_idempotency_unique',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
1
2
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
3
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
4
|
import {
|
|
@@ -15,10 +16,21 @@ import type { CredentialsService } from '../../integrations/lib/credentials-serv
|
|
|
15
16
|
import type { IntegrationStateService } from '../../integrations/lib/state-service'
|
|
16
17
|
import type { IntegrationLogService } from '../../integrations/lib/log-service'
|
|
17
18
|
import { conflict } from '@open-mercato/shared/lib/crud/errors'
|
|
18
|
-
import { GatewayTransaction } from '../data/entities'
|
|
19
|
+
import { GatewaySessionInitialization, GatewayTransaction } from '../data/entities'
|
|
19
20
|
import { canApplyManualAction, isValidTransition, type ManualGatewayAction } from './status-machine'
|
|
20
21
|
import { emitPaymentGatewayEvent } from '../events'
|
|
21
22
|
import { readGatewayMetadata, readWebhookLog } from './transaction-fields'
|
|
23
|
+
import {
|
|
24
|
+
buildPaymentSessionOperationKey,
|
|
25
|
+
claimPaymentSessionInitialization,
|
|
26
|
+
findPaymentSessionInitialization,
|
|
27
|
+
reclaimPaymentSessionInitialization,
|
|
28
|
+
refreshPaymentSessionInitialization,
|
|
29
|
+
releasePaymentSessionInitialization,
|
|
30
|
+
} from './session-idempotency'
|
|
31
|
+
|
|
32
|
+
const PAYMENT_SESSION_CLAIM_STALE_MS = 30_000
|
|
33
|
+
const PAYMENT_SESSION_WAIT_INTERVAL_MS = 25
|
|
22
34
|
|
|
23
35
|
function assertManualActionAllowed(action: ManualGatewayAction, transaction: GatewayTransaction): void {
|
|
24
36
|
const current = transaction.unifiedStatus as UnifiedPaymentStatus
|
|
@@ -48,11 +60,17 @@ export interface PaymentGatewayServiceDeps {
|
|
|
48
60
|
integrationCredentialsService: CredentialsService
|
|
49
61
|
integrationStateService?: IntegrationStateService
|
|
50
62
|
integrationLogService?: IntegrationLogService
|
|
63
|
+
sessionClaimOptions?: {
|
|
64
|
+
staleAfterMs?: number
|
|
65
|
+
heartbeatIntervalMs?: number
|
|
66
|
+
pollIntervalMs?: number
|
|
67
|
+
}
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
export interface CreatePaymentSessionInput {
|
|
54
71
|
providerKey: string
|
|
55
72
|
paymentId: string
|
|
73
|
+
idempotencyKey?: string
|
|
56
74
|
orderId?: string
|
|
57
75
|
amount: number
|
|
58
76
|
currencyCode: string
|
|
@@ -69,6 +87,12 @@ export interface CreatePaymentSessionInput {
|
|
|
69
87
|
|
|
70
88
|
export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
|
|
71
89
|
const { em, integrationCredentialsService, integrationLogService } = deps
|
|
90
|
+
const claimStaleAfterMs = Math.max(1, deps.sessionClaimOptions?.staleAfterMs ?? PAYMENT_SESSION_CLAIM_STALE_MS)
|
|
91
|
+
const claimHeartbeatIntervalMs = Math.max(
|
|
92
|
+
1,
|
|
93
|
+
deps.sessionClaimOptions?.heartbeatIntervalMs ?? Math.floor(claimStaleAfterMs / 3),
|
|
94
|
+
)
|
|
95
|
+
const claimPollIntervalMs = Math.max(1, deps.sessionClaimOptions?.pollIntervalMs ?? PAYMENT_SESSION_WAIT_INTERVAL_MS)
|
|
72
96
|
|
|
73
97
|
async function findTransactionOrThrow(
|
|
74
98
|
transactionId: string,
|
|
@@ -152,6 +176,109 @@ export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
|
|
|
152
176
|
return { adapter, credentials }
|
|
153
177
|
}
|
|
154
178
|
|
|
179
|
+
function createGatewayTransaction(
|
|
180
|
+
manager: EntityManager,
|
|
181
|
+
input: CreatePaymentSessionInput,
|
|
182
|
+
session: CreateSessionResult,
|
|
183
|
+
id?: string,
|
|
184
|
+
): GatewayTransaction {
|
|
185
|
+
const data = {
|
|
186
|
+
paymentId: input.paymentId,
|
|
187
|
+
providerKey: input.providerKey,
|
|
188
|
+
providerSessionId: session.sessionId,
|
|
189
|
+
unifiedStatus: session.status,
|
|
190
|
+
redirectUrl: session.redirectUrl
|
|
191
|
+
?? (session.clientSession?.type === 'redirect' ? session.clientSession.redirectUrl : null),
|
|
192
|
+
clientSecret: session.clientSecret ?? null,
|
|
193
|
+
amount: String(input.amount),
|
|
194
|
+
currencyCode: input.currencyCode,
|
|
195
|
+
gatewayMetadata: {
|
|
196
|
+
...(session.providerData ?? {}),
|
|
197
|
+
...(session.clientSession ? { clientSession: session.clientSession } : {}),
|
|
198
|
+
},
|
|
199
|
+
organizationId: input.organizationId,
|
|
200
|
+
tenantId: input.tenantId,
|
|
201
|
+
deletedAt: null,
|
|
202
|
+
}
|
|
203
|
+
return id
|
|
204
|
+
? manager.create(GatewayTransaction, { id, ...data })
|
|
205
|
+
: manager.create(GatewayTransaction, data)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function restoreSession(transaction: GatewayTransaction): CreateSessionResult {
|
|
209
|
+
const metadata = readGatewayMetadata(transaction.gatewayMetadata)
|
|
210
|
+
const { clientSession, ...providerData } = metadata
|
|
211
|
+
return {
|
|
212
|
+
sessionId: readProviderSessionId(transaction),
|
|
213
|
+
status: transaction.unifiedStatus as UnifiedPaymentStatus,
|
|
214
|
+
...(transaction.clientSecret ? { clientSecret: transaction.clientSecret } : {}),
|
|
215
|
+
...(transaction.redirectUrl ? { redirectUrl: transaction.redirectUrl } : {}),
|
|
216
|
+
...(Object.keys(providerData).length > 0 ? { providerData } : {}),
|
|
217
|
+
...(clientSession && typeof clientSession === 'object'
|
|
218
|
+
? { clientSession: clientSession as CreateSessionResult['clientSession'] }
|
|
219
|
+
: {}),
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function recordCreatedSession(
|
|
224
|
+
transaction: GatewayTransaction,
|
|
225
|
+
input: CreatePaymentSessionInput,
|
|
226
|
+
scope: { organizationId: string; tenantId: string },
|
|
227
|
+
): Promise<void> {
|
|
228
|
+
await emitPaymentGatewayEvent('payment_gateways.session.created', {
|
|
229
|
+
transactionId: transaction.id,
|
|
230
|
+
paymentId: transaction.paymentId,
|
|
231
|
+
providerKey: transaction.providerKey,
|
|
232
|
+
status: transaction.unifiedStatus,
|
|
233
|
+
organizationId: transaction.organizationId,
|
|
234
|
+
tenantId: transaction.tenantId,
|
|
235
|
+
})
|
|
236
|
+
await writeTransactionLog(
|
|
237
|
+
transaction.providerKey,
|
|
238
|
+
scope,
|
|
239
|
+
transaction.id,
|
|
240
|
+
'info',
|
|
241
|
+
'Payment session created',
|
|
242
|
+
{
|
|
243
|
+
paymentId: transaction.paymentId,
|
|
244
|
+
providerSessionId: transaction.providerSessionId,
|
|
245
|
+
status: transaction.unifiedStatus,
|
|
246
|
+
amount: input.amount,
|
|
247
|
+
currencyCode: input.currencyCode,
|
|
248
|
+
},
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function startClaimHeartbeat(
|
|
253
|
+
ownership: { id: string; claimToken: string },
|
|
254
|
+
scope: { organizationId: string; tenantId: string },
|
|
255
|
+
): () => Promise<void> {
|
|
256
|
+
let stopped = false
|
|
257
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
258
|
+
let pendingRefresh = Promise.resolve()
|
|
259
|
+
|
|
260
|
+
const schedule = () => {
|
|
261
|
+
timer = setTimeout(() => {
|
|
262
|
+
pendingRefresh = refreshPaymentSessionInitialization(em, ownership, scope, new Date())
|
|
263
|
+
.then(
|
|
264
|
+
(refreshed) => {
|
|
265
|
+
if (refreshed && !stopped) schedule()
|
|
266
|
+
},
|
|
267
|
+
() => {
|
|
268
|
+
if (!stopped) schedule()
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
}, claimHeartbeatIntervalMs)
|
|
272
|
+
}
|
|
273
|
+
schedule()
|
|
274
|
+
|
|
275
|
+
return async () => {
|
|
276
|
+
stopped = true
|
|
277
|
+
if (timer) clearTimeout(timer)
|
|
278
|
+
await pendingRefresh
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
155
282
|
return {
|
|
156
283
|
async createPaymentSession(input: CreatePaymentSessionInput): Promise<{ transaction: GatewayTransaction; session: CreateSessionResult }> {
|
|
157
284
|
const scope = { organizationId: input.organizationId, tenantId: input.tenantId }
|
|
@@ -160,6 +287,7 @@ export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
|
|
|
160
287
|
const sessionInput: CreateSessionInput = {
|
|
161
288
|
paymentId: input.paymentId,
|
|
162
289
|
orderId: input.orderId,
|
|
290
|
+
idempotencyKey: input.idempotencyKey,
|
|
163
291
|
tenantId: input.tenantId,
|
|
164
292
|
organizationId: input.organizationId,
|
|
165
293
|
amount: input.amount,
|
|
@@ -174,50 +302,93 @@ export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
|
|
|
174
302
|
credentials,
|
|
175
303
|
}
|
|
176
304
|
|
|
177
|
-
|
|
305
|
+
if (!input.idempotencyKey) {
|
|
306
|
+
const session = await adapter.createSession(sessionInput)
|
|
307
|
+
const transaction = createGatewayTransaction(em, input, session)
|
|
308
|
+
await em.persist(transaction).flush()
|
|
309
|
+
await recordCreatedSession(transaction, input, scope)
|
|
310
|
+
return { transaction, session }
|
|
311
|
+
}
|
|
178
312
|
|
|
179
|
-
const
|
|
313
|
+
const operationKey = buildPaymentSessionOperationKey({
|
|
314
|
+
idempotencyKey: input.idempotencyKey,
|
|
180
315
|
paymentId: input.paymentId,
|
|
181
316
|
providerKey: input.providerKey,
|
|
182
|
-
providerSessionId: session.sessionId,
|
|
183
|
-
unifiedStatus: session.status,
|
|
184
|
-
redirectUrl: session.redirectUrl
|
|
185
|
-
?? (session.clientSession?.type === 'redirect' ? session.clientSession.redirectUrl : null),
|
|
186
|
-
clientSecret: session.clientSecret ?? null,
|
|
187
|
-
amount: String(input.amount),
|
|
188
|
-
currencyCode: input.currencyCode,
|
|
189
|
-
gatewayMetadata: {
|
|
190
|
-
...(session.providerData ?? {}),
|
|
191
|
-
...(session.clientSession ? { clientSession: session.clientSession } : {}),
|
|
192
|
-
},
|
|
193
|
-
organizationId: input.organizationId,
|
|
194
|
-
tenantId: input.tenantId,
|
|
195
|
-
})
|
|
196
|
-
await em.persist(transaction).flush()
|
|
197
|
-
await emitPaymentGatewayEvent('payment_gateways.session.created', {
|
|
198
|
-
transactionId: transaction.id,
|
|
199
|
-
paymentId: transaction.paymentId,
|
|
200
|
-
providerKey: transaction.providerKey,
|
|
201
|
-
status: transaction.unifiedStatus,
|
|
202
|
-
organizationId: transaction.organizationId,
|
|
203
|
-
tenantId: transaction.tenantId,
|
|
204
|
-
})
|
|
205
|
-
await writeTransactionLog(
|
|
206
|
-
transaction.providerKey,
|
|
207
317
|
scope,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
318
|
+
})
|
|
319
|
+
while (true) {
|
|
320
|
+
const existing = await findPaymentSessionInitialization(em, operationKey, input.providerKey, scope)
|
|
321
|
+
if (existing?.gatewayTransactionId) {
|
|
322
|
+
const transaction = await findOneWithDecryption(
|
|
323
|
+
em.fork(),
|
|
324
|
+
GatewayTransaction,
|
|
325
|
+
{
|
|
326
|
+
id: existing.gatewayTransactionId,
|
|
327
|
+
organizationId: scope.organizationId,
|
|
328
|
+
tenantId: scope.tenantId,
|
|
329
|
+
deletedAt: null,
|
|
330
|
+
},
|
|
331
|
+
undefined,
|
|
332
|
+
scope,
|
|
333
|
+
)
|
|
334
|
+
if (!transaction) {
|
|
335
|
+
throw new Error('Completed payment session is missing its gateway transaction')
|
|
336
|
+
}
|
|
337
|
+
return { transaction, session: restoreSession(transaction) }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const claimedAt = new Date()
|
|
341
|
+
const staleBefore = new Date(claimedAt.getTime() - claimStaleAfterMs)
|
|
342
|
+
const ownership = existing
|
|
343
|
+
? await reclaimPaymentSessionInitialization(em, existing, scope, claimedAt, staleBefore)
|
|
344
|
+
: await claimPaymentSessionInitialization(em, operationKey, input.providerKey, scope, claimedAt)
|
|
345
|
+
|
|
346
|
+
if (!ownership) {
|
|
347
|
+
await new Promise((resolve) => setTimeout(resolve, claimPollIntervalMs))
|
|
348
|
+
continue
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let session: CreateSessionResult
|
|
352
|
+
const stopHeartbeat = startClaimHeartbeat(ownership, scope)
|
|
353
|
+
try {
|
|
354
|
+
session = await adapter.createSession({ ...sessionInput, idempotencyKey: operationKey })
|
|
355
|
+
} catch (error) {
|
|
356
|
+
await releasePaymentSessionInitialization(em, ownership, scope).catch(() => undefined)
|
|
357
|
+
throw error
|
|
358
|
+
} finally {
|
|
359
|
+
await stopHeartbeat()
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const transactionId = randomUUID()
|
|
363
|
+
const transaction = await em.fork().transactional(async (transactionEm) => {
|
|
364
|
+
const finalizedRows = await transactionEm.nativeUpdate(
|
|
365
|
+
GatewaySessionInitialization,
|
|
366
|
+
{
|
|
367
|
+
id: ownership.id,
|
|
368
|
+
claimToken: ownership.claimToken,
|
|
369
|
+
gatewayTransactionId: null,
|
|
370
|
+
organizationId: scope.organizationId,
|
|
371
|
+
tenantId: scope.tenantId,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
gatewayTransactionId: transactionId,
|
|
375
|
+
claimToken: null,
|
|
376
|
+
claimedAt: null,
|
|
377
|
+
updatedAt: new Date(),
|
|
378
|
+
},
|
|
379
|
+
)
|
|
380
|
+
if (finalizedRows === 0) return null
|
|
381
|
+
const created = createGatewayTransaction(transactionEm, input, session, transactionId)
|
|
382
|
+
await transactionEm.persist(created).flush()
|
|
383
|
+
return created
|
|
384
|
+
})
|
|
219
385
|
|
|
220
|
-
|
|
386
|
+
if (!transaction) {
|
|
387
|
+
continue
|
|
388
|
+
}
|
|
389
|
+
await recordCreatedSession(transaction, input, scope)
|
|
390
|
+
return { transaction, session }
|
|
391
|
+
}
|
|
221
392
|
},
|
|
222
393
|
|
|
223
394
|
async capturePayment(transactionId: string, amount: number | undefined, scope: { organizationId: string; tenantId: string }): Promise<CaptureResult> {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
2
|
+
import { UniqueConstraintViolationException } from '@mikro-orm/core'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
5
|
+
import { GatewaySessionInitialization } from '../data/entities'
|
|
6
|
+
|
|
7
|
+
type Scope = { organizationId: string; tenantId: string }
|
|
8
|
+
|
|
9
|
+
export type OwnedPaymentSessionInitialization = {
|
|
10
|
+
id: string
|
|
11
|
+
claimToken: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildPaymentSessionOperationKey(input: {
|
|
15
|
+
idempotencyKey: string
|
|
16
|
+
paymentId: string
|
|
17
|
+
providerKey: string
|
|
18
|
+
scope: Scope
|
|
19
|
+
}): string {
|
|
20
|
+
const digest = createHash('sha256')
|
|
21
|
+
.update(JSON.stringify([
|
|
22
|
+
input.scope.tenantId,
|
|
23
|
+
input.scope.organizationId,
|
|
24
|
+
input.providerKey,
|
|
25
|
+
input.paymentId,
|
|
26
|
+
input.idempotencyKey,
|
|
27
|
+
]))
|
|
28
|
+
.digest('hex')
|
|
29
|
+
return `om-payment-session:${digest}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function findPaymentSessionInitialization(
|
|
33
|
+
em: EntityManager,
|
|
34
|
+
operationKey: string,
|
|
35
|
+
providerKey: string,
|
|
36
|
+
scope: Scope,
|
|
37
|
+
): Promise<GatewaySessionInitialization | null> {
|
|
38
|
+
return findOneWithDecryption(
|
|
39
|
+
em.fork(),
|
|
40
|
+
GatewaySessionInitialization,
|
|
41
|
+
{
|
|
42
|
+
operationKey,
|
|
43
|
+
providerKey,
|
|
44
|
+
organizationId: scope.organizationId,
|
|
45
|
+
tenantId: scope.tenantId,
|
|
46
|
+
},
|
|
47
|
+
undefined,
|
|
48
|
+
scope,
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function claimPaymentSessionInitialization(
|
|
53
|
+
em: EntityManager,
|
|
54
|
+
operationKey: string,
|
|
55
|
+
providerKey: string,
|
|
56
|
+
scope: Scope,
|
|
57
|
+
claimedAt: Date,
|
|
58
|
+
): Promise<OwnedPaymentSessionInitialization | null> {
|
|
59
|
+
const claimEm = em.fork()
|
|
60
|
+
const claimToken = randomUUID()
|
|
61
|
+
const record = claimEm.create(GatewaySessionInitialization, {
|
|
62
|
+
operationKey,
|
|
63
|
+
providerKey,
|
|
64
|
+
claimToken,
|
|
65
|
+
claimedAt,
|
|
66
|
+
gatewayTransactionId: null,
|
|
67
|
+
organizationId: scope.organizationId,
|
|
68
|
+
tenantId: scope.tenantId,
|
|
69
|
+
})
|
|
70
|
+
try {
|
|
71
|
+
await claimEm.persist(record).flush()
|
|
72
|
+
return { id: record.id, claimToken }
|
|
73
|
+
} catch (error: unknown) {
|
|
74
|
+
if (error instanceof UniqueConstraintViolationException) return null
|
|
75
|
+
throw error
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function reclaimPaymentSessionInitialization(
|
|
80
|
+
em: EntityManager,
|
|
81
|
+
claim: GatewaySessionInitialization,
|
|
82
|
+
scope: Scope,
|
|
83
|
+
claimedAt: Date,
|
|
84
|
+
staleBefore: Date,
|
|
85
|
+
): Promise<OwnedPaymentSessionInitialization | null> {
|
|
86
|
+
const claimToken = randomUUID()
|
|
87
|
+
const claimedRows = await em.fork().nativeUpdate(
|
|
88
|
+
GatewaySessionInitialization,
|
|
89
|
+
{
|
|
90
|
+
id: claim.id,
|
|
91
|
+
operationKey: claim.operationKey,
|
|
92
|
+
providerKey: claim.providerKey,
|
|
93
|
+
organizationId: scope.organizationId,
|
|
94
|
+
tenantId: scope.tenantId,
|
|
95
|
+
gatewayTransactionId: null,
|
|
96
|
+
$or: [
|
|
97
|
+
{ claimToken: null },
|
|
98
|
+
{ claimedAt: { $lt: staleBefore } },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
{ claimToken, claimedAt, updatedAt: claimedAt },
|
|
102
|
+
)
|
|
103
|
+
return claimedRows > 0 ? { id: claim.id, claimToken } : null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function releasePaymentSessionInitialization(
|
|
107
|
+
em: EntityManager,
|
|
108
|
+
ownership: OwnedPaymentSessionInitialization,
|
|
109
|
+
scope: Scope,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
await em.fork().nativeUpdate(
|
|
112
|
+
GatewaySessionInitialization,
|
|
113
|
+
{
|
|
114
|
+
id: ownership.id,
|
|
115
|
+
claimToken: ownership.claimToken,
|
|
116
|
+
organizationId: scope.organizationId,
|
|
117
|
+
tenantId: scope.tenantId,
|
|
118
|
+
gatewayTransactionId: null,
|
|
119
|
+
},
|
|
120
|
+
{ claimToken: null, claimedAt: null, updatedAt: new Date() },
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function refreshPaymentSessionInitialization(
|
|
125
|
+
em: EntityManager,
|
|
126
|
+
ownership: OwnedPaymentSessionInitialization,
|
|
127
|
+
scope: Scope,
|
|
128
|
+
claimedAt: Date,
|
|
129
|
+
): Promise<boolean> {
|
|
130
|
+
const refreshedRows = await em.fork().nativeUpdate(
|
|
131
|
+
GatewaySessionInitialization,
|
|
132
|
+
{
|
|
133
|
+
id: ownership.id,
|
|
134
|
+
claimToken: ownership.claimToken,
|
|
135
|
+
organizationId: scope.organizationId,
|
|
136
|
+
tenantId: scope.tenantId,
|
|
137
|
+
gatewayTransactionId: null,
|
|
138
|
+
},
|
|
139
|
+
{ claimedAt, updatedAt: claimedAt },
|
|
140
|
+
)
|
|
141
|
+
return refreshedRows > 0
|
|
142
|
+
}
|