Skip to content

feat: add decline code categorization and Stripe error integration#9

Open
hideokamoto wants to merge 2 commits intomainfrom
claude/docs-site-json-generation-g4BUT
Open

feat: add decline code categorization and Stripe error integration#9
hideokamoto wants to merge 2 commits intomainfrom
claude/docs-site-json-generation-g4BUT

Conversation

@hideokamoto
Copy link
Owner

@hideokamoto hideokamoto commented Jan 15, 2026

New Features

Decline Code Categorization

  • Add getDeclineCategory() to classify codes as SOFT_DECLINE or HARD_DECLINE
  • Add isHardDecline() helper to identify permanent declines
  • Add isSoftDecline() helper to identify temporary declines
  • Based on official Stripe documentation guidelines

Stripe Error Object Support

  • Add getMessageFromStripeError() for direct error object handling
  • Simplifies integration with Stripe SDK error handling

Implementation Details

  • Added category field to all 44 decline codes in database
  • Classification based on Stripe's soft/hard decline guidelines
  • Added DeclineCategory and StripeError types
  • Comprehensive test coverage with 34 passing tests
  • Updated documentation with usage examples

Testing

  • All tests passing (34/34)
  • TDD approach: Red-Green-Refactor cycle
  • Type-safe implementation with full TypeScript support

Summary by CodeRabbit

Release Notes

  • New Features

    • Added decline categorization functions: getDeclineCategory(), isHardDecline(), and isSoftDecline().
    • Added Stripe error integration function: getMessageFromStripeError().
    • Added Japanese locale support alongside English.
    • Exported new types: DeclineCategory and StripeError.
  • Documentation

    • Expanded API documentation with usage examples for new decline categorization and error integration features.

✏️ Tip: You can customize this high-level summary in your review settings.

## New Features

### Decline Code Categorization
- Add `getDeclineCategory()` to classify codes as SOFT_DECLINE or HARD_DECLINE
- Add `isHardDecline()` helper to identify permanent declines
- Add `isSoftDecline()` helper to identify temporary declines
- Based on official Stripe documentation guidelines

### Stripe Error Object Support
- Add `getMessageFromStripeError()` for direct error object handling
- Simplifies integration with Stripe SDK error handling

## Implementation Details
- Added `category` field to all 44 decline codes in database
- Classification based on Stripe's soft/hard decline guidelines
- Added `DeclineCategory` and `StripeError` types
- Comprehensive test coverage with 34 passing tests
- Updated documentation with usage examples

## Testing
- All tests passing (34/34)
- TDD approach: Red-Green-Refactor cycle
- Type-safe implementation with full TypeScript support
Add a complete documentation data generation pipeline that produces:
- decline-codes.json: All decline codes with translations
- functions.json: API reference for all exported functions
- types.json: Type definitions and their documentation
- metadata.json: Package metadata and statistics
- api.json: TypeDoc-generated API documentation

This enables external Next.js apps to consume the documentation data
via npm package or CDN (unpkg/jsdelivr).

Scripts added:
- npm run docs:generate - Custom JSON generation
- npm run docs:api - TypeDoc JSON/HTML generation
- npm run docs - Run both
@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

The PR extends the stripe-decline-codes library with decline categorization (soft/hard), Stripe error message extraction utilities, and automated API documentation generation via TypeDoc and a custom generation script. New types, functions, and enriched data are introduced alongside comprehensive documentation artifacts.

Changes

Cohort / File(s) Summary
Core API & Types
src/index.ts, src/types.ts
Added four new utility functions: getDeclineCategory(), isHardDecline(), isSoftDecline(), and getMessageFromStripeError(). Introduced new types DeclineCategory and StripeError to the public API.
Data Enrichment
src/data/decline-codes.ts
Augmented all 43 DECLINE_CODES entries with a category field assigning SOFT_DECLINE or HARD_DECLINE classification.
Documentation Tooling & Configuration
typedoc.json, scripts/generate-docs.ts, package.json, biome.json
Added TypeDoc configuration for API doc generation, created generation script to produce decline-codes, types, functions, and metadata JSON files. Added new scripts (docs:generate, docs:api, docs) and dev dependencies (tsx, typedoc). Extended biome.json includes patterns.
Documentation Artifacts
docs-data/api.json, docs-data/decline-codes.json, docs-data/functions.json, docs-data/metadata.json, docs-data/types.json
Auto-generated API reference files documenting package exports (types, constants, functions), decline code catalog with categories/translations, function signatures with examples, and package metadata.
Project Configuration & Documentation
.gitignore, README.md
Added ignore pattern for docs-data/api/. Updated README with new feature sections demonstrating decline categorization and Stripe error integration with usage examples.
Test Coverage
tests/index.test.ts
Added comprehensive tests for new functions validating category mapping, hard/soft decline checks, and message extraction from Stripe errors with locale support.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hideokamoto/stripe-decline-codes#6 — Both PRs modify DECLINE_CODES data structure and introduce category-related helpers for decline code classification and message formatting.

