logic updated to check for receipt.info.fiscal_status === 'done'

This commit is contained in:
O K
2026-06-02 17:50:29 +03:00
parent 93ab7e060d
commit db6945fbff
4 changed files with 2 additions and 539 deletions

View File

@@ -1,330 +0,0 @@
import '@shopify/ui-extensions/customer-account';
import { render } from 'preact';
import { useState, useEffect, useCallback } from 'preact/hooks';
async function queryCustomerAccountAPI(query, variables = {}) {
console.log('[HutkoFiscalDebug] queryCustomerAccountAPI call:', { query, variables });
try {
const res = await fetch(
'shopify://customer-account/api/2025-10/graphql.json',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
}
);
console.log('[HutkoFiscalDebug] queryCustomerAccountAPI response status:', res.status, res.statusText);
if (!res.ok) {
throw new Error(`API request failed: ${res.status} ${res.statusText}`);
}
const data = await res.json();
console.log('[HutkoFiscalDebug] queryCustomerAccountAPI response data:', data);
return data;
} catch (error) {
console.error('[HutkoFiscalDebug] queryCustomerAccountAPI exception:', error);
throw error;
}
}
export default function extension() {
render(<Extension />, document.body);
}
const MOCK_RECEIPTS = [
{
id: 9871,
info: {
payment_id: "rNrWsih9Uj06ivn87af9HWiuj",
amount: 2.0,
currency: "UAH",
order_name: "#1082",
hutko_order_id: 8961,
order_time: "2026-05-08 12:40:41.737232",
hutko_merchant_id: 1000142,
order_status: "reversed",
card_number: "535528*9003",
email: "v.paliienko@hutko.org",
fiscal_status: "done",
fiscal_provider_name: "ereceipt",
transaction_type: "purchase",
fiscal_date: "21052026",
fiscal_time: "182702",
fiscal_number: 4001317034,
fiscal_receipt_number: "6914923030",
display_datetime: "21.05.2026 18:27:02",
links: {
external_provider: "https://cabinet.tax.gov.ua/cashregs/check?id=6914923030&fn=4001317034&date=20260521&time=18.27&sm=2.0"
}
}
},
{
id: 9873,
info: {
payment_id: "rNrWsih9Uj06ivn87af9HWiuj",
amount: 2.0,
currency: "UAH",
order_name: "#1082",
hutko_order_id: 8961,
order_time: "2026-05-08 12:40:41.737232",
hutko_merchant_id: 1000142,
order_status: "reversed",
card_number: "535528*9003",
email: "v.paliienko@hutko.org",
fiscal_status: "done",
fiscal_provider_name: "ereceipt",
transaction_type: "reverse",
fiscal_date: "21052026",
fiscal_time: "182823",
fiscal_number: 4001317034,
fiscal_receipt_number: "6914937076",
display_datetime: "21.05.2026 18:28:23",
links: {
external_provider: "https://cabinet.tax.gov.ua/cashregs/check?id=6914937076&fn=4001317034&date=20260521&time=18.28&sm=2.0"
}
}
}
];
function Extension() {
const translate = shopify.i18n.translate;
// Get order ID from the order status context signal
const orderId = shopify.order?.value?.id;
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [receipts, setReceipts] = useState([]);
const [pollIndex, setPollIndex] = useState(0);
const fetchFiscalData = useCallback(async (isManual = false) => {
console.log('[HutkoFiscalDebug] fetchFiscalData triggered. isManual:', isManual, 'orderId:', orderId);
if (isManual) {
setPollIndex(0);
}
if (!orderId) {
console.warn('[HutkoFiscalDebug] fetchFiscalData called but orderId is missing/undefined. Loading stopped.');
setLoading(false);
return;
}
setLoading(true);
setError(null);
try {
// Step 1: Read fiscal_receipts from pre-loaded appMetafields (declared in TOML)
let receiptIds = [];
const metafields = shopify.appMetafields?.value;
console.log('[HutkoFiscalDebug] shopify.appMetafields?.value:', metafields);
if (metafields && metafields.length > 0) {
const entry = metafields.find(
(e) =>
e.metafield?.namespace === 'hutko-fiscal' &&
e.metafield?.key === 'fiscal_receipts',
);
console.log('[HutkoFiscalDebug] Found metafield entry for hutko-fiscal.fiscal_receipts:', entry);
if (entry?.metafield?.value && typeof entry.metafield.value === 'string') {
try {
receiptIds = JSON.parse(entry.metafield.value);
console.log('[HutkoFiscalDebug] Parsed receiptIds from appMetafields:', receiptIds);
} catch (e) {
console.error('[HutkoFiscalDebug] Error parsing fiscal_receipts from appMetafields:', e, 'Raw value:', entry.metafield.value);
}
}
}
// Fallback: query via Customer Account API if appMetafields didn't have it
if (receiptIds.length === 0) {
console.log('[HutkoFiscalDebug] receiptIds is empty. Querying Customer Account API fallback...');
const indexRes = await queryCustomerAccountAPI(
`query getOrderMetafield($id: ID!) {
order(id: $id) {
metafield(namespace: "hutko-fiscal", key: "fiscal_receipts") { value }
}
}`,
{ id: orderId },
);
console.log('[HutkoFiscalDebug] Fallback query indexRes:', indexRes);
const rawValue = indexRes?.data?.order?.metafield?.value;
console.log('[HutkoFiscalDebug] Fallback query raw value:', rawValue);
try {
receiptIds = JSON.parse(rawValue || '[]');
console.log('[HutkoFiscalDebug] Parsed receiptIds from fallback query:', receiptIds);
} catch (e) {
console.error('[HutkoFiscalDebug] Error parsing fallback query fiscal_receipts JSON:', e);
}
}
if (receiptIds.length === 0) {
console.warn('[HutkoFiscalDebug] No receipt IDs found after appMetafields and fallback query. Setting empty receipts.');
setReceipts([]);
setLoading(false);
return;
}
// Step 2: Fetch per-receipt info via Customer Account API
console.log('[HutkoFiscalDebug] Fetching detailed info for receipt IDs:', receiptIds);
const detailed = await Promise.all(
receiptIds.map(async (id) => {
console.log(`[HutkoFiscalDebug] Fetching data for receipt ID: ${id}`);
const res = await queryCustomerAccountAPI(
`query getReceiptData($orderId: ID!, $infoKey: String!) {
order(id: $orderId) {
info: metafield(namespace: "hutko-fiscal", key: $infoKey) { value }
}
}`,
{
orderId,
infoKey: `fiscal_info_${id}`,
},
);
console.log(`[HutkoFiscalDebug] Detailed query response for ID ${id}:`, res);
let info = {};
try {
info = JSON.parse(res?.data?.order?.info?.value || '{}');
} catch (e) { }
return { id, info };
})
);
console.log('[HutkoFiscalDebug] All detailed receipt data resolved successfully:', detailed);
setReceipts(detailed);
} catch (e) {
console.error('[HutkoFiscalDebug] Critical error in fetchFiscalData:', e);
setError(shopify.i18n.translate('hutko.failed_to_load_receipts'));
} finally {
setLoading(false);
}
}, [orderId]);
useEffect(() => {
fetchFiscalData();
}, [fetchFiscalData]);
useEffect(() => {
const isWaiting = receipts.some((receipt) => receipt.info.fiscal_status !== 'done');
console.log('[HutkoFiscalDebug] Polling check. receipts count:', receipts.length, 'isWaiting:', isWaiting, 'pollIndex:', pollIndex);
if (!isWaiting) {
if (pollIndex !== 0) {
console.log('[HutkoFiscalDebug] No receipts are waiting. Resetting pollIndex to 0.');
setPollIndex(0);
}
return;
}
const delays = [5000, 15000, 20000];
if (pollIndex < delays.length) {
console.log(`[HutkoFiscalDebug] Setting poll timer. Delay: ${delays[pollIndex]}ms. Current pollIndex: ${pollIndex}`);
const timer = setTimeout(() => {
console.log(`[HutkoFiscalDebug] Poll timer fired. Advancing pollIndex to ${pollIndex + 1} and fetching data.`);
setPollIndex((prev) => prev + 1);
fetchFiscalData();
}, delays[pollIndex]);
return () => clearTimeout(timer);
} else {
console.log('[HutkoFiscalDebug] pollIndex reached max delay threshold. Stopping auto-poll.');
}
}, [fetchFiscalData, receipts, pollIndex]);
if (loading) {
return (
<s-box padding="base" border="base" borderRadius="base">
<s-stack direction="inline" alignItems="center" gap="base">
<s-spinner size="small"></s-spinner>
<s-text>{translate('hutko.loading')}</s-text>
</s-stack>
</s-box>
);
}
if (error) {
return <s-banner tone="critical">{error}</s-banner>;
}
// For testing purposes, if no receipts are found, use mock receipts
const displayReceipts = receipts.length === 2 ? MOCK_RECEIPTS : receipts;
if (displayReceipts.length === 0) {
return null;
}
const logoSrc = "data:image/svg+xml,%3Csvg width='120' height='30' viewBox='188 423 624 154' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M747.38 473.832C762.386 473.832 774.926 478.66 784.944 488.305C794.962 497.957 800 509.817 800 523.877C800 537.938 794.962 549.594 784.944 559.246C774.876 568.897 762.386 573.72 747.38 573.72C732.374 573.72 719.898 568.947 709.923 559.344C699.955 549.742 694.967 537.938 694.967 523.871C694.967 509.803 700.005 498.104 710.023 488.403C720.095 478.713 732.504 473.835 747.38 473.824V473.832ZM474.833 530.079C474.833 535.826 476.46 540.445 479.713 543.992C482.966 547.531 487.373 549.332 492.984 549.333C499.491 549.333 504.687 547.433 508.621 543.634C512.555 539.835 514.547 534.648 514.547 528.032V475.729H540.883V571.51L540.832 571.461H514.705V561.963C507.467 569.407 497.972 573.15 486.27 573.15C474.568 573.15 465.653 569.61 458.781 562.524C451.909 555.438 448.498 546.102 448.498 534.55V475.721H474.833V530.079ZM266.725 535.497L322.589 501.641L256.912 571.921L255.929 543.519L200.008 571.921H200L269.09 426.28L266.725 535.497ZM375.332 485.276C382.57 477.832 392.065 474.089 403.768 474.089C415.47 474.089 424.384 477.629 431.256 484.715C438.128 491.802 441.539 501.138 441.539 512.689V571.517H415.204V517.161C415.204 511.413 413.577 506.794 410.324 503.248C407.071 499.708 402.664 497.907 397.053 497.907C390.546 497.907 385.35 499.806 381.416 503.605C377.482 507.404 375.49 512.591 375.49 519.208V571.51H349.154V475.729L349.205 452.648H375.332V485.276ZM588.437 475.729H611.991V498.88H588.443V537.326C588.443 541.531 589.44 544.616 591.482 546.459C593.532 548.31 596.836 549.284 601.407 549.284H611.991V571.51H599.508C574.434 571.51 561.943 560.576 561.943 538.658V498.929H547.833V475.729H561.943V452.647H588.437V475.729ZM645.649 518.639H645.707L678.126 475.729H708.238L670.674 523.315L709.75 571.461H676.75L645.649 532.09V571.461H618.941V475.729H645.649V518.639ZM747.38 497.389C739.877 497.389 733.635 499.905 728.598 504.938C723.567 509.971 721.044 516.279 721.044 523.877C721.044 531.475 723.517 537.994 728.39 543.027C733.27 548.059 739.404 550.569 746.807 550.569C754.209 550.569 760.666 548.003 765.904 542.921C771.1 537.84 773.722 531.475 773.722 523.877C773.722 516.279 771.207 509.964 766.169 504.938H766.162C761.132 499.905 754.883 497.389 747.38 497.389Z' fill='%23E40B2D'/%3E%3C/svg%3E";
return (<s-box>
<s-stack gap="base" padding="base" border="base" borderRadius="base">
<s-heading>hutko {translate('hutko.fiscal_receipts')}</s-heading>
{displayReceipts.map((receipt) => (
<s-box key={receipt.id} padding="base" border="base" borderRadius="base">
<s-stack gap="base">
<s-stack direction="inline" justifyContent="space-between" alignItems="center">
<s-stack gap="none">
{(receipt.info.fiscal_status === 'done') || receipt.info.display_datetime ? (
<>
{receipt.info.display_datetime && (
<s-text type="strong">{receipt.info.display_datetime}</s-text>
)}
{receipt.info.amount && (
<s-text>{receipt.info.amount} {receipt.info.currency || translate('hutko.currency')}</s-text>
)}
{receipt.info.fiscal_receipt_number && (
<s-text type="small">#{receipt.info.fiscal_receipt_number}</s-text>
)}
{receipt.info.fiscal_number && !receipt.info.fiscal_receipt_number && (
<s-text type="small">{translate('hutko.fiscal_number_label')}{receipt.info.fiscal_number}</s-text>
)}
</>
) : (
<s-text>{translate('hutko.waiting_for_fiscalisation', { id: receipt.id })}</s-text>
)}
</s-stack>
</s-stack>
<s-divider></s-divider>
{receipt.info.links && (
<s-stack direction="inline" gap="base">
{receipt.info.links.external_provider && (
<s-link href={receipt.info.links.external_provider} target="_blank">
{translate('hutko.provider')}
</s-link>
)}
</s-stack>
)}
</s-stack>
</s-box>
))}
<s-stack direction="inline" justifyContent="space-between" alignItems="center">
<s-image
src={logoSrc}
alt="hutko"
aspectRatio="4/1"
objectFit="contain"
sizes="120px"
inlineSize="auto"
loading="lazy"
></s-image>
<s-stack direction="inline" justifyContent="end">
<s-button
onClick={() => { fetchFiscalData(true); }}
disabled={loading}
>
{translate('hutko.refresh')}
</s-button>
</s-stack>
</s-stack>
</s-stack></s-box>
);
}

