DEV Community

kevin.s
kevin.s

Posted on • Edited on

Build a Crypto Payment Support Desk

A customer opens a support ticket:

I paid 20 minutes ago, but my order is still pending.

The support agent can see the order, but not the payment.

The payment provider shows activity, but the merchant application did not update. A webhook may have failed. Fulfillment may have crashed. The invoice may have expired before the transaction arrived.

The agent now has two options:

  1. Ask a developer to investigate.
  2. Give the customer a vague answer and wait.

Neither option scales.

A Crypto Payment Support Desk gives support teams the evidence, context, and controlled actions they need to investigate payment cases without searching raw logs or interrupting developers.

It is not another payment dashboard.

It is the operational interface between payment infrastructure, merchant systems, and customer support.

This article uses OxaPay as the payment infrastructure reference, but the architecture is provider-agnostic.

The problem is not payment status

A single status does not explain what happened.

Consider this state:

Payment: paid
Order: paid
Fulfillment: failed
Enter fullscreen mode Exit fullscreen mode

The customer paid successfully, but the merchant still owes them a product or service.

Now consider this:

Payment: paying
Order: paid
Fulfillment: completed
Enter fullscreen mode Exit fullscreen mode

The merchant delivered before the payment reached the approved paid state.

Both cases require support attention, but for completely different reasons.

An agent needs more than:

status = paid
Enter fullscreen mode Exit fullscreen mode

They need to know:

  • which order belongs to the payment
  • when the payment session was created
  • which status changes occurred
  • whether the webhook was received and verified
  • whether the merchant application processed it
  • whether fulfillment started
  • whether fulfillment completed
  • whether a reconciliation mismatch exists
  • what action is safe
  • what the customer should be told

The support product is built around this context.

Where the Support Desk fits

A Crypto PaymentOps service manages the broader payment lifecycle.

A crypto payment reconciliation tool detects disagreements between payments, orders, fulfillment, and finance records.

The Support Desk has a narrower role:

Turn payment evidence and operational exceptions into cases that a support agent can understand and resolve safely.

The reconciliation engine may detect:

Provider payment is paid
Local order is pending
Enter fullscreen mode Exit fullscreen mode

The Support Desk should then show the agent:

  • the affected customer
  • the order and payment identifiers
  • the event timeline
  • the evidence behind the mismatch
  • the recommended action
  • the actions the agent is authorized to perform
  • a safe response to send to the customer

Reconciliation finds the problem.

The Support Desk helps a person handle it.

Design around support questions

Start with the questions agents receive most often.

Typical tickets include:

  • I paid, but my order is still pending.
  • My invoice expired after I sent the payment.
  • I sent less than the requested amount.
  • The payment is still confirming.
  • My payment is complete, but I did not receive the product.
  • I paid using an old invoice.
  • I cannot find the transaction in my account.
  • I used a different network or currency.
  • My subscription was not activated.
  • My payment was refunded, but the account state is unclear.

Each question should map to a defined case type, evidence checklist, escalation path, and customer response.

Without that structure, the product becomes another log viewer.

The case workspace

Every support case should open into one workspace.

The agent should not need to switch between the merchant store, payment provider, webhook logs, fulfillment system, and support tool.

A useful workspace has five areas.

Customer and order context

Show:

  • customer name or email
  • merchant order ID
  • product or plan
  • expected amount
  • expected currency
  • order status
  • fulfillment status
  • account or subscription status

Payment context

Show:

  • OxaPay track_id
  • payment type
  • requested amount
  • paid amount
  • payment currency
  • selected network
  • current provider status
  • invoice creation time
  • expiry time
  • transaction hash when available
  • latest provider verification time

Evidence timeline

Combine payment and merchant events into one chronological view.

10:03:12  Merchant order created
10:03:13  OxaPay invoice created
10:03:13  track_id linked to order
10:04:42  Webhook received: paying
10:04:42  HMAC signature verified
10:05:11  Webhook received: paid
10:05:12  Order marked paid
10:05:12  Fulfillment job queued
10:05:18  Fulfillment failed
10:10:00  Support case created
Enter fullscreen mode Exit fullscreen mode

Diagnosis

