Skip to content

Add metadata, filtering, and message formatting features#6

Merged
hideokamoto merged 1 commit intomainfrom
claude/rename-library-features-01L8GCPmpRaFDSeXQmzE7VCa
Nov 18, 2025
Merged

Add metadata, filtering, and message formatting features#6
hideokamoto merged 1 commit intomainfrom
claude/rename-library-features-01L8GCPmpRaFDSeXQmzE7VCa

Conversation

@hideokamoto
Copy link
Owner

@hideokamoto hideokamoto commented Nov 18, 2025

  • Added metadata (category, retryable, severity, requiresUserAction) to all 44 decline codes
  • Implemented getDeclineCodeMetadata() function
  • Implemented filtering functions:
    • getDeclineCodesByCategory()
    • getDeclineCodesBySeverity()
    • getRetryableDeclineCodes()
    • getDeclineCodesRequiringUserAction()
  • Implemented formatDeclineMessage() for custom message templates
  • Added comprehensive tests for all new functions
  • Updated README with detailed documentation and usage examples

Summary by CodeRabbit

  • New Features

    • Added formatDeclineMessage() function to format decline messages with locale support and optional variable substitution for dynamic messaging.
  • Documentation

    • Updated API reference with new message formatting capability and usage examples.
    • Added Translation type to public exports.
  • Tests

    • Added comprehensive test coverage for the new formatting functionality across locales.

@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Introduces a new public API function formatDeclineMessage that formats decline code messages with optional variable substitution using {{key}} placeholders. Updates documentation with API reorganization including core functions subsection. Exports new Translation type. Adds comprehensive test coverage.

Changes

Cohort / File(s) Summary
Documentation
README.md
Adds public API documentation for formatDeclineMessage, reorganizes API reference with new "Core Functions" subsection, adds "Message Formatting" section, and exports Translation type.
Implementation
src/index.ts
Adds new public function formatDeclineMessage(declineCode, locale, variables) that retrieves base message via getDeclineMessage, then replaces {{key}} placeholders with provided variables if present.
Tests
tests/index.test.ts
Adds comprehensive test coverage for formatDeclineMessage across multiple locales, with/without variables, and invalid decline codes.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant formatDeclineMessage
    participant getDeclineMessage
    participant Result

    Caller->>formatDeclineMessage: formatDeclineMessage(code, locale, variables)
    formatDeclineMessage->>getDeclineMessage: getDeclineMessage(code, locale)
    getDeclineMessage-->>formatDeclineMessage: base message or undefined
    
    alt message not found
        formatDeclineMessage-->>Result: undefined
    else message found & no variables
        formatDeclineMessage-->>Result: base message
    else message found & variables provided
        formatDeclineMessage->>formatDeclineMessage: replace {{key}} placeholders
        formatDeclineMessage-->>Result: formatted message
    end
    
    Result-->>Caller: final result
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • The placeholder replacement logic is straightforward string manipulation
  • Three files with complementary changes (documentation, implementation, tests)
  • Test coverage is comprehensive and follows standard patterns
  • Key focus areas: verify placeholder regex/replacement logic handles edge cases (missing variables, malformed placeholders)

Poem

🐰 A message formatter hops into view,
With variables dancing in {{brackets}} too!
From many tongues it speaks with grace,
Filling placeholders in their rightful place.
Documentation gleams, tests all pass—
A tidy feature, first-class! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'metadata, filtering, and message formatting features' but the raw_summary shows only message formatting changes (formatDeclineMessage function and related tests); no changes related to metadata fields or filtering functions are evident in the provided code summaries. Update the title to reflect only the actual changes: 'Add formatDeclineMessage function for custom message formatting' or verify that metadata and filtering changes are included in the changeset.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/rename-library-features-01L8GCPmpRaFDSeXQmzE7VCa

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @hideokamoto, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the stripe-decline-codes library by integrating rich metadata into all existing decline codes. This metadata enables developers to implement more sophisticated error handling logic through new filtering functions and provides a flexible message formatting utility for improved user communication. The changes aim to make the library more powerful and adaptable for various application needs.