Poem

🐰 Soft declines and hard ones too,
Stripe errors sorted through and through,
A rabbit's docs now auto-flow,
With categories and messages in tow!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding decline code categorization (SOFT_DECLINE vs HARD_DECLINE) and Stripe error integration via new utility functions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 library's utility by providing a structured way to understand and react to Stripe payment declines. It allows developers to programmatically determine the nature of a decline (soft vs. hard) and seamlessly integrate with Stripe's error objects to present appropriate, localized messages to end-users. These changes streamline the process of building resilient payment flows and improving the customer experience.

Highlights

  • Decline Code Categorization: Introduced new functions: getDeclineCategory(), isHardDecline(), and isSoftDecline() to classify Stripe decline codes as either 'SOFT_DECLINE' (temporary, retryable) or 'HARD_DECLINE' (permanent, not retryable), based on official Stripe documentation.
  • Stripe Error Object Integration: Added getMessageFromStripeError() to directly extract localized user-facing messages from Stripe SDK error objects, simplifying error handling and user feedback.
  • Data Enrichment and Documentation: All 44 existing Stripe decline codes in the database now include a category field. New DeclineCategory and StripeError types have been added, and the README has been updated with usage examples for the new features. Comprehensive documentation JSON files are now generated.
  • Improved Development Workflow: New scripts and configurations for generating documentation (TypeDoc and custom JSON files) have been added, enhancing the project's maintainability and clarity.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

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 is a great pull request that adds valuable new features for categorizing Stripe decline codes and simplifying error handling. The code is clean, well-documented, and thoroughly tested. I have a couple of suggestions to improve the documentation examples and the maintainability of the documentation generation script. Also, I noticed a small discrepancy in the documentation: the PR description and README.md mention 44 decline codes, but the actual count in the data and generated docs appears to be 43. It would be great to update this for accuracy.

Comment on lines +161 to +175
} catch (error) {
if (error.type === 'StripeCardError') {
// Get localized message directly from error object
const userMessage = getMessageFromStripeError(error, 'ja');

// Check if retry is recommended
if (error.decline_code && isHardDecline(error.decline_code)) {
// Ask for a different payment method
console.log('Please use a different card');
} else {
// Retry is possible
console.log('You may retry this payment');
}
}
}

Choose a reason for hiding this comment

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

medium

This is a great example! To make it more robust and type-safe for TypeScript users, it's worth noting that the error in a catch block is of type unknown by default in modern TypeScript. Accessing error.type directly can lead to a compile error under strict settings.

A common practice is to perform type-narrowing to ensure the error object has the expected shape before using it. Here is a suggestion for a more type-safe catch block:

Suggested change
} catch (error) {
if (error.type === 'StripeCardError') {
// Get localized message directly from error object
const userMessage = getMessageFromStripeError(error, 'ja');
// Check if retry is recommended
if (error.decline_code && isHardDecline(error.decline_code)) {
// Ask for a different payment method
console.log('Please use a different card');
} else {
// Retry is possible
console.log('You may retry this payment');
}
}
}
} catch (error) {
if (error && typeof error === 'object' && 'type' in error && error.type === 'StripeCardError') {
// Now TypeScript knows `error` is an object with a `type` property.
// We can safely use it with your library's helper functions.
const stripeError = error as { decline_code?: string };
// Get localized message directly from error object
const userMessage = getMessageFromStripeError(stripeError, 'ja');
// Check if retry is recommended
if (stripeError.decline_code && isHardDecline(stripeError.decline_code)) {
// Ask for a different payment method
console.log('Please use a different card');
} else {
// Retry is possible
console.log('You may retry this payment');
}
}
}

Comment on lines +1 to +413
#!/usr/bin/env npx tsx
/**
* Generate documentation JSON files for the stripe-decline-codes library.
* This script creates:
* - decline-codes.json: Complete decline code database with all translations
* - types.json: Type definitions and their documentation
* - metadata.json: Package metadata and version information
*/

import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = join(__dirname, '..');
const outputDir = join(rootDir, 'docs-data');

// Ensure output directory exists
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}

