feat: add decline code categorization and Stripe error integration#9
feat: add decline code categorization and Stripe error integration#9hideokamoto wants to merge 2 commits intomainfrom
Conversation
## 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
📝 WalkthroughWalkthroughThe 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
Summary of ChangesHello @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
🧠 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 AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| } 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'); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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:
| } 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'); | |
| } | |
| } | |
| } |
| #!/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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
isHardDeclineandisSoftDeclinereturnfalsefor invalid decline codes (sincegetDeclineCategoryreturnsundefined). 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
isSoftDeclineJSDoc could similarly clarify that invalid codes returnfalse.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (15)
.gitignoreREADME.mdbiome.jsondocs-data/api.jsondocs-data/decline-codes.jsondocs-data/functions.jsondocs-data/metadata.jsondocs-data/types.jsonpackage.jsonscripts/generate-docs.tssrc/data/decline-codes.tssrc/index.tssrc/types.tstests/index.test.tstypedoc.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-datato thefilesarray 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:generatefor custom JSON generation,docs:apifor TypeDoc, anddocsas a combined command.src/types.ts (3)
6-9: LGTM!The
DeclineCategorytype is well-defined with clear documentation. Using a union type with string literals provides strong type safety.
29-30: LGTM!Adding the
categoryfield toDeclineCodeInfoproperly extends the interface to support decline categorization, with appropriate JSDoc documentation.
45-53: LGTM!The
StripeErrorinterface is well-designed with:
- Optional fields matching Stripe's error structure
- An index signature
[key: string]: unknownfor extensibility, accommodating additional properties Stripe may includeThis provides flexibility while maintaining type safety for the known fields.
src/index.ts (3)
152-170: LGTM!The
getDeclineCategoryfunction is well-implemented with proper validation using the existingisValidDeclineCodehelper before accessing the data.
223-232: LGTM!The
getMessageFromStripeErrorfunction cleanly extracts thedecline_codefrom a Stripe error object and delegates to the existinggetDeclineMessagefunction. The early return for missingdecline_codehandles 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
DeclineCodevalues (lines 31-75), which aligns with the source type definition, further confirming the code count discrepancy noted indecline-codes.json.docs-data/decline-codes.json (1)
1-5: Fix grammatical error: remove "the" before "please" in user action messages.Multiple
nextUserActionfields 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 thenextUserActionvalues.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 forgetDeclineCategory.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 validateisHardDeclinebehavior.The test cases properly verify true for hard declines, false for soft declines, and false for invalid codes (which aligns with the implementation returning
falsewhengetDeclineCategoryreturnsundefined).
189-205: LGTM! Tests correctly validateisSoftDeclinebehavior.Symmetric with
isHardDeclinetests, properly covering soft decline codes returning true, hard declines returning false, and invalid codes returning false.
207-255: LGTM! Comprehensive tests forgetMessageFromStripeError.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
fileURLToPathfor ESM__dirnameequivalent, and safe directory creation withrecursive: 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. Sincetsxis used to run the script (perpackage.json's"docs:generate": "tsx scripts/generate-docs.ts"), andtsconfig.jsonspecifies"moduleResolution": "bundler", the.jsimport will automatically map to thesrc/index.tssource 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
getMessageFromStripeErrorwithisHardDeclinefor 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
DeclineCategoryandStripeErroralongside existing types.docs-data/api.json (1)
1-2081: LGTM! TypeDoc-generated API documentation is complete.The generated
api.jsoncorrectly 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.", |
There was a problem hiding this comment.
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.
| "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.
| "stats": { | ||
| "totalDeclineCodes": 43, | ||
| "softDeclineCodes": 16, | ||
| "hardDeclineCodes": 27, | ||
| "totalFunctions": 10, | ||
| "totalTypes": 7 |
There was a problem hiding this comment.
🧩 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"])
PYRepository: 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 -200Repository: 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.tsRepository: 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 txtRepository: 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 -20Repository: 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 -20Repository: 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:]}")
PYRepository: 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}")
PYRepository: 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".
| "tsx": "^4.19.0", | ||
| "typedoc": "^0.27.0", |
There was a problem hiding this comment.
🧩 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.
|
@coderabbitai これ、next.jsアプリ側ではどのように利用してdocサイトを作ることになりますか?tsdocとか何かしらのライブラリをいれるべき? |
|
Tip For best results, initiate chat on the files or code changes.
アーキテクチャ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して表示]
Next.js側での利用方法
1. パッケージのインストールnpm install stripe-decline-codes2. 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>
);
}推奨される構成JSONファイルがすでに構造化されたデータとして提供されているため、Next.js側では単純にimportして表示するだけで済みます。マークダウンのレンダリングが必要な場合は |
New Features
Decline Code Categorization
getDeclineCategory()to classify codes as SOFT_DECLINE or HARD_DECLINEisHardDecline()helper to identify permanent declinesisSoftDecline()helper to identify temporary declinesStripe Error Object Support
getMessageFromStripeError()for direct error object handlingImplementation Details
categoryfield to all 44 decline codes in databaseDeclineCategoryandStripeErrortypesTesting
Summary by CodeRabbit
Release Notes
New Features
getDeclineCategory(),isHardDecline(), andisSoftDecline().getMessageFromStripeError().DeclineCategoryandStripeError.Documentation
✏️ Tip: You can customize this high-level summary in your review settings.