Highlights

  • Enriched Decline Code Metadata: All 44 Stripe decline codes now include detailed metadata such as 'category', 'retryable' status, 'severity' level, and whether 'requiresUserAction'.
  • Metadata Retrieval Function: A new getDeclineCodeMetadata() function has been introduced to easily retrieve this new metadata for any given decline code.
  • Advanced Filtering Capabilities: New functions like getDeclineCodesByCategory(), getDeclineCodesBySeverity(), getRetryableDeclineCodes(), and getDeclineCodesRequiringUserAction() allow for efficient filtering of decline codes based on their metadata.
  • Custom Message Formatting: The formatDeclineMessage() function provides a flexible way to generate user-facing messages with support for custom variable substitution.
  • Comprehensive Testing: Extensive test cases have been added to ensure the reliability and correctness of all newly implemented features.
  • Updated Documentation: The README.md has been thoroughly updated with detailed explanations and usage examples for all new metadata, filtering, and message formatting functionalities.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant new functionality by adding metadata to decline codes, along with filtering functions and a message formatting utility. The implementation is clean and well-documented in the README. The new types and data are well-structured.

My review focuses on two main points:

  1. A potential vulnerability in the formatDeclineMessage function where using a regular expression with an unescaped key could lead to unexpected behavior. I've suggested a safer alternative.
  2. A gap in test coverage for the new formatDeclineMessage function. The existing test does not actually verify the variable replacement logic, leaving a key part of the new feature untested.

Overall, this is a great addition to the library. Addressing these points will improve the robustness and reliability of the new features.