// Dynamic import to use the built library
async function main() {
const { DECLINE_CODES, DOC_VERSION, getAllDeclineCodes } = await import('../src/index.js');

// Generate decline-codes.json
const softDeclineCodes: string[] = [];
const hardDeclineCodes: string[] = [];

const codes = Object.entries(DECLINE_CODES).map(([code, info]) => {
// Track codes by category
if (info.category === 'SOFT_DECLINE') {
softDeclineCodes.push(code);
} else {
hardDeclineCodes.push(code);
}

return {
code,
category: info.category,
description: info.description,
nextSteps: info.nextSteps,
nextUserAction: info.nextUserAction,
translations: info.translations || {},
};
});

const declineCodesData = {
$schema: './schemas/decline-codes.schema.json',
version: DOC_VERSION,
generatedAt: new Date().toISOString(),
totalCodes: getAllDeclineCodes().length,
categories: {
SOFT_DECLINE: {
description: 'Temporary declines that may succeed if retried',
codes: softDeclineCodes,
},
HARD_DECLINE: {
description: 'Permanent declines that should not be retried',
codes: hardDeclineCodes,
},
},
codes,
};

writeFileSync(join(outputDir, 'decline-codes.json'), JSON.stringify(declineCodesData, null, 2));
console.log('✓ Generated decline-codes.json');

// Generate types.json
const typesData = {
$schema: './schemas/types.schema.json',
version: DOC_VERSION,
generatedAt: new Date().toISOString(),
types: [
{
name: 'Locale',
kind: 'type-alias',
description: 'Supported locale codes for decline code translations',
definition: "'en' | 'ja'",
values: ['en', 'ja'],
},
{
name: 'DeclineCategory',
kind: 'type-alias',
description: "Decline code categories based on Stripe's classification",
definition: "'SOFT_DECLINE' | 'HARD_DECLINE'",
values: ['SOFT_DECLINE', 'HARD_DECLINE'],
},
{
name: 'DeclineCode',
kind: 'type-alias',
description: 'All supported Stripe decline codes',
definition: 'Union of all decline code string literals',
values: getAllDeclineCodes(),
},
{
name: 'Translation',
kind: 'interface',
description: 'Translation for a specific locale',
properties: [
{ name: 'description', type: 'string', description: 'Translated description' },
{ name: 'nextUserAction', type: 'string', description: 'Translated user action message' },
],
},
{
name: 'DeclineCodeInfo',
kind: 'interface',
description: 'Decline code information including descriptions and recommended actions',
properties: [
{
name: 'description',
type: 'string',
description: 'Technical description of why the payment was declined',
},
{
name: 'nextSteps',
type: 'string',
description: 'Recommended next steps for merchants',
},
{
name: 'nextUserAction',
type: 'string',
description: 'User-facing message that can be shown to customers',
},
{
name: 'category',
type: 'DeclineCategory',
description: 'Category of the decline (soft or hard)',
},
{
name: 'translations',
type: 'Partial<Record<Locale, Translation>>',
description: 'Translations for different locales',
optional: true,
},
],
},
{
name: 'DeclineCodeResult',
kind: 'interface',
description: 'Result containing decline code information and metadata',
properties: [
{ name: 'docVersion', type: 'string', description: 'Stripe API documentation version' },
{
name: 'code',
type: 'DeclineCodeInfo | {}',
description: 'Decline code information, or empty object if code not found',
},
],
},
{
name: 'StripeError',
kind: 'interface',
description: 'Stripe error object with decline code information',
properties: [
{ name: 'type', type: 'string', description: 'Error type', optional: true },
{
name: 'decline_code',
type: 'string',
description: 'The decline code from Stripe',
optional: true,
},
{
name: 'message',
type: 'string',
description: 'Error message from Stripe',
optional: true,
},
],
},
],
};

writeFileSync(join(outputDir, 'types.json'), JSON.stringify(typesData, null, 2));
console.log('✓ Generated types.json');

// Generate functions.json (API reference)
const functionsData = {
$schema: './schemas/functions.schema.json',
version: DOC_VERSION,
generatedAt: new Date().toISOString(),
functions: [
{
name: 'getDeclineDescription',
description: 'Get decline code information with description and recommended actions',
signature: 'getDeclineDescription(declineCode?: string): DeclineCodeResult',
parameters: [
{
name: 'declineCode',
type: 'string',
optional: true,
description: 'The Stripe decline code to look up',
},
],
returns: {
type: 'DeclineCodeResult',
description: 'Object containing the decline code information and documentation version',
},
example: `const result = getDeclineDescription('insufficient_funds');
console.log(result.code.description);
// => "The card has insufficient funds to complete the purchase."`,
},
{
name: 'getDeclineMessage',
description: 'Get localized decline code message for end users',
signature: 'getDeclineMessage(declineCode: string, locale?: Locale): string | undefined',
parameters: [
{
name: 'declineCode',
type: 'string',
optional: false,
description: 'The Stripe decline code',
},
{
name: 'locale',
type: 'Locale',
optional: true,
description: "The locale to use (default: 'en')",
},
],
returns: {
type: 'string | undefined',
description: 'User-facing message in the specified locale, or undefined if not found',
},
example: `const message = getDeclineMessage('insufficient_funds', 'ja');
console.log(message);
// => "別のお支払い方法を使用してもう一度お試しください。"`,
},
{
name: 'getAllDeclineCodes',
description: 'Get all available decline codes',
signature: 'getAllDeclineCodes(): DeclineCode[]',
parameters: [],
returns: {
type: 'DeclineCode[]',
description: 'Array of all supported decline code strings',
},
example: `const codes = getAllDeclineCodes();
console.log(codes.length); // => 44`,
},
{
name: 'isValidDeclineCode',
description: 'Check if a decline code is valid',
signature: 'isValidDeclineCode(code: string): code is DeclineCode',
parameters: [
{ name: 'code', type: 'string', optional: false, description: 'The code to validate' },
],
returns: { type: 'boolean', description: 'True if the code exists in the database' },
example: `isValidDeclineCode('insufficient_funds'); // => true
isValidDeclineCode('invalid_code'); // => false`,
},
{
name: 'getDocVersion',
description: 'Get the documentation version for the decline codes data',
signature: 'getDocVersion(): string',
parameters: [],
returns: { type: 'string', description: 'The Stripe API documentation version string' },
example: `const version = getDocVersion();
console.log(version); // => "2024-12-18"`,
},
{
name: 'formatDeclineMessage',
description: 'Format a decline message with custom template variables',
signature:
'formatDeclineMessage(declineCode: string, locale?: Locale, variables?: Record<string, string>): string | undefined',
parameters: [
{
name: 'declineCode',
type: 'string',
optional: false,
description: 'The Stripe decline code',
},
{
name: 'locale',
type: 'Locale',
optional: true,
description: "The locale to use (default: 'en')",
},
{
name: 'variables',
type: 'Record<string, string>',
optional: true,
description: 'Optional variables to replace in the message template',
},
],
returns: {
type: 'string | undefined',
description: 'Formatted user-facing message with variables replaced',
},
example: `const message = formatDeclineMessage('insufficient_funds', 'en', {
merchantName: 'Acme Store'
});`,
},
{
name: 'getDeclineCategory',
description: 'Get the category of a decline code (SOFT_DECLINE or HARD_DECLINE)',
signature: 'getDeclineCategory(code: string): DeclineCategory | undefined',
parameters: [
{
name: 'code',
type: 'string',
optional: false,
description: 'The decline code to categorize',
},
],
returns: {
type: 'DeclineCategory | undefined',
description: 'The category of the decline code, or undefined if invalid',
},
example: `getDeclineCategory('insufficient_funds'); // => 'SOFT_DECLINE'
getDeclineCategory('fraudulent'); // => 'HARD_DECLINE'`,
},
{
name: 'isHardDecline',
description: 'Check if a decline code is a hard decline (permanent, should not retry)',
signature: 'isHardDecline(code: string): boolean',
parameters: [
{
name: 'code',
type: 'string',
optional: false,
description: 'The decline code to check',
},
],
returns: { type: 'boolean', description: 'True if the code is a hard decline' },
example: `isHardDecline('fraudulent'); // => true
isHardDecline('insufficient_funds'); // => false`,
},
{
name: 'isSoftDecline',
description: 'Check if a decline code is a soft decline (temporary, can retry)',
signature: 'isSoftDecline(code: string): boolean',
parameters: [
{
name: 'code',
type: 'string',
optional: false,
description: 'The decline code to check',
},
],
returns: { type: 'boolean', description: 'True if the code is a soft decline' },
example: `isSoftDecline('insufficient_funds'); // => true
isSoftDecline('fraudulent'); // => false`,
},
{
name: 'getMessageFromStripeError',
description: 'Extract localized message from a Stripe error object',
signature:
'getMessageFromStripeError(error: StripeError, locale?: Locale): string | undefined',
parameters: [
{
name: 'error',
type: 'StripeError',
optional: false,
description: 'The Stripe error object',
},
{
name: 'locale',
type: 'Locale',
optional: true,
description: "The locale to use (default: 'en')",
},
],
returns: {
type: 'string | undefined',
description: 'User-facing message in the specified locale, or undefined if not found',
},
example: `const stripeError = {
type: 'StripeCardError',
decline_code: 'insufficient_funds',
message: 'Your card has insufficient funds.'
};
const message = getMessageFromStripeError(stripeError, 'ja');
// => "別のお支払い方法を使用してもう一度お試しください。"`,
},
],
};

writeFileSync(join(outputDir, 'functions.json'), JSON.stringify(functionsData, null, 2));
console.log('✓ Generated functions.json');

// Generate metadata.json
const packageJson = await import('../package.json', { with: { type: 'json' } });
const metadataData = {
$schema: './schemas/metadata.schema.json',
package: {
name: packageJson.default.name,
version: packageJson.default.version,
description: packageJson.default.description,
repository: packageJson.default.repository?.url || '',
license: packageJson.default.license,
},
stripeDocVersion: DOC_VERSION,
generatedAt: new Date().toISOString(),
supportedLocales: ['en', 'ja'],
stats: {
totalDeclineCodes: getAllDeclineCodes().length,
softDeclineCodes: declineCodesData.categories.SOFT_DECLINE.codes.length,
hardDeclineCodes: declineCodesData.categories.HARD_DECLINE.codes.length,
totalFunctions: functionsData.functions.length,
totalTypes: typesData.types.length,
},
};

writeFileSync(join(outputDir, 'metadata.json'), JSON.stringify(metadataData, null, 2));
console.log('✓ Generated metadata.json');

console.log('\n✅ All documentation JSON files generated successfully!');
console.log(` Output directory: ${outputDir}`);
}

main().catch(console.error);

Choose a reason for hiding this comment

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

medium

This script is a great way to automate documentation generation! For even better long-term maintainability, you could consider using the api.json file (which is already being generated by TypeDoc) as the single source of truth for generating functions.json and types.json.

Currently, the function signatures, descriptions, and type properties are manually defined here. By parsing api.json instead, this script would automatically pick up any changes from the JSDoc comments in the source code, reducing the chance of documentation becoming outdated or inconsistent.

This is just a suggestion for future improvement, as the current implementation works correctly.

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: 3

🤖 Fix all issues with AI agents
In `@docs-data/decline-codes.json`:
- Line 67: The JSON entries for the nextUserAction field contain a typo "the
please contact" — update the source data where the decline codes are defined
(the decline-codes module / declineCodes constant) to replace "the please
contact" with "please contact" for all affected entries (search for
nextUserAction values containing "the please contact"), then rebuild/regenerate
any outputs so the corrected text propagates.

In `@docs-data/metadata.json`:
- Around line 16-21: Update public-facing docs to reflect the correct total of
43 decline codes: change any copy that says "44" to "43" (e.g., the README claim
"All 44 Stripe decline codes included") and update inline example comments in
docs-data files (functions.json, api.json) that currently show "// => 44" to "//
=> 43"; ensure consistency with metadata.json fields "totalDeclineCodes",
"softDeclineCodes", and "hardDeclineCodes".

