# ANAF e-Factura TypeScript SDK

A comprehensive TypeScript SDK for interacting with the Romanian ANAF e-Factura system. This SDK provides OAuth 2.0 authentication, document upload/download, validation, UBL generation, and company data lookup capabilities.

## Features

- **OAuth 2.0 Authentication**: Complete OAuth flow with USB token support
- **Document Operations**: Upload, status checking, and download
- **Message Management**: List and paginate invoice messages
- **Validation**: XML validation and digital signature verification
- **PDF Conversion**: Convert XML invoices to PDF format
- **UBL Generation**: Create compliant UBL 2.1 XML invoices
- **Company Data Lookup**: Fetch Romanian company details from public ANAF API
- **TypeScript**: Full type safety and IntelliSense support

## Installation

```bash
pnpm add efactura-ts-sdk
```

## Quick Start

The SDK is organized into four main classes:

### 1. AnafAuthenticator - OAuth 2.0 Authentication

```typescript
import { AnafAuthenticator } from 'efactura-ts-sdk';

const auth = new AnafAuthenticator({
  clientId: 'your-oauth-client-id',
  clientSecret: 'your-oauth-client-secret',
  redirectUri: 'https://your-app.com/oauth/callback',
});

// Get authorization URL (user will authenticate with USB token)
const authUrl = auth.getAuthorizationUrl();
console.log('Redirect user to:', authUrl);

// Exchange authorization code for tokens
const tokens = await auth.exchangeCodeForToken(authorizationCode);
console.log('Access token:', tokens.access_token);

// Refresh tokens when needed
const newTokens = await auth.refreshAccessToken(tokens.refresh_token);
```

### 2. AnafClient - API Operations

```typescript
import { AnafClient } from 'efactura-ts-sdk';

const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: true, // Use test environment
});

// Upload a document
const uploadResult = await client.uploadDocument(tokens.access_token, xmlContent, {
  standard: 'UBL',
  executare: true,
});

// Check upload status
const status = await client.getUploadStatus(tokens.access_token, uploadResult.index_incarcare);

// Download processed document
if (status.id_descarcare) {
  const result = await client.downloadDocument(tokens.access_token, status.id_descarcare);
}

// List recent messages
const messages = await client.getMessages(tokens.access_token, {
  zile: 7, // Last 7 days
  filtru: 'E', // Only errors
});

// Validate XML
const validation = await client.validateXml(tokens.access_token, xmlContent, 'FACT1');

// Convert XML to PDF
const pdfBuffer = await client.convertXmlToPdf(tokens.access_token, xmlContent, 'FACT1');
```

### 3. UblBuilder - UBL XML Generation

```typescript
import { UblBuilder } from 'efactura-ts-sdk';

const builder = new UblBuilder();

const xml = builder.generateInvoiceXml({
  invoiceNumber: 'INV-2024-001',
  issueDate: new Date(),
  supplier: {
    registrationName: 'Company SRL',
    companyId: 'RO12345678',
    vatNumber: 'RO12345678',
    address: {
      street: 'Str. Example 1',
      city: 'Bucharest',
      postalZone: '010101',
    },
  },
  customer: {
    registrationName: 'Customer SRL',
    companyId: 'RO87654321',
    address: {
      street: 'Str. Customer 2',
      city: 'Cluj-Napoca',
      postalZone: '400001',
    },
  },
  lines: [
    {
      description: 'Product/Service',
      quantity: 1,
      unitPrice: 100,
      taxPercent: 19,
    },
  ],
  isSupplierVatPayer: true,
});
```

### 4. AnafDetailsClient - Company Data Lookup

```typescript
import { AnafDetailsClient } from 'efactura-ts-sdk';

const detailsClient = new AnafDetailsClient({
  timeout: 30000,
  url: 'https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva', // Optional: custom ANAF API URL
});

// Fetch company data by VAT code (single company)
const result = await detailsClient.getCompanyData('RO12345678');
if (result.success) {
  console.log('Company:', result.data[0].name);
  console.log('Address:', result.data[0].address);
  console.log('VAT registered:', result.data[0].scpTva);
  console.log('Registration number:', result.data[0].registrationNumber);
  console.log('Phone:', result.data[0].contactPhone);
} else {
  console.error('Error:', result.error);
}

// Validate VAT code format
const isValid = await detailsClient.isValidVatCode('RO12345678');
console.log('Valid format:', isValid);

// Batch fetch multiple companies (single API call)
const batchResult = await detailsClient.batchGetCompanyData(['RO12345678', 'RO87654321', 'RO11111111']);
if (batchResult.success) {
  batchResult.data.forEach((company, index) => {
    console.log(`Company ${index + 1}:`, company.name);
    console.log(`VAT registered:`, company.scpTva);
  });
} else {
  console.error('Batch error:', batchResult.error);
}

// Configuration options
const customClient = new AnafDetailsClient({
  timeout: 60000, // 60 second timeout
  url: 'https://custom-anaf-proxy.example.com/api/tva', // Custom endpoint (e.g., proxy server)
});
```