Comment on lines +255 to +261
it('should replace variables in message template', () => {
const message = formatDeclineMessage('insufficient_funds', 'en', {
merchantName: 'Acme Store',
});
// Base message doesn't have placeholders, so should remain unchanged
expect(message).toBe('Please try again using an alternative payment method.');
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This test case's title is misleading. It claims to test variable replacement, but the message for insufficient_funds has no placeholders. As a result, the test only verifies that the function doesn't alter a string with no placeholders, leaving the actual replacement logic untested. This is a significant gap in test coverage for the new formatDeclineMessage feature.

I recommend two changes:

  1. Rename this test to reflect what it actually does, for example: it('should return the base message if no placeholders are present in the template').
  2. Add a new test case that properly verifies the variable replacement. Since there are no messages with placeholders in the current dataset, you might need to temporarily add one to a decline code for testing purposes, or mock the getDeclineMessage function to return a templated string.

Example of a new, effective test:

it('should correctly replace placeholders in a message template', () => {
  // This test requires a decline code whose message is a template string.
  // For example, if 'generic_decline' had a message "Action required for {{merchantName}}."
  const message = formatDeclineMessage('code_with_template', 'en', {
    merchantName: 'Acme Store',
  });
  expect(message).toBe('Action required for Acme Store.');
});

Comment on lines +243 to +244
const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
formattedMessage = formattedMessage.replace(placeholder, value);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Constructing a regular expression from a variable key can be unsafe if the key contains special regex characters (e.g., ., *, +). This could lead to incorrect replacements or even break the regex. A safer and more modern approach is to use String.prototype.replaceAll(), which handles this correctly without needing a regular expression for simple string substitutions.

Suggested change
const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
formattedMessage = formattedMessage.replace(placeholder, value);
formattedMessage = formattedMessage.replaceAll(`{{${key}}}`, value);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/data/decline-codes.ts (1)

460-465: Consider the semantics of requiresUserAction for merchant_blacklist.

The merchant_blacklist decline has requiresUserAction: false, yet the nextUserAction instructs users to contact their card issuer. This creates a semantic ambiguity: does requiresUserAction mean "the user can resolve this" or "the user should take action"?

For merchant_blacklist, the user cannot directly resolve the block (only the merchant can update their blocklist), so false may be correct. However, consider documenting this semantic distinction to clarify that requiresUserAction: false means "user cannot directly resolve" rather than "no action message exists."

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03924d4 and d42c56f.

📒 Files selected for processing (5)
  • README.md (5 hunks)
  • src/data/decline-codes.ts (43 hunks)
  • src/index.ts (2 hunks)
  • src/types.ts (2 hunks)
  • tests/index.test.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/types.ts (1)
src/index.ts (3)
  • DeclineCodeCategory (255-255)
  • DeclineSeverity (259-259)
  • DeclineCodeMetadata (257-257)
tests/index.test.ts (1)
src/index.ts (6)
  • getDeclineCodeMetadata (134-140)
  • getDeclineCodesByCategory (155-157)
  • getDeclineCodesBySeverity (172-174)
  • getRetryableDeclineCodes (188-190)
  • getDeclineCodesRequiringUserAction (203-205)
  • formatDeclineMessage (225-248)
src/index.ts (1)
src/types.ts (5)
  • DeclineCodeMetadata (34-43)
  • DeclineCodeCategory (17-24)
  • DeclineCode (74-117)
  • DeclineSeverity (29-29)
  • Locale (4-4)
🪛 ast-grep (0.40.0)
src/index.ts

[warning] 242-242: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\{\\{${key}\\}\\}, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (7)
src/types.ts (1)

14-59: LGTM! Well-structured type definitions.

The new metadata types are clearly defined with helpful inline documentation. The extension of DeclineCodeInfo to include metadata integrates smoothly with the existing structure.

src/data/decline-codes.ts (1)

28-786: Comprehensive metadata coverage looks good.

The metadata has been systematically added to all 44 decline codes with thoughtful categorization. The assignments generally align well with the decline code semantics (e.g., authentication errors are retryable, insufficient funds is high severity and not retryable).

README.md (1)

18-302: Excellent documentation updates.

The README has been thoroughly updated with clear examples for all new features. The reorganized API Reference with dedicated sections for metadata, filtering, and formatting functions makes the documentation easy to navigate. The usage examples are practical and demonstrate real-world integration patterns.

tests/index.test.ts (1)

119-267: Comprehensive test coverage for new features.

The test suite thoroughly covers all new metadata and filtering functions with appropriate edge case handling (invalid codes, undefined returns, etc.). Good practice testing both positive and negative cases.

src/index.ts (3)

116-140: LGTM! Clean metadata accessor.

The getDeclineCodeMetadata function is straightforward with proper validation and clear JSDoc documentation.


142-205: Well-implemented filtering functions.

All four filtering functions (getDeclineCodesByCategory, getDeclineCodesBySeverity, getRetryableDeclineCodes, getDeclineCodesRequiringUserAction) follow a consistent pattern with clear documentation and examples.


253-262: LGTM! Type exports properly expanded.

The type exports correctly include all new public types: DeclineCodeCategory, DeclineCodeMetadata, and DeclineSeverity.

Comment on lines +225 to +248
export function formatDeclineMessage(
declineCode: string,
locale: Locale = 'en',
variables?: Record<string, string>,
): string | undefined {
const baseMessage = getDeclineMessage(declineCode, locale);

if (!baseMessage) {
return undefined;
}

if (!variables) {
return baseMessage;
}

// Replace variables in the format {{variableName}}
let formattedMessage = baseMessage;
for (const [key, value] of Object.entries(variables)) {
const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
formattedMessage = formattedMessage.replace(placeholder, value);
}

return formattedMessage;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Address potential ReDoS vulnerability in regex construction.

The function constructs a regular expression from user-supplied variable keys without sanitization (line 243). While the keys come from the variables object keys rather than values, a malicious caller could still provide keys with regex special characters or catastrophic backtracking patterns, leading to Regular Expression Denial of Service (ReDoS).

Recommended fix: Escape the key before constructing the regex:

   // Replace variables in the format {{variableName}}
   let formattedMessage = baseMessage;
   for (const [key, value] of Object.entries(variables)) {
-    const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
+    const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+    const placeholder = new RegExp(`\\{\\{${escapedKey}\\}\\}`, 'g');
     formattedMessage = formattedMessage.replace(placeholder, value);
   }

Alternatively, use a simpler string replacement approach:

   // Replace variables in the format {{variableName}}
   let formattedMessage = baseMessage;
   for (const [key, value] of Object.entries(variables)) {
-    const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
-    formattedMessage = formattedMessage.replace(placeholder, value);
+    formattedMessage = formattedMessage.replaceAll(`{{${key}}}`, value);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function formatDeclineMessage(
declineCode: string,
locale: Locale = 'en',
variables?: Record<string, string>,
): string | undefined {
const baseMessage = getDeclineMessage(declineCode, locale);
if (!baseMessage) {
return undefined;
}
if (!variables) {
return baseMessage;
}
// Replace variables in the format {{variableName}}
let formattedMessage = baseMessage;
for (const [key, value] of Object.entries(variables)) {
const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
formattedMessage = formattedMessage.replace(placeholder, value);
}
return formattedMessage;
}
export function formatDeclineMessage(
declineCode: string,
locale: Locale = 'en',
variables?: Record<string, string>,
): string | undefined {
const baseMessage = getDeclineMessage(declineCode, locale);
if (!baseMessage) {
return undefined;
}
if (!variables) {
return baseMessage;
}
// Replace variables in the format {{variableName}}
let formattedMessage = baseMessage;
for (const [key, value] of Object.entries(variables)) {
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const placeholder = new RegExp(`\\{\\{${escapedKey}\\}\\}`, 'g');
formattedMessage = formattedMessage.replace(placeholder, value);
}
return formattedMessage;
}
Suggested change
export function formatDeclineMessage(
declineCode: string,
locale: Locale = 'en',
variables?: Record<string, string>,
): string | undefined {
const baseMessage = getDeclineMessage(declineCode, locale);
if (!baseMessage) {
return undefined;
}
if (!variables) {
return baseMessage;
}
// Replace variables in the format {{variableName}}
let formattedMessage = baseMessage;
for (const [key, value] of Object.entries(variables)) {
const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
formattedMessage = formattedMessage.replace(placeholder, value);
}
return formattedMessage;
}
export function formatDeclineMessage(
declineCode: string,
locale: Locale = 'en',
variables?: Record<string, string>,
): string | undefined {
const baseMessage = getDeclineMessage(declineCode, locale);
if (!baseMessage) {
return undefined;
}
if (!variables) {
return baseMessage;
}
// Replace variables in the format {{variableName}}
let formattedMessage = baseMessage;
for (const [key, value] of Object.entries(variables)) {
formattedMessage = formattedMessage.replaceAll(`{{${key}}}`, value);
}
return formattedMessage;
}
🧰 Tools
🪛 ast-grep (0.40.0)

[warning] 242-242: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\{\\{${key}\\}\\}, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
In src/index.ts around lines 225 to 248, the code builds a RegExp from variable
keys (`new RegExp(\`\\{\\{${key}\\}\\}\`, 'g')`) which can be abused to cause
ReDoS; fix this by escaping any regex metacharacters in `key` before
constructing the RegExp (implement a small escape function that replaces
characters like [\^$.|?*+(){}\\] with backslash-escaped versions and use that
escapedKey in the pattern), or replace the regex approach entirely with a safe
string-based replacement (e.g., construct the placeholder string `{{key}}` and
use split/join or indexOf/substring replacement to perform global replacements)
so user-supplied keys cannot inject regex patterns.

This commit adds only Stripe-official data based features:
- formatDeclineMessage() for custom message templates with variable substitution
- Comprehensive tests for message formatting
- Updated README with usage examples

Note: Removed previously added metadata features (category, retryable, severity,
requiresUserAction) as they are not part of official Stripe API and could be
considered unreliable.

Focus on providing official Stripe decline code data with localization support.
@hideokamoto hideokamoto force-pushed the claude/rename-library-features-01L8GCPmpRaFDSeXQmzE7VCa branch from d42c56f to d9a63d4 Compare November 18, 2025 02:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/index.ts (1)

145-146: Address the ReDoS vulnerability flagged in previous reviews.

This regex construction issue has already been identified by previous reviewers. As suggested, use String.prototype.replaceAll() for safe string replacement:

-    const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
-    formattedMessage = formattedMessage.replace(placeholder, value);
+    formattedMessage = formattedMessage.replaceAll(`{{${key}}}`, value);
🧹 Nitpick comments (2)
src/index.ts (1)

117-125: Enhance the JSDoc example to demonstrate variable substitution.

The example shows the variables parameter being passed but doesn't demonstrate how placeholders in the message template are replaced. Consider showing a message that contains {{merchantName}} or {{supportEmail}} placeholders in the output to clearly illustrate the variable substitution feature.

README.md (1)

173-181: Clarify the Message Formatting example.

The example shows passing merchantName and supportEmail variables, but the decline message for 'insufficient_funds' doesn't contain corresponding {{merchantName}} or {{supportEmail}} placeholders. This may mislead users into thinking these variables will be substituted when they won't.

Consider either:

  • Showing a hypothetical message with placeholders in the comment, or
  • Noting that this demonstrates the API signature and that actual messages would need to contain the corresponding placeholders
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d42c56f and d9a63d4.

📒 Files selected for processing (3)
  • README.md (4 hunks)
  • src/index.ts (1 hunks)
  • tests/index.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/index.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/index.ts (1)
src/types.ts (1)
  • Locale (4-4)
🪛 ast-grep (0.40.0)
src/index.ts

[warning] 144-144: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\{\\{${key}\\}\\}, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

@hideokamoto hideokamoto merged commit efa09f0 into main Nov 18, 2025
4 checks passed
@hideokamoto hideokamoto deleted the claude/rename-library-features-01L8GCPmpRaFDSeXQmzE7VCa branch November 18, 2025 02:35
@coderabbitai coderabbitai bot mentioned this pull request Dec 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants