{"record":{"id":"7b3cef24199a24a6","repo":"GoogleCloudPlatform/microservices-demo","slug":"expiredcreditcard","errorCode":null,"errorMessage":"ExpiredCreditCard","messagePattern":"ExpiredCreditCard","errorType":"validation","errorClass":"ExpiredCreditCard","httpStatus":null,"severity":"error","filePath":"src/paymentservice/charge.js","lineNumber":80,"sourceCode":"  const { amount, credit_card: creditCard } = request;\n  const cardNumber = creditCard.credit_card_number;\n  const cardInfo = cardValidator(cardNumber);\n  const {\n    card_type: cardType,\n    valid\n  } = cardInfo.getCardDetails();\n\n  if (!valid) { throw new InvalidCreditCard(); }\n\n  // Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will\n  // throw UnacceptedCreditCard error.\n  if (!(cardType === 'visa' || cardType === 'mastercard')) { throw new UnacceptedCreditCard(cardType); }\n\n  // Also validate expiration is > today.\n  const currentMonth = new Date().getMonth() + 1;\n  const currentYear = new Date().getFullYear();\n  const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard;\n  if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); }\n\n  logger.info(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \\\n    Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`);\n\n  return { transaction_id: uuidv4() };\n};\n","sourceCodeStart":62,"sourceCodeEnd":87,"githubUrl":"https://github.com/GoogleCloudPlatform/microservices-demo/blob/72ba613a05f7fcee51cf1d0badff401b6ae7074d/src/paymentservice/charge.js#L62-L87","documentation":"ExpiredCreditCard is thrown by charge() in src/paymentservice/charge.js when the card's expiration (credit_card_expiration_month/year) is not after the current month, computed as currentYear*12+currentMonth > year*12+month. The card may be valid and accepted, but it has lapsed. It is a CreditCardError with code 400 (Invalid Argument) and includes the masked card number and expiry date in the message.","triggerScenarios":"Calling Charge with credit_card.credit_card_expiration_year/month set to a past date, e.g. {year: 2020, month: 1}; a year-2038-style clock issue on the host making currentYear wrong; off-by-one where the card expires in the current month (current month counts as expired).","commonSituations":"Hardcoded test payloads with stale expiry dates; test suites failing after time passes (fixtures written years ago); expired sandbox credentials in demo data; server clock skew.","solutions":["Update the request payload to an expiration date in the future, e.g. {credit_card_expiration_month: 12, credit_card_expiration_year: 2030}","Check the machine's system clock (date) — a wrong currentYear makes every card appear expired","Regenerate stale fixtures/secrets that contain hardcoded expiry dates","If a card expiring in the current month should be chargeable, adjust the comparison in charge.js line 80"],"exampleFix":"// before\ncredit_card: { credit_card_number: '4012-8888-8888-1881', credit_card_expiration_month: 1, credit_card_expiration_year: 2020, ... }\n// after\ncredit_card: { credit_card_number: '4012-8888-8888-1881', credit_card_expiration_month: 12, credit_card_expiration_year: 2030, ... }","handlingStrategy":"validation","validationCode":"function isCardNotExpired({ credit_card_expiration_month: m, credit_card_expiration_year: y }) {\n  if (!Number.isInteger(m) || !Number.isInteger(y) || m < 1 || m > 12) return false;\n  const now = new Date();\n  return (now.getFullYear() * 12 + (now.getMonth() + 1)) <= (y * 12 + m);\n}\nif (!isCardNotExpired(creditCard)) {\n  return res.status(400).json({ error: 'Card has expired; please use another card.' });\n}","typeGuard":"function isFutureExpiry(month, year) {\n  return Number.isInteger(month) && Number.isInteger(year) &&\n    (new Date().getFullYear() * 12 + new Date().getMonth() + 1) <= (year * 12 + month);\n}","tryCatchPattern":"try {\n  const result = await paymentClient.Charge(chargeRequest);\n} catch (err) {\n  if (err.message === 'ExpiredCreditCard' || /expired on/i.test(err.message)) {\n    return res.status(400).json({ error: err.message, hint: 'Please update the expiration date or use a different card.' });\n  }\n  throw err;\n}","preventionTips":["Check expiry client-side at checkout and prompt for card update","Avoid hardcoded expiry dates in test data — generate dates several years in the future","Verify server clock (NTP) so currentYear is correct","Regenerate fixtures periodically; treat expiry fields as time-sensitive data"],"tags":["grpc","payment","expiry","validation"],"backgroundTag":"card-expired","analyzedSha":"72ba613a05f7fcee51cf1d0badff401b6ae7074d","analyzedAt":"2026-09-02T01:15:09.673Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}