View as Markdown

llms.txt

This guide isn't available right now

This instance couldn't load its guide catalog. The guide returns as soon as the catalog is readable again.

Back to the guides

No such guide

This instance publishes no guide under that address. It may have been renamed, or it may belong to a feature this installation hasn't enabled.

Back to the guides

That guide is part of the product documentation

This guide is written for someone operating WinkPG through its screens rather than integrating against it, so it lives in the application's own help section instead of here. Sign in to WinkPG and open Help to read it.

Back to the guides

Guides Integration

Sandbox test cards

One scenario per sandbox classification test card, with the request to send, the classification to expect on the response, and the behavior each card lets you observe.

The sandbox publishes eight test cards that classify as something specific: personal debit on two rails, prepaid, commercial on two rails, government purchase, healthcare, and EBT. This guide walks one scenario per card. Each scenario gives you the request to send, the classification to expect on the response, and at least one downstream behavior the card lets you observe and verify.

The card numbers, expiries, and verification values here are the ones the sandbox testing guide's test-card table publishes. They're network test numbers: issued to nobody, backed by no funds, and never sent to a card network.

How the sandbox classifies a card

WinkPG classifies every card from its BIN, the leading digits of the number, by looking it up in the platform's BIN database. The sandbox test cards sit in front of that database as an exact-match overlay: when the full number you send is one of the eight, the lookup answers with the classification the card declares instead of reading the database.

Three consequences follow, and each scenario below depends on at least one of them:

  • The match is on the whole number, never on a prefix. A card that shares a test card's leading digits resolves from the BIN database like any other card. Only the exact number classifies as shown.
  • It classifies the same way everywhere. The overlay applies in every environment and for every merchant, so a merchant in Processor Test mode sees the same classification for the same number that a Loopback merchant sees, and so does a real processor test host for the numbers taken from a processor's certification set.
  • The card decides its classification, not the outcome. The sandbox still picks the result from the transaction amount, the billing ZIP, the CVV, and any control field you send, exactly as the sandbox testing guide describes. A test card that classifies as debit is approved or declined by the amount, the same as any other card.

The one exception is the EBT card, which takes no amount trigger. Its scenario says so.

What every scenario sends and reads

Every scenario is a POST to /api/transactions on a sandbox merchant whose Processor Mode is Loopback. The requests differ only in the card, the amount, and the fields the scenario is exercising. Substitute YOUR_API_KEY with a sandbox key.

Send the number as digits only, with the expiry the card declares, and the card's CVV in cvv as a number. Every card-entry surface on WinkPG collects the CVV, the CVV-keyed verification triggers in the sandbox testing guide read it, and a request without it exercises less than a real one: send it on every scenario, as the examples do.

Three places on the response carry the classification, and the scenarios refer to them by name:

  • cardData.binData is the classification the platform stored for the card: brand, type (Credit or Debit), category, issuedEntity (Personal or Commercial), fundingSource (Credit, Debit, or Prepaid), and flags (commercial, government, healthcare, prepaid, regulated). The issuer.organization names the sandbox issuer rather than a bank, which is how you can tell a sandbox classification from a BIN database match on a stored transaction.
  • responseData is what the sandbox processor answered: cardIndicator echoes the brand, detailedProductId echoes the card type, routingIndicator says which rail the sale was processed on, and processorResponseDetail.isCommercialCard is true for a commercial or government card.
  • loopbackSimulation is the trace of what the sandbox saw. A classification card adds an entry with a family of TestCard and a catalogId naming the card, such as visa-debit. The entry never carries the number.

The response is a full transaction, so the examples below show only the fields the scenario is about.

Visa debit

