juspay/hyperswitch · error · Error

Expected no deny action for non-blocklisted card

Error message

Expected no deny action for non-blocklisted card

What it means

Thrown by paymentsEligibilityCheck when POST /payments/{paymentId}/eligibility returns 200 but the response contains sdk_next_action.next_action.deny while the fixture Response does not expect one. The command treats presence of a deny action for a supposedly non-blocklisted card as a test failure (blocklist leaking onto clean cards).

Source

Thrown at cypress-tests/cypress/support/commands.js:7829

            .to.have.property("payment_id")
            .to.equal(paymentId);

          // Check for expected response structure based on test case
          if (resData.body.sdk_next_action?.next_action?.deny) {
            expect(response.body).to.have.property("sdk_next_action");
            expect(response.body.sdk_next_action).to.have.property(
              "next_action"
            );
            expect(response.body.sdk_next_action.next_action).to.have.property(
              "deny"
            );
            expect(response.body.sdk_next_action.next_action.deny)
              .to.have.property("message")
              .to.equal(resData.body.sdk_next_action.next_action.deny.message);
          } else {
            // For non-blocklisted cards, we expect no deny action
            if (response.body.sdk_next_action?.next_action?.deny) {
              throw new Error(
                "Expected no deny action for non-blocklisted card"
              );
            }
          }
        } else {
          throw new Error(
            `Eligibility check failed with status: ${response.status} and message: ${response.body?.error?.message}`
          );
        }
      });
    });
  }
);

// DDC Race Condition Test Commands
Cypress.Commands.add(
  "ddcServerSideRaceConditionTest",
  (confirmData, globalState) => {

View on GitHub (pinned to 9b8b89dc37)

Solutions

  1. Clean up blocklist rules in an after() hook (cy.blocklistDeleteRule) for every spec that creates them
  2. Use distinct card BINs for deny and allow eligibility tests to avoid rule overlap
  3. If the connector legitimately denies, add sdk_next_action.next_action.deny.message to the fixture Response
  4. Verify via GET /blocklist that no unexpected rule covers the card being tested

Example fix

// before
after(() => {
  // no cleanup → stale card_bin rule denies later specs
});
// after
after(() => {
  cy.blocklistDeleteRule('card_bin', blocklistedBin, globalState);
});
Defensive patterns

Strategy: validation

Validate before calling

// precondition: no lingering rules should cover the card under test
const cardBin = body.payment_method_data.card_number.slice(0, 6);
cy.request({
  method: 'GET',
  url: `${globalState.get('baseUrl')}/blocklist?data_kind=card_bin`,
  headers: { 'api-key': globalState.get('apiKey') },
  failOnStatusCode: false,
}).then((res) => {
  const covered = res.status === 200 && JSON.stringify(res.body).includes(cardBin);
  expect(covered, 'no stale blocklist rule covers this card').to.be.false;
});

Type guard

const hasDenyAction = (b) =>
  Boolean(b?.sdk_next_action?.next_action?.deny);

Prevention

When it happens

Trigger: The card used in the eligibility check matches a blocklist rule left over from an earlier test/spec (overlapping BIN); server-side false positive denying a clean card; fixture Response block missing the deny structure while the connector legitimately returns one.

Common situations: Shared test card BINs across specs with rules created in one spec still active during another; parallelized specs racing blocklist create/delete; expected-deny fixtures written without the sdk_next_action.next_action.deny node.

Related errors


AI-assisted analysis of juspay/hyperswitch@9b8b89dc37 (2026-08-16). Data as JSON: /api/errors/c8f9aed7560a29ca. Report an issue: GitHub.