## AnafDetailsClient Configuration

The `AnafDetailsClient` supports the following configuration options:

| Option    | Type     | Default                                                     | Description                     |
| --------- | -------- | ----------------------------------------------------------- | ------------------------------- |
| `timeout` | `number` | `30000`                                                     | Request timeout in milliseconds |
| `url`     | `string` | `'https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva'` | ANAF API endpoint URL           |

### Configuration Examples

```typescript
// Default configuration
const client = new AnafDetailsClient();

// Custom timeout
const clientWithTimeout = new AnafDetailsClient({
  timeout: 60000, // 60 seconds
});

// Custom API endpoint (useful for proxy servers or testing)
const clientWithCustomUrl = new AnafDetailsClient({
  url: 'https://your-proxy.example.com/anaf-api',
  timeout: 45000,
});

// Minimal configuration
const minimalClient = new AnafDetailsClient({
  timeout: 15000, // Fast timeout for quick responses
});
```

### Use Cases for Custom URL

- **Proxy Server**: Route requests through your own proxy for logging/monitoring
- **Load Balancer**: Distribute requests across multiple ANAF endpoints
- **Testing**: Point to a mock server during development
- **Regional Endpoints**: Use different ANAF regional servers if available
- **Corporate Firewall**: Route through approved corporate gateways

## Complete Example

```typescript
import { AnafAuthenticator, AnafClient, AnafDetailsClient, UblBuilder } from 'efactura-ts-sdk';

// Setup
const auth = new AnafAuthenticator({
  clientId: process.env.ANAF_CLIENT_ID,
  clientSecret: process.env.ANAF_CLIENT_SECRET,
  redirectUri: 'https://myapp.com/oauth/callback',
});

const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: true,
});

const detailsClient = new AnafDetailsClient();
const builder = new UblBuilder();

// 1. Get customer company data
const customerData = await detailsClient.getCompanyData('RO87654321');
if (!customerData.success) {
  throw new Error(`Customer not found: ${customerData.error}`);
}

// 2. Authentication (one-time setup)
const authUrl = auth.getAuthorizationUrl();
// Direct user to authUrl, they authenticate with USB token
const tokens = await auth.exchangeCodeForToken(authCode);

// 3. Generate invoice XML using fetched company data
const xml = builder.generateInvoiceXml({
  invoiceNumber: 'INV-2024-001',
  issueDate: new Date(),
  supplier: {
    registrationName: 'My Company SRL',
    companyId: 'RO12345678',
    vatNumber: 'RO12345678',
    address: {
      street: 'Str. Example 1',
      city: 'Bucharest',
      postalZone: '010101',
    },
  },
  customer: {
    registrationName: customerData.data[0].name,
    companyId: customerData.data[0].vatCode,
    vatNumber: customerData.data[0].vatCode,
    address: {
      street: customerData.data[0].address,
      city: 'Cluj-Napoca', // Parse from address if needed
      postalZone: customerData.data[0].postalCode || '400001',
    },
  },
  lines: [
    {
      description: 'Consulting Services',
      quantity: 1,
      unitPrice: 1000,
      taxPercent: customerData.data[0].scpTva ? 19 : 0, // Apply VAT if customer is VAT registered
    },
  ],
  isSupplierVatPayer: true,
});

// 4. Upload to ANAF
const uploadResult = await client.uploadDocument(tokens.access_token, xml);

// 5. Monitor status
const status = await client.getUploadStatus(tokens.access_token, uploadResult.index_incarcare);

// 6. Download result
if (status.id_descarcare) {
  const result = await client.downloadDocument(tokens.access_token, status.id_descarcare);
}
```

## Development & Testing

### Prerequisites

1. **USB Security Token**: Required for ANAF authentication

   - Supported tokens: Any qualified certificate from Romanian CA
   - Install manufacturer drivers (SafeNet, Gemalto, etc.)
   - Certificate must be registered with ANAF SPV