Show:

  • case type
  • priority
  • likely failure layer
  • supporting evidence
  • missing evidence
  • recommended action
  • escalation owner

Safe actions

Only show actions the current agent can perform.

Examples:

  • refresh payment information
  • resend a customer-safe message
  • retry an idempotent fulfillment job
  • assign the case
  • request developer review
  • request finance review
  • close the case with a resolution note

Do not expose unrestricted payment overrides to ordinary support agents.

Add support-specific tables

Your PaymentOps database may already contain orders, payment sessions, payment events, and fulfillment jobs.

The Support Desk should add support-specific records instead of duplicating the entire payment model.

CREATE TABLE support_cases (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL,
  order_id UUID,
  payment_session_id UUID,
  reconciliation_case_id UUID,
  case_type TEXT NOT NULL,
  priority TEXT NOT NULL DEFAULT 'normal',
  status TEXT NOT NULL DEFAULT 'open',
  assigned_to UUID,
  summary TEXT NOT NULL,
  recommended_action TEXT,
  customer_message_key TEXT,
  opened_by TEXT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
  resolved_at TIMESTAMP
);

CREATE TABLE support_case_notes (
  id UUID PRIMARY KEY,
  case_id UUID NOT NULL REFERENCES support_cases(id),
  author_id UUID,
  author_role TEXT NOT NULL,
  visibility TEXT NOT NULL DEFAULT 'internal',
  note TEXT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE support_actions (
  id UUID PRIMARY KEY,
  case_id UUID NOT NULL REFERENCES support_cases(id),
  actor_id UUID NOT NULL,
  actor_role TEXT NOT NULL,
  action_type TEXT NOT NULL,
  idempotency_key TEXT,
  status TEXT NOT NULL,
  request_data JSONB,
  result_data JSONB,
  error_message TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  completed_at TIMESTAMP,
  UNIQUE (idempotency_key)
);

CREATE TABLE provider_snapshots (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL,
  payment_session_id UUID,
  provider TEXT NOT NULL,
  provider_track_id TEXT NOT NULL,
  source TEXT NOT NULL,
  raw_response JSONB NOT NULL,
  fetched_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE timeline_events (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL,
  order_id UUID,
  payment_session_id UUID,
  support_case_id UUID,
  source TEXT NOT NULL,
  event_type TEXT NOT NULL,
  title TEXT NOT NULL,
  details JSONB,
  occurred_at TIMESTAMP NOT NULL,
  recorded_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Enter fullscreen mode Exit fullscreen mode

The timeline_events table is especially useful.

Instead of asking the UI to combine several raw tables on every request, your services can write normalized events into a shared timeline.

Possible sources include:

merchant_order
oxapay_webhook
provider_lookup
fulfillment_worker
reconciliation_engine
support_agent
finance_system
Enter fullscreen mode Exit fullscreen mode

Raw provider payloads can remain available to authorized developers, while support agents see readable timeline events.

Build a support case snapshot

The frontend should request one normalized case object.

It should not call five backend services directly.

export async function buildSupportCaseSnapshot(caseId, actor) {
  const supportCase = await db.supportCase.findUnique({
    where: { id: caseId },
  });

  if (!supportCase) {
    throw new Error("Support case not found");
  }

  assertMerchantAccess(actor, supportCase.merchantId);

  const [
    order,
    payment,
    timeline,
    notes,
    reconciliationCase,
  ] = await Promise.all([
    supportCase.orderId
      ? db.order.findUnique({
          where: { id: supportCase.orderId },
        })
      : null,

    supportCase.paymentSessionId
      ? db.paymentSession.findUnique({
          where: { id: supportCase.paymentSessionId },
          include: {
            transactions: true,
            fulfillmentJobs: true,
          },
        })
      : null,

    db.timelineEvent.findMany({
      where: { supportCaseId: caseId },
      orderBy: { occurredAt: "asc" },
    }),

    db.supportCaseNote.findMany({
      where: {
        caseId,
        visibility: actor.isCustomer ? "customer" : undefined,
      },
      orderBy: { createdAt: "asc" },
    }),

    supportCase.reconciliationCaseId
      ? db.reconciliationCase.findUnique({
          where: { id: supportCase.reconciliationCaseId },
        })
      : null,
  ]);

  return {
    case: supportCase,
    customer: buildCustomerSafeIdentity(order),
    order: summarizeOrder(order),
    payment: summarizePayment(payment, actor.role),
    diagnosis: buildDiagnosis({
      supportCase,
      order,
      payment,
      reconciliationCase,
    }),
    timeline: timeline.map(toReadableTimelineEvent),
    notes,
    availableActions: getAvailableActions({
      actor,
      supportCase,
      order,
      payment,
    }),
  };
}
Enter fullscreen mode Exit fullscreen mode

The response should be role-aware.

A support agent may see the transaction hash and payment status.

A developer may also see payload hashes, processing errors, and callback metadata.

A customer should see only a limited, public-safe status.

Classify cases automatically

The classification engine turns operational state into support work.

It should produce:

  • case type
  • priority
  • diagnosis
  • recommended action
  • customer message key
  • escalation owner
export function classifySupportCase({
  payment,
  order,
  fulfillment,
  webhookHealth,
  customerClaim,
}) {
  if (!payment) {
    return {
      type: "payment_not_found",
      priority: "high",
      diagnosis:
        "No payment session could be matched to the supplied order or customer evidence.",
      recommendedAction:
        "Request the transaction hash, currency, network, amount, and approximate payment time.",
      customerMessageKey: "payment_not_found",
      escalationOwner: "support",
    };
  }

  if (
    payment.internalStatus === "paid_confirmed" &&
    fulfillment.status !== "completed"
  ) {
    return {
      type: "paid_not_fulfilled",
      priority: "high",
      diagnosis:
        "The provider confirmed payment, but the merchant fulfillment process did not complete.",
      recommendedAction:
        "Inspect the fulfillment job and retry it only through an idempotent action.",
      customerMessageKey: "paid_not_fulfilled",
      escalationOwner: "operations",
    };
  }

  if (payment.internalStatus === "confirming") {
    return {
      type: "payment_detected_not_final",
      priority: "normal",
      diagnosis:
        "Payment activity has been detected, but the payment has not reached the final paid state.",
      recommendedAction:
        "Do not fulfill yet. Refresh the provider status or wait for the paid callback.",
      customerMessageKey: "payment_detected_not_final",
      escalationOwner: "support",
    };
  }

  if (payment.internalStatus === "underpaid_review") {
    return {
      type: "underpaid_payment",
      priority: "normal",
      diagnosis:
        "The amount received does not satisfy the requested payment amount.",
      recommendedAction:
        "Apply the merchant policy for completing, manually accepting, or rejecting an underpayment.",
      customerMessageKey: "underpaid_payment",
      escalationOwner: "operations",
    };
  }

  if (
    payment.internalStatus === "expired" &&
    customerClaim?.claimsPayment
  ) {
    return {
      type: "expired_with_payment_claim",
      priority: "high",
      diagnosis:
        "The invoice expired, but the customer reports that funds were sent.",
      recommendedAction:
        "Refresh Payment Information and search provider history before issuing a replacement invoice.",
      customerMessageKey: "expired_payment_review",
      escalationOwner: "support",
    };
  }

  if (
    payment.internalStatus === "paid_confirmed" &&
    order.paymentStatus !== "paid"
  ) {
    return {
      type: "provider_paid_local_pending",
      priority: "high",
      diagnosis:
        "The provider confirms payment, but the merchant order remains pending.",
      recommendedAction:
        "Verify the exact order mapping and escalate the local state mismatch.",
      customerMessageKey: "payment_confirmed_order_pending",
      escalationOwner: "operations",
    };
  }

  if (
    webhookHealth.lastProviderStatus === "paid" &&
    !webhookHealth.localPaidEventFound
  ) {
    return {
      type: "paid_webhook_missing",
      priority: "high",
      diagnosis:
        "The provider reports a paid payment, but no corresponding paid webhook exists locally.",
      recommendedAction:
        "Create a verified recovery event and investigate callback delivery health.",
      customerMessageKey: "payment_under_review",
      escalationOwner: "developer",
    };
  }

  return {
    type: "general_payment_review",
    priority: "normal",
    diagnosis:
      "The available evidence does not match a known automated support case.",
    recommendedAction:
      "Review the timeline and collect any missing customer evidence.",
    customerMessageKey: "payment_under_review",
    escalationOwner: "support",
  };
}
Enter fullscreen mode Exit fullscreen mode

Do not guess that a customer used the wrong network unless the evidence supports that conclusion.

If the payment cannot be matched, the correct classification is usually:

unmatched customer claim
Enter fullscreen mode Exit fullscreen mode

The agent can then request:

  • transaction hash
  • sent amount
  • currency
  • network
  • approximate payment time
  • sender address when appropriate

The system should separate confirmed evidence from customer-provided claims.

Use an evidence-based case taxonomy

Start with a small set of cases.

Case type What it means Default action
paid_not_fulfilled Payment is confirmed, delivery failed Retry or escalate fulfillment
payment_detected_not_final Activity exists, payment is not final Wait and refresh
underpaid_payment Received amount is insufficient Apply underpayment policy
expired_with_payment_claim Invoice expired, customer says funds were sent Verify provider records
provider_paid_local_pending Provider and merchant order disagree Repair or escalate local state
paid_webhook_missing Provider shows paid, local event is missing Recover and inspect webhook health
payment_not_found No safe payment match exists Request transaction evidence
refunded_access_active Refund exists, service remains active Apply refund and access policy

Every case should answer four questions:

What do we know?

What do we not know?

What is the safest next action?

Who owns that action?
Enter fullscreen mode Exit fullscreen mode

That structure prevents support agents from making financial decisions based on incomplete evidence.

Treat OxaPay webhooks as evidence

OxaPay payment callbacks can include paying and paid status updates.

The merchant should wait for paid before treating the payment as ready for normal fulfillment.

OxaPay signs the raw callback body using HMAC SHA-512 with the Merchant API Key. The receiving endpoint should validate the HMAC header and return HTTP 200 with ok after successful processing.

Webhook delivery can be retried, so duplicate callbacks are normal.

The Support Desk should record:

  • whether the callback arrived
  • whether its HMAC was valid
  • whether the event was already stored
  • whether processing succeeded
  • which state transition it produced
  • whether a downstream action failed

An agent should see:

Webhook received and verified
Enter fullscreen mode Exit fullscreen mode

A developer may see:

Payload hash: 4e5b...
Processing attempt: 2
Queue job: failed
Error: fulfillment adapter timeout
Enter fullscreen mode Exit fullscreen mode

Do not expose raw JSON as the primary support interface.

Raw payloads are evidence for developers, not explanations for agents.

Add a provider refresh action

Support agents need a controlled way to retrieve the latest provider state.

OxaPay's Payment Information endpoint can retrieve a payment using its track_id.

export async function refreshOxaPayPayment({
  merchantApiKey,
  trackId,
}) {
  const response = await fetch(
    `https://api.oxapay.com/v1/payment/${encodeURIComponent(trackId)}`,
    {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        merchant_api_key: merchantApiKey,
      },
    },
  );

  const body = await response.json();

  if (!response.ok) {
    throw new Error(
      body?.error?.message ??
        `Payment lookup failed with status ${response.status}`,
    );
  }

  return body.data;
}
Enter fullscreen mode Exit fullscreen mode

The result should be stored as a provider snapshot before updating the current local view.

await db.providerSnapshot.create({
  data: {
    merchantId,
    paymentSessionId,
    provider: "oxapay",
    providerTrackId: trackId,
    source: "support_agent_refresh",
    rawResponse: providerPayment,
    fetchedAt: new Date(),
  },
});
Enter fullscreen mode Exit fullscreen mode

Do not delete previous snapshots.

They help explain what the system knew at each point in time.

Use Payment History for recovery

A customer ticket should not be the first signal that a payment was missed.

Run scheduled synchronization against OxaPay Payment History.

The job should:

  • retrieve recent provider payment records
  • upsert them by track_id
  • compare them with local payment sessions
  • identify missing webhook events
  • identify provider/local status mismatches
  • open or update support cases

Use overlapping query windows and idempotent upserts.

For example:

Run every 10 minutes
Query at least the previous 30 minutes
Upsert by provider + track_id
Compare provider and local state
Create only one open case per mismatch
Enter fullscreen mode Exit fullscreen mode

The wider window protects against delayed records and temporary job failures.

Make support actions safe

A support action should be:

  • authorized
  • explicit
  • auditable
  • idempotent where possible
  • restricted by payment state
  • linked to a case
const ACTION_PERMISSIONS = {
  support_agent: [
    "refresh_payment",
    "add_note",
    "assign_case",
    "send_customer_message",
    "escalate_case",
  ],
  operations_manager: [
    "refresh_payment",
    "add_note",
    "assign_case",
    "send_customer_message",
    "escalate_case",
    "retry_fulfillment",
    "resolve_case",
  ],
  developer_admin: [
    "refresh_payment",
    "inspect_webhook",
    "retry_event_processing",
    "view_raw_payload",
  ],
  finance_admin: [
    "view_financial_evidence",
    "export_case",
    "approve_finance_resolution",
  ],
};

function assertActionAllowed(role, actionType) {
  const actions = ACTION_PERMISSIONS[role] ?? [];

  if (!actions.includes(actionType)) {
    throw new Error(`Role ${role} cannot perform ${actionType}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Do not let an ordinary support agent:

  • mark an unverified payment as paid
  • change provider evidence
  • initiate a payout
  • issue a refund
  • manually accept an underpayment
  • edit the audit history
  • view unmasked API credentials

A payment tool is not safer because it has fewer buttons.

It is safer because every button has a defined policy.

Retry fulfillment idempotently

A common support action is retrying fulfillment after confirmed payment.

The action must not deliver twice.

export async function retryFulfillment({
  actor,
  supportCase,
  payment,
  order,
}) {
  assertActionAllowed(actor.role, "retry_fulfillment");

  if (payment.internalStatus !== "paid_confirmed") {
    throw new Error(
      "Fulfillment cannot be retried without confirmed payment",
    );
  }

  const idempotencyKey = [
    "support-retry",
    supportCase.id,
    order.id,
    order.fulfillmentVersion,
  ].join(":");

  return db.supportAction.upsert({
    where: { idempotencyKey },
    create: {
      caseId: supportCase.id,
      actorId: actor.id,
      actorRole: actor.role,
      actionType: "retry_fulfillment",
      idempotencyKey,
      status: "queued",
      requestData: {
        orderId: order.id,
      },
    },
    update: {},
  });
}
Enter fullscreen mode Exit fullscreen mode

The action record should exist before the job is published.

This creates an audit trail even if the queue or downstream fulfillment provider fails.

Give agents customer-safe responses

Support agents should not explain blockchain confirmations, webhook delivery, and internal state machines from scratch.

Provide templates based on case type.

Payment detected but not final

We can see payment activity for your invoice, but the payment has not reached its final paid status yet. Your order will update automatically after the payment is confirmed.
Enter fullscreen mode Exit fullscreen mode

Paid but not fulfilled

Your payment has been confirmed. The delivery step did not complete automatically, so our team is reviewing the order now. You do not need to send another payment.
Enter fullscreen mode Exit fullscreen mode

Underpaid payment

The amount received for this invoice is lower than the requested amount. Our team is reviewing the payment according to the merchant's underpayment policy and will provide the next step.
Enter fullscreen mode Exit fullscreen mode

Expired invoice with payment claim

This invoice expired before the payment process completed. We are checking the transaction details before asking you to create a new invoice.
Enter fullscreen mode Exit fullscreen mode

Payment not found

We could not safely match the available information to a payment yet. Please send the transaction hash, currency, network, amount, and approximate payment time.
Enter fullscreen mode Exit fullscreen mode

Templates should never claim that:

  • a payment is confirmed when it is not
  • a refund is guaranteed
  • access will be restored before a decision
  • funds are lost
  • the customer definitely used the wrong network

The response must reflect the evidence available to the agent.

Add a customer-facing status page

Some tickets can be prevented entirely.

Give customers a limited payment status page:

/orders/ORD-1842/payment-status
Enter fullscreen mode Exit fullscreen mode

It can show:

  • invoice created
  • waiting for payment
  • payment activity detected
  • payment confirmed
  • order being processed
  • order delivered
  • invoice expired
  • support review required

Do not expose:

  • raw webhook payloads
  • API responses
  • internal notes
  • developer errors
  • other customer records
  • merchant credentials
  • security diagnostics

A safe status page can reduce repeated tickets such as:

Has my payment arrived yet?

It also creates a consistent source of truth between the customer and the support team.

Integrate with existing help desks

Do not try to replace Zendesk, Intercom, Freshdesk, Help Scout, or the merchant's existing ticketing system in the first version.

Add crypto payment intelligence to the tools agents already use.

A practical flow looks like this:

Customer opens support ticket
          |
          v
Agent enters order ID
          |
          v
Payment Support Desk finds the payment
          |
          v
Case type and evidence are returned
          |
          v
Private note is added to the ticket
          |
          v
Agent sends an approved response
Enter fullscreen mode Exit fullscreen mode

The integration can add:

  • a link to the payment case
  • current payment status
  • order and fulfillment status
  • issue classification
  • recommended action
  • customer response template
  • escalation owner

This keeps the payment logic in your product while the conversation remains in the merchant's existing support system.

The MVP to build

The first version should solve a small number of cases extremely well.

Build:

  • search by order ID and track_id
  • payment and order summary
  • combined evidence timeline
  • Payment Information refresh
  • scheduled Payment History recovery
  • five core case classifications
  • support notes
  • role-based actions
  • customer-safe response templates
  • case assignment and escalation
  • daily unresolved-case report

Start with these cases:

paid_not_fulfilled
payment_detected_not_final
underpaid_payment
expired_with_payment_claim
provider_paid_local_pending
Enter fullscreen mode Exit fullscreen mode

Do not start with:

  • refunds
  • payouts
  • revenue splits
  • AI-generated financial decisions
  • complex multi-agent routing
  • a complete replacement for existing ticketing platforms

Those features can be added after the core workflow proves its value.

Metrics that prove value

The product should measure operational improvement.

Useful metrics include:

  • average payment-ticket handling time
  • percentage of cases resolved without developer escalation
  • paid-but-not-fulfilled cases
  • provider/local state mismatches
  • webhook processing failures
  • unresolved cases by age
  • repeated issue categories
  • time from paid to fulfillment
  • support tickets per 100 payments
  • cases prevented by the customer status page

These metrics help the merchant see why the support layer matters.

They also show which part of the payment workflow needs improvement.

For example, a high number of paid_not_fulfilled cases is not mainly a support problem.

It may indicate a failing fulfillment adapter.

A high number of expired-payment claims may indicate unclear checkout instructions or an invoice lifetime that does not match customer behavior.

The Support Desk should reveal systemic problems, not only close individual tickets.

How this becomes a developer product

The strongest offer is not:

I built a dashboard for OxaPay payments.

A better offer is:

I give your support team one place to investigate crypto payment issues, understand what happened, take safe actions, and respond to customers without asking a developer to inspect every case.

The same core product can be adapted for:

  • digital product delivery
  • SaaS plan activation
  • hosting provisioning
  • license issuance
  • paid communities
  • course access
  • international service invoices

Each vertical has different fulfillment logic, but the support questions remain similar.

That makes the Support Desk reusable without making it generic.

Final takeaway

Crypto payment support is not solved by showing agents a transaction table.

Agents need context.

They need to see how the payment, order, webhook, fulfillment process, and customer claim relate to each other.

A useful Crypto Payment Support Desk provides:

  • searchable payment evidence
  • an understandable event timeline
  • automatic case classification
  • clear escalation ownership
  • restricted and auditable actions
  • customer-safe responses
  • provider refresh and recovery paths

OxaPay provides the payment primitives: invoices, track_id values, signed webhooks, Payment Information, Payment History, payment statuses, and static addresses.

The developer turns those primitives into a support workflow.

The goal is not to make support agents understand payment infrastructure.

The goal is to give them enough reliable evidence to answer one question:

What happened to this customer's payment, and what is the safest next action?

What payment case would you automate first: paid but not fulfilled, underpaid invoices, expired payment claims, or missing webhooks?

References

Top comments (0)