Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Guidance for AI assistants working in this repository.

## Project

`@digitizers/sumit-react` — React component (`<SumitCheckout />`), checkout state hook (`useSumitCheckout`), and Next.js route helpers (`createSumitChargeRoute`, `createSumitWebhookRoute`) for SUMIT / OfficeGuy / Upay payments.
`@godigitizer/sumit-react` — React component (`<SumitCheckout />`), checkout state hook (`useSumitCheckout`), and Next.js route helpers (`createSumitChargeRoute`, `createSumitWebhookRoute`) for SUMIT / OfficeGuy / Upay payments.

Companion package: [`@digitizers/sumit-api`](https://github.com/Digitizers/sumit-api) (peer dependency).
Companion package: [`@godigitizer/sumit-api`](https://github.com/Digitizers/sumit-api) (peer dependency).

## Architecture

Expand Down Expand Up @@ -34,7 +34,7 @@ This package handles payments. Three rules:
2. **Webhook verification is constant-time AND length-independent.** `verifySumitSharedSecret` hashes both the candidate and the secret to a fixed-length digest before comparing — a length-dependent path leaks the secret's byte-length via response timing.
3. **Tokenization is single-flight.** `<SumitCheckout />` uses a synchronous `useRef` guard so two rapid submits cannot both fire `CreateToken` (a stale-closure on `useState` would let the second slip through).

All payloads forwarded to clients pass through `redactSumitPayload` from `@digitizers/sumit-api`.
All payloads forwarded to clients pass through `redactSumitPayload` from `@godigitizer/sumit-api`.

## Workflow

Expand Down
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# @digitizers/sumit-react
# @godigitizer/sumit-react

[![npm](https://img.shields.io/npm/v/@digitizers/sumit-react.svg)](https://www.npmjs.com/package/@digitizers/sumit-react)
[![types](https://img.shields.io/npm/types/@digitizers/sumit-react.svg)](https://www.npmjs.com/package/@digitizers/sumit-react)
[![license](https://img.shields.io/npm/l/@digitizers/sumit-react.svg)](LICENSE)
[![npm](https://img.shields.io/npm/v/@godigitizer/sumit-react.svg)](https://www.npmjs.com/package/@godigitizer/sumit-react)
[![types](https://img.shields.io/npm/types/@godigitizer/sumit-react.svg)](https://www.npmjs.com/package/@godigitizer/sumit-react)
[![license](https://img.shields.io/npm/l/@godigitizer/sumit-react.svg)](LICENSE)
[![react](https://img.shields.io/badge/react-%E2%89%A518-61DAFB?logo=react&logoColor=white)](package.json)
[![next](https://img.shields.io/badge/next.js-app%20router-000000?logo=next.js&logoColor=white)](https://nextjs.org)

> React components and Next.js route helpers for [SUMIT / OfficeGuy / Upay](https://sumit.co.il) payments. The companion to [`@digitizers/sumit-api`](https://github.com/Digitizers/sumit-api).
> React components and Next.js route helpers for [SUMIT / OfficeGuy / Upay](https://sumit.co.il) payments. The companion to [`@godigitizer/sumit-api`](https://github.com/Digitizers/sumit-api).

Ship a working SUMIT checkout flow in a React or Next.js app with two files: a Client Component and a route handler.

Expand Down Expand Up @@ -38,7 +38,7 @@ Ship a working SUMIT checkout flow in a React or Next.js app with two files: a C
## Install

```bash
pnpm add @digitizers/sumit-react @digitizers/sumit-api
pnpm add @godigitizer/sumit-react @godigitizer/sumit-api
```

`react` (and optionally `next`) are peer dependencies of your app. SUMIT's `payments.js` is loaded from `https://app.sumit.co.il/scripts/payments.js` at runtime.
Expand All @@ -50,7 +50,7 @@ pnpm add @digitizers/sumit-react @digitizers/sumit-api
```tsx
"use client";

import { SumitCheckout, useSumitCheckout } from "@digitizers/sumit-react/client";
import { SumitCheckout, useSumitCheckout } from "@godigitizer/sumit-react/client";

export function Checkout() {
const checkout = useSumitCheckout();
Expand Down Expand Up @@ -100,7 +100,7 @@ The component renders the inputs SUMIT expects (`og-ccnum`, `og-expmonth`, `og-e

```ts
// app/api/sumit/charge/route.ts
import { createSumitChargeRoute } from "@digitizers/sumit-react/next";
import { createSumitChargeRoute } from "@godigitizer/sumit-react/next";

export const POST = createSumitChargeRoute({
companyId: Number(process.env.SUMIT_COMPANY_ID),
Expand All @@ -118,7 +118,7 @@ What the handler does:
| Step | Behaviour |
| --------- | -------------------------------------------------------------------------------------------------------- |
| Validate | Checks the JSON body shape (`singleUseToken`, `customer`, `item`). |
| Build | Calls `buildRecurringChargePayload` from `@digitizers/sumit-api`. |
| Build | Calls `buildRecurringChargePayload` from `@godigitizer/sumit-api`. |
| Send | `POST`s to `https://api.sumit.co.il/billing/recurring/charge/`. |
| Normalize | Calls `normalizeRecurringChargeResponse`. |
| Respond | `200` success, `402` declined, `400` bad input, `502` upstream failure — sensitive fields **redacted**. |
Expand All @@ -129,7 +129,7 @@ What the handler does:

```ts
// app/api/sumit/webhook/route.ts
import { createSumitWebhookRoute, verifySumitSharedSecret } from "@digitizers/sumit-react/next";
import { createSumitWebhookRoute, verifySumitSharedSecret } from "@godigitizer/sumit-react/next";

export const POST = createSumitWebhookRoute({
verify: verifySumitSharedSecret(process.env.SUMIT_WEBHOOK_SECRET!),
Expand Down Expand Up @@ -173,14 +173,14 @@ Header verification is preferred because query strings are commonly stored in ac
| **Server credential leakage** | The full `apiKey` lives only in `createSumitChargeRoute`; `./client` and `./next` are separate exports so client bundles cannot transitively pull the server secret. |
| **Webhook spoofing** | `verifySumitSharedSecret` checks the `x-sumit-secret` header by default and hashes both the candidate and the secret to a fixed 32-byte digest before comparing — the comparison is constant-time **and** length-independent, so response timing leaks neither secret content nor secret length. Query-string secrets are opt-in only because URLs commonly land in logs. |
| **Double-submit / token reuse** | `<SumitCheckout />` uses a synchronous ref guard so two rapid submits cannot both fire `CreateToken` (single-use tokens are exactly that — single-use). |
| **Logging sensitive data** | Every event the route helpers return passes through `redactSumitPayload` from `@digitizers/sumit-api`. |
| **Logging sensitive data** | Every event the route helpers return passes through `redactSumitPayload` from `@godigitizer/sumit-api`. |

---

## API surface

```ts
// from @digitizers/sumit-react/client
// from @godigitizer/sumit-react/client
SumitCheckout(props): JSX.Element
props.companyId, apiPublicKey, environment?, language?
props.requireCvv?, requireCitizenId?
Expand All @@ -190,7 +190,7 @@ useSumitCheckout(): { ref, status, error, token, submit, reset, handleToken, han
loadSumitPayments(env?): Promise<SumitPaymentsSdk>
createSingleUseToken(settings): Promise<string>

// from @digitizers/sumit-react/next
// from @godigitizer/sumit-react/next
createSumitChargeRoute(config): (request: Request) => Promise<Response>
createSumitWebhookRoute(config): (request: Request) => Promise<Response>
verifySumitSharedSecret(secret, options?): SumitWebhookVerifier
Expand All @@ -200,7 +200,7 @@ verifySumitSharedSecret(secret, options?): SumitWebhookVerifier

## Local development

This package has `@digitizers/sumit-api` as a peer dependency. While `sumit-api` is being published to npm, the dev dependency in this repo points at `file:../sumit-api`, so cloning both repos as siblings is the supported local setup:
This package has `@godigitizer/sumit-api` as a peer dependency. While `sumit-api` is being published to npm, the dev dependency in this repo points at `file:../sumit-api`, so cloning both repos as siblings is the supported local setup:

```text
~/code/
Expand All @@ -217,7 +217,7 @@ pnpm test # vitest run
pnpm build # tsc → dist/
```

Once `@digitizers/sumit-api` is published, the dev dependency will switch to a regular semver range and CI will install it from the registry.
Once `@godigitizer/sumit-api` is published, the dev dependency will switch to a regular semver range and CI will install it from the registry.

---

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@digitizers/sumit-react",
"name": "@godigitizer/sumit-react",
"version": "0.1.1",
"description": "React components and Next.js route helpers for SUMIT/OfficeGuy/Upay payments.",
"license": "MIT",
Expand Down Expand Up @@ -48,11 +48,11 @@
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"@digitizers/sumit-api": ">=0.1.0",
"@godigitizer/sumit-api": ">=0.1.0",
"react": ">=18.0.0"
},
"devDependencies": {
"@digitizers/sumit-api": "file:../sumit-api",
"@godigitizer/sumit-api": "file:../sumit-api",
"@testing-library/react": "^16.1.0",
"@types/node": "^20.19.35",
"@types/react": "^19.0.0",
Expand Down
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/next/createChargeRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import {
buildRecurringChargePayload,
normalizeRecurringChargeResponse,
redactSumitPayload,
} from "@digitizers/sumit-api";
} from "@godigitizer/sumit-api";
import type {
BuildRecurringChargePayloadParams,
NormalizedSumitEvent,
SumitCurrency,
} from "@digitizers/sumit-api";
} from "@godigitizer/sumit-api";

const DEFAULT_BASE_URL = "https://api.sumit.co.il";
const DEFAULT_PATH = "/billing/recurring/charge/";
Expand Down
4 changes: 2 additions & 2 deletions src/next/createWebhookRoute.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { normalizeSumitIncomingPayload, redactSumitPayload } from "@digitizers/sumit-api";
import type { NormalizedSumitEvent } from "@digitizers/sumit-api";
import { normalizeSumitIncomingPayload, redactSumitPayload } from "@godigitizer/sumit-api";
import type { NormalizedSumitEvent } from "@godigitizer/sumit-api";

export type SumitWebhookVerifier = (request: Request) => boolean | Promise<boolean>;

Expand Down
Loading