In `@package.json`:
- Around line 71-72: Update the devDependencies entries for "tsx" and "typedoc"
in package.json: bump "tsx" from ^4.19.0 to ^4.21.0 and "typedoc" from ^0.27.0
to ^0.28.16 (or to the latest stable versions), then reinstall dependencies to
update the lockfile (npm install / yarn install) and run the test/build commands
to ensure no breaking changes; locate the "tsx" and "typedoc" entries in
package.json to make the change.
🧹 Nitpick comments (2)
src/index.ts (2)

184-186: Consider documenting the behavior for invalid codes.

Both isHardDecline and isSoftDecline return false for invalid decline codes (since getDeclineCategory returns undefined). This is reasonable behavior, but the JSDoc could clarify this to prevent confusion.

📝 Suggested documentation enhancement
 /**
  * Check if a decline code is a hard decline (permanent, should not retry)
  *
  * `@param` code - The decline code to check
- * `@returns` True if the code is a hard decline
+ * `@returns` True if the code is a valid hard decline, false otherwise (including invalid codes)
  *

200-202: Same consideration applies here for consistency.

The isSoftDecline JSDoc could similarly clarify that invalid codes return false.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf2669 and 81946b5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .gitignore
  • README.md
  • biome.json
  • docs-data/api.json
  • docs-data/decline-codes.json
  • docs-data/functions.json
  • docs-data/metadata.json
  • docs-data/types.json
  • package.json
  • scripts/generate-docs.ts
  • src/data/decline-codes.ts
  • src/index.ts
  • src/types.ts
  • tests/index.test.ts
  • typedoc.json
🧰 Additional context used
🧬 Code graph analysis (4)
src/index.ts (2)
src/types.ts (3)
  • DeclineCategory (9-9)
  • StripeError (48-53)
  • Locale (4-4)
src/data/decline-codes.ts (1)
  • DECLINE_CODES (14-572)
src/types.ts (1)
src/index.ts (2)
  • DeclineCategory (238-238)
  • StripeError (243-243)
tests/index.test.ts (1)
src/index.ts (4)
  • getDeclineCategory (164-170)
  • isHardDecline (184-186)
  • isSoftDecline (200-202)
  • getMessageFromStripeError (223-232)
scripts/generate-docs.ts (1)
src/index.ts (1)
  • getAllDeclineCodes (74-76)
🔇 Additional comments (33)
.gitignore (1)

27-30: LGTM: ignore generated TypeDoc HTML output only.

Keeping the HTML output ignored while retaining JSON artifacts is a good balance for repo hygiene.

typedoc.json (1)

1-13: TypeDoc config looks solid.

Clear entry point, JSON output, and exclusion of non-public members align with the intended public API docs.

biome.json (1)

8-10: LGTM: broadened include patterns.

The added paths line up with the new scripts/tests/docs JSON in this PR.

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

14-571: Nice addition of category fields across decline codes.

This makes the dataset richer and aligns with the new helper APIs without altering existing semantics.

package.json (2)

21-26: LGTM!

Adding docs-data to the files array ensures the generated documentation artifacts are included in the published package, making them available for downstream consumers.


37-40: LGTM!

The documentation scripts are well-organized with a clear separation of concerns: docs:generate for custom JSON generation, docs:api for TypeDoc, and docs as a combined command.

src/types.ts (3)

6-9: LGTM!

The DeclineCategory type is well-defined with clear documentation. Using a union type with string literals provides strong type safety.


29-30: LGTM!

Adding the category field to DeclineCodeInfo properly extends the interface to support decline categorization, with appropriate JSDoc documentation.


45-53: LGTM!

The StripeError interface is well-designed with:

  • Optional fields matching Stripe's error structure
  • An index signature [key: string]: unknown for extensibility, accommodating additional properties Stripe may include

This provides flexibility while maintaining type safety for the known fields.

src/index.ts (3)

152-170: LGTM!

The getDeclineCategory function is well-implemented with proper validation using the existing isValidDeclineCode helper before accessing the data.


223-232: LGTM!

The getMessageFromStripeError function cleanly extracts the decline_code from a Stripe error object and delegates to the existing getDeclineMessage function. The early return for missing decline_code handles the case where the error doesn't contain decline information.


237-244: LGTM!

The new type exports (DeclineCategory, StripeError) are properly added to the public API surface.

docs-data/types.json (1)

1-169: LGTM!

The generated types documentation accurately reflects the TypeScript type definitions. The structure is well-organized and provides useful metadata for downstream documentation consumers.

Note: This file shows 44 DeclineCode values (lines 31-75), which aligns with the source type definition, further confirming the code count discrepancy noted in decline-codes.json.

docs-data/decline-codes.json (1)

1-5: Fix grammatical error: remove "the" before "please" in user action messages.

Multiple nextUserAction fields contain the grammatical error "the please contact" instead of "please contact". This appears in at least 8 decline codes. Search for and replace "the please" with "please" throughout the nextUserAction values.

Likely an incorrect or invalid review comment.

docs-data/functions.json (1)

1-197: LGTM! Well-structured API documentation artifact.

The functions.json file comprehensively documents all 10 exported functions with clear descriptions, type signatures, parameter details, and usage examples. The structure aligns well with the implemented API surface.

tests/index.test.ts (5)

6-14: LGTM! New function imports correctly added.

The imports for the new categorization and Stripe error utilities align with the exported API surface.


148-168: LGTM! Comprehensive test coverage for getDeclineCategory.

Tests cover both soft and hard decline categorization with representative codes from each category, plus the invalid code edge case returning undefined.


170-187: LGTM! Tests correctly validate isHardDecline behavior.

The test cases properly verify true for hard declines, false for soft declines, and false for invalid codes (which aligns with the implementation returning false when getDeclineCategory returns undefined).


189-205: LGTM! Tests correctly validate isSoftDecline behavior.

Symmetric with isHardDecline tests, properly covering soft decline codes returning true, hard declines returning false, and invalid codes returning false.


207-255: LGTM! Comprehensive tests for getMessageFromStripeError.

Good coverage of the key scenarios: successful extraction with default and explicit locales, missing decline_code, non-card errors, and invalid decline codes.

scripts/generate-docs.ts (7)

1-21: LGTM! Standard ESM setup with proper directory handling.

Correct use of fileURLToPath for ESM __dirname equivalent, and safe directory creation with recursive: true.


27-68: LGTM! Decline codes data generation is well-structured.

Correctly categorizes codes into soft/hard declines and extracts all relevant fields with appropriate fallback for translations.


70-176: LGTM! Type definitions are well-documented.

The types JSON correctly captures all exported types with their descriptions, properties, and optionality flags matching the source definitions.


178-381: LGTM! Function documentation is comprehensive and accurate.

All 10 exported functions are documented with correct signatures, parameter types, return types, and helpful examples.


383-411: LGTM! Metadata generation uses modern import assertions.

The with: { type: 'json' } syntax is the standard approach for JSON imports in modern Node.js/TypeScript. Stats are correctly derived from the generated data.


413-413: Acceptable error handling for a build script.

The catch(console.error) ensures errors are logged and the process fails, which is appropriate for a documentation generation script. More granular error handling could be added but is not essential.


24-26: The import path will resolve correctly as configured. Since tsx is used to run the script (per package.json's "docs:generate": "tsx scripts/generate-docs.ts"), and tsconfig.json specifies "moduleResolution": "bundler", the .js import will automatically map to the src/index.ts source file. The bundler module resolution strategy is designed specifically for this pattern and handles TypeScript-to-JavaScript module mapping transparently at runtime.

README.md (5)

19-20: LGTM! Feature list updated to highlight new capabilities.

Clear descriptions of the decline categorization and Stripe SDK integration features.


123-143: LGTM! Clear documentation for decline categorization.

The examples effectively demonstrate the three categorization functions with helpful comments explaining when to use soft vs hard decline checks.


145-176: LGTM! Practical example of Stripe error handling.

The example demonstrates a real-world pattern combining getMessageFromStripeError with isHardDecline for intelligent retry logic.


240-274: LGTM! API reference accurately documents new functions.

Clear parameter descriptions and return types for all categorization and Stripe error integration functions.


328-337: LGTM! Type exports correctly documented.

The import example now includes DeclineCategory and StripeError alongside existing types.

docs-data/api.json (1)

1-2081: LGTM! TypeDoc-generated API documentation is complete.

The generated api.json correctly reflects the full public API surface including all interfaces, type aliases, variables, and functions with their source references and documentation comments.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

"category": "SOFT_DECLINE",
"description": "The payment cannot be authorized.",
"nextSteps": "The payment should be attempted again. If it still cannot be processed, the customer needs to contact their card issuer.",
"nextUserAction": "Please try again. If it still cannot be processed, the please contact your card issuer.",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor grammatical error: "the please" should be "please".

The nextUserAction field contains "the please contact" which is grammatically incorrect.

-      "nextUserAction": "Please try again. If it still cannot be processed, the please contact your card issuer.",
+      "nextUserAction": "Please try again. If it still cannot be processed, please contact your card issuer.",

This same error appears in multiple entries (lines 210, 223, 236, 353, 457, 470, 600). Consider fixing this in the source data (src/data/decline-codes.ts) so it propagates to all generated outputs.

📝 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
"nextUserAction": "Please try again. If it still cannot be processed, the please contact your card issuer.",
"nextUserAction": "Please try again. If it still cannot be processed, please contact your card issuer.",
🤖 Prompt for AI Agents
In `@docs-data/decline-codes.json` at line 67, The JSON entries for the
nextUserAction field contain a typo "the please contact" — update the source
data where the decline codes are defined (the decline-codes module /
declineCodes constant) to replace "the please contact" with "please contact" for
all affected entries (search for nextUserAction values containing "the please
contact"), then rebuild/regenerate any outputs so the corrected text propagates.

Comment on lines +16 to +21
"stats": {
"totalDeclineCodes": 43,
"softDeclineCodes": 16,
"hardDeclineCodes": 27,
"totalFunctions": 10,
"totalTypes": 7
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Count top-level decline codes
count=$(rg -n '^  [a-z_]+:' src/data/decline-codes.ts | wc -l | tr -d ' ')
echo "decline-codes.ts count: $count"

python - <<'PY'
import json
with open('docs-data/metadata.json') as f:
    meta = json.load(f)
print("metadata totalDeclineCodes:", meta["stats"]["totalDeclineCodes"])
PY

Repository: hideokamoto/stripe-decline-codes

Length of output: 162


🏁 Script executed:

# Find and check PR summary or related documentation
fd -t f '.*' docs-data/ --exec head -50 {} \; 2>/dev/null | head -200

Repository: hideokamoto/stripe-decline-codes

Length of output: 5695


🏁 Script executed:

# Check the actual decline-codes.ts structure more carefully
head -100 src/data/decline-codes.ts

Repository: hideokamoto/stripe-decline-codes

Length of output: 3860


🏁 Script executed:

# Search for any mention of "44" related to decline codes in docs or PR files
rg -i "44.*decline|decline.*44" --type json --type md --type txt

Repository: hideokamoto/stripe-decline-codes

Length of output: 518


🏁 Script executed:

# Check for CHANGELOG or PR-related files
fd -t f -e md -e txt | grep -iE "(changelog|readme|release|pr\.)" | head -20

Repository: hideokamoto/stripe-decline-codes

Length of output: 85


🏁 Script executed:

# Look for any recent commits or PR descriptions that might mention the count
git log --oneline --all -20 | head -20

Repository: hideokamoto/stripe-decline-codes

Length of output: 812


🏁 Script executed:

# Count actual decline codes defined in the source
python <<'PY'
import re

# Read the decline-codes.ts file
with open('src/data/decline-codes.ts', 'r') as f:
    content = f.read()

# Extract all keys from the DECLINE_CODES object using regex
# Match patterns like "  code_name: {"
matches = re.findall(r'^\s+([a-z_]+):\s*\{', content, re.MULTILINE)
print(f"Total decline codes in source: {len(matches)}")
print(f"First 10: {matches[:10]}")
print(f"Last 10: {matches[-10:]}")
PY

Repository: hideokamoto/stripe-decline-codes

Length of output: 432


🏁 Script executed:

# Also verify the JSON files show the same
python <<'PY'
import json

# Check all relevant JSON files
files = ['docs-data/metadata.json', 'docs-data/decline-codes.json']

for filepath in files:
    try:
        with open(filepath) as f:
            data = json.load(f)
        if 'stats' in data:
            print(f"{filepath}: totalDeclineCodes = {data['stats']['totalDeclineCodes']}")
        elif 'totalCodes' in data:
            print(f"{filepath}: totalCodes = {data['totalCodes']}")
            # Count actual codes
            soft = len(data.get('categories', {}).get('SOFT_DECLINE', {}).get('codes', []))
            hard = len(data.get('categories', {}).get('HARD_DECLINE', {}).get('codes', []))
            print(f"  Breakdown: SOFT={soft}, HARD={hard}, Total={soft+hard}")
    except Exception as e:
        print(f"Error reading {filepath}: {e}")
PY

Repository: hideokamoto/stripe-decline-codes

Length of output: 209


Update README.md and documentation examples: decline codes are 43, not 44.

The metadata.json is correct at 43 total codes (16 soft + 27 hard). However, README.md claims "All 44 Stripe decline codes included" and code examples in docs-data (functions.json, api.json) show // => 44. These public-facing docs must be updated to reflect the actual count of 43.

🤖 Prompt for AI Agents
In `@docs-data/metadata.json` around lines 16 - 21, Update public-facing docs to
reflect the correct total of 43 decline codes: change any copy that says "44" to
"43" (e.g., the README claim "All 44 Stripe decline codes included") and update
inline example comments in docs-data files (functions.json, api.json) that
currently show "// => 44" to "// => 43"; ensure consistency with metadata.json
fields "totalDeclineCodes", "softDeclineCodes", and "hardDeclineCodes".

Comment on lines +71 to +72
"tsx": "^4.19.0",
"typedoc": "^0.27.0",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest versions of tsx and typedoc on npm
echo "Checking tsx latest version:"
curl -s https://registry.npmjs.org/tsx/latest | jq '.version'

echo "Checking typedoc latest version:"
curl -s https://registry.npmjs.org/typedoc/latest | jq '.version'

Repository: hideokamoto/stripe-decline-codes

Length of output: 156


Update devDependencies to latest versions.

tsx can be updated from ^4.19.0 to ^4.21.0 (2 patch versions behind), and typedoc should be updated from ^0.27.0 to ^0.28.16 (1 minor version behind). Consider updating both to the latest stable releases.

🤖 Prompt for AI Agents
In `@package.json` around lines 71 - 72, Update the devDependencies entries for
"tsx" and "typedoc" in package.json: bump "tsx" from ^4.19.0 to ^4.21.0 and
"typedoc" from ^0.27.0 to ^0.28.16 (or to the latest stable versions), then
reinstall dependencies to update the lockfile (npm install / yarn install) and
run the test/build commands to ensure no breaking changes; locate the "tsx" and
"typedoc" entries in package.json to make the change.

@hideokamoto
Copy link
Owner Author

@coderabbitai これ、next.jsアプリ側ではどのように利用してdocサイトを作ることになりますか?tsdocとか何かしらのライブラリをいれるべき?

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Tip

For best results, initiate chat on the files or code changes.

@hideokamoto さん、このPRで生成されるJSON ファイルは、Next.jsアプリ側で直接importして利用できます。TSDocやTypedocをNext.js側にインストールする必要はありません

アーキテクチャ

graph LR
    A[このライブラリ] -->|npm run docs| B[JSON生成]
    B --> C[docs-data/*.json]
    C -->|npm publish| D[npmパッケージ]
    D -->|npm install| E[Next.jsアプリ]
    E --> F[JSONをimportして表示]
Loading

Next.js側での利用方法

package.jsonfiles配列に"docs-data"が含まれているため、これらのJSONファイルはパッケージと一緒に公開されます。

1. パッケージのインストール

npm install stripe-decline-codes

2. JSONファイルのimport

// app/docs/page.tsx
import declineCodes from 'stripe-decline-codes/docs-data/decline-codes.json';
import functions from 'stripe-decline-codes/docs-data/functions.json';
import types from 'stripe-decline-codes/docs-data/types.json';
import metadata from 'stripe-decline-codes/docs-data/metadata.json';
import api from 'stripe-decline-codes/docs-data/api.json';

export default function DocsPage() {
  return (
    <div>
      <h1>{metadata.package.name}</h1>
      <p>Total Decline Codes: {metadata.stats.totalDeclineCodes}</p>
      
      {/* Decline Codesのリスト表示 */}
      {declineCodes.codes.map(code => (
        <div key={code.code}>
          <h2>{code.code}</h2>
          <p>{code.description}</p>
          <p>Category: {code.category}</p>
        </div>
      ))}
    </div>
  );
}