View File

@@ -178,7 +178,7 @@ function Extension() {
<s-stack key={receipt.id} gap="base">
<s-stack direction="inline" justifyContent="space-between" block-align="center">
{receipt.info.display_datetime ? (
{receipt.info.fiscal_status === 'done' ? (
<>
{receipt.info.display_datetime && (
<s-chip font-weight="bold" ><s-icon slot="graphic" type="calendar" size="small"></s-icon>&nbsp;{receipt.info.display_datetime}</s-chip>

View File

@@ -1,207 +0,0 @@
import '@shopify/ui-extensions/preact';
import { render } from 'preact';
import { useState, useEffect, useCallback } from 'preact/hooks';
export default function extension() {
render(<Extension />, document.body);
}
function Extension() {
const translate = shopify.i18n.translate;
const orderId = shopify.data.selected?.[0]?.id;
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [receipts, setReceipts] = useState([]);
const fetchFiscalData = useCallback(async () => {
if (!orderId) {
setError(translate('hutko.no_order_selected'));
setLoading(false);
return;
}
setLoading(true);
setError(null);
try {
const indexRes = await fetch('shopify:admin/api/graphql.json', {
method: 'POST',
body: JSON.stringify({
query: `
query getOrderMetafields($id: ID!) {
order(id: $id) {
receipts: metafield(namespace: "hutko-fiscal", key: "fiscal_receipts") { value }
}
}
`,
variables: { id: orderId }
})
});
const indexData = await indexRes.json();
const receiptIds = JSON.parse(
indexData?.data?.order?.receipts?.value || '[]'
);
if (receiptIds.length === 0) {
setReceipts([]);
setLoading(false);
return;
}
const detailed = await Promise.all(
receiptIds.map(async (id) => {
const res = await fetch('shopify:admin/api/graphql.json', {
method: 'POST',
body: JSON.stringify({
query: `
query getReceiptData($id: ID!, $infoKey: String!, $storageKey: String!) {
order(id: $id) {
info: metafield(namespace: "hutko-fiscal", key: $infoKey) { value }
storage: metafield(namespace: "hutko-fiscal", key: $storageKey) { value }
}
}
`,
variables: {
id: orderId,
infoKey: `fiscal_info_${id}`,
storageKey: `fiscal_storage_${id}`,
}
})
});
const resData = await res.json();
let info = {};
try {
info = JSON.parse(resData?.data?.order?.info?.value || '{}');
} catch (e) { }
let storage = {};
try {
storage = JSON.parse(resData?.data?.order?.storage?.value || '{}');
} catch (e) { }
return { id, info, storage };
})
);
setReceipts(detailed);
} catch (e) {
console.error('Failed to fetch fiscal data:', e);
setError(translate('hutko.failed_to_load_receipts') + e.message);
} finally {
setLoading(false);
}
}, [orderId, translate]);
useEffect(() => {
fetchFiscalData();
}, [fetchFiscalData]);
const logoSrc = "data:image/svg+xml,%3Csvg width='120' height='30' viewBox='188 423 624 154' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M747.38 473.832C762.386 473.832 774.926 478.66 784.944 488.305C794.962 497.957 800 509.817 800 523.877C800 537.938 794.962 549.594 784.944 559.246C774.876 568.897 762.386 573.72 747.38 573.72C732.374 573.72 719.898 568.947 709.923 559.344C699.955 549.742 694.967 537.938 694.967 523.871C694.967 509.803 700.005 498.104 710.023 488.403C720.095 478.713 732.504 473.835 747.38 473.824V473.832ZM474.833 530.079C474.833 535.826 476.46 540.445 479.713 543.992C482.966 547.531 487.373 549.332 492.984 549.333C499.491 549.333 504.687 547.433 508.621 543.634C512.555 539.835 514.547 534.648 514.547 528.032V475.729H540.883V571.51L540.832 571.461H514.705V561.963C507.467 569.407 497.972 573.15 486.27 573.15C474.568 573.15 465.653 569.61 458.781 562.524C451.909 555.438 448.498 546.102 448.498 534.55V475.721H474.833V530.079ZM266.725 535.497L322.589 501.641L256.912 571.921L255.929 543.519L200.008 571.921H200L269.09 426.28L266.725 535.497ZM375.332 485.276C382.57 477.832 392.065 474.089 403.768 474.089C415.47 474.089 424.384 477.629 431.256 484.715C438.128 491.802 441.539 501.138 441.539 512.689V571.517H415.204V517.161C415.204 511.413 413.577 506.794 410.324 503.248C407.071 499.708 402.664 497.907 397.053 497.907C390.546 497.907 385.35 499.806 381.416 503.605C377.482 507.404 375.49 512.591 375.49 519.208V571.51H349.154V475.729L349.205 452.648H375.332V485.276ZM588.437 475.729H611.991V498.88H588.443V537.326C588.443 541.531 589.44 544.616 591.482 546.459C593.532 548.31 596.836 549.284 601.407 549.284H611.991V571.51H599.508C574.434 571.51 561.943 560.576 561.943 538.658V498.929H547.833V475.729H561.943V452.647H588.437V475.729ZM645.649 518.639H645.707L678.126 475.729H708.238L670.674 523.315L709.75 571.461H676.75L645.649 532.09V571.461H618.941V475.729H645.649V518.639ZM747.38 497.389C739.877 497.389 733.635 499.905 728.598 504.938C723.567 509.971 721.044 516.279 721.044 523.877C721.044 531.475 723.517 537.994 728.39 543.027C733.27 548.059 739.404 550.569 746.807 550.569C754.209 550.569 760.666 548.003 765.904 542.921C771.1 537.84 773.722 531.475 773.722 523.877C773.722 516.279 771.207 509.964 766.169 504.938H766.162C761.132 499.905 754.883 497.389 747.38 497.389Z' fill='%23E40B2D'/%3E%3C/svg%3E";
return (
<s-admin-block heading={translate('hutko.fiscal_receipts_title')}>
<s-stack gap="base">
{error && (
<s-banner tone="critical">{error}</s-banner>
)}
{loading && !error && (
<s-box padding="base">
<s-text>{translate('hutko.loading')}</s-text>
</s-box>
)}
{!loading && receipts.length === 0 && !error && (
<s-banner tone="info">{translate('hutko.no_receipts')}</s-banner>
)}
{!loading && receipts.length > 0 && receipts.map((receipt) => (
<s-stack key={receipt.id} gap="base">
<s-stack direction="inline" justifyContent="space-between" block-align="center">
{receipt.info.display_datetime ? (
<>
{receipt.info.display_datetime && (
<s-chip font-weight="bold" ><s-icon slot="graphic" type="calendar-time" size="small"></s-icon>&nbsp;{receipt.info.display_datetime}</s-chip>
)}
{receipt.info.amount && (
<s-chip><s-icon slot="graphic" type="money" size="small"></s-icon>&nbsp;{receipt.info.amount} {receipt.info.currency || translate('hutko.currency')}</s-chip>
)}
{receipt.info.fiscal_status !== 'done' && (
<s-chip>{translate('hutko.processing')}</s-chip>
)}
{receipt.info.transaction_type && (
<s-chip>
{receipt.info.transaction_type === 'purchase'
? translate('hutko.transaction_type_purchase')
: receipt.info.transaction_type === 'reverse'
? translate('hutko.transaction_type_reverse')
: receipt.info.transaction_type}
</s-chip>
)}
<s-button commandFor={`customer-menu-${receipt.id}`} icon="menu-horizontal"></s-button>
<s-menu id={`customer-menu-${receipt.id}`} accessibilityLabel={translate('hutko.customer_actions')}>
{receipt.info.fiscal_receipt_number && (
<s-button
icon="store-managed"
>
{translate('hutko.fn')}&nbsp;{receipt.info.fiscal_receipt_number}
</s-button>
)}
{receipt.info.links && (
<>
{receipt.info.links.external_provider && (
<s-button href={receipt.info.links.external_provider} target="_blank" icon="external">
{translate('hutko.provider')}
</s-button>
)}
</>
)}
</s-menu>
</>
) : (
<s-text>{translate('hutko.waiting_for_fiscalisation', { id: receipt.id })}</s-text>
)}
</s-stack>
<s-divider />
</s-stack>
))}
<s-stack direction="inline" justifyContent="space-between" alignItems="center"><s-image
src={logoSrc}
alt="hutko"
sizes="120px"
aspectRatio="4/1"
inlineSize="auto"
objectFit="contain"
loading="lazy"
></s-image>
<s-stack direction="inline" inline-alignment="end">
<s-button onClick={fetchFiscalData} disabled={loading} icon="refresh"></s-button>
</s-stack></s-stack>
</s-stack>
</s-admin-block>
);
}

View File

@@ -126,7 +126,7 @@ function Extension() {
<s-stack key={receipt.id} gap="base">
<s-stack direction="inline" justifyContent="space-between" block-align="center">
{receipt.info.display_datetime ? (
{receipt.info.fiscal_status === 'done' ? (
<>
{receipt.info.display_datetime && (
<s-chip font-weight="bold" ><s-icon slot="graphic" type="calendar-time" size="small"></s-icon>&nbsp;{receipt.info.display_datetime}</s-chip>