You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This audit covers the entire @chargily/chargily-pay SDK codebase. The SDK is a functional HTTP wrapper around the Chargily Pay V2 API, but it contains 5 critical bugs, 8 high-severity issues, and lacks essential infrastructure for a production payment library.
The SDK has no tests, no CI/CD, no linting, no retry/timeout logic, no caching, and no typed error handling — all of which are expected in a payment gateway SDK.
Critical Bugs (5)
CRITICAL-1: updateProduct, updatePrice, updatePaymentLink use POST instead of PATCH
Files:src/classes/client.ts lines 192, 263, 386
updateCustomer correctly uses PATCH, but the three other update methods use POST. This will either create duplicate resources or return 405 errors from the API.
// client.ts:192 — BUGreturnthis.request(`products/${product_id}`,'POST',update_data);// Should be: 'PATCH'// client.ts:263 — BUGreturnthis.request(`prices/${price_id}`,'POST',update_data);// Should be: 'PATCH'// client.ts:386-390 — BUGreturnthis.request(`payment-links/${payment_link_id}`,'POST',update_data);// Should be: 'PATCH'
Fix: Replace 'POST' with 'PATCH' in all three methods.
CRITICAL-2: verifySignature throws instead of returning false
File:src/utils/index.ts lines 14-37
The function signature says returns boolean, and the early guard (line 16) correctly returns false. But when the signature is actually invalid (the main failure case), it throws an Error instead of returning false.
// Line 16 — correct behaviorif(!signature)returnfalse;// Lines 29-34 — inconsistent behaviorif(signatureBuffer.length!==digest.length||!crypto.timingSafeEqual(digest,signatureBuffer)){thrownewError('The signature is invalid.');// BUG: should return false}
This forces developers into a confusing try/catch + boolean pattern for a single function.
Fix: Replace the throw with return false.
CRITICAL-3: console.log in production library code
File:src/utils/index.ts line 36
console.log('The signature is valid');// Pollutes every consumer's stdoutreturntrue;
Library code must never log to the console. This will print on every valid webhook in every app using this SDK.
Fix: Remove the console.log statement.
CRITICAL-4: success_url validation logic is broken
Since 'https://...' always starts with 'http', the second condition is dead code. This also means 'httpanything' or 'http-malicious' pass validation. Additionally, failure_url and webhook_endpoint are never validated.
Fix: Use new URL() constructor for validation, and validate all URL fields.
CRITICAL-5: API error response body is never parsed
File:src/classes/client.ts lines 88-100
When the API returns 4xx/5xx, the error body (which contains the actual error message, validation details, etc.) is discarded:
if(!response.ok){// The JSON body with {message, errors} is never readthrownewError(`API request failed with status ${response.status}: ${response.statusText}`);}
Developers cannot programmatically know why an API call failed — they only get "status 422: Unprocessable Entity" with no details.
Fix: Parse the error body and throw a typed ChargilyApiError with status, statusText, and body.
High Severity Issues (8)
HIGH-1: sigPrefix is an empty placeholder
File:src/utils/index.ts line 19
constsigPrefix='';// Define if there's a specific prefix used
This is a TODO comment left as production code. If Chargily's API uses a prefix like sha256= (as GitHub webhooks do), all signature verifications will silently fail.
HIGH-2: No pagination beyond page 1
File:src/classes/client.ts — all list*() methods
All list methods accept per_page but have no page parameter. Developers cannot access data beyond the first page. The ListResponse type correctly models current_page, last_page, next_page_url — but the SDK provides no way to use them.
// Every list method looks like this:publicasynclistCustomers(per_page: number=10): Promise<ListResponse<Customer>>{const endpoint =`customers?per_page=${per_page}`;// No page parameter — stuck on page 1 forever}
HIGH-3: getCheckoutItems and getPaymentLinkItems return wrong types
File:src/classes/client.ts lines 344-354, 424-434
These methods return ListResponse<CheckoutItemParams> and ListResponse<PaymentLinkItemParams> (input types), but the API returns CheckoutItem and PaymentLinkItem (response types with id, amount, currency, etc.). TypeScript will lie to developers about the shape of the data.
HIGH-4: CreateCustomerParams allows empty objects
File:src/types/param.ts lines 3-18
All fields are optional (?). The Chargily API requires at least name, but createCustomer({}) compiles fine and fails at runtime.
HIGH-5: No per_page validation
File:src/classes/client.ts — all list*() methods
per_page: 0, per_page: -1, or per_page: 999999 are sent to the API without any bounds checking.
HIGH-6: UpdatePriceParams.metadata is required instead of optional
File:src/types/param.ts lines 79-82
exportinterfaceUpdatePriceParams{metadata: Record<string,any>;// Missing '?' — forces metadata even if you don't want to update it}
HIGH-7: Type mismatch on shipping_address
File:src/types/param.ts line 127 vs src/types/data.ts line 222
Input type says shipping_address?: string but the response type says shipping_address: Address (an object with country, state, address). These are incompatible.
HIGH-8: Non-nullable fields that should be nullable
File:src/types/data.ts
Several Checkout fields are typed as non-nullable but are optional at creation:
description: string → should be string | null
failure_url: string → should be string | null
webhook_endpoint: string → should be string | null
shipping_address: Address → should be Address | null
Code like checkout.description.toLowerCase() will crash at runtime when the API returns null.
// Warn developers when API key mode doesn't match configured modeconstbalance=awaitclient.getBalance();if(balance.livemode!==(this.mode==='live')){console.warn('[chargily] Mode mismatch: configured as ${this.mode} but API key is ${balance.livemode ? "live" : "test"}');}
Chargily Pay JavaScript SDK - Security & Code Audit Report
Author: @ramdaniAli
Date: 2026-03-08
SDK Version: 2.1.0
Scope: Full codebase review (src/*, package.json, tsconfig.json)
Executive Summary
This audit covers the entire
@chargily/chargily-paySDK codebase. The SDK is a functional HTTP wrapper around the Chargily Pay V2 API, but it contains 5 critical bugs, 8 high-severity issues, and lacks essential infrastructure for a production payment library.The SDK has no tests, no CI/CD, no linting, no retry/timeout logic, no caching, and no typed error handling — all of which are expected in a payment gateway SDK.
Critical Bugs (5)
CRITICAL-1:
updateProduct,updatePrice,updatePaymentLinkuse POST instead of PATCHFiles:
src/classes/client.tslines 192, 263, 386updateCustomercorrectly usesPATCH, but the three other update methods usePOST. This will either create duplicate resources or return 405 errors from the API.Fix: Replace
'POST'with'PATCH'in all three methods.CRITICAL-2:
verifySignaturethrows instead of returningfalseFile:
src/utils/index.tslines 14-37The function signature says
returns boolean, and the early guard (line 16) correctly returnsfalse. But when the signature is actually invalid (the main failure case), it throws an Error instead of returningfalse.This forces developers into a confusing try/catch + boolean pattern for a single function.
Fix: Replace the
throwwithreturn false.CRITICAL-3:
console.login production library codeFile:
src/utils/index.tsline 36Library code must never log to the console. This will print on every valid webhook in every app using this SDK.
Fix: Remove the
console.logstatement.CRITICAL-4:
success_urlvalidation logic is brokenFile:
src/classes/client.tslines 294-299Since
'https://...'always starts with'http', the second condition is dead code. This also means'httpanything'or'http-malicious'pass validation. Additionally,failure_urlandwebhook_endpointare never validated.Fix: Use
new URL()constructor for validation, and validate all URL fields.CRITICAL-5: API error response body is never parsed
File:
src/classes/client.tslines 88-100When the API returns 4xx/5xx, the error body (which contains the actual error message, validation details, etc.) is discarded:
Developers cannot programmatically know why an API call failed — they only get "status 422: Unprocessable Entity" with no details.
Fix: Parse the error body and throw a typed
ChargilyApiErrorwithstatus,statusText, andbody.High Severity Issues (8)
HIGH-1:
sigPrefixis an empty placeholderFile:
src/utils/index.tsline 19This is a TODO comment left as production code. If Chargily's API uses a prefix like
sha256=(as GitHub webhooks do), all signature verifications will silently fail.HIGH-2: No pagination beyond page 1
File:
src/classes/client.ts— alllist*()methodsAll list methods accept
per_pagebut have nopageparameter. Developers cannot access data beyond the first page. TheListResponsetype correctly modelscurrent_page,last_page,next_page_url— but the SDK provides no way to use them.HIGH-3:
getCheckoutItemsandgetPaymentLinkItemsreturn wrong typesFile:
src/classes/client.tslines 344-354, 424-434These methods return
ListResponse<CheckoutItemParams>andListResponse<PaymentLinkItemParams>(input types), but the API returnsCheckoutItemandPaymentLinkItem(response types withid,amount,currency, etc.). TypeScript will lie to developers about the shape of the data.HIGH-4:
CreateCustomerParamsallows empty objectsFile:
src/types/param.tslines 3-18All fields are optional (
?). The Chargily API requires at leastname, butcreateCustomer({})compiles fine and fails at runtime.HIGH-5: No
per_pagevalidationFile:
src/classes/client.ts— alllist*()methodsper_page: 0,per_page: -1, orper_page: 999999are sent to the API without any bounds checking.HIGH-6:
UpdatePriceParams.metadatais required instead of optionalFile:
src/types/param.tslines 79-82HIGH-7: Type mismatch on
shipping_addressFile:
src/types/param.tsline 127 vssrc/types/data.tsline 222Input type says
shipping_address?: stringbut the response type saysshipping_address: Address(an object withcountry,state,address). These are incompatible.HIGH-8: Non-nullable fields that should be nullable
File:
src/types/data.tsSeveral
Checkoutfields are typed as non-nullable but are optional at creation:description: string→ should bestring | nullfailure_url: string→ should bestring | nullwebhook_endpoint: string→ should bestring | nullshipping_address: Address→ should beAddress | nullCode like
checkout.description.toLowerCase()will crash at runtime when the API returnsnull.Missing Features — Proposals
1. Typed Error Hierarchy
This lets developers distinguish network failures from API validation errors from authentication issues.
2. Retry with Exponential Backoff + Timeout
The
request()method should:AbortControllerfor request timeoutRetry-Afterheader support3. Idempotency Key Support
Essential for payment operations to prevent duplicate charges on network retries.
4. Webhook Event Types
5. Full Pagination Support
6. API Status / Health Check
With mode mismatch detection:
7. Payment Link Customization
If the API supports it, expose options like:
8. Caching Layer (Optional Redis Support)
Cache
getProduct,getPrice,getCustomerresponses to reduce API calls under high load.Missing Infrastructure
@types/nodefrom^14to^18, addengines: { node: ">=18" }es2017+ instead ofes5(SDK usesfetchwhich is Node 18+)How to Reproduce
Next Steps
I (@ramdaniAli) am willing to contribute fixes for all the issues listed above. I propose to work in the following order:
pageparameter +listAll*()async generatorsI'd appreciate maintainer feedback on priorities and any API constraints before starting implementation.
This audit was conducted as a community contribution to improve the SDK's reliability and developer experience.