Handle a CVV mismatch
Drive a CVV match, a no-match, and an issuer that can't check, so your integration handles the security code the payer typed rather than assuming it was verified.
Signed in, you can run this blueprint against your own sandbox merchant one call at a time, with the values from each call threaded into the next. Sign in to run it.
4 steps, 3 API callsPaymentsTransactions
Samples use {{API_KEY}} for your API key and {{BASE_URL}} for this instance's API address. Anything else in double braces is a value an earlier step gave you.
Drive each response in the sandbox
1. Send a sale whose security code matches
API call
POST /api/transactions
Sending the CVV 111 makes the sandbox answer with the cardholder-verification response code "M" and report it as "CVV match" in the response. The baseline again. Everything below is only meaningful next to this one.
Values this step gives you
curl -X POST "{{BASE_URL}}/api/transactions" \
-H "api-key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Sale",
"cardData": {
"cardNumber": "4111111111111111",
"nameOnCard": "Jane Doe",
"expirationMonth": 12,
"expirationYear": 2030,
"cvv": 111
},
"invoiceData": {
"amounts": { "base": 10.00, "total": 10.00 }
}
}'using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");
var response = await http.PostAsJsonAsync("/api/transactions", new
{
transactionType = "Sale",
cardData = new
{
cardNumber = "4111111111111111",
nameOnCard = "Jane Doe",
expirationMonth = 12,
expirationYear = 2030,
cvv = 111
},
invoiceData = new
{
amounts = new { @base = 10.00m, total = 10.00m }
}
});
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var validation = result.GetProperty("responseData").GetProperty("cardValidationData");
var cv = validation.GetProperty("cvResponse").GetString();What this step answers with
Abridged to the properties this step depends on. A real response carries more.
{
"id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
"merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
"resultCode": "Ok",
"authorizedAmount": 10.00,
"responseData": {
"resultCode": "Ok",
"resultMessage": "Approved",
"cardValidationData": {
"cvResponse": "M",
"cvResultText": "CVV match"
}
}
}2. Send a sale whose security code fails to match
API call
POST /api/transactions
Sending the CVV 222 makes the sandbox answer with the cardholder-verification response code "N" and report it as "CVV no match" in the response. The transaction approves anyway. This is the case that surprises people. The platform reports a wrong security code rather than refusing it, and an integration that stores the card on this response has stored one the payer may not hold.
Values this step gives you
curl -X POST "{{BASE_URL}}/api/transactions" \
-H "api-key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Sale",
"cardData": {
"cardNumber": "4111111111111111",
"nameOnCard": "Jane Doe",
"expirationMonth": 12,
"expirationYear": 2030,
"cvv": 222
},
"invoiceData": {
"amounts": { "base": 10.00, "total": 10.00 }
}
}'using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");
var response = await http.PostAsJsonAsync("/api/transactions", new
{
transactionType = "Sale",
cardData = new
{
cardNumber = "4111111111111111",
nameOnCard = "Jane Doe",
expirationMonth = 12,
expirationYear = 2030,
cvv = 222
},
invoiceData = new
{
amounts = new { @base = 10.00m, total = 10.00m }
}
});
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var validation = result.GetProperty("responseData").GetProperty("cardValidationData");
var cv = validation.GetProperty("cvResponse").GetString();What this step answers with
Abridged to the properties this step depends on. A real response carries more.
{
"id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
"merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
"resultCode": "Ok",
"authorizedAmount": 10.00,
"responseData": {
"resultCode": "Ok",
"resultMessage": "Approved",
"cardValidationData": {
"cvResponse": "N",
"cvResultText": "CVV no match"
}
}
}3. Send a sale the issuer can't check
API call
POST /api/transactions
Sending the CVV 555 makes the sandbox answer with the cardholder-verification response code "U" and report it as "Issuer unable to process CVV" in the response. The third state. The code went out and no answer came back, which is evidence of nothing either way and shouldn't score as a match.
Values this step gives you
curl -X POST "{{BASE_URL}}/api/transactions" \
-H "api-key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Sale",
"cardData": {
"cardNumber": "4111111111111111",
"nameOnCard": "Jane Doe",
"expirationMonth": 12,
"expirationYear": 2030,
"cvv": 555
},
"invoiceData": {
"amounts": { "base": 10.00, "total": 10.00 }
}
}'using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");
var response = await http.PostAsJsonAsync("/api/transactions", new
{
transactionType = "Sale",
cardData = new
{
cardNumber = "4111111111111111",
nameOnCard = "Jane Doe",
expirationMonth = 12,
expirationYear = 2030,
cvv = 555
},
invoiceData = new
{
amounts = new { @base = 10.00m, total = 10.00m }
}
});
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var validation = result.GetProperty("responseData").GetProperty("cardValidationData");
var cv = validation.GetProperty("cvResponse").GetString();What this step answers with
Abridged to the properties this step depends on. A real response carries more.
{
"id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
"merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
"resultCode": "Ok",
"authorizedAmount": 10.00,
"responseData": {
"resultCode": "Ok",
"resultMessage": "Approved",
"cardValidationData": {
"cvResponse": "U",
"cvResultText": "Issuer unable to process CVV"
}
}
}Read the verification codes
4. Compare the three responses
On your side
The code is on responseData.cardValidationData.cvResponse and the text the simulator reported it with is on responseData.cardValidationData.cvResultText. Every call above sent the sandbox's guaranteed-approval amount, so the result code was the same on all three and only the verification code moved. The sandbox honours 10 CVV triggers in total. The full table is on the testing page rather than repeated here. As with AVS, none of these change the result code, and the merchant's card verification settings decide whether a no-match refuses the payment. The one to get right in your own code is the difference between a code that was checked and wrong and one that was never checked at all. Collapsing them into a single failure branch refuses payers whose issuer never answered.