npm package: @hypay/typescript-sdk
Description: TypeScript SDK for the Hypay API
Author: Reouven Mimoun
Latest version: 1.0.1

# @hypay/typescript-sdk

A fully typed, production-ready TypeScript SDK for the [Hyp Pay](https://pay.hyp.co.il/) payment gateway API (Israel).

## Features

- **Pay Protocol** — Generate signed payment page URLs with tamper-proof signatures
- **Signature Verification** — Verify transactions from success/failure redirect pages
- **Soft Protocol** — Server-side transactions with tokens, Apple Pay, and Google Pay
- **Token Management** — Obtain and reuse tokens for recurring charges (auto-parses Tokef)
- **J5 Transactions** — Reserve and later charge credit lines
- **Postpone Transactions** — Create delayed charges and commit them within 72 hours
- **Cancel & Refund** — Cancel same-day transactions or refund by transaction ID / token
- **Subscriptions (HK)** — Create standing orders with initial payments, terminate/activate
- **EzCount Invoices** — Generate signed invoice URLs, build item strings with validation
- **Offline Invoices** — Cash, Check, and Multi-check invoice creation
- **Error Handling** — Typed errors with full Hypay + Shva EMV error code mappings
- **Production Ready** — Timeouts, custom fetch, lifecycle hooks, Israeli ID validation
- **Tree-shakable** — ESM + CJS dual output, zero runtime dependencies

## Installation

```bash
npm install @hypay/typescript-sdk
```

**Requirements:** Node.js >= 18 (uses native `fetch`)

## Quick Start

```typescript
import { HypayClient, Coin, PageLang } from "@hypay/typescript-sdk";

const client = new HypayClient({
  masof: "0010131918",       // Terminal number (10 digits)
  apiKey: "your-api-key",    // API key from Settings > Terminal Settings
  passP: "your-password",    // PassP from terminal settings
});
```

---

## Pay Protocol — Payment Page

### Step 1 + 2: Create a Signed Payment Page URL

```typescript
const url = await client.createPaymentPageUrl({
  Info: "Order #12345",
  Amount: 100,
  UserId: "203269535",
  ClientName: "Israel",
  ClientLName: "Israeli",
  email: "customer@example.com",
  phone: "098610338",
  cell: "0505555555",
  street: "Levanon 3",
  city: "Netanya",
  zip: "42361",
  Tash: 2,
  Coin: Coin.ILS,
  PageLang: PageLang.HEB,
  tmp: 1,
  MoreData: true,
  Sign: true,
  UTF8: true,
  UTF8out: true,
  sendemail: true,
});

// Redirect the customer to `url`
```

### Step 1 only: Sign Parameters (for custom URL building)

```typescript
const result = await client.sign({
  Info: "Order #123",
  Amount: 100,
  // ...
});

console.log(result.signature); // The signature hash
console.log(result.raw);       // Full signed query string

// Build the URL yourself
const paymentUrl = client.buildPaymentPageUrl(result.raw);
```

### Step 3: Handle Success Redirect

When the customer completes payment, they're redirected to your success page with transaction parameters in the URL.

```typescript
import { parsePaymentPageResponse } from "@hypay/typescript-sdk";

// In your success page handler:
const response = parsePaymentPageResponse(req.url);
console.log(response.Id);      // Transaction ID
console.log(response.CCode);   // "0" = success
console.log(response.Amount);  // Amount charged
console.log(response.ACode);   // Confirmation code
console.log(response.L4digit); // Last 4 digits (when MoreData=True)
console.log(response.Bank);    // Acquirer (when MoreData=True)
```

### Step 4: Verify Transaction Signature

```typescript
// Option A: Verify from parsed parameters
import { parseRedirectUrl } from "@hypay/typescript-sdk";

const params = parseRedirectUrl(req.url);
const result = await client.verify(params);

if (result.verified) {
  // Transaction is legitimate — process the order
} else {
  // CCode=902 — verification failed, do NOT process
}

// Option B: Verify directly from URL
const result = await client.verifyRedirectUrl(req.url);
if (result.verified) { /* OK */ }
```

---

## Soft Protocol — Server-Side Transactions

### Token-Based Transaction

```typescript
const result = await client.soft({
  CC: "1315872608557940000",   // Token (19 digits)
  Tmonth: "04",
  Tyear: "2025",
  Amount: 50,
  Info: "Subscription renewal",
  UserId: "203269535",
  ClientName: "Israel",
  ClientLName: "Israeli",
  email: "customer@example.com",
  Coin: Coin.ILS,
  Token: true,
  MoreData: true,
  UTF8: true,
  UTF8out: true,
  sendemail: true,
});

console.log(result.Id);    // Transaction ID
console.log(result.ACode); // Confirmation code
console.log(result.UID);   // UID for J5 charge
// Access MoreData fields via result.params:
console.log(result.params.Bank);    // Acquirer
console.log(result.params.Brand);   // Card brand
console.log(result.params.L4digit); // Last 4 digits
```

### Apple Pay / Google Pay

```typescript
const result = await client.soft({
  WalletToken: walletTokenJsonString,  // From Apple Pay / Google Pay SDK
  Amount: 100,
  Info: "Purchase",
  UserId: "203269535",
  ClientName: "Israel",
  Coin: Coin.ILS,
  MoreData: true,
  UTF8: true,
  UTF8out: true,
});
```

---

## Token Management

### Get a Token

```typescript
const token = await client.getToken({
  TransId: "12788261",
  Fild1: "customer-123",  // Your reference (not saved in Hypay)
});

console.log(token.Token);   // "1315872608557940000" (19 digits)
console.log(token.Tokef);   // "2504" (YYMM format)
console.log(token.Tmonth);  // "04"  (auto-parsed)
console.log(token.Tyear);   // "2025" (auto-parsed)

// Save token.Token, token.Tmonth, token.Tyear, and customer details
// (Hypay does NOT store user info or card validity with the token)
```

### Use Token in a Soft Transaction

```typescript
await client.soft({
  CC: token.Token,
  Tmonth: token.Tmonth,
  Tyear: token.Tyear,
  Token: true,
  Amount: 50,
  Info: "Monthly charge",
  UserId: savedUserId,
  ClientName: savedName,
});
```

### Cross-Terminal Token Usage

```typescript
await client.soft({
  CC: token.Token,
  Tmonth: token.Tmonth,
  Tyear: token.Tyear,
  Token: true,
  tOwner: "0010020610",  // Terminal that owns the token
  Amount: 50,
  Info: "Cross-terminal charge",
  UserId: "203269535",
  ClientName: "Israel",
});
```

### Manual Tokef Parsing

```typescript
import { parseTokef } from "@hypay/typescript-sdk";

const { Tmonth, Tyear } = parseTokef("2504");
// Tmonth = "04", Tyear = "2025"
```

---

## J5 Transaction — Credit Line Reservation

### Reserve a Credit Line

```typescript
const reservation = await client.soft({
  CC: token.Token,
  Tmonth: token.Tmonth,
  Tyear: token.Tyear,
  Token: true,
  J5: true,         // Reserve credit line
  MoreData: true,    // Required to get UID
  Amount: 100,
  Info: "Hotel reservation",
  UserId: "203269535",
  ClientName: "Israel",
});

// Save reservation.UID and reservation.ACode
```

### Charge the Reservation (amount <= original)

```typescript
const charge = await client.chargeJ5(
  {
    CC: token.Token,
    Tmonth: token.Tmonth,
    Tyear: token.Tyear,
    Token: true,
    Amount: 80,
    Info: "Hotel checkout",
    UserId: "203269535",
    ClientName: "Israel",
  },
  {
    "inputObj.originalUid": reservation.UID!,
    "inputObj.originalAmount": 8000,  // 80 ILS in agorot
    AuthNum: reservation.ACode,
    "inputObj.authorizationCodeManpik": 7,
  }
);
```

---

## Postpone Transaction

### Create a Postponed Transaction

```typescript
const postponed = await client.soft({
  CC: token.Token,
  Tmonth: token.Tmonth,
  Tyear: token.Tyear,
  Token: true,
  Postpone: true,
  Amount: 100,
  Info: "Pending order verification",
  UserId: "203269535",
  ClientName: "Israel",
  MoreData: true,
});

// postponed.CCode === "800" (valid postpone)
```

### Commit Within 72 Hours

```typescript
const result = await client.commitTransaction({
  TransId: postponed.Id,
  SendHesh: true,
  heshDesc: "Payment for order 1234",
  UTF8: true,
  UTF8out: true,
});

console.log(result.HeshASM); // Invoice number created
```

---

## Cancel & Refund

### Cancel (Same Day, Before 23:20)

```typescript
const result = await client.cancelTransaction({
  TransId: "5890796",
});
```

### Refund by Transaction ID

```typescript
const result = await client.refund({
  TransId: "12290620",
  Amount: 10,
  Tash: 1,
  SendHesh: true,
  UTF8: true,
  UTF8out: true,
});

console.log(result.Id);      // New refund transaction ID
console.log(result.HeshASM); // Credit invoice number
```

### Refund by Token (PAYout)

```typescript
const result = await client.refundByToken({
  CC: "6907500685494032346",
  Tmonth: "04",
  Tyear: "2023",
  Amount: 2,
  Info: "Refund for order 123",
  zPass: "1234",  // Secret refund password (sent to terminal owner's phone)
  Token: true,
  UserId: "000000000",
  ClientName: "Israel",
  SendHesh: true,
  sendemail: true,
  UTF8: true,
  UTF8out: true,
});
```

---

## Subscriptions (HK Module)

### Create a Subscription

```typescript
const url = await client.createSubscriptionUrl({
  Info: "Monthly subscription",
  Amount: 120,
  HK: true,
  Tash: 999,            // Unlimited payments
  freq: 1,               // Monthly
  FirstDate: "2025-06-01",
  OnlyOnApprove: true,
  UserId: "203269535",
  ClientName: "Israel",
  email: "customer@example.com",
  Coin: Coin.ILS,
  UTF8: true,
  UTF8out: true,
  Sign: true,
  MoreData: true,
  sendemail: true,
  SendHesh: true,
});

// The success redirect URL will include HKId (agreement number)
```

### Subscription with Initial Payment

```typescript
const url = await client.createSubscriptionWithInitialPayment({
  Info: "Plan with setup fee",
  Amount: 120,               // Monthly recurring amount
  HK: true,
  Tash: 999,
  freq: 1,
  TashFirstPayment: 50,      // Setup fee amount
  FirstPaymentTash: 3,       // Charge setup fee for 3 months
  FixTash: true,
  OnlyOnApprove: true,
  UserId: "203269535",
  ClientName: "Israel",
  Coin: Coin.ILS,
  UTF8: true,
  UTF8out: true,
  Sign: true,
  MoreData: true,
});
```

### Terminate / Activate an Agreement

```typescript
import { HKNewStatus } from "@hypay/typescript-sdk";

// Terminate
await client.terminateSubscription("64239");
// or: await client.updateHKStatus({ HKId: "64239", NewStat: HKNewStatus.Terminate });

// Reactivate
await client.activateSubscription("64239");
// or: await client.updateHKStatus({ HKId: "64239", NewStat: HKNewStatus.Activate });
```

---

## EzCount Invoices

### Build Invoice Items

```typescript
import { buildItemsString, calculateItemsTotal } from "@hypay/typescript-sdk";

const items = [
  { code: "001", description: "Widget A", quantity: 2, price: 50 },
  { code: "002", description: "Widget B", quantity: 1, price: 100 },
];

const heshDesc = buildItemsString(items);
// "[001~Widget A~2~50][002~Widget B~1~100]"

const total = calculateItemsTotal(items);
// 200 (must match Amount parameter)
```

### Get a Signed Invoice URL

```typescript
const invoiceUrl = await client.getInvoiceUrl({
  TransId: "55373520",
  type: "EZCOUNT",
});

// Or for old system:
const pdfUrl = await client.getInvoiceUrl({
  TransId: "55373520",
  type: "PDF",
  HeshORCopy: true,  // Authentic copy
});
```

---

## Offline Invoices

### Cash Invoice

```typescript
const result = await client.createOfflineInvoice({
  TransType: "Cash",
  Info: "Cash payment",
  Amount: 200,
  UserId: "203269535",
  ClientName: "Israel",
  email: "customer@example.com",
  Pritim: true,
  heshDesc: buildItemsString([
    { code: "001", description: "Product A", quantity: 2, price: 50 },
    { code: "002", description: "Product B", quantity: 1, price: 100 },
  ]),
  SendHesh: true,
  UTF8: true,
  UTF8out: true,
});
```

### Check Invoice

```typescript
const result = await client.createOfflineInvoice({
  TransType: "Check",
  Info: "Check payment",
  Amount: 200,
  Bank: "10",
  Snif: "912",
  PAN: "1234456",
  CheckNum: "11111111",
  Date: "20250211",
  UserId: "203269535",
  ClientName: "Israel",
  SendHesh: true,
  UTF8: true,
  UTF8out: true,
});
```

### Multi-Check Invoice

```typescript
const result = await client.createOfflineInvoice({
  TransType: "Multi",
  Info: "Multi-check payment",
  Amount: 200,  // Total
  Bank: "10,12,11",
  Snif: "912,826,921",
  PAN: "1234456,1111111,222222",
  CheckNum: "11111111,112223,3554568",
  Date: "20250211,20250911,20251012",
  UserId: "203269535",
  ClientName: "Israel",
  SendHesh: true,
  UTF8: true,
  UTF8out: true,
});
```

---

## Error Handling

### Typed Errors

```typescript
import { HypayError, HypayNetworkError, HypayTimeoutError } from "@hypay/typescript-sdk";

try {
  await client.soft({ /* ... */ });
} catch (error) {
  if (error instanceof HypayError) {
    console.error(`CCode: ${error.code}`);       // e.g., "902"
    console.error(`Message: ${error.message}`);   // "Authentication error..."
    console.error(`Raw: ${error.raw}`);           // Full response string
    console.error(`Params:`, error.params);       // Parsed response
  } else if (error instanceof HypayTimeoutError) {
    console.error(`Timeout after ${error.timeoutMs}ms`);
  } else if (error instanceof HypayNetworkError) {
    console.error(`Network error: ${error.message}`);
    console.error(`Cause:`, error.cause);
  }
}
```

### Error Code Utilities

```typescript
import {
  isSuccessCode,
  isShvaError,
  isHypayError,
  getErrorMessage,
  ALL_ERROR_CODES,
} from "@hypay/typescript-sdk";

isSuccessCode("0");    // true  — Approved
isSuccessCode("800");  // true  — Postponed (valid)
isSuccessCode("902");  // false — Authentication error

isShvaError("6");      // true  — Incorrect CVV (Shva)
isHypayError("902");   // true  — Authentication error (Hypay)

getErrorMessage("902"); // "Authentication error — reference differs from configured method"
getErrorMessage("6");   // "Incorrect ID or CVV"
```

---

## Validation Helpers

```typescript
import {
  isValidMasof,
  isTestMasof,
  isValidToken,
  isValidEmail,
  isValidIsraeliId,
} from "@hypay/typescript-sdk";

isValidMasof("0010131918");  // true (10 digits)
isTestMasof("0010131918");   // true (starts with 00100)
isTestMasof("1234567890");   // false

isValidToken("1315872608557940000");  // true (19 digits)
isValidEmail("test@example.com");     // true

isValidIsraeliId("203269535");  // true (valid Teudat Zehut)
isValidIsraeliId("000000000");  // true (special test value)
```

---

## Advanced Configuration

### Custom Timeout

```typescript
const client = new HypayClient({
  masof: "0010131918",
  apiKey: "your-key",
  timeout: 60_000,  // 60 seconds
});
```

### Custom Fetch (Node.js < 18 or test mocks)

```typescript
import nodeFetch from "node-fetch";

const client = new HypayClient({
  masof: "0010131918",
  apiKey: "your-key",
  fetch: nodeFetch as unknown as typeof fetch,
});
```

### Lifecycle Hooks (Logging, Metrics)

```typescript
const client = new HypayClient({
  masof: "0010131918",
  apiKey: "your-key",
  hooks: {
    onRequest: (ctx) => {
      console.log(`[Hypay] ${ctx.method} ${ctx.action}`, ctx.params);
    },
    onResponse: (ctx) => {
      console.log(`[Hypay] ${ctx.action} responded in ${ctx.durationMs}ms`, {
        status: ctx.statusCode,
        CCode: ctx.parsedParams.CCode,
      });
    },
    onError: (error, ctx) => {
      console.error(`[Hypay] ${ctx.action} failed: ${error.code} — ${error.message}`);
      // Send to your error tracking (Sentry, Datadog, etc.)
    },
  },
});
```

---

## Testing

Use a test terminal (Masof starting with `00100`) for development:

| Parameter | Test Value |
|-----------|-----------|
| Masof | `0010131918` |
| PassP | `yaad` |
| API Key | `7110eda4d09e062aa5e4a390b0a572ac0d2c0220` |

**Test Credit Card:**

| Field | Value |
|-------|-------|
| Number | `5326107300020772` |
| Expiry | `05/31` |
| CVV | `033` |
| ID | `890108558` or `000000000` |

Use low amounts (5-10 ILS) for testing. To simulate failures, use a real credit card on a test terminal.

---

## API Reference

### Client Methods

| Method | Description |
|--------|-------------|
| `sign(params)` | Sign payment page parameters (APISign) |
| `buildPaymentPageUrl(qs)` | Build payment page URL from signed query string |
| `createPaymentPageUrl(params)` | Sign + build URL in one call |
| `verify(params)` | Verify transaction from success page params |
| `verifyRedirectUrl(url)` | Parse URL + verify in one call |
| `soft(params)` | Server-side transaction (token, wallet, card) |
| `getToken(params)` | Get reusable token from a transaction |
| `chargeJ5(tokenParams, j5Params)` | Charge a J5 credit line reservation |
| `commitTransaction(params)` | Commit a postponed (800) transaction |
| `cancelTransaction(params)` | Cancel a same-day transaction |
| `refund(params)` | Refund by transaction ID (zikoyAPI) |
| `refundByToken(params)` | Refund via token + zPass (PAYout) |
| `createSubscriptionUrl(params)` | Create HK subscription payment page |
| `createSubscriptionWithInitialPayment(params)` | Subscription with setup fee |
| `updateHKStatus(params)` | Change standing order status |
| `terminateSubscription(hkId)` | Terminate a standing order |
| `activateSubscription(hkId)` | Activate a standing order |
| `getInvoiceUrl(params)` | Get signed EzCount invoice URL |
| `createOfflineInvoice(params)` | Create Cash/Check/Multi invoice |

### Helper Functions

| Function | Description |
|----------|-------------|
| `parseQueryString(qs)` | Parse URL query string to Record |
| `parseRedirectUrl(url)` | Parse success/failure redirect URL |
| `parsePaymentPageResponse(url)` | Parse redirect URL to typed response |
| `parseTokef(tokef)` | Parse YYMM token expiry to Tmonth/Tyear |
| `buildItemsString(items)` | Build heshDesc invoice items string |
| `calculateItemsTotal(items)` | Calculate total from invoice items |
| `isValidMasof(masof)` | Validate terminal number format |
| `isTestMasof(masof)` | Check if terminal is a test terminal |
| `isValidToken(token)` | Validate 19-digit token format |
| `isValidEmail(email)` | Basic email validation |
| `isValidIsraeliId(id)` | Validate Israeli ID (Teudat Zehut) |
| `isSuccessCode(ccode)` | Check if CCode is success (0/600/700/800) |
| `isShvaError(ccode)` | Check if CCode is Shva error (1-200) |
| `isHypayError(ccode)` | Check if CCode is Hypay error (201-999) |
| `getErrorMessage(ccode)` | Get human-readable error description |

---

## License

MIT