2. **ANAF OAuth Application**: Register at [ANAF Portal](https://anaf.ro)
   - Navigate: Servicii Online → Înregistrare utilizatori → DEZVOLTATORI APLICAȚII
   - Register application with your callback URL

### Environment Setup

Create a `.env` file in your project root:

```env
ANAF_CLIENT_ID=your_oauth_client_id_here
ANAF_CLIENT_SECRET=your_oauth_client_secret_here
```

### Local Development with ngrok

For local testing, you need a public HTTPS URL for OAuth callbacks:

1. **Install ngrok**:

   ```bash
   # Using npm
   npm install -g ngrok

   # Or download from https://ngrok.com/
   ```

2. **Expose local server**:

   ```bash
   # Start your local server on port 3000
   npm start

   # In another terminal, expose it publicly
   ngrok http 3000
   ```

3. **Update OAuth Settings**:

   - Copy the ngrok HTTPS URL (e.g., `https://abc123.ngrok.io`)
   - Register callback URL: `https://abc123.ngrok.io/oauth/callback`
   - Update your AnafAuthenticator configuration:

   ```typescript
   const auth = new AnafAuthenticator({
     clientId: process.env.ANAF_CLIENT_ID,
     clientSecret: process.env.ANAF_CLIENT_SECRET,
     redirectUri: 'https://abc123.ngrok.io/oauth/callback', // Your ngrok URL
   });
   ```

### OAuth Authentication Flow

The complete OAuth flow with USB token authentication:

1. **Generate Authorization URL**:

   ```typescript
   const authUrl = auth.getAuthorizationUrl();
   console.log('Direct user to:', authUrl);
   ```

2. **User Authentication Process**:

   - User clicks/visits the authorization URL
   - ANAF login page opens
   - **Insert USB Token**: User inserts USB security token
   - **Enter PIN**: User enters token PIN when prompted
   - **Certificate Selection**: Browser shows certificate selection dialog
   - **Select Certificate**: User selects appropriate certificate
   - **Authorize Application**: User grants permissions to your app
   - **Redirect**: Browser redirects to your callback URL with authorization code

3. **Handle Callback**:

   ```typescript
   // Your callback endpoint receives: ?code=AUTH_CODE&state=STATE
   app.get('/oauth/callback', async (req, res) => {
     const { code } = req.query;

     try {
       const tokens = await auth.exchangeCodeForToken(code);
       // Store tokens securely
       res.send('Authentication successful!');
     } catch (error) {
       res.status(400).send('Authentication failed');
     }
   });
   ```

4. **Use Access Token**:

   ```typescript
   // Token is valid for 1 hour
   const client = new AnafClient({ vatNumber: 'RO12345678' });
   const result = await client.uploadDocument(tokens.access_token, xmlContent);
   ```

5. **Refresh Tokens**:
   ```typescript
   // Refresh before expiration
   const newTokens = await auth.refreshAccessToken(tokens.refresh_token);
   ```

### Automated Testing

The SDK includes comprehensive Jest tests with an integrated OAuth flow:

```bash
# Run all tests
pnpm test

# Run OAuth authentication tests with callback server
pnpm test:auth

# Run tests with coverage
pnpm test:coverage
```

### Manual OAuth Testing

The test suite includes a helpful OAuth testing flow:

1. **Start Test**:

   ```bash
   pnpm test:auth
   ```

2. **Callback Server**: Automatically starts on `http://localhost:4040`

3. **Get OAuth URL**: Test displays authorization URL in console

4. **Complete OAuth**:

   - Copy URL to browser
   - Insert USB token when prompted
   - Enter PIN and select certificate
   - Authorize application
   - Browser redirects to `localhost:4040/callback`

5. **Automatic Token Handling**: Test captures code and exchanges for tokens

### Testing Environment

- **Test Environment**: All tests use ANAF test environment
- **OAuth Endpoints**: `logincert.anaf.ro`
- **API Endpoints**: `api.anaf.ro/test`
- **Callback URL**: `http://localhost:4040/callback` (for tests)

### Token Management

- Tokens are automatically saved to `token.secret` during tests
- Access tokens expire in 1 hour
- Refresh tokens have longer validity
- Tests automatically refresh expired tokens
- Invalid tokens are cleaned up automatically

### Troubleshooting

#### USB Token Issues

```
❌ Certificate selection failed
```

**Solutions**:

- Ensure USB token is properly inserted
- Install manufacturer drivers
- Try different browsers (Chrome recommended)
- Check certificate validity in browser settings

#### OAuth Callback Issues

```
❌ Redirect URI mismatch
```

**Solutions**:

- Verify callback URL matches registered URL exactly
- Include protocol (https://) and path
- For ngrok: use HTTPS URL, not HTTP
- Check for trailing slashes

#### Network Issues

```
❌ Connection refused or timeout
```

**Solutions**:

- Check internet connection
- Verify firewall settings
- For ngrok: ensure tunnel is active
- Try different ngrok region: `ngrok http 3000 --region eu`

#### Token Expiration

```
❌ Access token expired
```

**Solutions**:

- Use refresh token to get new access token
- Implement automatic token refresh in your app
- Store token expiration time and refresh proactively

## API Coverage

The SDK implements all endpoints from the ANAF e-Factura OpenAPI specification:

### Authentication

- ✅ OAuth 2.0 authorization flow
- ✅ Token exchange and refresh

### Document Operations

- ✅ Upload documents (`/upload`, `/uploadb2c`)
- ✅ Check upload status (`/stareMesaj`)
- ✅ Download processed documents (`/descarcare`)

### Message Management

- ✅ List messages with pagination (`/listaMesajePaginatieFactura`)
- ✅ List recent messages (`/listaMesajeFactura`)

### Validation & Conversion

- ✅ XML validation (`/validare/{standard}`)
- ✅ Digital signature validation (`/api/validate/signature`)
- ✅ XML to PDF conversion (`/transformare/{standard}`)
- ✅ XML to PDF without validation (`/transformare/{standard}/DA`)

### UBL Generation

- ✅ UBL 2.1 compliant XML generation
- ✅ Romanian CIUS-RO specification support

### Company Data Lookup

- ✅ Fetch company data by VAT code
- ✅ Validate VAT code format
- ✅ Batch fetch multiple companies
- ✅ Cache management

## Environment Configuration

The SDK supports both test and production environments:

```typescript
// Test environment (recommended for development)
const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: true,
});

// Production environment
const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: false,
});
```

## Error Handling

The SDK provides specific error types for different scenarios:

```typescript
import { AnafAuthenticationError, AnafValidationError, AnafApiError } from 'efactura-ts-sdk';

try {
  await client.uploadDocument(token, xml);
} catch (error) {
  if (error instanceof AnafAuthenticationError) {
    // Handle authentication issues - refresh token or re-authenticate
    console.log('Authentication failed:', error.message);
  } else if (error instanceof AnafValidationError) {
    // Handle validation errors - fix XML or parameters
    console.log('Validation error:', error.message);
  } else if (error instanceof AnafApiError) {
    // Handle API errors - check status, retry, or contact support
    console.log('API error:', error.message);
  }
}
```

## TypeScript Support

The SDK is written in TypeScript and provides comprehensive type definitions:

```typescript
import type { InvoiceInput, UploadStatus, ListMessagesResponse, ValidationResult, OAuthTokens } from 'efactura-ts-sdk';
```

## Security Best Practices

- **Never commit tokens**: Add `token.secret` and `.env` to `.gitignore`
- **Use HTTPS**: Always use HTTPS for OAuth callbacks in production
- **Validate certificates**: Ensure USB token certificates are valid and not expired
- **Secure token storage**: Store tokens securely (encrypted, database, secure storage)
- **Implement refresh**: Automatically refresh tokens before expiration
- **Test environment**: Use test mode for development and staging

## Production Deployment

When deploying to production:

1. **Register Production OAuth App**:

   - Use your production domain for callback URL
   - Get separate client credentials for production

2. **Environment Configuration**:

   ```typescript
   const client = new AnafClient({
     vatNumber: 'RO12345678',
     testMode: false, // Production mode
   });
   ```

3. **Secure Callback Handling**:

   - Use HTTPS for all OAuth callbacks
   - Validate state parameter
   - Implement CSRF protection
   - Log authentication events

4. **Token Management**:
   - Store tokens securely (encrypted database)
   - Implement automatic refresh
   - Handle refresh token expiration gracefully
   - Monitor token usage and expiration

## License

MIT License - see LICENSE file for details.

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Run tests and linting
6. Submit a pull request

## Support

For questions about:

- **ANAF e-Factura API**: Check [ANAF official documentation](https://mfinante.gov.ro/web/efactura/informatii-tehnice)
- **This SDK**: Open an issue on GitHub
- **OAuth Setup**: Consult ANAF SPV documentation: [ANAF official documentation](https://static.anaf.ro/static/10/Anaf/Informatii_R/API/Oauth_procedura_inregistrare_aplicatii_portal_ANAF.pdf)
- **USB Token Issues**: Contact your certificate provider

---

**Perfect for**: SaaS applications, accounting software, ERP integrations, invoicing systems

**Requirements**: USB security token, ANAF OAuth registration, Node.js 16+
