{"_id":"@astermd-hq/checkoutchamp-client","name":"@astermd-hq/checkoutchamp-client","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@astermd-hq/checkoutchamp-client","version":"0.0.1","description":"Unofficial Node.js client for the Checkout Champ API: orders, leads, upsales, campaigns, customers, transactions and lander clicks, with opt-in redacted request logging.","keywords":["checkoutchamp","checkout-champ","api-client","rest-client","crm","ecommerce","orders","payments"],"license":"MIT","author":{"name":"AsterMD","email":"admin@astermd.com"},"homepage":"https://github.com/astermd/npm-checkoutchamp-client","repository":{"type":"git","url":"git+https://github.com/astermd/npm-checkoutchamp-client.git"},"bugs":{"url":"https://github.com/astermd/npm-checkoutchamp-client/issues","email":"admin@astermd.com"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"sideEffects":false,"engines":{"node":">=22"},"scripts":{"lint":"eslint .","lint:fix":"eslint . --fix","lint:package":"publint --strict","format":"prettier --write .","format:check":"prettier --check .","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:interop":"node test/interop/esm.test.mjs && node test/interop/cjs.test.cjs","build":"tsup","gate":"npm run format:check && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:interop && npm run lint:package"},"devDependencies":{"@eslint/js":"^10.0.1","@types/node":"^22.10.0","@vitest/coverage-v8":"^5.0.0","eslint":"^10.10.0","eslint-config-prettier":"^10.0.0","prettier":"^3.4.0","publint":"^0.3.0","tsup":"^8.3.0","typescript":"^5.9.3","typescript-eslint":"^8.70.0","vitest":"^5.0.0"},"_id":"@astermd-hq/checkoutchamp-client@0.0.1","gitHead":"b47bdefaad678676928cd2dbb44b5e001276959f","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-JmohOrzzMIdR3c8inD4E6d+D04rV9Fmz0q08uHk/kqbuqGNVViN9Vz+noQhA3aY2SH98sPiHYOeBA1Uszv4Nkw==","shasum":"a7a1e6823647027d342eb5ee0e572248ebfe626b","tarball":"https://registry.npmjs.org/@astermd-hq/checkoutchamp-client/-/checkoutchamp-client-0.0.1.tgz","fileCount":9,"unpackedSize":772661,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@astermd-hq%2fcheckoutchamp-client@0.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDlYiutzn7uduS6TIr99YtW0Q2uT2l6+sQ0U9WyVsYBpwIgIj4XBO2BvSmhfS8gMqplGCeiqDj2W1xvG9IYlykxo1k="}]},"_npmUser":{"name":"astermd","email":"admin@astermd.com"},"directories":{},"maintainers":[{"name":"astermd","email":"admin@astermd.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/checkoutchamp-client_0.0.1_1789059190225_0.2626048311880369"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-10T16:53:10.042Z","0.0.1":"2026-09-10T16:53:10.372Z","modified":"2026-09-10T16:53:10.878Z"},"maintainers":[{"name":"astermd","email":"admin@astermd.com"}],"description":"Unofficial Node.js client for the Checkout Champ API: orders, leads, upsales, campaigns, customers, transactions and lander clicks, with opt-in redacted request logging.","homepage":"https://github.com/astermd/npm-checkoutchamp-client","keywords":["checkoutchamp","checkout-champ","api-client","rest-client","crm","ecommerce","orders","payments"],"repository":{"type":"git","url":"git+https://github.com/astermd/npm-checkoutchamp-client.git"},"author":{"name":"AsterMD","email":"admin@astermd.com"},"bugs":{"url":"https://github.com/astermd/npm-checkoutchamp-client/issues","email":"admin@astermd.com"},"license":"MIT","readme":"# @astermd-hq/checkoutchamp-client\n\nA small, dependency-free Node.js client for the Checkout Champ API — orders,\nleads, upsales, campaigns, customers, transactions and lander clicks — with\nopt-in request logging that is redacted by default.\n\n> **Unofficial.** This is an independent client library. It is not the official\n> Checkout Champ Node.js SDK and is not affiliated with, endorsed by or\n> supported by Checkout Champ. \"Checkout Champ\" and related marks belong to\n> their owner, <https://checkoutchamp.com/>. The name is used here only to\n> identify the API this library talks to. See [LICENSE](LICENSE) for the full\n> notice.\n\nAPI reference: **<https://apidocs.checkoutchamp.com/>**\n\n## Requirements\n\n- **Node.js 22 or newer**, server-side only.\n\nNo runtime dependencies. CI runs the full gate on Node 22 and 24.\n\n## Installation\n\n```bash\nnpm install @astermd-hq/checkoutchamp-client\n```\n\n## Quick start\n\n```ts\nimport { API } from '@astermd-hq/checkoutchamp-client';\n\nconst api = new API(loginId, password); // host defaults to api.checkoutchamp.com\n\nconst { response } = await api.orderQuery({ orderId: '123' }).getInObject();\n\nif (response.result === 'SUCCESS') {\n  // …\n}\n```\n\nThe login ID and password are **required arguments**. This package ships no\ndefault credentials and reads none from the environment.\n\n## ⚠️ Credentials travel in the URL\n\nThe Checkout Champ API authenticates by taking `loginId` and `password` as\n**query string parameters**. That is the provider's design, and this client\nfollows it — but it means your account password appears in the URL of every\nrequest.\n\nBefore deploying, read the [security policy](SECURITY.md). In short: anything\nbetween your application and the internet that records outbound request URLs —\na forward proxy, an egress gateway, an APM agent, a TLS-inspecting appliance —\nwill record your password in cleartext. Audit that path first.\n\nWhat this package does about it:\n\n- TLS certificate verification is always on, with no option to disable it.\n- Debug logging **masks credentials in the logged URL** rather than writing it\n  verbatim (see below).\n- `getPayloadInfo()` never returns credentials.\n\n## Configuration\n\n```ts\nconst api = new API(loginId, password, {\n  host: 'api.checkoutchamp.com',\n  basePath: '',\n  timeout: 30,\n  connectTimeout: 10,\n  debug: false,\n  debugRedact: true,\n  debugFile: undefined,\n  debugRetentionDays: 7,\n  debugTimezone: 'UTC',\n  debugSink: undefined,\n});\n```\n\n| Option               | Type                                       | Default                 | Purpose                                                                                              |\n| -------------------- | ------------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------- |\n| `host`               | `string`                                   | `api.checkoutchamp.com` | **Bare hostname only.** A scheme, path, query, userinfo or backslash is rejected.                    |\n| `basePath`           | `string`                                   | `''`                    | Optional path prefix below the host.                                                                 |\n| `timeout`            | `number`                                   | `30`                    | Transfer timeout in seconds.                                                                         |\n| `connectTimeout`     | `number`                                   | `10`                    | Connection timeout in seconds.                                                                       |\n| `debug`              | `boolean`                                  | `false`                 | Master switch for request/response logging.                                                          |\n| `debugRedact`        | `boolean`                                  | `true`                  | Mask credentials and sensitive fields in the logged URL, headers and body.                           |\n| `debugFile`          | `string`                                   | —                       | Base path for the built-in dated file sink. Required when `debug` is on and no `debugSink` is given. |\n| `debugRetentionDays` | `number`                                   | `7`                     | Days of log history to keep. `0` keeps everything.                                                   |\n| `debugTimezone`      | `string`                                   | `UTC`                   | IANA timezone for log timestamps and dated filenames.                                                |\n| `debugSink`          | `(entry: string) => void \\| Promise<void>` | —                       | Replaces the file sink entirely.                                                                     |\n\nIf your account is issued a different hostname, pass it — the client treats\nevery host identically and applies no environment branching of its own:\n\n```ts\nconst api = new API(loginId, password, { host: 'crm-host-from-your-account' });\n```\n\n`host` accepts a **bare hostname** matched against a strict allowlist:\ndot-separated labels of ASCII letters, digits and hyphens, with an optional\nnumeric `:port`. Anything else — a scheme, a path, a query string, userinfo\n(`@`), or a backslash — is rejected outright, so the scheme and path assembly\nstay inside the client:\n\n```ts\nnew API(id, pw, { host: 'https://api.checkoutchamp.com' }); // throws\nnew API(id, pw, { host: 'api.checkoutchamp.com/v1' }); // throws\nnew API(id, pw, { host: 'api.checkoutchamp.com', basePath: 'v1' }); // correct\n```\n\n## Reading a response\n\nEvery resource method returns a call object that has not been sent yet.\nAwaiting it, or any of its accessors, performs the request exactly once — the\naccessors below all read the same cached result, so calling more than one of\nthem on the same call object never issues a second request. This package adds\nno envelope of its own; what comes back is the provider's own response:\n\n```ts\nawait api.orderQuery().get(); // raw JSON string\nawait api.orderQuery().getInObject(); // decoded to a value\n```\n\nThere is no `getInArray()` in this port. The Composer package's `getInArray()`\nand `getInObject()` differed only in whether PHP's `json_decode` produced an\nassociative array or a `stdClass` object; `JSON.parse` in Node produces exactly\none shape, so there is exactly one decoding accessor. See\n[Migrating from the PHP package](#migrating-from-the-php-package) below.\n\nPass `true` to also receive the endpoint and the parameters you sent:\n\n```ts\nawait api.orderQuery({ orderId: '123' }).getInObject(true);\n```\n\n```ts\n{\n  response: { /* the provider's body, verbatim */ },\n  payload: {\n    endPoint: 'https://api.checkoutchamp.com/order/query/',\n    orderId: '123',\n  },\n}\n```\n\n`payload` never contains your _configured_ `loginId` or `password` — that is\nthe whole guarantee. It otherwise echoes back whatever you passed in\n`params`, verbatim, so on `importOrder`, `preauth` and `importUpsale` it is\nthe full cardholder object you supplied: treat it with the same care you'd\ngive the request URL on those endpoints. A `password` key you (not the\nclient) put in `params` also survives into `payload` even though it never\nreaches the wire — the configured credential silently overwrites it there.\n\nIf the request never reached the provider, `getInObject()` returns\n`{ curlError: '...' }` instead, and `get()` returns the bare failure message\nstring in place of a body. The `curlError` key name is kept for parity with\nthe Composer package this was ported from, even though nothing here uses\ncURL. A response that is not valid JSON raises a `CheckoutChampError`.\n\n## Available methods\n\nConsult the [Checkout Champ API reference](https://apidocs.checkoutchamp.com/)\nfor the fields each endpoint accepts. Every call is a `POST`, and the\nparameters object is sent as query string parameters.\n\n### Orders, leads and upsales\n\n| Method                  | Request                |\n| ----------------------- | ---------------------- |\n| `orderQuery(params?)`   | `POST /order/query/`   |\n| `importLeads(params?)`  | `POST /leads/import/`  |\n| `updateOrder(params?)`  | `POST /order/update/`  |\n| `preauth(params?)`      | `POST /order/preauth/` |\n| `importOrder(params?)`  | `POST /order/import/`  |\n| `importUpsale(params?)` | `POST /upsale/import/` |\n| `confirm(params?)`      | `POST /order/confirm/` |\n| `qa(params?)`           | `POST /order/qa/`      |\n\n### Campaigns\n\n| Method                   | Request                 |\n| ------------------------ | ----------------------- |\n| `campaignQuery(params?)` | `POST /campaign/query/` |\n\n### Customers\n\n| Method                   | Request                   |\n| ------------------------ | ------------------------- |\n| `customerQuery(params?)` | `POST /customer/query/`   |\n| `addnote(params?)`       | `POST /customer/addnote/` |\n\n### Transactions\n\n| Method                       | Request                     |\n| ---------------------------- | --------------------------- |\n| `transactionsQuery(params?)` | `POST /transactions/query/` |\n\n### Landers\n\n| Method                   | Request                             |\n| ------------------------ | ----------------------------------- |\n| `importClick(params?)`   | `POST /landers/clicks/import/`      |\n| `confirmPaypal(params?)` | `POST /transactions/confirmPaypal/` |\n\nEvery method also accepts a second, optional argument of per-call settings —\nsee [`headerRequired`](#headerrequired) below.\n\nExamples:\n\n```ts\nawait api\n  .importLeads({\n    campaignId: '7',\n    emailAddress: 'buyer@example.test',\n  })\n  .getInObject();\n\nawait api.importOrder({ sessionId: 'sess_1', product1_id: '9' }).getInObject();\n\nawait api.qa({ orderId: '123', qaStatus: 'APPROVED' }).getInObject();\n```\n\nParameters you supply cannot shadow the credentials — they are appended last,\nso a `password` key in `params` is overridden by the configured value.\n\n### `headerRequired`\n\nPass `{ headerRequired: true }` as the second argument to any resource method\nto wrap the decoded response with its transport metadata as\n`{ content, header }`, instead of the bare body:\n\n```ts\nconst { response } = await api.orderQuery({ orderId: '123' }, { headerRequired: true }).getInObject();\n// response = {\n//   content: { result: 'SUCCESS', ... },\n//   header: { http_code: 200, url: 'https://api.checkoutchamp.com/order/query/', redirect_count: 0, total_time: 0.2, content_type: 'application/json' },\n// }\n```\n\n`header.url` carries scheme, host and path only — its query string is always\nremoved, deliberately, since that is where `loginId`, `password` and, on the\nbilling endpoints, cardholder data ride. Nothing else in `header` carries\nrequest parameters.\n\nThis is a **per-call** option, not a property you set on a resource — there is\nno resource object exposed to set a property on in this port.\n\n## Errors\n\nEverything the package raises is a `CheckoutChampError`, which extends\n`Error`:\n\n```ts\nimport { CheckoutChampError } from '@astermd-hq/checkoutchamp-client';\n\ntry {\n  const { response } = await api.orderQuery({ orderId: id }).getInObject();\n} catch (e) {\n  if (e instanceof CheckoutChampError) {\n    // empty credentials, non-bare host, unknown method, or a non-JSON response\n  }\n}\n```\n\nProvider-side rejections come back in the response body as the provider's own\n`result` / `message` fields, and transport failures come back as `curlError`\n(from `getInObject()`) or the bare message string (from `get()`) — neither is\nan exception.\n\n## Debug logging\n\nLogging is off unless you turn it on. When on, entries are redacted by default\nand written as copy-pasteable cURL commands with the response beneath.\n\n```ts\nconst api = new API(loginId, password, {\n  debug: true,\n  debugFile: '/var/log/checkoutchamp/client.log',\n  debugRetentionDays: 7,\n});\n```\n\nWhich produces `/var/log/checkoutchamp/client-2026-08-16.log` containing:\n\n```\n[2026-08-16 09:14:02.481000 UTC]\ncurl --location --request POST 'https://api.checkoutchamp.com/order/import/?sessionId=sess_1&cardNumber=[REDACTED]&cvv=[REDACTED]&loginId=[REDACTED]&password=[REDACTED]'\n\n# Response: HTTP 200\n{\"result\":\"SUCCESS\",\"message\":{\"orderId\":\"123\"}}\n```\n\nThe timestamp's fractional part carries millisecond precision, zero-padded to\nsix digits to keep the same width as the Composer package's true microseconds\n(`.481000`, not `.481930`) — Node has no finer-grained wall clock to read from.\nThis is a documented fidelity loss against the PHP original, not a bug.\n\nBecause logging is asynchronous, **call `await api.flushDebugLog()`** before a\nshort-lived process exits — a script, a CLI command, a serverless handler —\nso a queued write is not lost when the process ends. It resolves immediately\nwhen logging is off.\n\n### The URL is redacted, not logged verbatim\n\nThis is a deliberate departure from how most API clients log. Because Checkout\nChamp puts **everything** in the query string — credentials, customer details\nand cardholder data alike — logging the URL verbatim would write your account\npassword and full card numbers to disk on every call.\n\nSo redaction covers the URL too:\n\n- **Masked:** `loginId`, `password`, and every sensitive field name — card\n  number, CVV/CVC, expiry, bank account and routing numbers, IBAN, SSN, tax ID,\n  date of birth, and every `*_token`.\n- **Also masked by shape:** any 13–19 digit value that passes a Luhn check, so a\n  card number is caught even under an unexpected parameter name.\n- **URL fragments are masked wholesale.** Any fragment with content — anything\n  after a `#` — becomes `#[REDACTED]` regardless of what it looks like, because\n  a fragment has no defined internal structure to mask selectively.\n- **Preserved:** the scheme, host and path, plus every parameter that is not\n  sensitive. You can always see which endpoint was called with which order ID.\n\nHeaders and response bodies are redacted by the same field rules.\n\n**Redaction never changes what is sent or what you receive.** The logger reads\nfrom immutable request and response objects and produces a string; the wire\nrequest and the value returned to your code are untouched. The test suite\nasserts this directly.\n\n### Turning redaction off\n\n```ts\nconst api = new API(loginId, password, {\n  debug: true,\n  debugRedact: false, // writes your password and full card numbers to disk\n  debugFile: '/tmp/checkoutchamp-debug.log',\n});\n```\n\nLocal debugging only. **Never enable it in production.**\n\n### File rotation and retention\n\nThe built-in sink writes one file per calendar day, deriving the name from your\nbase path: `/var/log/checkoutchamp/client.log` becomes\n`client-2026-08-16.log`, `client-2026-08-17.log`, and so on.\n\nPruning removes files older than `debugRetentionDays` (default 7; `0` keeps\neverything). It only ever matches this package's own dated filename pattern for\nyour base path — other files in the directory are never touched — and it reads\nthe age from the filename rather than the modification time, so an appended-to\nor restored file keeps its true age. It runs once per process, not once per\nrequest.\n\n`debugRetentionDays: N` in fact retains **`N + 1` calendar dates**, not `N`: a\nfile dated exactly `N` days before the current date is kept, so a window of 7\nkeeps today's file plus the seven days before it — eight dates in total. This\nmatches the Composer package's own cutoff arithmetic exactly, so the same\nconfiguration keeps the same files under either client.\n\n### Sending logs somewhere else\n\nSupply a function and the file sink is replaced entirely. The package then\nwrites no files, and retention becomes your responsibility:\n\n```ts\nconst api = new API(loginId, password, {\n  debug: true,\n  debugSink: async (entry: string): Promise<void> => {\n    await myLogger.debug(entry);\n  },\n});\n```\n\nThe function receives the finished entry, already redacted unless you opted\nout, and may be asynchronous — the logger awaits it on its own serialized\nchain so a slow destination cannot interleave two entries. This is the\nextension point for any external destination — a structured logger, a queue, a\nlog shipper, an object store. A failure inside your sink is caught and\ndiscarded: logging must never break an API call.\n\n### Logs stay sensitive after redaction\n\nA redacted entry still records which account touched which order, customer and\ntransaction, and when. Store logs on encrypted volumes, restrict read access,\nship them only to systems cleared for that data, and apply a retention period\nat least as strict as the rest of your order data.\n\n## Proxy support\n\n```ts\nawait api.withProxy('proxy.example.test:8080', 'user', 'password').orderQuery({ orderId: '123' }).getInObject();\n```\n\nThe setting applies to the next call only.\n\n`proxyUrl` is a bare `host:port`, with no scheme — like `host` on `ClientConfig`,\nit is not a URL. Passing `http://proxy.example.test:8080` fails with a DNS\nlookup error for the literal host `http` (`getaddrinfo ENOTFOUND http`),\nsince the whole string is parsed as `host:port`.\n\nA proxy is supported for **`https` targets only** — every URL this client\nbuilds is `https://`, so this is not a practical limitation, but a `Request`\nbuilt by hand against a non-`https` target with a proxy set returns a\ntransport error rather than being silently sent direct.\n\n## Custom transport\n\nPass anything implementing `HttpClientInterface` as the fourth constructor\nargument to route requests through your own stack, or to test without a\nnetwork:\n\n```ts\nimport { API, type HttpClientInterface, type Request, Response } from '@astermd-hq/checkoutchamp-client';\n\nclass MyTransport implements HttpClientInterface {\n  async send(request: Request): Promise<Response> {\n    console.log(request.method, request.url);\n    return new Response(200, '{\"result\":\"SUCCESS\"}', { http_code: 200 });\n  }\n}\n\nconst api = new API(loginId, password, {}, new MyTransport());\n```\n\nThe default implementation is `HttpsClient`, built on `node:https` — there is\nno `CurlClient` in this port, since there is no cURL. An implementation\n**must not throw** on transport failure; report it through\n`Response.transportError` instead, so the debug logger can still record the\nattempt and the caller receives the documented `curlError` shape rather than\nan unhandled exception.\n\n## Further documentation\n\n- [Integration guide](docs/INTEGRATION_GUIDE.md) — setup, error handling,\n  production logging, troubleshooting.\n- [Architecture](docs/ARCHITECTURE.md) — how a call flows through the package\n  and where to extend it.\n- [Security policy](SECURITY.md) — private disclosure and credential handling.\n- [Changelog](CHANGELOG.md)\n\n## Migrating from the PHP package\n\n| PHP                                                            | Node                                                           |\n| -------------------------------------------------------------- | -------------------------------------------------------------- |\n| `$api->orderQuery([...])->getInArray()`                        | `await api.orderQuery({...}).getInObject()`                    |\n| `$api->orderQuery([...])->getInObject()`                       | `await api.orderQuery({...}).getInObject()`                    |\n| `$api->get()` / `getInArray()` / `getInObject()` on the client | the accessors live on the returned call                        |\n| `$resource->headerRequired = true`                             | `api.orderQuery(params, { headerRequired: true })`             |\n| `CurlClient`                                                   | `HttpsClient`                                                  |\n| —                                                              | `await api.flushDebugLog()` before a short-lived process exits |\n\n## Development\n\n```bash\nnpm ci\nnpm run gate # lint → typecheck → test → build → interop tests → publint\n```\n\nSee [CLAUDE.md](CLAUDE.md) for the conventions the gate enforces and\n[CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute. No test in this\nsuite makes a network call.\n\n## Support\n\nEmail **admin@astermd.com**, or open an issue at\n<https://github.com/astermd/npm-checkoutchamp-client/issues>.\n\nReport security issues privately — see [SECURITY.md](SECURITY.md). Do not open\na public issue for a vulnerability.\n\n## Compliance\n\nUsing this package does not by itself make your application PCI DSS, HIPAA or\nGDPR compliant. It is one component in your system. Scoping, encryption at\nrest, access control, audit logging, breach procedures and your agreements\nwith Checkout Champ and your payment processors remain your responsibility.\n\nNote in particular that `importOrder`, `preauth` and `importUpsale` transmit\ncardholder data as query parameters. Determine with your assessor whether that\nplaces the systems handling those requests — and anything that logs their\nURLs — inside your PCI DSS scope.\n\n## License\n\nMIT — see [LICENSE](LICENSE), including the trademark and affiliation notice.\n","readmeFilename":"README.md","_rev":"1-e139cb4ab0c7f216428c19f8efc17d42"}