3. 動的ルーティングの例

// app/docs/[code]/page.tsx
import { notFound } from 'next/navigation';
import declineCodes from 'stripe-decline-codes/docs-data/decline-codes.json';

export function generateStaticParams() {
  return declineCodes.codes.map(code => ({
    code: code.code,
  }));
}

export default function CodePage({ params }: { params: { code: string } }) {
  const codeData = declineCodes.codes.find(c => c.code === params.code);
  
  if (!codeData) {
    notFound();
  }
  
  return (
    <div>
      <h1>{codeData.code}</h1>
      <p>{codeData.description}</p>
      <h2>Next Steps</h2>
      <p>{codeData.nextSteps}</p>
      {codeData.translations?.ja && (
        <div>
          <h2>日本語</h2>
          <p>{codeData.translations.ja.description}</p>
        </div>
      )}
    </div>
  );
}

推奨される構成

next-app/
├── app/
│   ├── docs/
│   │   ├── page.tsx          # Overview
│   │   ├── functions/
│   │   │   └── page.tsx      # functions.json を利用
│   │   ├── types/
│   │   │   └── page.tsx      # types.json を利用
│   │   └── codes/
│   │       └── [code]/
│   │           └── page.tsx  # decline-codes.json を利用

JSONファイルがすでに構造化されたデータとして提供されているため、Next.js側では単純にimportして表示するだけで済みます。マークダウンのレンダリングが必要な場合はreact-markdownなどを追加すると良いでしょう。

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