Number 4002960001111116, expiry 12/2030, CVV 123. Classifies as personal debit on the Visa rail.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-debit-0001",
    "cardData": {
      "cardNumber": "4002960001111116",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123,
      "isDebitRouting": true
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'

Expect on the response:

{
  "cardData": {
    "binData": {
      "brand": "Visa",
      "type": "Debit",
      "category": "classic",
      "issuedEntity": "Personal",
      "fundingSource": "Debit",
      "flags": { "commercial": false, "government": false, "healthcare": false, "prepaid": false, "regulated": false }
    }
  },
  "responseData": {
    "resultCode": "Ok",
    "cardIndicator": "Visa",
    "detailedProductId": "Debit",
    "routingIndicator": "ProcessedAsDebit",
    "processorResponseDetail": { "isCommercialCard": false }
  },
  "loopbackSimulation": {
    "entries": [
      { "family": "TestCard", "catalogId": "visa-debit", "matchedOn": "Published test card", "isDefault": false }
    ]
  }
}

What the card lets you observe:

  • Debit routing on the response. With isDebitRouting set to true on the card, as a PIN debit request sends it, routingIndicator reads ProcessedAsDebit. Send the same request without it and a keyed card reads ProcessedAsCredit. Nothing in the classification sets the flag for you: a debit card keyed without PIN data is a signature debit sale and reads as credit-routed, which is what a real processor reports. The loopback.routing control field outranks both.
  • Card acceptance policy. On a merchant whose card acceptance policy has Accept debit cards turned off, the request is refused with HTTP 400 and the code Transactions:CardNotAcceptedByMerchantPolicy; the reason datum reads FundingSource, and no transaction is created. Turn the setting back on and the same request is approved.
  • Surcharge. On a merchant that's actively surcharging, surchargeResult.surcharged is false with ineligibilityReason of DebitCard, and invoiceData.amounts.surcharge stays empty: debit cards are never surcharged. A credit card on the same merchant is surcharged.
  • Convenience fee. A configured convenience fee is assessed on this card exactly as it's assessed on a credit card. The merchant's debit exemption toggle is stored, but no runtime path applies it today, so don't build a test that expects the fee to be withheld from a debit card.

Mastercard debit

Number 5555531000000010, expiry 12/2030, CVV 123. Classifies as personal debit on the Mastercard rail.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-mastercard-debit-0001",
    "cardData": {
      "cardNumber": "5555531000000010",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'

Expect cardData.binData with brand of Mastercard, type of Debit, category of standard, issuedEntity of Personal, fundingSource of Debit, and every flag false. On responseData, cardIndicator reads Mastercard, detailedProductId reads Debit, and routingIndicator reads ProcessedAsCredit, because this request was keyed without isDebitRouting. The trace entry's catalogId is mastercard-debit.

What the card lets you observe is the same set as the Visa debit card, on the other rail: the Accept debit cards refusal, the DebitCard surcharge ineligibility, the convenience fee assessed as configured, and ProcessedAsDebit once you add isDebitRouting. Send this card as well as the Visa one, because several of the sandbox's amount triggers are brand-scoped and a client that has only ever sent one debit brand hasn't exercised its own brand handling.

Mastercard prepaid

Number 5312411232145699, expiry 12/2030, CVV 123. Classifies as a prepaid card on the debit rail.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-mastercard-prepaid-0001",
    "cardData": {
      "cardNumber": "5312411232145699",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 2.78, "total": 2.78 }
    },
    "customFields": [
      { "name": "loopback.prepaidIndicator", "value": "P" }
    ]
  }'

Expect cardData.binData with brand of Mastercard, type of Debit, category of prepaid, fundingSource of Prepaid, and flags.prepaid of true. Prepaid isn't a third rail: the card type stays Debit, and the prepaid flag is what moves the funding source. The trace entry's catalogId is mastercard-prepaid.

What the card lets you observe:

  • Partial approval. The request above pairs the card with the sandbox's partial-approval amount table. The loopback.prepaidIndicator control field set to P (or loopback.partialAuthIndicator set to true) selects that table, and 2.78 is one of its amounts: the response comes back with resultCode of Partial, responseData.amounts.approved of 2.57, and responseData.amounts.balanceDue for the rest. The card doesn't select the table; the control field does. It's listed here because a partial approval is the outcome an integration has to handle on a prepaid card, and this is the card whose classification says why.
  • Card acceptance policy. Prepaid sits on top of the debit rail, so the policy judges this card by Accept prepaid cards and by Accept debit cards, and refuses it when either is off. The refusal is the same HTTP 400 with Transactions:CardNotAcceptedByMerchantPolicy and a reason of FundingSource.
  • Surcharge. On an actively surcharging merchant, surchargeResult.ineligibilityReason reads PrepaidCard. Prepaid cards are never surcharged.
  • Convenience fee. Assessed as configured. The merchant's prepaid exemption toggle is stored and not applied at runtime, the same as the debit one.

Visa commercial

Number 4005562231212123, expiry 12/2030, CVV 123. Classifies as a commercial purchasing card.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-commercial-0001",
    "cardData": {
      "cardNumber": "4005562231212123",
      "nameOnCard": "Acme Purchasing",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "tax": 0.80, "total": 10.80 }
    },
    "level2Data": {
      "poNumber": "PO-10042"
    }
  }'

Expect cardData.binData with brand of Visa, type of Credit, category of purchasing, issuedEntity of Commercial, fundingSource of Credit, and flags.commercial of true. On responseData, processorResponseDetail.isCommercialCard is true. The trace entry's catalogId is visa-commercial.

What the card lets you observe:

  • The commercial-card indicator. isCommercialCard on the processor response detail is derived from the stored BIN data, so it reads true for this card and false for every personal card above. Branch on it the way you would on a live processor's response.
  • Enhanced data enforcement. When enhanced-data qualification is enabled for the deployment and the merchant's Enhanced Data Enforcement is Warn or Strict, the platform scores the Level 2 and Level 3 data on every commercial-card sale before it's created. The request above passes: it carries a purchase order number and a tax amount inside the accepted share of the total. Remove level2Data and set tax to 0.00, and the assessment reports two findings, MissingCustomerCode and ZeroTaxWithoutExemptFlag. Under Warn the sale is still created and the response carries the findings on enhancedDataQualification with isPreview of true. Under Strict the sale is refused with HTTP 400 and a validation error for each finding carrying the code Transactions:EnhancedDataRequirementsNotMet. A personal card is never assessed, whatever the setting.
  • Card acceptance policy. On a merchant with Accept commercial cards turned off, the request is refused with Transactions:CardNotAcceptedByMerchantPolicy and a reason of IssuedEntity.

American Express commercial

Number 378730000000006, expiry 12/2030, CVV 1234. Classifies as a commercial corporate card on the American Express rail. The verification value is four digits, as it's on every American Express card. The API accepts a three- or four-digit value on any brand, but the hosted and in-app card-entry surfaces size the CVV field by brand, so this is the card that shows whether your own entry surface does too.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-amex-commercial-0001",
    "cardData": {
      "cardNumber": "378730000000006",
      "nameOnCard": "Acme Purchasing",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 1234
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'

Expect cardData.binData with brand of Amex, type of Credit, category of corporate, issuedEntity of Commercial, flags.commercial of true, and flags.regulated of true, which is how the source certification data classifies this number. On responseData, cardIndicator reads Amex and processorResponseDetail.isCommercialCard is true. The trace entry's catalogId is amex-commercial.

What the card lets you observe:

  • Enhanced data on a brand with no published bounds. The request above sends no purchase order and no tax, so under Warn or Strict the assessment reports the same two findings the Visa commercial card reports. The difference is what Strict does with them: nothing. Refusal is narrowed to Visa and Mastercard, the two brands whose data-rate programs publish bounds to score against, so an American Express sale is assessed and reported, never refused. If your integration reads the findings to decide whether to prompt for more data, this card is the one that proves the read doesn't depend on a refusal.
  • Card acceptance policy. Refused under Accept commercial cards off, with a reason of IssuedEntity, the same as the Visa commercial card.
  • The four-digit CVV. The sandbox's CVV-keyed verification triggers apply to this card with four-digit values. A client that formats or validates the CVV as exactly three digits can't send this card's value at all, which is the defect the card exists to surface.

Visa government purchase (GSA)

Number 4486000000000005, expiry 12/2030, CVV 123. Classifies as a government purchase card, which is also a commercial card.

No processor publishes a government test card, so this number is synthesized in the government purchase-card range. It resolves only through the sandbox classification and never from the BIN database, which makes it the one card in this guide that a merchant in Processor Test mode can't use: a real processor test host doesn't know it.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-gsa-0001",
    "cardData": {
      "cardNumber": "4486000000000005",
      "nameOnCard": "Agency Cardholder",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "isTaxExempt": true,
      "amounts": { "base": 10.00, "total": 10.00 }
    },
    "level2Data": {
      "poNumber": "PO-10043"
    }
  }'

Expect cardData.binData with brand of Visa, type of Credit, category of purchasing government, issuedEntity of Commercial, flags.commercial of true, and flags.government of true. On responseData, processorResponseDetail.isCommercialCard is true. The trace entry's catalogId is visa-gsa.

What the card lets you observe:

  • Where the government flag is visible. On the API it's cardData.binData.flags.government. In the back office, the transaction detail shows the card as issued to a commercial entity and doesn't draw a separate government chip, so the flag is read from the API response or from the platform administrator's BIN lookup page, where it shows as GSA. Don't look for it on the transaction detail.
  • Enhanced data expectations. Government purchase cards are commercial cards, so the enhanced-data assessment engages under Warn and Strict exactly as it does for the Visa commercial card. The request above passes because it sends a purchase order and declares the sale tax exempt; a government purchase is tax exempt, and isTaxExempt is what keeps a zero tax amount from being reported as a finding. Drop it to see ZeroTaxWithoutExemptFlag.
  • Card acceptance policy. The policy judges this card by Accept GSA cards alone. Turning Accept commercial cards off doesn't refuse it, even though it's commercial; turning Accept GSA cards off does, with a reason of IssuedEntity.

Visa healthcare (FSA)

Number 4373191234567806, expiry 12/2030, CVV 123. Classifies as a healthcare card: prepaid debit with the healthcare flag.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-fsa-0001",
    "cardData": {
      "cardNumber": "4373191234567806",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 45.00, "total": 45.00 }
    },
    "fsa": {
      "qhpAmount": 45.00,
      "rxAmount": 30.00
    }
  }'

Expect cardData.binData with brand of Visa, type of Debit, category of prepaid healthcare, fundingSource of Prepaid, flags.healthcare of true, and flags.prepaid of true. The trace entry's catalogId is visa-fsa.

What the card lets you observe:

  • The healthcare flag. It's on cardData.binData.flags.healthcare, and the transaction detail in the back office draws it as an active Healthcare chip on the payment method panel. This is the flag healthcare-eligibility logic keys on, where a merchant has any.
  • What the platform does with a healthcare card today. It accepts the fsa amounts (qualified health plan, prescription, vision, dental, clinical, copay, and transit) on the request, stores them on the transaction, and passes them to the processor as healthcare amounts. It doesn't validate the purchase against an inventory information approval system, doesn't check whether the merchant is registered for one, and doesn't refuse a non-healthcare purchase on a healthcare card. Send fsa amounts to exercise the fields; don't expect the platform to decide eligibility, because it doesn't.
  • Prepaid behavior. Because the funding source is prepaid, this card is refused under Accept prepaid cards off (and under Accept debit cards off), and an actively surcharging merchant records PrepaidCard as the surcharge ineligibility reason.

EBT

Number 5076800001111112, expiry 12/2030, CVV 123. Classifies the way a real EBT card does: brand EBT on the debit rail. EBT isn't a card network, and the sandbox treats the card differently from every other one in this guide in two ways: the tender is never inferred from the card, and the amount triggers don't apply.

curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-ebt-0001",
    "cardData": {
      "cardNumber": "5076800001111112",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123,
      "tenderType": "EbtSnap",
      "entryMode": "UnencryptedCardReaderSwipe",
      "pin": "ENCRYPTED_PIN_BLOCK",
      "keySerialNumber": "KSN_FROM_YOUR_PIN_PAD"
    },
    "invoiceData": {
      "amounts": { "base": 12.50, "total": 12.50 }
    },
    "customFields": [
      { "name": "loopback.availableBalance", "value": "87.50" }
    ]
  }'

Expect cardData.binData with brand of EBT, type of Debit, category of standard, and every flag false. On responseData, cardIndicator reads EBT, detailedProductId reads Debit, and amounts.availableBalance reads 87.50 from the control field. The trace entry's catalogId is ebt.

What the card lets you observe:

  • The tender has to be sent. Send tenderType of EbtSnap, EbtCash, or Ewic. An EBT tender requires PIN data and a card-present entry mode: a request without pin is refused with Transactions:TenderRequiresPin, and one keyed with entryMode of Manual is refused with Transactions:TenderRequiresCardPresent. The sandbox doesn't read the PIN block or the key serial number, so the placeholders above are enough there; on a real processor they come from a PIN pad.
  • The merchant has to have the tender enabled. The merchant's Card tenders settings (EBT SNAP, EBT Cash, eWIC) and a processor profile that lists the tender are what admit the sale to the rail. Send the request to a merchant without them and the routing decision on the request log shows the profile eliminated for PaymentTypeNotSupported; read that decision rather than the result code, because whether the gateway then refuses or falls back is a routing decision, not a card one.
  • What happens without the tender. Send the same number with no tenderType and the sale processes as an ordinary debit card: no PIN is required, the amount triggers apply again, and the classification still reads brand EBT. That's the shape of the mistake this card exists to catch, a client that expects the card to pick the rail.
  • No amount trigger. The sandbox approves an EBT sale at any amount unless a control field says otherwise, so an amount that declines a Visa sale approves here. Force an outcome with loopback.resultCode.
  • Balances and the eWIC discount. The response amounts for benefit cards come from control fields: loopback.beginningBalance and loopback.availableBalance set the balances, and loopback.ewicDiscount sets the eWIC discount on an Ewic sale. Send them to see the amounts your integration displays after a benefit purchase.

Reading the result

Two surfaces show you what the sandbox saw, and they agree with each other by construction because both read the same stored classification.

The sandbox simulation panel. Open the transaction in the back office and find the Sandbox Simulation panel beside the routing decision. It lists every trigger the request fired, and a classification card adds a Test card row naming the card by its catalog id, with the card's own purpose beside it. The row carries the id and never the number. This is the same information as the loopbackSimulation field on the response, drawn for a person.

The BIN lookup page. A platform administrator can open BIN Lookup under Developer in the back office, paste the full number, and read the same classification the transaction stored: brand, card type, card category, the sandbox issuer, and the Commercial, Regulated, Healthcare, and GSA flags. Paste the whole number: a prefix resolves from the BIN database, and only the full number resolves through the sandbox overlay.

If the two disagree with the response you got, the request didn't carry the number you think it did. Check for a space or a separator in cardNumber; the overlay matches digits only.

See also

Reconnecting to the server

Could not reconnect

This session has ended

Attempt 1

Your work on this page is still here. Retrying keeps it; reloading starts the page again.

The server no longer holds this page's state, so it has to be loaded again.