mirror of
https://github.com/duhanbalci/iyzico.git
synced 2026-03-03 20:29:18 +00:00
init
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
node_modules/
|
||||||
|
coverage/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
80
README.md
Normal file
80
README.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# @iyzico/payment
|
||||||
|
|
||||||
|
TypeScript SDK for iyzico payment gateway.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @iyzico/payment
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IyzicoClient } from '@iyzico/payment';
|
||||||
|
|
||||||
|
const client = new IyzicoClient({
|
||||||
|
apiKey: 'your-api-key',
|
||||||
|
secretKey: 'your-secret-key',
|
||||||
|
baseUrl: 'https://sandbox-api.iyzipay.com', // optional, defaults to sandbox
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a payment
|
||||||
|
const payment = await client.payment.create({
|
||||||
|
price: '100.00',
|
||||||
|
paidPrice: '100.00',
|
||||||
|
currency: 'TRY',
|
||||||
|
installment: 1,
|
||||||
|
basketId: 'B12345',
|
||||||
|
paymentCard: {
|
||||||
|
cardHolderName: 'John Doe',
|
||||||
|
cardNumber: '5528790000000008',
|
||||||
|
expireMonth: '12',
|
||||||
|
expireYear: '2030',
|
||||||
|
cvc: '123',
|
||||||
|
},
|
||||||
|
buyer: { /* ... */ },
|
||||||
|
shippingAddress: { /* ... */ },
|
||||||
|
billingAddress: { /* ... */ },
|
||||||
|
basketItems: [ /* ... */ ],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check BIN
|
||||||
|
const binInfo = await client.binCheck.check({ binNumber: '552879' });
|
||||||
|
|
||||||
|
// Get installment options
|
||||||
|
const installments = await client.installment.check({
|
||||||
|
binNumber: '552879',
|
||||||
|
price: '100.00',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
- `payment` - 3DS and Non-3DS payments
|
||||||
|
- `binCheck` - BIN number lookup
|
||||||
|
- `installment` - Installment options
|
||||||
|
- `cardStorage` - Save and manage cards
|
||||||
|
- `cancelRefund` - Cancellations and refunds
|
||||||
|
- `reporting` - Transaction reports
|
||||||
|
- `subscription` - Subscription management
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Option | Type | Default | Description |
|
||||||
|
|--------|------|---------|-------------|
|
||||||
|
| apiKey | string | - | API key (required) |
|
||||||
|
| secretKey | string | - | Secret key (required) |
|
||||||
|
| baseUrl | string | sandbox | API base URL |
|
||||||
|
| locale | 'tr' \| 'en' | 'tr' | Response locale |
|
||||||
|
| retryOnRateLimit | boolean | false | Retry on 429 |
|
||||||
|
| maxRetries | number | 3 | Max retry attempts |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- TypeScript 5.0+ (recommended)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
2465
package-lock.json
generated
Normal file
2465
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
package.json
Normal file
51
package.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "@iyzico/payment",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Iyzico payment system TypeScript library",
|
||||||
|
"main": "./dist/index.cjs",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.cts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": {
|
||||||
|
"import": "./dist/index.d.mts",
|
||||||
|
"require": "./dist/index.d.cts"
|
||||||
|
},
|
||||||
|
"import": "./dist/index.mjs",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsdown",
|
||||||
|
"watch": "tsdown --watch",
|
||||||
|
"dev": "tsdown --watch",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:unit": "vitest run tests/unit",
|
||||||
|
"test:integration": "vitest run tests/integration",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:coverage": "vitest run --coverage"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"iyzico",
|
||||||
|
"payment",
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^10.1.0",
|
||||||
|
"@types/node": "^25.0.3",
|
||||||
|
"@vitest/coverage-v8": "^4.0.16",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"tsdown": "^0.18.4",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vitest": "^4.0.16"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/auth.ts
Normal file
52
src/auth.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* HMAC SHA256 authentication utilities for İyzico API
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createHmac } from 'crypto';
|
||||||
|
import { generateRandomKey } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates authorization header for İyzico API requests
|
||||||
|
* @param apiKey - API key
|
||||||
|
* @param secretKey - Secret key
|
||||||
|
* @param uriPath - Request URI path (e.g., '/payment/bin/check')
|
||||||
|
* @param requestBody - Request body object (will be JSON stringified)
|
||||||
|
* @param randomKey - Optional random key (if not provided, will be generated)
|
||||||
|
* @returns Authorization header value: 'IYZWSv2 {base64EncodedAuthorization}'
|
||||||
|
*/
|
||||||
|
export function generateAuthorization(
|
||||||
|
apiKey: string,
|
||||||
|
secretKey: string,
|
||||||
|
uriPath: string,
|
||||||
|
requestBody?: unknown,
|
||||||
|
randomKey?: string
|
||||||
|
): { authorization: string; randomKey: string } {
|
||||||
|
// Generate random key if not provided
|
||||||
|
const rndKey = randomKey || generateRandomKey();
|
||||||
|
|
||||||
|
// Create payload: randomKey + uriPath + requestBody
|
||||||
|
let payload = rndKey + uriPath;
|
||||||
|
if (requestBody !== undefined && requestBody !== null) {
|
||||||
|
// JSON stringify with no spaces (as per İyzico documentation)
|
||||||
|
const bodyString = JSON.stringify(requestBody);
|
||||||
|
payload += bodyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate HMAC SHA256 hash
|
||||||
|
const encryptedData = createHmac('sha256', secretKey)
|
||||||
|
.update(payload)
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
// Create authorization string
|
||||||
|
const authorizationString = `apiKey:${apiKey}&randomKey:${rndKey}&signature:${encryptedData}`;
|
||||||
|
|
||||||
|
// Base64 encode
|
||||||
|
const base64EncodedAuthorization = Buffer.from(authorizationString).toString('base64');
|
||||||
|
|
||||||
|
// Return with IYZWSv2 prefix (note: space between prefix and encoded string)
|
||||||
|
return {
|
||||||
|
authorization: `IYZWSv2 ${base64EncodedAuthorization}`,
|
||||||
|
randomKey: rndKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
88
src/client.ts
Normal file
88
src/client.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* İyzico Client - Main client class
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Locale } from './types';
|
||||||
|
import { PaymentService } from './services/payment';
|
||||||
|
import { BinCheckService } from './services/bin-check';
|
||||||
|
import { CancelRefundService } from './services/cancel-refund';
|
||||||
|
import { CardStorageService } from './services/card-storage';
|
||||||
|
import { ReportingService } from './services/reporting';
|
||||||
|
import { InstallmentService } from './services/installment';
|
||||||
|
import { SubscriptionService } from './services/subscription';
|
||||||
|
|
||||||
|
export interface IyzicoClientConfig {
|
||||||
|
apiKey: string;
|
||||||
|
secretKey: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
locale?: Locale;
|
||||||
|
retryOnRateLimit?: boolean;
|
||||||
|
maxRetries?: number;
|
||||||
|
retryDelay?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SANDBOX_URL = 'https://sandbox-api.iyzipay.com';
|
||||||
|
const DEFAULT_PRODUCTION_URL = 'https://api.iyzipay.com';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İyzico API Client
|
||||||
|
*/
|
||||||
|
export class IyzicoClient {
|
||||||
|
public readonly apiKey: string;
|
||||||
|
public readonly secretKey: string;
|
||||||
|
public readonly baseUrl: string;
|
||||||
|
private readonly locale: Locale;
|
||||||
|
public readonly retryOnRateLimit: boolean;
|
||||||
|
public readonly maxRetries: number;
|
||||||
|
public readonly retryDelay: number;
|
||||||
|
|
||||||
|
// Services
|
||||||
|
public readonly payment: PaymentService;
|
||||||
|
public readonly binCheck: BinCheckService;
|
||||||
|
public readonly cancelRefund: CancelRefundService;
|
||||||
|
public readonly cardStorage: CardStorageService;
|
||||||
|
public readonly reporting: ReportingService;
|
||||||
|
public readonly installment: InstallmentService;
|
||||||
|
public readonly subscription: SubscriptionService;
|
||||||
|
|
||||||
|
constructor(config: IyzicoClientConfig) {
|
||||||
|
if (!config.apiKey) {
|
||||||
|
throw new Error('API key is required');
|
||||||
|
}
|
||||||
|
if (!config.secretKey) {
|
||||||
|
throw new Error('Secret key is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiKey = config.apiKey;
|
||||||
|
this.secretKey = config.secretKey;
|
||||||
|
this.baseUrl = config.baseUrl || DEFAULT_SANDBOX_URL;
|
||||||
|
this.locale = config.locale || 'tr';
|
||||||
|
this.retryOnRateLimit = config.retryOnRateLimit ?? false;
|
||||||
|
this.maxRetries = config.maxRetries ?? 3;
|
||||||
|
this.retryDelay = config.retryDelay ?? 1000;
|
||||||
|
|
||||||
|
// Initialize services
|
||||||
|
this.payment = new PaymentService(this);
|
||||||
|
this.binCheck = new BinCheckService(this);
|
||||||
|
this.cancelRefund = new CancelRefundService(this);
|
||||||
|
this.cardStorage = new CardStorageService(this);
|
||||||
|
this.reporting = new ReportingService(this);
|
||||||
|
this.installment = new InstallmentService(this);
|
||||||
|
this.subscription = new SubscriptionService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current locale
|
||||||
|
*/
|
||||||
|
getLocale(): Locale {
|
||||||
|
return this.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the base URL
|
||||||
|
*/
|
||||||
|
getBaseUrl(): string {
|
||||||
|
return this.baseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
47
src/errors.ts
Normal file
47
src/errors.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Custom error classes for İyzico API
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ErrorResponse } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base error class for all İyzico errors
|
||||||
|
*/
|
||||||
|
export class IyzicoError extends Error {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
public readonly code?: string,
|
||||||
|
public readonly originalError?: unknown
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'IyzicoError';
|
||||||
|
Object.setPrototypeOf(this, IyzicoError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request error - thrown when request validation fails
|
||||||
|
*/
|
||||||
|
export class IyzicoRequestError extends IyzicoError {
|
||||||
|
constructor(message: string, originalError?: unknown) {
|
||||||
|
super(message, 'REQUEST_ERROR', originalError);
|
||||||
|
this.name = 'IyzicoRequestError';
|
||||||
|
Object.setPrototypeOf(this, IyzicoRequestError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response error - thrown when API returns an error response
|
||||||
|
*/
|
||||||
|
export class IyzicoResponseError extends IyzicoError {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
public readonly errorResponse: ErrorResponse,
|
||||||
|
originalError?: unknown
|
||||||
|
) {
|
||||||
|
super(message, errorResponse.errorCode, originalError);
|
||||||
|
this.name = 'IyzicoResponseError';
|
||||||
|
Object.setPrototypeOf(this, IyzicoResponseError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
190
src/http.ts
Normal file
190
src/http.ts
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* HTTP request handler for İyzico API
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BaseResponse, ErrorResponse } from './types';
|
||||||
|
import {
|
||||||
|
IyzicoError,
|
||||||
|
IyzicoRequestError,
|
||||||
|
IyzicoResponseError,
|
||||||
|
} from './errors';
|
||||||
|
import { generateAuthorization } from './auth';
|
||||||
|
|
||||||
|
export interface HttpRequestOptions {
|
||||||
|
method: 'GET' | 'POST' | 'DELETE';
|
||||||
|
path: string;
|
||||||
|
body?: unknown;
|
||||||
|
apiKey: string;
|
||||||
|
secretKey: string;
|
||||||
|
baseUrl: string;
|
||||||
|
retryOnRateLimit?: boolean;
|
||||||
|
maxRetries?: number;
|
||||||
|
retryDelay?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep utility for retry delays
|
||||||
|
*/
|
||||||
|
function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an error is a rate limit error
|
||||||
|
*/
|
||||||
|
function isRateLimitError(error: unknown): boolean {
|
||||||
|
if (error instanceof IyzicoResponseError) {
|
||||||
|
// Check for rate limit error codes or messages
|
||||||
|
const errorCode = error.errorResponse?.errorCode?.toLowerCase() || '';
|
||||||
|
const errorMessage = error.errorResponse?.errorMessage?.toLowerCase() || '';
|
||||||
|
return (
|
||||||
|
errorCode.includes('rate') ||
|
||||||
|
errorCode.includes('limit') ||
|
||||||
|
errorCode === '429' ||
|
||||||
|
errorMessage.includes('rate') ||
|
||||||
|
errorMessage.includes('limit') ||
|
||||||
|
errorMessage.includes('too many requests')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes an HTTP request to İyzico API with optional retry on rate limit
|
||||||
|
* @param options - Request options
|
||||||
|
* @returns Parsed JSON response
|
||||||
|
* @throws IyzicoError on failure
|
||||||
|
*/
|
||||||
|
export async function makeRequest<T>(options: HttpRequestOptions): Promise<T> {
|
||||||
|
const {
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
body,
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
retryOnRateLimit = false,
|
||||||
|
maxRetries = 3,
|
||||||
|
retryDelay = 1000,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let lastError: unknown;
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
while (attempts <= maxRetries) {
|
||||||
|
try {
|
||||||
|
// For GET requests, extract base path without query string for signature calculation
|
||||||
|
// The query string should be part of the URL but not the signature
|
||||||
|
let signaturePath = path;
|
||||||
|
if (method === 'GET') {
|
||||||
|
// Extract path without query string
|
||||||
|
const queryIndex = path.indexOf('?');
|
||||||
|
if (queryIndex !== -1) {
|
||||||
|
signaturePath = path.substring(0, queryIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate authorization header using base path (without query string for GET)
|
||||||
|
const { authorization, randomKey } = generateAuthorization(
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
signaturePath,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build URL (full path with query string for GET requests)
|
||||||
|
const url = `${baseUrl}${path}`;
|
||||||
|
|
||||||
|
// Prepare headers
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Authorization': authorization,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-iyzi-rnd': randomKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare request options
|
||||||
|
const requestOptions: RequestInit = {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add body for POST and DELETE requests
|
||||||
|
if ((method === 'POST' || method === 'DELETE') && body !== undefined) {
|
||||||
|
requestOptions.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make request
|
||||||
|
const response = await fetch(url, requestOptions);
|
||||||
|
|
||||||
|
// Parse response
|
||||||
|
const data = (await response.json()) as unknown;
|
||||||
|
|
||||||
|
// Check if response is an error
|
||||||
|
const baseResponse = data as BaseResponse | null;
|
||||||
|
if (
|
||||||
|
(baseResponse?.status === 'failure') ||
|
||||||
|
!response.ok
|
||||||
|
) {
|
||||||
|
const errorResponse = data as ErrorResponse;
|
||||||
|
const error = new IyzicoResponseError(
|
||||||
|
errorResponse.errorMessage || 'API request failed',
|
||||||
|
errorResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retry on rate limit if enabled
|
||||||
|
if (retryOnRateLimit && isRateLimitError(error) && attempts < maxRetries) {
|
||||||
|
attempts++;
|
||||||
|
const delay = retryDelay * attempts; // Exponential backoff
|
||||||
|
await sleep(delay);
|
||||||
|
lastError = error;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data as T;
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
|
||||||
|
// Retry on rate limit if enabled
|
||||||
|
if (retryOnRateLimit && isRateLimitError(error) && attempts < maxRetries) {
|
||||||
|
attempts++;
|
||||||
|
const delay = retryDelay * attempts; // Exponential backoff
|
||||||
|
await sleep(delay);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle network errors
|
||||||
|
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||||
|
throw new IyzicoRequestError('Network error: Failed to connect to İyzico API', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle response errors
|
||||||
|
if (error instanceof IyzicoResponseError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle request errors
|
||||||
|
if (error instanceof IyzicoRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other errors
|
||||||
|
if (error instanceof IyzicoError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap unknown errors
|
||||||
|
throw new IyzicoError(
|
||||||
|
error instanceof Error ? error.message : 'Unknown error occurred',
|
||||||
|
undefined,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we exhausted retries, throw the last error
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
|
||||||
22
src/index.ts
Normal file
22
src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* İyzico Payment Library
|
||||||
|
* Main exports
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Client
|
||||||
|
export { IyzicoClient } from './client';
|
||||||
|
export type { IyzicoClientConfig } from './client';
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
export {
|
||||||
|
IyzicoError,
|
||||||
|
IyzicoRequestError,
|
||||||
|
IyzicoResponseError,
|
||||||
|
} from './errors';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
// Utils (for backward compatibility if needed)
|
||||||
|
export { generateRandomNumber, generateRandomKey } from './utils';
|
||||||
|
|
||||||
31
src/services/bin-check.ts
Normal file
31
src/services/bin-check.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* BIN Check Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BinCheckRequest, BinCheckResponse } from '../types';
|
||||||
|
import { makeRequest } from '../http';
|
||||||
|
import type { IyzicoClient } from '../client';
|
||||||
|
|
||||||
|
export class BinCheckService {
|
||||||
|
constructor(private client: IyzicoClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks BIN (Bank Identification Number) information
|
||||||
|
* @param request - BIN check request
|
||||||
|
* @returns BIN check response
|
||||||
|
*/
|
||||||
|
async check(request: BinCheckRequest): Promise<BinCheckResponse> {
|
||||||
|
return makeRequest<BinCheckResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/bin/check',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
76
src/services/cancel-refund.ts
Normal file
76
src/services/cancel-refund.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Cancel & Refund Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CancelRequest,
|
||||||
|
CancelResponse,
|
||||||
|
RefundRequest,
|
||||||
|
RefundResponse,
|
||||||
|
RefundV2Request,
|
||||||
|
RefundV2Response,
|
||||||
|
} from '../types';
|
||||||
|
import { makeRequest } from '../http';
|
||||||
|
import type { IyzicoClient } from '../client';
|
||||||
|
|
||||||
|
export class CancelRefundService {
|
||||||
|
constructor(private client: IyzicoClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a payment
|
||||||
|
* @param request - Cancel request
|
||||||
|
* @returns Cancel response
|
||||||
|
*/
|
||||||
|
async cancel(request: CancelRequest): Promise<CancelResponse> {
|
||||||
|
return makeRequest<CancelResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/cancel',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refunds a payment (v1 - using paymentTransactionId)
|
||||||
|
* @param request - Refund request
|
||||||
|
* @returns Refund response
|
||||||
|
*/
|
||||||
|
async refund(request: RefundRequest): Promise<RefundResponse> {
|
||||||
|
return makeRequest<RefundResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/refund',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refunds a payment (v2 - using paymentId)
|
||||||
|
* @param request - Refund v2 request
|
||||||
|
* @returns Refund v2 response
|
||||||
|
*/
|
||||||
|
async refundV2(request: RefundV2Request): Promise<RefundV2Response> {
|
||||||
|
return makeRequest<RefundV2Response>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v2/payment/refund',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
79
src/services/card-storage.ts
Normal file
79
src/services/card-storage.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Card Storage Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CardCreateRequest,
|
||||||
|
CardCreateWithUserKeyRequest,
|
||||||
|
CardCreateResponse,
|
||||||
|
CardListRequest,
|
||||||
|
CardListResponse,
|
||||||
|
CardDeleteRequest,
|
||||||
|
CardDeleteResponse,
|
||||||
|
} from '../types';
|
||||||
|
import { makeRequest } from '../http';
|
||||||
|
import type { IyzicoClient } from '../client';
|
||||||
|
|
||||||
|
export class CardStorageService {
|
||||||
|
constructor(private client: IyzicoClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a card (for new user or existing user)
|
||||||
|
* @param request - Card create request
|
||||||
|
* @returns Card create response
|
||||||
|
*/
|
||||||
|
async createCard(
|
||||||
|
request: CardCreateRequest | CardCreateWithUserKeyRequest
|
||||||
|
): Promise<CardCreateResponse> {
|
||||||
|
return makeRequest<CardCreateResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/cardstorage/card',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists cards for a user
|
||||||
|
* @param request - Card list request
|
||||||
|
* @returns Card list response
|
||||||
|
*/
|
||||||
|
async listCards(request: CardListRequest): Promise<CardListResponse> {
|
||||||
|
return makeRequest<CardListResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/cardstorage/cards',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a card
|
||||||
|
* @param request - Card delete request
|
||||||
|
* @returns Card delete response
|
||||||
|
*/
|
||||||
|
async deleteCard(request: CardDeleteRequest): Promise<CardDeleteResponse> {
|
||||||
|
return makeRequest<CardDeleteResponse>({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/cardstorage/card',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
31
src/services/installment.ts
Normal file
31
src/services/installment.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Installment Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { InstallmentRequest, InstallmentResponse } from '../types';
|
||||||
|
import { makeRequest } from '../http';
|
||||||
|
import type { IyzicoClient } from '../client';
|
||||||
|
|
||||||
|
export class InstallmentService {
|
||||||
|
constructor(private client: IyzicoClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries installment options for a BIN number and price
|
||||||
|
* @param request - Installment query request
|
||||||
|
* @returns Installment options response
|
||||||
|
*/
|
||||||
|
async query(request: InstallmentRequest): Promise<InstallmentResponse> {
|
||||||
|
return makeRequest<InstallmentResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/iyzipos/installment',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
117
src/services/payment.ts
Normal file
117
src/services/payment.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* Payment Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ThreeDSInitializeRequest,
|
||||||
|
ThreeDSInitializeResponse,
|
||||||
|
ThreeDSAuthRequest,
|
||||||
|
ThreeDSV2AuthRequest,
|
||||||
|
Non3DPaymentRequest,
|
||||||
|
PaymentResponse,
|
||||||
|
ThreeDSV2PaymentResponse,
|
||||||
|
PaymentDetailRequest,
|
||||||
|
PaymentDetailResponse,
|
||||||
|
} from '../types';
|
||||||
|
import { makeRequest } from '../http';
|
||||||
|
import type { IyzicoClient } from '../client';
|
||||||
|
|
||||||
|
export class PaymentService {
|
||||||
|
constructor(private client: IyzicoClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes 3DS payment
|
||||||
|
* @param request - 3DS initialize request
|
||||||
|
* @returns 3DS initialize response with HTML content
|
||||||
|
*/
|
||||||
|
async initialize3DS(request: ThreeDSInitializeRequest): Promise<ThreeDSInitializeResponse> {
|
||||||
|
return makeRequest<ThreeDSInitializeResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/3dsecure/initialize',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes 3DS v1 payment
|
||||||
|
* @param request - 3DS v1 auth request
|
||||||
|
* @returns Payment response
|
||||||
|
*/
|
||||||
|
async complete3DS(request: ThreeDSAuthRequest): Promise<PaymentResponse> {
|
||||||
|
return makeRequest<PaymentResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/3dsecure/auth',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes 3DS v2 payment
|
||||||
|
* @param request - 3DS v2 auth request
|
||||||
|
* @returns 3DS v2 payment response
|
||||||
|
*/
|
||||||
|
async complete3DSV2(request: ThreeDSV2AuthRequest): Promise<ThreeDSV2PaymentResponse> {
|
||||||
|
return makeRequest<ThreeDSV2PaymentResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/v2/3dsecure/auth',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates non-3DS payment
|
||||||
|
* @param request - Non-3DS payment request
|
||||||
|
* @returns Payment response
|
||||||
|
*/
|
||||||
|
async createNon3DS(request: Non3DPaymentRequest): Promise<PaymentResponse> {
|
||||||
|
return makeRequest<PaymentResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/auth',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets payment detail
|
||||||
|
* @param request - Payment detail request
|
||||||
|
* @returns Payment detail response
|
||||||
|
*/
|
||||||
|
async getDetail(request: PaymentDetailRequest): Promise<PaymentDetailResponse> {
|
||||||
|
return makeRequest<PaymentDetailResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/payment/detail',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
81
src/services/reporting.ts
Normal file
81
src/services/reporting.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Reporting Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
PaymentTransactionsRequest,
|
||||||
|
PaymentTransactionsResponse,
|
||||||
|
PaymentDetailsRequest,
|
||||||
|
PaymentDetailsResponse,
|
||||||
|
} from '../types';
|
||||||
|
import { makeRequest } from '../http';
|
||||||
|
import type { IyzicoClient } from '../client';
|
||||||
|
|
||||||
|
export class ReportingService {
|
||||||
|
constructor(private client: IyzicoClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets payment transactions for a specific date
|
||||||
|
* @param request - Payment transactions request
|
||||||
|
* @returns Payment transactions response
|
||||||
|
*/
|
||||||
|
async getPaymentTransactions(
|
||||||
|
request: PaymentTransactionsRequest
|
||||||
|
): Promise<PaymentTransactionsResponse> {
|
||||||
|
// Build query string from request
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
queryParams.append('page', request.page.toString());
|
||||||
|
queryParams.append('transactionDate', request.transactionDate);
|
||||||
|
if (request.locale) {
|
||||||
|
queryParams.append('locale', request.locale);
|
||||||
|
}
|
||||||
|
if (request.conversationId) {
|
||||||
|
queryParams.append('conversationId', request.conversationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeRequest<PaymentTransactionsResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path: `/v2/reporting/payment/transactions?${queryParams.toString()}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets payment details
|
||||||
|
* @param request - Payment details request
|
||||||
|
* @returns Payment details response
|
||||||
|
*/
|
||||||
|
async getPaymentDetails(request: PaymentDetailsRequest): Promise<PaymentDetailsResponse> {
|
||||||
|
// Build query string from request
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (request.paymentId) {
|
||||||
|
queryParams.append('paymentId', request.paymentId);
|
||||||
|
}
|
||||||
|
if (request.paymentConversationId) {
|
||||||
|
queryParams.append('paymentConversationId', request.paymentConversationId);
|
||||||
|
}
|
||||||
|
if (request.locale) {
|
||||||
|
queryParams.append('locale', request.locale);
|
||||||
|
}
|
||||||
|
if (request.conversationId) {
|
||||||
|
queryParams.append('conversationId', request.conversationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeRequest<PaymentDetailsResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path: `/v2/reporting/payment/details?${queryParams.toString()}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
537
src/services/subscription.ts
Normal file
537
src/services/subscription.ts
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
/**
|
||||||
|
* Subscription Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
// Product types
|
||||||
|
ProductCreateRequest,
|
||||||
|
ProductCreateResponse,
|
||||||
|
ProductListRequest,
|
||||||
|
ProductListResponse,
|
||||||
|
ProductGetResponse,
|
||||||
|
ProductUpdateRequest,
|
||||||
|
ProductUpdateResponse,
|
||||||
|
ProductDeleteResponse,
|
||||||
|
// Pricing Plan types
|
||||||
|
PricingPlanCreateRequest,
|
||||||
|
PricingPlanCreateResponse,
|
||||||
|
PricingPlanListRequest,
|
||||||
|
PricingPlanListResponse,
|
||||||
|
PricingPlanGetResponse,
|
||||||
|
PricingPlanUpdateRequest,
|
||||||
|
PricingPlanUpdateResponse,
|
||||||
|
PricingPlanDeleteResponse,
|
||||||
|
// Customer types
|
||||||
|
CustomerListRequest,
|
||||||
|
CustomerListResponse,
|
||||||
|
CustomerGetResponse,
|
||||||
|
CustomerUpdateRequest,
|
||||||
|
CustomerUpdateResponse,
|
||||||
|
// Subscription types
|
||||||
|
SubscriptionInitializeRequest,
|
||||||
|
SubscriptionInitializeResponse,
|
||||||
|
SubscriptionInitializeWithCustomerRequest,
|
||||||
|
SubscriptionCheckoutFormInitializeRequest,
|
||||||
|
SubscriptionCheckoutFormInitializeResponse,
|
||||||
|
SubscriptionListRequest,
|
||||||
|
SubscriptionListResponse,
|
||||||
|
SubscriptionGetResponse,
|
||||||
|
SubscriptionCancelResponse,
|
||||||
|
SubscriptionActivateResponse,
|
||||||
|
SubscriptionUpgradeRequest,
|
||||||
|
SubscriptionUpgradeResponse,
|
||||||
|
SubscriptionRetryRequest,
|
||||||
|
SubscriptionRetryResponse,
|
||||||
|
CardUpdateCheckoutFormInitializeRequest,
|
||||||
|
CardUpdateCheckoutFormInitializeResponse,
|
||||||
|
} from '../types';
|
||||||
|
import { makeRequest } from '../http';
|
||||||
|
import type { IyzicoClient } from '../client';
|
||||||
|
|
||||||
|
export class SubscriptionService {
|
||||||
|
constructor(private client: IyzicoClient) {}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Product Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new subscription product
|
||||||
|
* @param request - Product create request
|
||||||
|
* @returns Product create response
|
||||||
|
*/
|
||||||
|
async createProduct(request: ProductCreateRequest): Promise<ProductCreateResponse> {
|
||||||
|
return makeRequest<ProductCreateResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v2/subscription/products',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists subscription products with pagination
|
||||||
|
* @param request - Optional pagination parameters
|
||||||
|
* @returns Paginated product list response
|
||||||
|
*/
|
||||||
|
async listProducts(request?: ProductListRequest): Promise<ProductListResponse> {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (request?.page !== undefined) queryParams.set('page', String(request.page));
|
||||||
|
if (request?.count !== undefined) queryParams.set('count', String(request.count));
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const path = `/v2/subscription/products${queryString ? `?${queryString}` : ''}`;
|
||||||
|
|
||||||
|
return makeRequest<ProductListResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a subscription product by reference code
|
||||||
|
* @param productReferenceCode - Product reference code
|
||||||
|
* @returns Product get response
|
||||||
|
*/
|
||||||
|
async getProduct(productReferenceCode: string): Promise<ProductGetResponse> {
|
||||||
|
return makeRequest<ProductGetResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path: `/v2/subscription/products/${productReferenceCode}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a subscription product
|
||||||
|
* @param productReferenceCode - Product reference code
|
||||||
|
* @param request - Product update request
|
||||||
|
* @returns Product update response
|
||||||
|
*/
|
||||||
|
async updateProduct(productReferenceCode: string, request: ProductUpdateRequest): Promise<ProductUpdateResponse> {
|
||||||
|
return makeRequest<ProductUpdateResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: `/v2/subscription/products/${productReferenceCode}`,
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a subscription product
|
||||||
|
* @param productReferenceCode - Product reference code
|
||||||
|
* @returns Product delete response
|
||||||
|
*/
|
||||||
|
async deleteProduct(productReferenceCode: string): Promise<ProductDeleteResponse> {
|
||||||
|
return makeRequest<ProductDeleteResponse>({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: `/v2/subscription/products/${productReferenceCode}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Pricing Plan Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new pricing plan for a product
|
||||||
|
* @param productReferenceCode - Product reference code
|
||||||
|
* @param request - Pricing plan create request
|
||||||
|
* @returns Pricing plan create response
|
||||||
|
*/
|
||||||
|
async createPricingPlan(productReferenceCode: string, request: PricingPlanCreateRequest): Promise<PricingPlanCreateResponse> {
|
||||||
|
return makeRequest<PricingPlanCreateResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: `/v2/subscription/products/${productReferenceCode}/pricing-plans`,
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists pricing plans for a product with pagination
|
||||||
|
* @param productReferenceCode - Product reference code
|
||||||
|
* @param request - Optional pagination parameters
|
||||||
|
* @returns Paginated pricing plan list response
|
||||||
|
*/
|
||||||
|
async listPricingPlans(productReferenceCode: string, request?: PricingPlanListRequest): Promise<PricingPlanListResponse> {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (request?.page !== undefined) queryParams.set('page', String(request.page));
|
||||||
|
if (request?.count !== undefined) queryParams.set('count', String(request.count));
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const path = `/v2/subscription/products/${productReferenceCode}/pricing-plans${queryString ? `?${queryString}` : ''}`;
|
||||||
|
|
||||||
|
return makeRequest<PricingPlanListResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a pricing plan by reference code
|
||||||
|
* @param pricingPlanReferenceCode - Pricing plan reference code
|
||||||
|
* @returns Pricing plan get response
|
||||||
|
*/
|
||||||
|
async getPricingPlan(pricingPlanReferenceCode: string): Promise<PricingPlanGetResponse> {
|
||||||
|
return makeRequest<PricingPlanGetResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path: `/v2/subscription/pricing-plans/${pricingPlanReferenceCode}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a pricing plan
|
||||||
|
* @param pricingPlanReferenceCode - Pricing plan reference code
|
||||||
|
* @param request - Pricing plan update request
|
||||||
|
* @returns Pricing plan update response
|
||||||
|
*/
|
||||||
|
async updatePricingPlan(pricingPlanReferenceCode: string, request: PricingPlanUpdateRequest): Promise<PricingPlanUpdateResponse> {
|
||||||
|
return makeRequest<PricingPlanUpdateResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: `/v2/subscription/pricing-plans/${pricingPlanReferenceCode}`,
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a pricing plan
|
||||||
|
* @param pricingPlanReferenceCode - Pricing plan reference code
|
||||||
|
* @returns Pricing plan delete response
|
||||||
|
*/
|
||||||
|
async deletePricingPlan(pricingPlanReferenceCode: string): Promise<PricingPlanDeleteResponse> {
|
||||||
|
return makeRequest<PricingPlanDeleteResponse>({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: `/v2/subscription/pricing-plans/${pricingPlanReferenceCode}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Customer Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists subscription customers with pagination
|
||||||
|
* @param request - Optional pagination parameters
|
||||||
|
* @returns Paginated customer list response
|
||||||
|
*/
|
||||||
|
async listCustomers(request?: CustomerListRequest): Promise<CustomerListResponse> {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (request?.page !== undefined) queryParams.set('page', String(request.page));
|
||||||
|
if (request?.count !== undefined) queryParams.set('count', String(request.count));
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const path = `/v2/subscription/customers${queryString ? `?${queryString}` : ''}`;
|
||||||
|
|
||||||
|
return makeRequest<CustomerListResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a subscription customer by reference code
|
||||||
|
* @param customerReferenceCode - Customer reference code
|
||||||
|
* @returns Customer get response
|
||||||
|
*/
|
||||||
|
async getCustomer(customerReferenceCode: string): Promise<CustomerGetResponse> {
|
||||||
|
return makeRequest<CustomerGetResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path: `/v2/subscription/customers/${customerReferenceCode}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a subscription customer
|
||||||
|
* @param customerReferenceCode - Customer reference code
|
||||||
|
* @param request - Customer update request
|
||||||
|
* @returns Customer update response
|
||||||
|
*/
|
||||||
|
async updateCustomer(customerReferenceCode: string, request: CustomerUpdateRequest): Promise<CustomerUpdateResponse> {
|
||||||
|
return makeRequest<CustomerUpdateResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: `/v2/subscription/customers/${customerReferenceCode}`,
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Subscription Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new subscription (NON3D)
|
||||||
|
* @param request - Subscription initialize request
|
||||||
|
* @returns Subscription initialize response
|
||||||
|
*/
|
||||||
|
async initialize(request: SubscriptionInitializeRequest): Promise<SubscriptionInitializeResponse> {
|
||||||
|
return makeRequest<SubscriptionInitializeResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v2/subscription/initialize',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a subscription for an existing customer
|
||||||
|
* @param request - Subscription initialize with customer request
|
||||||
|
* @returns Subscription initialize response
|
||||||
|
*/
|
||||||
|
async initializeWithCustomer(request: SubscriptionInitializeWithCustomerRequest): Promise<SubscriptionInitializeResponse> {
|
||||||
|
return makeRequest<SubscriptionInitializeResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v2/subscription/initialize/with-customer',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a subscription using checkout form
|
||||||
|
* @param request - Subscription checkout form initialize request
|
||||||
|
* @returns Subscription checkout form initialize response
|
||||||
|
*/
|
||||||
|
async initializeCheckoutForm(request: SubscriptionCheckoutFormInitializeRequest): Promise<SubscriptionCheckoutFormInitializeResponse> {
|
||||||
|
return makeRequest<SubscriptionCheckoutFormInitializeResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v2/subscription/checkoutform/initialize',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists subscriptions with filtering and pagination
|
||||||
|
* @param request - Optional filter and pagination parameters
|
||||||
|
* @returns Paginated subscription list response
|
||||||
|
*/
|
||||||
|
async list(request?: SubscriptionListRequest): Promise<SubscriptionListResponse> {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (request?.subscriptionReferenceCode) queryParams.set('subscriptionReferenceCode', request.subscriptionReferenceCode);
|
||||||
|
if (request?.customerReferenceCode) queryParams.set('customerReferenceCode', request.customerReferenceCode);
|
||||||
|
if (request?.pricingPlanReferenceCode) queryParams.set('pricingPlanReferenceCode', request.pricingPlanReferenceCode);
|
||||||
|
if (request?.parentReferenceCode) queryParams.set('parent', request.parentReferenceCode);
|
||||||
|
if (request?.subscriptionStatus) queryParams.set('subscriptionStatus', request.subscriptionStatus);
|
||||||
|
if (request?.startDate !== undefined) queryParams.set('startDate', String(request.startDate));
|
||||||
|
if (request?.endDate !== undefined) queryParams.set('endDate', String(request.endDate));
|
||||||
|
if (request?.page !== undefined) queryParams.set('page', String(request.page));
|
||||||
|
if (request?.count !== undefined) queryParams.set('count', String(request.count));
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const path = `/v2/subscription/subscriptions${queryString ? `?${queryString}` : ''}`;
|
||||||
|
|
||||||
|
return makeRequest<SubscriptionListResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a subscription by reference code
|
||||||
|
* @param subscriptionReferenceCode - Subscription reference code
|
||||||
|
* @returns Subscription get response
|
||||||
|
*/
|
||||||
|
async get(subscriptionReferenceCode: string): Promise<SubscriptionGetResponse> {
|
||||||
|
return makeRequest<SubscriptionGetResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
path: `/v2/subscription/subscriptions/${subscriptionReferenceCode}`,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels an active subscription
|
||||||
|
* @param subscriptionReferenceCode - Subscription reference code
|
||||||
|
* @returns Subscription cancel response
|
||||||
|
*/
|
||||||
|
async cancel(subscriptionReferenceCode: string): Promise<SubscriptionCancelResponse> {
|
||||||
|
return makeRequest<SubscriptionCancelResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: `/v2/subscription/subscriptions/${subscriptionReferenceCode}/cancel`,
|
||||||
|
body: {},
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates a pending subscription
|
||||||
|
* @param subscriptionReferenceCode - Subscription reference code
|
||||||
|
* @returns Subscription activate response
|
||||||
|
*/
|
||||||
|
async activate(subscriptionReferenceCode: string): Promise<SubscriptionActivateResponse> {
|
||||||
|
return makeRequest<SubscriptionActivateResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: `/v2/subscription/subscriptions/${subscriptionReferenceCode}/activate`,
|
||||||
|
body: {},
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrades a subscription to a new pricing plan
|
||||||
|
* @param subscriptionReferenceCode - Subscription reference code
|
||||||
|
* @param request - Subscription upgrade request
|
||||||
|
* @returns Subscription upgrade response
|
||||||
|
*/
|
||||||
|
async upgrade(subscriptionReferenceCode: string, request: SubscriptionUpgradeRequest): Promise<SubscriptionUpgradeResponse> {
|
||||||
|
return makeRequest<SubscriptionUpgradeResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: `/v2/subscription/subscriptions/${subscriptionReferenceCode}/upgrade`,
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries a failed subscription payment
|
||||||
|
* @param request - Retry payment request with order reference code
|
||||||
|
* @returns Subscription retry response
|
||||||
|
*/
|
||||||
|
async retryPayment(request: SubscriptionRetryRequest): Promise<SubscriptionRetryResponse> {
|
||||||
|
return makeRequest<SubscriptionRetryResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v2/subscription/operation/retry',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes card update via checkout form
|
||||||
|
* @param request - Card update checkout form initialize request
|
||||||
|
* @returns Card update checkout form initialize response
|
||||||
|
*/
|
||||||
|
async initializeCardUpdate(request: CardUpdateCheckoutFormInitializeRequest): Promise<CardUpdateCheckoutFormInitializeResponse> {
|
||||||
|
return makeRequest<CardUpdateCheckoutFormInitializeResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v2/subscription/card-update/checkoutform/initialize',
|
||||||
|
body: request,
|
||||||
|
apiKey: this.client.apiKey,
|
||||||
|
secretKey: this.client.secretKey,
|
||||||
|
baseUrl: this.client.baseUrl,
|
||||||
|
retryOnRateLimit: this.client.retryOnRateLimit,
|
||||||
|
maxRetries: this.client.maxRetries,
|
||||||
|
retryDelay: this.client.retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/types/bin-check.ts
Normal file
83
src/types/bin-check.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* BIN Sorgulama modelleri
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BaseResponse, Locale, Status } from './common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIN Sorgulama Request
|
||||||
|
*/
|
||||||
|
export interface BinCheckRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
binNumber: string;
|
||||||
|
conversationId?: string;
|
||||||
|
price?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taksit detayı modeli
|
||||||
|
*/
|
||||||
|
export interface InstallmentDetail {
|
||||||
|
installmentPrice: number;
|
||||||
|
totalPrice: number;
|
||||||
|
installmentNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taksit Sorgulama Request
|
||||||
|
*/
|
||||||
|
export interface InstallmentRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
binNumber: string;
|
||||||
|
price: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taksit Detay Bilgileri
|
||||||
|
*/
|
||||||
|
export interface InstallmentDetailItem {
|
||||||
|
binNumber: string;
|
||||||
|
price: number;
|
||||||
|
cardType: string;
|
||||||
|
cardAssociation: string;
|
||||||
|
cardFamilyName: string;
|
||||||
|
force3ds: 0 | 1;
|
||||||
|
bankCode: number;
|
||||||
|
bankName: string;
|
||||||
|
forceCvc: 0 | 1;
|
||||||
|
commercial: 0 | 1;
|
||||||
|
dccEnabled: 0 | 1;
|
||||||
|
agricultureEnabled?: 0 | 1;
|
||||||
|
installmentPrices: InstallmentDetail[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taksit Sorgulama Response
|
||||||
|
*/
|
||||||
|
export interface InstallmentResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
installmentDetails?: InstallmentDetailItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIN Sorgulama Response
|
||||||
|
*/
|
||||||
|
export interface BinCheckResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
binNumber?: string;
|
||||||
|
cardType?: string;
|
||||||
|
cardAssociation?: string;
|
||||||
|
cardFamily?: string;
|
||||||
|
bankName?: string;
|
||||||
|
bankCode?: number;
|
||||||
|
commercial?: 0 | 1;
|
||||||
|
installmentDetails?: InstallmentDetailItem[];
|
||||||
|
}
|
||||||
|
|
||||||
97
src/types/cancel-refund.ts
Normal file
97
src/types/cancel-refund.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* İptal ve İade işlemleri için modeller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BaseResponse, Locale, Currency, Status, CancelReason } from './common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İptal Request
|
||||||
|
*/
|
||||||
|
export interface CancelRequest {
|
||||||
|
paymentId: string;
|
||||||
|
conversationId?: string;
|
||||||
|
ip?: string;
|
||||||
|
locale?: Locale;
|
||||||
|
reason?: CancelReason;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İptal Response
|
||||||
|
*/
|
||||||
|
export interface CancelResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
paymentId?: string;
|
||||||
|
price?: number;
|
||||||
|
currency?: Currency;
|
||||||
|
authCode?: string;
|
||||||
|
hostReference?: string;
|
||||||
|
cancelHostReference?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İade Request (v1 - paymentTransactionId ile)
|
||||||
|
*/
|
||||||
|
export interface RefundRequest {
|
||||||
|
paymentTransactionId: string;
|
||||||
|
price: number;
|
||||||
|
conversationId?: string;
|
||||||
|
ip?: string;
|
||||||
|
locale?: Locale;
|
||||||
|
currency?: Currency;
|
||||||
|
reason?: CancelReason;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İade Response (v1)
|
||||||
|
*/
|
||||||
|
export interface RefundResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
paymentId?: string;
|
||||||
|
paymentTransactionId?: string;
|
||||||
|
price?: number;
|
||||||
|
currency?: Currency;
|
||||||
|
authCode?: string;
|
||||||
|
hostReference?: string;
|
||||||
|
refundHostReference?: string;
|
||||||
|
retryable?: boolean;
|
||||||
|
signature?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İade Request (v2 - paymentId ile)
|
||||||
|
*/
|
||||||
|
export interface RefundV2Request {
|
||||||
|
paymentId: string;
|
||||||
|
price: number;
|
||||||
|
conversationId?: string;
|
||||||
|
ip?: string;
|
||||||
|
locale?: Locale;
|
||||||
|
currency?: Currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İade Response (v2)
|
||||||
|
*/
|
||||||
|
export interface RefundV2Response extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
paymentId?: string;
|
||||||
|
price?: number;
|
||||||
|
currency?: Currency;
|
||||||
|
authCode?: string;
|
||||||
|
hostReference?: string;
|
||||||
|
refundHostReference?: string;
|
||||||
|
retryable?: boolean;
|
||||||
|
signature?: string;
|
||||||
|
}
|
||||||
|
|
||||||
112
src/types/card-storage.ts
Normal file
112
src/types/card-storage.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Kart saklama işlemleri için modeller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BaseResponse, Locale, Status, CardType, CardAssociation } from './common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart modeli
|
||||||
|
*/
|
||||||
|
export interface Card {
|
||||||
|
cardAlias?: string;
|
||||||
|
cardNumber: string;
|
||||||
|
expireYear: string;
|
||||||
|
expireMonth: string;
|
||||||
|
cardHolderName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart oluşturma Request (yeni kullanıcı + ilk kart)
|
||||||
|
*/
|
||||||
|
export interface CardCreateRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
externalId?: string;
|
||||||
|
email: string;
|
||||||
|
card: Card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart oluşturma Request (mevcut kullanıcıya kart ekleme)
|
||||||
|
*/
|
||||||
|
export interface CardCreateWithUserKeyRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
cardUserKey: string;
|
||||||
|
card: Card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart oluşturma Response
|
||||||
|
*/
|
||||||
|
export interface CardCreateResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
externalId?: string;
|
||||||
|
email?: string;
|
||||||
|
cardUserKey?: string;
|
||||||
|
cardToken?: string;
|
||||||
|
binNumber?: string;
|
||||||
|
lastFourDigits?: string;
|
||||||
|
cardType?: CardType;
|
||||||
|
cardAssociation?: CardAssociation;
|
||||||
|
cardFamily?: string;
|
||||||
|
cardAlias?: string;
|
||||||
|
cardBankCode?: number;
|
||||||
|
cardBankName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart listeleme Request
|
||||||
|
*/
|
||||||
|
export interface CardListRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
cardUserKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart detayı modeli
|
||||||
|
*/
|
||||||
|
export interface CardDetail {
|
||||||
|
cardFamily?: string;
|
||||||
|
cardType?: CardType;
|
||||||
|
cardAssociation?: CardAssociation;
|
||||||
|
lastFourDigits?: string;
|
||||||
|
binNumber?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart listeleme Response
|
||||||
|
*/
|
||||||
|
export interface CardListResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
cardUserKey?: string;
|
||||||
|
cardDetails?: CardDetail[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart silme Request
|
||||||
|
*/
|
||||||
|
export interface CardDeleteRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
cardUserKey: string;
|
||||||
|
cardToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kart silme Response
|
||||||
|
*/
|
||||||
|
export interface CardDeleteResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
165
src/types/common.ts
Normal file
165
src/types/common.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* Ortak kullanılan modeller
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Locale = 'tr' | 'en';
|
||||||
|
export type Currency = 'TRY' | 'USD' | 'EUR' | 'GBP' | 'NOK' | 'CHF';
|
||||||
|
export type PaymentChannel =
|
||||||
|
| 'WEB'
|
||||||
|
| 'MOBILE'
|
||||||
|
| 'MOBILE_WEB'
|
||||||
|
| 'MOBILE_IOS'
|
||||||
|
| 'MOBILE_ANDROID'
|
||||||
|
| 'MOBILE_WINDOWS'
|
||||||
|
| 'MOBILE_TABLET'
|
||||||
|
| 'MOBILE_PHONE';
|
||||||
|
export type PaymentGroup = 'PRODUCT' | 'LISTING' | 'SUBSCRIPTION' | 'OTHER';
|
||||||
|
export type ItemType = 'PHYSICAL' | 'VIRTUAL';
|
||||||
|
export type CardType = 'CREDIT_CARD' | 'DEBIT_CARD' | 'PREPAID_CARD';
|
||||||
|
export type CardAssociation = 'VISA' | 'MASTER_CARD' | 'AMERICAN_EXPRESS' | 'TROY';
|
||||||
|
export type Status = 'success' | 'failure';
|
||||||
|
export type CancelReason = 'OTHER' | 'FRAUD' | 'BUYER_REQUEST' | 'DOUBLE_PAYMENT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tüm response'lar için temel model
|
||||||
|
*/
|
||||||
|
export interface BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hata response modeli
|
||||||
|
*/
|
||||||
|
export interface ErrorResponse extends BaseResponse {
|
||||||
|
status: 'failure';
|
||||||
|
errorCode?: string;
|
||||||
|
errorMessage?: string;
|
||||||
|
errorGroup?: string;
|
||||||
|
errorCategory?: string;
|
||||||
|
paymentId?: string;
|
||||||
|
paymentTransactionId?: string;
|
||||||
|
price?: number;
|
||||||
|
retryable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adres modeli
|
||||||
|
*/
|
||||||
|
export interface Address {
|
||||||
|
contactName: string;
|
||||||
|
name?: string;
|
||||||
|
surname?: string;
|
||||||
|
address: string;
|
||||||
|
city: string;
|
||||||
|
country: string;
|
||||||
|
zipCode?: string;
|
||||||
|
phone?: string;
|
||||||
|
mobilePhone?: string;
|
||||||
|
province?: string;
|
||||||
|
line1?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fatura adresi modeli
|
||||||
|
*/
|
||||||
|
export interface BillingAddress extends Address {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kargo adresi modeli
|
||||||
|
*/
|
||||||
|
export interface ShippingAddress extends Address {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alıcı bilgileri modeli
|
||||||
|
*/
|
||||||
|
export interface Buyer {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
identityNumber: string;
|
||||||
|
email: string;
|
||||||
|
gsmNumber: string;
|
||||||
|
registrationAddress: string;
|
||||||
|
city: string;
|
||||||
|
country: string;
|
||||||
|
registrationDate?: string;
|
||||||
|
lastLoginDate?: string;
|
||||||
|
zipCode?: string;
|
||||||
|
ip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ödeme kartı modeli
|
||||||
|
*/
|
||||||
|
export interface PaymentCard {
|
||||||
|
cardHolderName: string;
|
||||||
|
cardNumber: string;
|
||||||
|
expireYear: string;
|
||||||
|
expireMonth: string;
|
||||||
|
cvc: string;
|
||||||
|
registerCard?: 0 | 1;
|
||||||
|
cardUserKey?: string;
|
||||||
|
cardToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sepet öğesi modeli
|
||||||
|
*/
|
||||||
|
export interface BasketItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
itemType: ItemType;
|
||||||
|
category1: string;
|
||||||
|
category2?: string;
|
||||||
|
quantity?: number;
|
||||||
|
itemDescription1?: string;
|
||||||
|
itemDescription2?: string;
|
||||||
|
subMerchantMemberId?: string;
|
||||||
|
subMerchantPrice?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İşlem kırılımı modeli
|
||||||
|
*/
|
||||||
|
export interface ItemTransaction {
|
||||||
|
paymentTransactionId: string;
|
||||||
|
itemId?: string;
|
||||||
|
price: number;
|
||||||
|
paidPrice: number;
|
||||||
|
transactionStatus?: number;
|
||||||
|
installment?: number;
|
||||||
|
merchantCommissionRate?: number;
|
||||||
|
merchantCommissionRateAmount?: number;
|
||||||
|
iyziCommissionRateAmount?: number;
|
||||||
|
iyziCommissionFee?: number;
|
||||||
|
blockageRate?: number;
|
||||||
|
blockageRateAmountMerchant?: number;
|
||||||
|
blockageRateAmountSubMerchant?: number;
|
||||||
|
blockageResolvedDate?: string;
|
||||||
|
subMerchantPrice?: number;
|
||||||
|
subMerchantPayoutRate?: number;
|
||||||
|
subMerchantPayoutAmount?: number;
|
||||||
|
merchantPayoutAmount?: number;
|
||||||
|
convertedPayout?: ConvertedPayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dönüştürülmüş ödeme modeli
|
||||||
|
*/
|
||||||
|
export interface ConvertedPayout {
|
||||||
|
paidPrice: number;
|
||||||
|
iyziCommissionRateAmount: number;
|
||||||
|
iyziCommissionFee: number;
|
||||||
|
blockageRateAmountMerchant: number;
|
||||||
|
blockageRateAmountSubMerchant: number;
|
||||||
|
subMerchantPayoutAmount: number;
|
||||||
|
merchantPayoutAmount: number;
|
||||||
|
iyziConversionRate?: number;
|
||||||
|
iyziConversionRateAmount?: number;
|
||||||
|
currency: Currency;
|
||||||
|
}
|
||||||
|
|
||||||
25
src/types/index.ts
Normal file
25
src/types/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Tüm type export'ları
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Common types
|
||||||
|
export * from './common';
|
||||||
|
|
||||||
|
// Payment types
|
||||||
|
export * from './payment';
|
||||||
|
|
||||||
|
// Bin check types
|
||||||
|
export * from './bin-check';
|
||||||
|
|
||||||
|
// Cancel & Refund types
|
||||||
|
export * from './cancel-refund';
|
||||||
|
|
||||||
|
// Card storage types
|
||||||
|
export * from './card-storage';
|
||||||
|
|
||||||
|
// Reporting types
|
||||||
|
export * from './reporting';
|
||||||
|
|
||||||
|
// Subscription types
|
||||||
|
export * from './subscription';
|
||||||
|
|
||||||
148
src/types/payment.ts
Normal file
148
src/types/payment.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* Ödeme işlemleri için modeller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
BaseResponse,
|
||||||
|
Locale,
|
||||||
|
Currency,
|
||||||
|
PaymentChannel,
|
||||||
|
PaymentGroup,
|
||||||
|
PaymentCard,
|
||||||
|
Buyer,
|
||||||
|
ShippingAddress,
|
||||||
|
BillingAddress,
|
||||||
|
BasketItem,
|
||||||
|
ItemTransaction,
|
||||||
|
Status,
|
||||||
|
} from './common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3DS Ödeme Başlatma Request
|
||||||
|
*/
|
||||||
|
export interface ThreeDSInitializeRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
price: number;
|
||||||
|
paidPrice: number;
|
||||||
|
currency?: Currency;
|
||||||
|
installment?: 1 | 2 | 3 | 4 | 6 | 9 | 12;
|
||||||
|
paymentChannel?: PaymentChannel;
|
||||||
|
basketId?: string;
|
||||||
|
paymentGroup?: PaymentGroup;
|
||||||
|
callbackUrl: string;
|
||||||
|
paymentCard: PaymentCard;
|
||||||
|
buyer: Buyer;
|
||||||
|
shippingAddress?: ShippingAddress;
|
||||||
|
billingAddress: BillingAddress;
|
||||||
|
basketItems: BasketItem[];
|
||||||
|
paymentSource?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3DS Ödeme Başlatma Response
|
||||||
|
*/
|
||||||
|
export interface ThreeDSInitializeResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
paymentId?: string;
|
||||||
|
htmlContent?: string;
|
||||||
|
threeDSHtmlContent?: string;
|
||||||
|
conversationId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3DS v1 Ödeme Tamamlama Request
|
||||||
|
*/
|
||||||
|
export interface ThreeDSAuthRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
paymentId: string;
|
||||||
|
conversationId?: string;
|
||||||
|
conversationData?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3DS v2 Ödeme Tamamlama Request
|
||||||
|
*/
|
||||||
|
export interface ThreeDSV2AuthRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
paymentId: string;
|
||||||
|
conversationId?: string;
|
||||||
|
paidPrice: number;
|
||||||
|
basketId: string;
|
||||||
|
currency: Currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-3DS Ödeme Request
|
||||||
|
*/
|
||||||
|
export interface Non3DPaymentRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
price: number;
|
||||||
|
paidPrice: number;
|
||||||
|
currency?: Currency;
|
||||||
|
installment?: 1 | 2 | 3 | 6 | 9 | 12;
|
||||||
|
paymentChannel?: PaymentChannel;
|
||||||
|
basketId?: string;
|
||||||
|
paymentGroup?: PaymentGroup;
|
||||||
|
paymentCard: PaymentCard;
|
||||||
|
buyer: Buyer;
|
||||||
|
shippingAddress?: ShippingAddress;
|
||||||
|
billingAddress: BillingAddress;
|
||||||
|
basketItems: BasketItem[];
|
||||||
|
paymentSource?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ödeme Response (3DS ve Non3DS için ortak)
|
||||||
|
*/
|
||||||
|
export interface PaymentResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
price?: number;
|
||||||
|
paidPrice?: number;
|
||||||
|
installment?: number;
|
||||||
|
paymentId?: string;
|
||||||
|
fraudStatus?: -1 | 0 | 1;
|
||||||
|
merchantCommissionRate?: number;
|
||||||
|
merchantCommissionRateAmount?: number;
|
||||||
|
iyziCommissionRateAmount?: number;
|
||||||
|
iyziCommissionFee?: number;
|
||||||
|
cardType?: string;
|
||||||
|
cardAssociation?: string;
|
||||||
|
cardFamily?: string;
|
||||||
|
binNumber?: string;
|
||||||
|
lastFourDigits?: string;
|
||||||
|
basketId?: string;
|
||||||
|
currency?: Currency;
|
||||||
|
itemTransactions?: ItemTransaction[];
|
||||||
|
authCode?: string;
|
||||||
|
phase?: string;
|
||||||
|
hostReference?: string;
|
||||||
|
signature?: string;
|
||||||
|
paymentStatus?: string;
|
||||||
|
mdStatus?: string;
|
||||||
|
hash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3DS v2 Ödeme Response
|
||||||
|
*/
|
||||||
|
export interface ThreeDSV2PaymentResponse extends PaymentResponse {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ödeme Sorgulama Request
|
||||||
|
*/
|
||||||
|
export interface PaymentDetailRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
paymentId?: string;
|
||||||
|
paymentConversationId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ödeme Sorgulama Response
|
||||||
|
*/
|
||||||
|
export interface PaymentDetailResponse extends PaymentResponse {}
|
||||||
|
|
||||||
98
src/types/reporting.ts
Normal file
98
src/types/reporting.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Raporlama işlemleri için modeller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BaseResponse, Locale, Currency, Status } from './common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İşlem raporlama Request
|
||||||
|
*/
|
||||||
|
export interface PaymentTransactionsRequest {
|
||||||
|
page: number;
|
||||||
|
transactionDate: string; // YYYY-MM-DD formatında
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İşlem öğesi modeli
|
||||||
|
*/
|
||||||
|
export interface TransactionItem {
|
||||||
|
transactionType: 'PAYMENT' | 'CANCEL' | 'REFUND';
|
||||||
|
transactionDate: string;
|
||||||
|
transactionId: string;
|
||||||
|
transactionStatus: number;
|
||||||
|
afterSettlement: 0 | 1;
|
||||||
|
paymentTxId?: string;
|
||||||
|
paymentId?: string;
|
||||||
|
conversationId?: string;
|
||||||
|
paymentPhase?: string;
|
||||||
|
price: number;
|
||||||
|
paidPrice: number;
|
||||||
|
transactionCurrency: Currency;
|
||||||
|
installment?: number;
|
||||||
|
threeDS: 0 | 1;
|
||||||
|
settlementCurrency?: Currency;
|
||||||
|
connectorType?: string;
|
||||||
|
posOrderId?: string;
|
||||||
|
authCode?: string;
|
||||||
|
hostReference?: string;
|
||||||
|
basketId?: string;
|
||||||
|
iyzicoCommission?: number;
|
||||||
|
iyzicoFee?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İşlem raporlama Response
|
||||||
|
*/
|
||||||
|
export interface PaymentTransactionsResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
transactions?: TransactionItem[];
|
||||||
|
currentPage?: number;
|
||||||
|
totalPageCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ödeme detay raporlama Request
|
||||||
|
*/
|
||||||
|
export interface PaymentDetailsRequest {
|
||||||
|
paymentId?: string;
|
||||||
|
paymentConversationId?: string;
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ödeme detay modeli
|
||||||
|
*/
|
||||||
|
export interface PaymentDetail {
|
||||||
|
paymentId: string;
|
||||||
|
paymentStatus: 1 | 2 | 3; // 1: Success, 2: Failure / INIT_THREEDS, 3: CALLBACK_THREEDS
|
||||||
|
paymentRefundStatus: 'NOT_REFUNDED' | 'PARTIALLY_REFUNDED' | 'TOTALLY_REFUNDED';
|
||||||
|
price: number;
|
||||||
|
paidPrice: number;
|
||||||
|
installment?: number;
|
||||||
|
merchantCommissionRate?: number;
|
||||||
|
merchantCommissionRateAmount?: number;
|
||||||
|
iyziCommissionRateAmount?: number;
|
||||||
|
iyziCommissionFee?: number;
|
||||||
|
paymentConversationId?: string;
|
||||||
|
fraudStatus?: -1 | 0 | 1;
|
||||||
|
cardType?: string;
|
||||||
|
cardAssociation?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ödeme detay raporlama Response
|
||||||
|
*/
|
||||||
|
export interface PaymentDetailsResponse extends BaseResponse {
|
||||||
|
status: Status;
|
||||||
|
locale?: Locale;
|
||||||
|
systemTime?: number;
|
||||||
|
conversationId?: string;
|
||||||
|
payments?: PaymentDetail[];
|
||||||
|
}
|
||||||
|
|
||||||
511
src/types/subscription.ts
Normal file
511
src/types/subscription.ts
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
/**
|
||||||
|
* Subscription API type definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BaseResponse, Locale, Currency } from './common';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Enums and Type Aliases
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type PaymentInterval = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
|
||||||
|
export type PlanPaymentType = 'RECURRING';
|
||||||
|
export type SubscriptionStatus = 'ACTIVE' | 'PENDING' | 'UNPAID' | 'UPGRADED' | 'CANCELED' | 'EXPIRED';
|
||||||
|
export type SubscriptionInitialStatus = 'ACTIVE' | 'PENDING';
|
||||||
|
export type UpgradePeriod = 'NOW' | 'NEXT_PERIOD';
|
||||||
|
export type SubscriptionProductStatus = 'ACTIVE' | 'INACTIVE';
|
||||||
|
export type SubscriptionCustomerStatus = 'ACTIVE' | 'INACTIVE';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Common Subscription Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paginated result wrapper
|
||||||
|
*/
|
||||||
|
export interface PaginatedResult<T> {
|
||||||
|
totalCount: number;
|
||||||
|
currentPage: number;
|
||||||
|
pageCount: number;
|
||||||
|
items: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription address model (different from payment Address)
|
||||||
|
*/
|
||||||
|
export interface SubscriptionAddress {
|
||||||
|
address: string;
|
||||||
|
zipCode?: string;
|
||||||
|
contactName: string;
|
||||||
|
city: string;
|
||||||
|
district?: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription customer model
|
||||||
|
*/
|
||||||
|
export interface SubscriptionCustomer {
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
email: string;
|
||||||
|
gsmNumber: string;
|
||||||
|
identityNumber: string;
|
||||||
|
billingAddress: SubscriptionAddress;
|
||||||
|
shippingAddress?: SubscriptionAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription customer resource (response model)
|
||||||
|
*/
|
||||||
|
export interface SubscriptionCustomerResource {
|
||||||
|
referenceCode: string;
|
||||||
|
createdDate: number;
|
||||||
|
status: SubscriptionCustomerStatus;
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
identityNumber: string;
|
||||||
|
email: string;
|
||||||
|
gsmNumber: string;
|
||||||
|
contactEmail?: string;
|
||||||
|
contactGsmNumber?: string;
|
||||||
|
billingAddress: SubscriptionAddress;
|
||||||
|
shippingAddress?: SubscriptionAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription payment card model
|
||||||
|
*/
|
||||||
|
export interface SubscriptionPaymentCard {
|
||||||
|
cardHolderName: string;
|
||||||
|
cardNumber: string;
|
||||||
|
expireMonth: string;
|
||||||
|
expireYear: string;
|
||||||
|
cvc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pricing plan model
|
||||||
|
*/
|
||||||
|
export interface PricingPlan {
|
||||||
|
referenceCode: string;
|
||||||
|
createdDate: string | number;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
currencyCode: Currency;
|
||||||
|
paymentInterval: PaymentInterval;
|
||||||
|
paymentIntervalCount: number;
|
||||||
|
trialPeriodDays?: number;
|
||||||
|
productReferenceCode: string;
|
||||||
|
planPaymentType: PlanPaymentType;
|
||||||
|
status: string;
|
||||||
|
recurrenceCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription product model
|
||||||
|
*/
|
||||||
|
export interface SubscriptionProduct {
|
||||||
|
referenceCode: string;
|
||||||
|
createdDate: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
status: SubscriptionProductStatus;
|
||||||
|
pricingPlans?: PricingPlan[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription order model
|
||||||
|
*/
|
||||||
|
export interface SubscriptionOrder {
|
||||||
|
referenceCode: string;
|
||||||
|
price: number;
|
||||||
|
currencyCode: Currency;
|
||||||
|
startPeriod: number;
|
||||||
|
endPeriod: number;
|
||||||
|
orderStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription resource model
|
||||||
|
*/
|
||||||
|
export interface SubscriptionResource {
|
||||||
|
referenceCode: string;
|
||||||
|
parentReferenceCode?: string;
|
||||||
|
pricingPlanName?: string;
|
||||||
|
pricingPlanReferenceCode: string;
|
||||||
|
productName?: string;
|
||||||
|
productReferenceCode?: string;
|
||||||
|
customerEmail?: string;
|
||||||
|
customerGsmNumber?: string;
|
||||||
|
customerReferenceCode: string;
|
||||||
|
subscriptionStatus: SubscriptionStatus;
|
||||||
|
trialDays?: number;
|
||||||
|
trialStartDate?: number;
|
||||||
|
trialEndDate?: number;
|
||||||
|
createdDate?: number;
|
||||||
|
startDate?: number;
|
||||||
|
endDate?: number;
|
||||||
|
orders?: SubscriptionOrder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Product Request/Response Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create product request
|
||||||
|
*/
|
||||||
|
export interface ProductCreateRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create product response
|
||||||
|
*/
|
||||||
|
export interface ProductCreateResponse extends BaseResponse {
|
||||||
|
data?: SubscriptionProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List products request
|
||||||
|
*/
|
||||||
|
export interface ProductListRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
page?: number;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List products response
|
||||||
|
*/
|
||||||
|
export interface ProductListResponse extends BaseResponse {
|
||||||
|
data?: PaginatedResult<SubscriptionProduct>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product response
|
||||||
|
*/
|
||||||
|
export interface ProductGetResponse extends BaseResponse {
|
||||||
|
data?: SubscriptionProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update product request
|
||||||
|
*/
|
||||||
|
export interface ProductUpdateRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update product response
|
||||||
|
*/
|
||||||
|
export interface ProductUpdateResponse extends BaseResponse {
|
||||||
|
data?: SubscriptionProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete product response
|
||||||
|
*/
|
||||||
|
export interface ProductDeleteResponse extends BaseResponse {}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Pricing Plan Request/Response Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create pricing plan request
|
||||||
|
*/
|
||||||
|
export interface PricingPlanCreateRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
currencyCode: Currency;
|
||||||
|
paymentInterval: PaymentInterval;
|
||||||
|
paymentIntervalCount?: number;
|
||||||
|
trialPeriodDays?: number;
|
||||||
|
planPaymentType: PlanPaymentType;
|
||||||
|
recurrenceCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create pricing plan response
|
||||||
|
*/
|
||||||
|
export interface PricingPlanCreateResponse extends BaseResponse {
|
||||||
|
data?: PricingPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List pricing plans request
|
||||||
|
*/
|
||||||
|
export interface PricingPlanListRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
page?: number;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List pricing plans response
|
||||||
|
*/
|
||||||
|
export interface PricingPlanListResponse extends BaseResponse {
|
||||||
|
data?: PaginatedResult<PricingPlan>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pricing plan response
|
||||||
|
*/
|
||||||
|
export interface PricingPlanGetResponse extends BaseResponse {
|
||||||
|
data?: PricingPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update pricing plan request
|
||||||
|
*/
|
||||||
|
export interface PricingPlanUpdateRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
name: string;
|
||||||
|
trialPeriodDays?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update pricing plan response
|
||||||
|
*/
|
||||||
|
export interface PricingPlanUpdateResponse extends BaseResponse {
|
||||||
|
data?: PricingPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete pricing plan response
|
||||||
|
*/
|
||||||
|
export interface PricingPlanDeleteResponse extends BaseResponse {}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Customer Request/Response Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List customers request
|
||||||
|
*/
|
||||||
|
export interface CustomerListRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
page?: number;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List customers response
|
||||||
|
*/
|
||||||
|
export interface CustomerListResponse extends BaseResponse {
|
||||||
|
data?: PaginatedResult<SubscriptionCustomerResource>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get customer response
|
||||||
|
*/
|
||||||
|
export interface CustomerGetResponse extends BaseResponse {
|
||||||
|
data?: SubscriptionCustomerResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update customer request
|
||||||
|
*/
|
||||||
|
export interface CustomerUpdateRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
name?: string;
|
||||||
|
surname?: string;
|
||||||
|
email?: string;
|
||||||
|
gsmNumber?: string;
|
||||||
|
identityNumber?: string;
|
||||||
|
billingAddress?: SubscriptionAddress;
|
||||||
|
shippingAddress?: SubscriptionAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update customer response
|
||||||
|
*/
|
||||||
|
export interface CustomerUpdateResponse extends BaseResponse {
|
||||||
|
data?: SubscriptionCustomerResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Subscription Request/Response Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize subscription request (NON3D)
|
||||||
|
*/
|
||||||
|
export interface SubscriptionInitializeRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
pricingPlanReferenceCode: string;
|
||||||
|
subscriptionInitialStatus: SubscriptionInitialStatus;
|
||||||
|
customer: SubscriptionCustomer;
|
||||||
|
paymentCard: SubscriptionPaymentCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize subscription response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionInitializeResponse extends BaseResponse {
|
||||||
|
data?: {
|
||||||
|
referenceCode: string;
|
||||||
|
parentReferenceCode?: string;
|
||||||
|
customerReferenceCode: string;
|
||||||
|
pricingPlanReferenceCode?: string;
|
||||||
|
subscriptionStatus?: SubscriptionStatus;
|
||||||
|
trialDays?: number;
|
||||||
|
trialStartDate?: number;
|
||||||
|
trialEndDate?: number;
|
||||||
|
createdDate?: number;
|
||||||
|
startDate?: number;
|
||||||
|
endDate?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize subscription with existing customer request
|
||||||
|
*/
|
||||||
|
export interface SubscriptionInitializeWithCustomerRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
pricingPlanReferenceCode: string;
|
||||||
|
subscriptionInitialStatus: SubscriptionInitialStatus;
|
||||||
|
customerReferenceCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize subscription checkout form request
|
||||||
|
*/
|
||||||
|
export interface SubscriptionCheckoutFormInitializeRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
callbackUrl: string;
|
||||||
|
pricingPlanReferenceCode: string;
|
||||||
|
subscriptionInitialStatus: SubscriptionInitialStatus;
|
||||||
|
customer: SubscriptionCustomer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize subscription checkout form response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionCheckoutFormInitializeResponse extends BaseResponse {
|
||||||
|
token?: string;
|
||||||
|
checkoutFormContent?: string;
|
||||||
|
tokenExpireTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List subscriptions request
|
||||||
|
*/
|
||||||
|
export interface SubscriptionListRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
subscriptionReferenceCode?: string;
|
||||||
|
customerReferenceCode?: string;
|
||||||
|
pricingPlanReferenceCode?: string;
|
||||||
|
parentReferenceCode?: string;
|
||||||
|
subscriptionStatus?: SubscriptionStatus;
|
||||||
|
startDate?: number;
|
||||||
|
endDate?: number;
|
||||||
|
page?: number;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List subscriptions response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionListResponse extends BaseResponse {
|
||||||
|
data?: PaginatedResult<SubscriptionResource>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get subscription response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionGetResponse extends BaseResponse {
|
||||||
|
data?: PaginatedResult<SubscriptionResource>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel subscription response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionCancelResponse extends BaseResponse {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate subscription response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionActivateResponse extends BaseResponse {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade subscription request
|
||||||
|
*/
|
||||||
|
export interface SubscriptionUpgradeRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
upgradePeriod: UpgradePeriod;
|
||||||
|
newPricingPlanReferenceCode: string;
|
||||||
|
useTrial?: boolean;
|
||||||
|
resetRecurrenceCount?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade subscription response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionUpgradeResponse extends BaseResponse {
|
||||||
|
data?: {
|
||||||
|
referenceCode: string;
|
||||||
|
parentReferenceCode?: string;
|
||||||
|
pricingPlanReferenceCode: string;
|
||||||
|
customerReferenceCode: string;
|
||||||
|
subscriptionStatus: SubscriptionStatus;
|
||||||
|
trialDays?: number;
|
||||||
|
createdDate?: number;
|
||||||
|
startDate?: number;
|
||||||
|
endDate?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry failed payment request
|
||||||
|
*/
|
||||||
|
export interface SubscriptionRetryRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
referenceCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry failed payment response
|
||||||
|
*/
|
||||||
|
export interface SubscriptionRetryResponse extends BaseResponse {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Card update checkout form initialize request
|
||||||
|
*/
|
||||||
|
export interface CardUpdateCheckoutFormInitializeRequest {
|
||||||
|
locale?: Locale;
|
||||||
|
conversationId?: string;
|
||||||
|
callbackUrl: string;
|
||||||
|
customerReferenceCode: string;
|
||||||
|
subscriptionReferenceCode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Card update checkout form initialize response
|
||||||
|
*/
|
||||||
|
export interface CardUpdateCheckoutFormInitializeResponse extends BaseResponse {
|
||||||
|
token?: string;
|
||||||
|
checkoutFormContent?: string;
|
||||||
|
tokenExpireTime?: number;
|
||||||
|
}
|
||||||
28
src/utils.ts
Normal file
28
src/utils.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random number between min and max (inclusive)
|
||||||
|
* @param min - Minimum value (default: 0)
|
||||||
|
* @param max - Maximum value (default: 100)
|
||||||
|
* @returns Random number between min and max
|
||||||
|
*/
|
||||||
|
export function generateRandomNumber(min: number = 0, max: number = 100): number {
|
||||||
|
if (min > max) {
|
||||||
|
throw new Error('Min value cannot be greater than max value');
|
||||||
|
}
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random key for İyzico API requests
|
||||||
|
* Uses timestamp + random number combination
|
||||||
|
* @returns Random key string
|
||||||
|
*/
|
||||||
|
export function generateRandomKey(): string {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const random = generateRandomNumber(100000, 999999);
|
||||||
|
return `${timestamp}${random}`;
|
||||||
|
}
|
||||||
|
|
||||||
49
tests/fixtures/card.ts
vendored
Normal file
49
tests/fixtures/card.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Card storage test fixtures with randomized personal data
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import type { Card, CardCreateRequest, CardCreateWithUserKeyRequest } from '../../src/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test card with randomized holder name
|
||||||
|
* Card number is fixed to iyzico sandbox test card
|
||||||
|
*/
|
||||||
|
export function createTestCard(): Card {
|
||||||
|
return {
|
||||||
|
cardAlias: faker.helpers.arrayElement(['My Card', 'Personal Card', 'Main Card', 'Default Card']),
|
||||||
|
cardNumber: '5528790000000008', // iyzico sandbox test card
|
||||||
|
expireYear: '2030',
|
||||||
|
expireMonth: '12',
|
||||||
|
cardHolderName: faker.person.fullName(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test card create request with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestCardCreateRequest(): CardCreateRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: faker.string.uuid(),
|
||||||
|
email: faker.internet.email().toLowerCase(),
|
||||||
|
externalId: `external-${faker.string.alphanumeric(10)}`,
|
||||||
|
card: createTestCard(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test card create request with user key
|
||||||
|
*/
|
||||||
|
export function createCardCreateWithUserKeyRequest(cardUserKey: string): CardCreateWithUserKeyRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: faker.string.uuid(),
|
||||||
|
cardUserKey,
|
||||||
|
card: createTestCard(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy exports for backward compatibility (static versions that generate once)
|
||||||
|
export const testCard = createTestCard();
|
||||||
|
export const testCardCreateRequest = createTestCardCreateRequest();
|
||||||
206
tests/fixtures/payment.ts
vendored
Normal file
206
tests/fixtures/payment.ts
vendored
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
/**
|
||||||
|
* Payment test fixtures with randomized personal data
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import type {
|
||||||
|
ThreeDSInitializeRequest,
|
||||||
|
Non3DPaymentRequest,
|
||||||
|
PaymentCard,
|
||||||
|
Buyer,
|
||||||
|
BillingAddress,
|
||||||
|
ShippingAddress,
|
||||||
|
BasketItem,
|
||||||
|
} from '../../src/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a valid Turkish identity number (TC Kimlik No)
|
||||||
|
* Uses a simple algorithm that produces valid-looking numbers
|
||||||
|
*/
|
||||||
|
function generateTurkishIdentityNumber(): string {
|
||||||
|
const digits: number[] = [];
|
||||||
|
|
||||||
|
// First digit cannot be 0
|
||||||
|
digits.push(faker.number.int({ min: 1, max: 9 }));
|
||||||
|
|
||||||
|
// Generate next 8 random digits
|
||||||
|
for (let i = 1; i < 9; i++) {
|
||||||
|
digits.push(faker.number.int({ min: 0, max: 9 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate 10th digit
|
||||||
|
const oddSum = digits[0] + digits[2] + digits[4] + digits[6] + digits[8];
|
||||||
|
const evenSum = digits[1] + digits[3] + digits[5] + digits[7];
|
||||||
|
const digit10 = (oddSum * 7 - evenSum) % 10;
|
||||||
|
digits.push(digit10 < 0 ? digit10 + 10 : digit10);
|
||||||
|
|
||||||
|
// Calculate 11th digit
|
||||||
|
const allSum = digits.reduce((sum, d) => sum + d, 0);
|
||||||
|
digits.push(allSum % 10);
|
||||||
|
|
||||||
|
return digits.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a Turkish phone number in the format +905XXXXXXXXX
|
||||||
|
*/
|
||||||
|
function generateTurkishPhoneNumber(): string {
|
||||||
|
const areaCode = faker.helpers.arrayElement(['530', '531', '532', '533', '534', '535', '536', '537', '538', '539', '540', '541', '542', '543', '544', '545', '546', '547', '548', '549', '550', '551', '552', '553', '554', '555', '556', '557', '558', '559']);
|
||||||
|
const number = faker.string.numeric(7);
|
||||||
|
return `+90${areaCode}${number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random IP address
|
||||||
|
*/
|
||||||
|
function generateIpAddress(): string {
|
||||||
|
return faker.internet.ipv4();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test payment card with randomized holder name
|
||||||
|
* Card number is fixed to iyzico sandbox test card
|
||||||
|
*/
|
||||||
|
export function createTestCard(): PaymentCard {
|
||||||
|
return {
|
||||||
|
cardHolderName: faker.person.fullName(),
|
||||||
|
cardNumber: '5528790000000008', // iyzico sandbox test card
|
||||||
|
expireMonth: '12',
|
||||||
|
expireYear: '2030',
|
||||||
|
cvc: '123',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test buyer with randomized personal data
|
||||||
|
*/
|
||||||
|
export function createTestBuyer(): Buyer {
|
||||||
|
const firstName = faker.person.firstName();
|
||||||
|
const lastName = faker.person.lastName();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `BY${faker.string.alphanumeric(6).toUpperCase()}`,
|
||||||
|
name: firstName,
|
||||||
|
surname: lastName,
|
||||||
|
identityNumber: generateTurkishIdentityNumber(),
|
||||||
|
email: faker.internet.email({ firstName, lastName }).toLowerCase(),
|
||||||
|
gsmNumber: generateTurkishPhoneNumber(),
|
||||||
|
registrationAddress: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||||
|
city: faker.location.city(),
|
||||||
|
country: 'Turkey',
|
||||||
|
ip: generateIpAddress(),
|
||||||
|
zipCode: faker.location.zipCode('#####'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test billing address with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestBillingAddress(): BillingAddress {
|
||||||
|
return {
|
||||||
|
contactName: faker.person.fullName(),
|
||||||
|
city: faker.location.city(),
|
||||||
|
country: 'Turkey',
|
||||||
|
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||||
|
zipCode: faker.location.zipCode('#####'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test shipping address with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestShippingAddress(): ShippingAddress {
|
||||||
|
return {
|
||||||
|
contactName: faker.person.fullName(),
|
||||||
|
city: faker.location.city(),
|
||||||
|
country: 'Turkey',
|
||||||
|
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||||
|
zipCode: faker.location.zipCode('#####'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates test basket items
|
||||||
|
*/
|
||||||
|
export function createTestBasketItems(): BasketItem[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: `BI${faker.string.alphanumeric(3).toUpperCase()}`,
|
||||||
|
name: faker.commerce.productName(),
|
||||||
|
category1: faker.commerce.department(),
|
||||||
|
category2: faker.commerce.productAdjective(),
|
||||||
|
itemType: 'PHYSICAL',
|
||||||
|
price: 0.3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `BI${faker.string.alphanumeric(3).toUpperCase()}`,
|
||||||
|
name: faker.commerce.productName(),
|
||||||
|
category1: 'Game',
|
||||||
|
category2: 'Online Game Items',
|
||||||
|
itemType: 'VIRTUAL',
|
||||||
|
price: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `BI${faker.string.alphanumeric(3).toUpperCase()}`,
|
||||||
|
name: faker.commerce.productName(),
|
||||||
|
category1: 'Electronics',
|
||||||
|
category2: faker.commerce.productAdjective(),
|
||||||
|
itemType: 'PHYSICAL',
|
||||||
|
price: 0.2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test 3DS initialize request with randomized data
|
||||||
|
*/
|
||||||
|
export function createTest3DSInitializeRequest(): ThreeDSInitializeRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: faker.string.uuid(),
|
||||||
|
price: 1.0,
|
||||||
|
paidPrice: 1.1,
|
||||||
|
currency: 'TRY',
|
||||||
|
installment: 1,
|
||||||
|
paymentChannel: 'WEB',
|
||||||
|
basketId: `B${faker.string.alphanumeric(5).toUpperCase()}`,
|
||||||
|
paymentGroup: 'PRODUCT',
|
||||||
|
callbackUrl: 'https://www.merchant.com/callback',
|
||||||
|
paymentCard: createTestCard(),
|
||||||
|
buyer: createTestBuyer(),
|
||||||
|
billingAddress: createTestBillingAddress(),
|
||||||
|
shippingAddress: createTestShippingAddress(),
|
||||||
|
basketItems: createTestBasketItems(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a test non-3DS payment request with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestNon3DPaymentRequest(): Non3DPaymentRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: faker.string.uuid(),
|
||||||
|
price: 1.0,
|
||||||
|
paidPrice: 1.1,
|
||||||
|
currency: 'TRY',
|
||||||
|
installment: 1,
|
||||||
|
paymentChannel: 'WEB',
|
||||||
|
basketId: `B${faker.string.alphanumeric(5).toUpperCase()}`,
|
||||||
|
paymentGroup: 'PRODUCT',
|
||||||
|
paymentCard: createTestCard(),
|
||||||
|
buyer: createTestBuyer(),
|
||||||
|
billingAddress: createTestBillingAddress(),
|
||||||
|
shippingAddress: createTestShippingAddress(),
|
||||||
|
basketItems: createTestBasketItems(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy exports for backward compatibility (static versions that generate once)
|
||||||
|
export const testCard = createTestCard();
|
||||||
|
export const testBuyer = createTestBuyer();
|
||||||
|
export const testBillingAddress = createTestBillingAddress();
|
||||||
|
export const testShippingAddress = createTestShippingAddress();
|
||||||
|
export const testBasketItems = createTestBasketItems();
|
||||||
|
export const test3DSInitializeRequest = createTest3DSInitializeRequest();
|
||||||
|
export const testNon3DPaymentRequest = createTestNon3DPaymentRequest();
|
||||||
193
tests/fixtures/subscription.ts
vendored
Normal file
193
tests/fixtures/subscription.ts
vendored
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
/**
|
||||||
|
* Subscription test fixtures with randomized personal data
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import type {
|
||||||
|
ProductCreateRequest,
|
||||||
|
PricingPlanCreateRequest,
|
||||||
|
SubscriptionCustomer,
|
||||||
|
SubscriptionPaymentCard,
|
||||||
|
SubscriptionAddress,
|
||||||
|
SubscriptionInitializeRequest,
|
||||||
|
SubscriptionCheckoutFormInitializeRequest,
|
||||||
|
} from '../../src/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a valid Turkish identity number (TC Kimlik No)
|
||||||
|
*/
|
||||||
|
function generateTurkishIdentityNumber(): string {
|
||||||
|
const digits: number[] = [];
|
||||||
|
|
||||||
|
// First digit cannot be 0
|
||||||
|
digits.push(faker.number.int({ min: 1, max: 9 }));
|
||||||
|
|
||||||
|
// Generate next 8 random digits
|
||||||
|
for (let i = 1; i < 9; i++) {
|
||||||
|
digits.push(faker.number.int({ min: 0, max: 9 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate 10th digit
|
||||||
|
const oddSum = digits[0] + digits[2] + digits[4] + digits[6] + digits[8];
|
||||||
|
const evenSum = digits[1] + digits[3] + digits[5] + digits[7];
|
||||||
|
const digit10 = (oddSum * 7 - evenSum) % 10;
|
||||||
|
digits.push(digit10 < 0 ? digit10 + 10 : digit10);
|
||||||
|
|
||||||
|
// Calculate 11th digit
|
||||||
|
const allSum = digits.reduce((sum, d) => sum + d, 0);
|
||||||
|
digits.push(allSum % 10);
|
||||||
|
|
||||||
|
return digits.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a Turkish phone number in the format +905XXXXXXXXX
|
||||||
|
*/
|
||||||
|
function generateTurkishPhoneNumber(): string {
|
||||||
|
const areaCode = faker.helpers.arrayElement(['530', '531', '532', '533', '534', '535', '536', '537', '538', '539', '540', '541', '542', '543', '544', '545', '546', '547', '548', '549', '550', '551', '552', '553', '554', '555', '556', '557', '558', '559']);
|
||||||
|
const number = faker.string.numeric(7);
|
||||||
|
return `+90${areaCode}${number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a subscription billing address with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestSubscriptionBillingAddress(): SubscriptionAddress {
|
||||||
|
return {
|
||||||
|
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||||
|
zipCode: faker.location.zipCode('#####'),
|
||||||
|
contactName: faker.person.fullName(),
|
||||||
|
city: faker.location.city(),
|
||||||
|
country: 'Turkey',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a subscription shipping address with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestSubscriptionShippingAddress(): SubscriptionAddress {
|
||||||
|
return {
|
||||||
|
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||||
|
zipCode: faker.location.zipCode('#####'),
|
||||||
|
contactName: faker.person.fullName(),
|
||||||
|
city: faker.location.city(),
|
||||||
|
country: 'Turkey',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a subscription customer with randomized personal data
|
||||||
|
*/
|
||||||
|
export function createTestSubscriptionCustomer(): SubscriptionCustomer {
|
||||||
|
const firstName = faker.person.firstName();
|
||||||
|
const lastName = faker.person.lastName();
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: firstName,
|
||||||
|
surname: lastName,
|
||||||
|
email: faker.internet.email({ firstName, lastName }).toLowerCase(),
|
||||||
|
gsmNumber: generateTurkishPhoneNumber(),
|
||||||
|
identityNumber: generateTurkishIdentityNumber(),
|
||||||
|
billingAddress: createTestSubscriptionBillingAddress(),
|
||||||
|
shippingAddress: createTestSubscriptionShippingAddress(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a subscription payment card with randomized holder name
|
||||||
|
* Card number is fixed to iyzico sandbox test card
|
||||||
|
*/
|
||||||
|
export function createTestSubscriptionPaymentCard(): SubscriptionPaymentCard {
|
||||||
|
return {
|
||||||
|
cardHolderName: faker.person.fullName(),
|
||||||
|
cardNumber: '5528790000000008', // iyzico sandbox test card
|
||||||
|
expireMonth: '12',
|
||||||
|
expireYear: '2030',
|
||||||
|
cvc: '123',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a product create request with unique name
|
||||||
|
*/
|
||||||
|
export function createTestProductCreateRequest(): ProductCreateRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
name: `Test ${faker.string.alphanumeric(6)}`,
|
||||||
|
description: faker.commerce.productDescription().substring(0,20),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a pricing plan create request
|
||||||
|
*/
|
||||||
|
export function createTestPricingPlanCreateRequest(): PricingPlanCreateRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
name: `Monthly Test Plan ${faker.string.alphanumeric(6)}`,
|
||||||
|
price: 99.99,
|
||||||
|
currencyCode: 'TRY',
|
||||||
|
paymentInterval: 'MONTHLY',
|
||||||
|
paymentIntervalCount: 1,
|
||||||
|
trialPeriodDays: 7,
|
||||||
|
planPaymentType: 'RECURRING',
|
||||||
|
recurrenceCount: 12,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a subscription initialize request with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestSubscriptionInitializeRequest(pricingPlanReferenceCode: string = ''): SubscriptionInitializeRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
pricingPlanReferenceCode,
|
||||||
|
subscriptionInitialStatus: 'ACTIVE',
|
||||||
|
customer: createTestSubscriptionCustomer(),
|
||||||
|
paymentCard: createTestSubscriptionPaymentCard(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a subscription checkout form request with randomized data
|
||||||
|
*/
|
||||||
|
export function createTestSubscriptionCheckoutFormRequest(pricingPlanReferenceCode: string = ''): SubscriptionCheckoutFormInitializeRequest {
|
||||||
|
return {
|
||||||
|
locale: 'tr',
|
||||||
|
callbackUrl: 'https://www.merchant.com/callback',
|
||||||
|
pricingPlanReferenceCode,
|
||||||
|
subscriptionInitialStatus: 'ACTIVE',
|
||||||
|
customer: createTestSubscriptionCustomer(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique product name for testing
|
||||||
|
*/
|
||||||
|
export function generateTestProductName(): string {
|
||||||
|
return `Test Product ${Date.now()}-${faker.string.alphanumeric(4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique pricing plan name for testing
|
||||||
|
*/
|
||||||
|
export function generateTestPlanName(): string {
|
||||||
|
return `Test Plan ${Date.now()}-${faker.string.alphanumeric(4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique email for testing
|
||||||
|
*/
|
||||||
|
export function generateTestEmail(): string {
|
||||||
|
return faker.internet.email().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy exports for backward compatibility (static versions that generate once)
|
||||||
|
export const testSubscriptionBillingAddress = createTestSubscriptionBillingAddress();
|
||||||
|
export const testSubscriptionShippingAddress = createTestSubscriptionShippingAddress();
|
||||||
|
export const testSubscriptionCustomer = createTestSubscriptionCustomer();
|
||||||
|
export const testSubscriptionPaymentCard = createTestSubscriptionPaymentCard();
|
||||||
|
export const testProductCreateRequest = createTestProductCreateRequest();
|
||||||
|
export const testPricingPlanCreateRequest = createTestPricingPlanCreateRequest();
|
||||||
|
export const testSubscriptionInitializeRequest = createTestSubscriptionInitializeRequest();
|
||||||
|
export const testSubscriptionCheckoutFormRequest = createTestSubscriptionCheckoutFormRequest();
|
||||||
224
tests/integration/bin-check.test.ts
Normal file
224
tests/integration/bin-check.test.ts
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/**
|
||||||
|
* BIN Check integration tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||||
|
import type { ErrorResponse } from '../../src/types';
|
||||||
|
|
||||||
|
describe('BIN Check Integration Tests', () => {
|
||||||
|
const client = createTestClient();
|
||||||
|
|
||||||
|
it('should check BIN for a valid card number', async () => {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '535805',
|
||||||
|
conversationId: 'test-bin-check-1',
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.binNumber).toBe('535805');
|
||||||
|
expect(response.cardType).toBeTruthy();
|
||||||
|
expect(response.cardAssociation).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check BIN with price for installment details', async () => {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '535805',
|
||||||
|
price: 100.0,
|
||||||
|
conversationId: 'test-bin-check-2',
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.binNumber).toBe('535805');
|
||||||
|
if (response.installmentDetails && response.installmentDetails.length > 0) {
|
||||||
|
expect(response.installmentDetails[0].installmentPrices).toBeTruthy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid BIN number', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '000000',
|
||||||
|
conversationId: 'test-bin-check-invalid',
|
||||||
|
});
|
||||||
|
// If API doesn't reject invalid BINs, verify response structure
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
// If it's a success response, verify it has expected structure
|
||||||
|
if (response.status === 'success') {
|
||||||
|
expect(response.binNumber).toBe('000000');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
// API might return an error for invalid BIN - verify it's an Iyzico error
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
// Verify error has proper structure
|
||||||
|
if (error.code) {
|
||||||
|
expect(typeof error.code).toBe('string');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge Cases - Invalid Fields', () => {
|
||||||
|
it('should handle missing binNumber', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: undefined as any,
|
||||||
|
conversationId: 'test-missing-bin',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty binNumber', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '',
|
||||||
|
conversationId: 'test-empty-bin',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle binNumber that is too short', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '12345', // 5 digits, should be 6
|
||||||
|
conversationId: 'test-short-bin',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle binNumber that is too long', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '1234567', // 7 digits, should be 6
|
||||||
|
conversationId: 'test-long-bin',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle binNumber with non-numeric characters', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: 'ABC123',
|
||||||
|
conversationId: 'test-nonnumeric-bin',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle negative price', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '535805',
|
||||||
|
price: -100.0,
|
||||||
|
conversationId: 'test-negative-price',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle zero price', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'tr',
|
||||||
|
binNumber: '535805',
|
||||||
|
price: 0,
|
||||||
|
conversationId: 'test-zero-price',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid locale', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: 'invalid' as any,
|
||||||
|
binNumber: '535805',
|
||||||
|
conversationId: 'test-invalid-locale',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing locale', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.binCheck.check({
|
||||||
|
locale: undefined as any,
|
||||||
|
binNumber: '535805',
|
||||||
|
conversationId: 'test-missing-locale',
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
367
tests/integration/cancel-refund.test.ts
Normal file
367
tests/integration/cancel-refund.test.ts
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
/**
|
||||||
|
* Cancel & Refund integration tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||||
|
import { testNon3DPaymentRequest } from '../fixtures/payment';
|
||||||
|
import type { ErrorResponse } from '../../src/types';
|
||||||
|
|
||||||
|
describe('Cancel & Refund Integration Tests', () => {
|
||||||
|
const client = createTestClient();
|
||||||
|
|
||||||
|
it('should cancel a payment', async () => {
|
||||||
|
// First create a payment
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-cancel-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.paymentId) {
|
||||||
|
// Then cancel it
|
||||||
|
const cancelResponse = await client.cancelRefund.cancel({
|
||||||
|
paymentId: paymentResponse.paymentId,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-cancel-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(cancelResponse);
|
||||||
|
expect(cancelResponse.paymentId).toBe(paymentResponse.paymentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should refund a payment using v1 endpoint', async () => {
|
||||||
|
// First create a payment
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-refund-v1-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.paymentId && paymentResponse.itemTransactions) {
|
||||||
|
// Get the first payment transaction ID
|
||||||
|
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||||
|
if (firstTransaction && firstTransaction.paymentTransactionId) {
|
||||||
|
// Then refund it using v1
|
||||||
|
const refundResponse = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||||
|
price: firstTransaction.price,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-refund-v1-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(refundResponse);
|
||||||
|
expect(refundResponse.paymentTransactionId).toBe(
|
||||||
|
firstTransaction.paymentTransactionId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should refund a payment using v2 endpoint', async () => {
|
||||||
|
// First create a payment
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-refund-v2-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.paymentId && paymentResponse.paidPrice) {
|
||||||
|
// Then refund it using v2
|
||||||
|
const refundResponse = await client.cancelRefund.refundV2({
|
||||||
|
paymentId: paymentResponse.paymentId,
|
||||||
|
price: paymentResponse.paidPrice,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-refund-v2-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(refundResponse);
|
||||||
|
expect(refundResponse.paymentId).toBe(paymentResponse.paymentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge Cases - Invalid Fields', () => {
|
||||||
|
it('should handle invalid paymentId for cancel', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.cancel({
|
||||||
|
paymentId: 'invalid-payment-id-99999',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-paymentid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty paymentId for cancel', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.cancel({
|
||||||
|
paymentId: '',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-empty-paymentid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing paymentId for cancel', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.cancel({
|
||||||
|
paymentId: undefined as any,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-missing-paymentid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid paymentTransactionId for refund', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: 'invalid-transaction-id-99999',
|
||||||
|
price: 1.0,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-transactionid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty paymentTransactionId for refund', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: '',
|
||||||
|
price: 1.0,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-empty-transactionid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing paymentTransactionId for refund', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: undefined as any,
|
||||||
|
price: 1.0,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-missing-transactionid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle negative price for refund', async () => {
|
||||||
|
// First create a payment to get a transaction ID
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-refund-negative-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||||
|
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||||
|
if (firstTransaction.paymentTransactionId) {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||||
|
price: -100.0,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-refund-negative-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle zero price for refund', async () => {
|
||||||
|
// First create a payment to get a transaction ID
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-refund-zero-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||||
|
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||||
|
if (firstTransaction.paymentTransactionId) {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||||
|
price: 0,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-refund-zero-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle refund amount greater than original payment', async () => {
|
||||||
|
// First create a payment
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-refund-excess-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||||
|
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||||
|
if (firstTransaction.paymentTransactionId) {
|
||||||
|
try {
|
||||||
|
// Try to refund more than the original amount
|
||||||
|
const excessAmount = (firstTransaction.price || 0) * 2;
|
||||||
|
const response = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||||
|
price: excessAmount,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-refund-excess-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid paymentId for refundV2', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.refundV2({
|
||||||
|
paymentId: 'invalid-payment-id-99999',
|
||||||
|
price: 1.0,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-paymentid-v2-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing price for refund', async () => {
|
||||||
|
// First create a payment to get a transaction ID
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-refund-missing-price-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||||
|
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||||
|
if (firstTransaction.paymentTransactionId) {
|
||||||
|
try {
|
||||||
|
const response = await client.cancelRefund.refund({
|
||||||
|
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||||
|
price: undefined as any,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-refund-missing-price-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
391
tests/integration/card-storage.test.ts
Normal file
391
tests/integration/card-storage.test.ts
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
/**
|
||||||
|
* Card Storage integration tests
|
||||||
|
*
|
||||||
|
* NOTE: These tests require the card storage feature to be enabled in the iyzico merchant panel.
|
||||||
|
* If the feature is not enabled, tests will be skipped automatically.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||||
|
import { testCardCreateRequest } from '../fixtures/card';
|
||||||
|
import { IyzicoResponseError } from '../../src/errors';
|
||||||
|
import type { ErrorResponse } from '../../src/types';
|
||||||
|
|
||||||
|
describe('Card Storage Integration Tests', () => {
|
||||||
|
const client = createTestClient();
|
||||||
|
|
||||||
|
// Flag to track if card storage feature is available
|
||||||
|
let cardStorageFeatureAvailable = true;
|
||||||
|
|
||||||
|
let cardUserKey: string | undefined;
|
||||||
|
let cardToken: string | undefined;
|
||||||
|
|
||||||
|
// Helper to skip test if card storage feature is not available
|
||||||
|
const skipIfUnavailable = () => {
|
||||||
|
if (!cardStorageFeatureAvailable) {
|
||||||
|
console.log('Skipping: Card storage feature not enabled in merchant panel');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should create a card for a new user', async () => {
|
||||||
|
const request = {
|
||||||
|
...testCardCreateRequest,
|
||||||
|
conversationId: `test-card-create-${Date.now()}`,
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard(request);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.cardUserKey).toBeTruthy();
|
||||||
|
expect(response.cardToken).toBeTruthy();
|
||||||
|
expect(response.lastFourDigits).toBeTruthy();
|
||||||
|
|
||||||
|
cardUserKey = response.cardUserKey;
|
||||||
|
cardToken = response.cardToken;
|
||||||
|
} catch (error) {
|
||||||
|
// Check if card storage feature is not enabled
|
||||||
|
if (error instanceof IyzicoResponseError) {
|
||||||
|
const errorCode = error.errorResponse?.errorCode;
|
||||||
|
const errorMessage = error.errorResponse?.errorMessage || '';
|
||||||
|
// Error code 100001 = system error, or specific message for card storage not enabled
|
||||||
|
if (errorCode === '100001' || errorMessage.includes('Kart saklama özelliği tanımlı değil')) {
|
||||||
|
cardStorageFeatureAvailable = false;
|
||||||
|
console.log('Card storage feature not enabled in merchant panel. Skipping card storage tests.');
|
||||||
|
return; // Skip this test gracefully
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list cards for a user', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
if (!cardUserKey) {
|
||||||
|
// Create a card first if we don't have one
|
||||||
|
const createResponse = await client.cardStorage.createCard({
|
||||||
|
...testCardCreateRequest,
|
||||||
|
conversationId: `test-list-${Date.now()}`,
|
||||||
|
externalId: `external-list-${Date.now()}`,
|
||||||
|
});
|
||||||
|
cardUserKey = createResponse.cardUserKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.cardStorage.listCards({
|
||||||
|
cardUserKey: cardUserKey!,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-list-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.cardUserKey).toBe(cardUserKey);
|
||||||
|
expect(response.cardDetails).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a card', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
if (!cardUserKey || !cardToken) {
|
||||||
|
// Create a card first if we don't have one
|
||||||
|
const createResponse = await client.cardStorage.createCard({
|
||||||
|
...testCardCreateRequest,
|
||||||
|
conversationId: `test-delete-${Date.now()}`,
|
||||||
|
externalId: `external-delete-${Date.now()}`,
|
||||||
|
});
|
||||||
|
cardUserKey = createResponse.cardUserKey;
|
||||||
|
cardToken = createResponse.cardToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.cardStorage.deleteCard({
|
||||||
|
cardUserKey: cardUserKey!,
|
||||||
|
cardToken: cardToken!,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-delete-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge Cases - Invalid Fields', () => {
|
||||||
|
it('should handle missing card data', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-missing-card-${Date.now()}`,
|
||||||
|
email: 'email@email.com',
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
card: undefined as any,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing email', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-missing-email-${Date.now()}`,
|
||||||
|
email: undefined as any,
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
card: testCardCreateRequest.card,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid email format', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-email-${Date.now()}`,
|
||||||
|
email: 'invalid-email-format',
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
card: testCardCreateRequest.card,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing externalId', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-missing-externalid-${Date.now()}`,
|
||||||
|
email: 'email@email.com',
|
||||||
|
externalId: undefined as any,
|
||||||
|
card: testCardCreateRequest.card,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid card number', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-card-number-${Date.now()}`,
|
||||||
|
email: 'email@email.com',
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
card: {
|
||||||
|
...testCardCreateRequest.card,
|
||||||
|
cardNumber: '1234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid expiry month', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-expmonth-${Date.now()}`,
|
||||||
|
email: 'email@email.com',
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
card: {
|
||||||
|
...testCardCreateRequest.card,
|
||||||
|
expireMonth: '13',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid expiry year (past year)', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
const pastYear = new Date().getFullYear() - 1;
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.createCard({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-expyear-${Date.now()}`,
|
||||||
|
email: 'email@email.com',
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
card: {
|
||||||
|
...testCardCreateRequest.card,
|
||||||
|
expireYear: String(pastYear),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid cardUserKey for listCards', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.listCards({
|
||||||
|
cardUserKey: 'invalid-user-key-99999',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-userkey-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty cardUserKey for listCards', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.listCards({
|
||||||
|
cardUserKey: '',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-empty-userkey-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid cardToken for deleteCard', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
// First create a card to get a valid cardUserKey
|
||||||
|
const createResponse = await client.cardStorage.createCard({
|
||||||
|
...testCardCreateRequest,
|
||||||
|
conversationId: `test-delete-invalid-token-${Date.now()}`,
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
});
|
||||||
|
assertSuccessResponse(createResponse);
|
||||||
|
|
||||||
|
if (createResponse.cardUserKey) {
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.deleteCard({
|
||||||
|
cardUserKey: createResponse.cardUserKey,
|
||||||
|
cardToken: 'invalid-card-token-99999',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-delete-invalid-token-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty cardToken for deleteCard', async () => {
|
||||||
|
if (skipIfUnavailable()) return;
|
||||||
|
|
||||||
|
// First create a card to get a valid cardUserKey
|
||||||
|
const createResponse = await client.cardStorage.createCard({
|
||||||
|
...testCardCreateRequest,
|
||||||
|
conversationId: `test-delete-empty-token-${Date.now()}`,
|
||||||
|
externalId: `external-${Date.now()}`,
|
||||||
|
});
|
||||||
|
assertSuccessResponse(createResponse);
|
||||||
|
|
||||||
|
if (createResponse.cardUserKey) {
|
||||||
|
try {
|
||||||
|
const response = await client.cardStorage.deleteCard({
|
||||||
|
cardUserKey: createResponse.cardUserKey,
|
||||||
|
cardToken: '',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-delete-empty-token-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
373
tests/integration/payment.test.ts
Normal file
373
tests/integration/payment.test.ts
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
/**
|
||||||
|
* Payment integration tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||||
|
import { testNon3DPaymentRequest, test3DSInitializeRequest } from '../fixtures/payment';
|
||||||
|
import { IyzicoResponseError } from '../../src/errors';
|
||||||
|
|
||||||
|
describe('Payment Integration Tests', () => {
|
||||||
|
const client = createTestClient();
|
||||||
|
|
||||||
|
it('should create a non-3DS payment', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-non3ds-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await client.payment.createNon3DS(request);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.paymentId).toBeTruthy();
|
||||||
|
expect(response.price).toBe(request.price);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get payment detail by paymentId', async () => {
|
||||||
|
// First create a payment
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-detail-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (!paymentResponse.paymentId) {
|
||||||
|
throw new Error('Payment ID is required for detail test');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then get payment detail
|
||||||
|
const detailResponse = await client.payment.getDetail({
|
||||||
|
paymentId: paymentResponse.paymentId,
|
||||||
|
locale: 'tr',
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(detailResponse);
|
||||||
|
expect(detailResponse.paymentId).toBe(paymentResponse.paymentId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize 3DS payment', async () => {
|
||||||
|
const request = {
|
||||||
|
...test3DSInitializeRequest,
|
||||||
|
conversationId: `test-3ds-init-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await client.payment.initialize3DS(request);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.paymentId).toBeTruthy();
|
||||||
|
expect(response.htmlContent || response.threeDSHtmlContent).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should complete 3DS v1 payment', async () => {
|
||||||
|
// First initialize a 3DS payment
|
||||||
|
const initRequest = {
|
||||||
|
...test3DSInitializeRequest,
|
||||||
|
conversationId: `test-3ds-v1-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initResponse = await client.payment.initialize3DS(initRequest);
|
||||||
|
assertSuccessResponse(initResponse);
|
||||||
|
|
||||||
|
if (!initResponse.paymentId) {
|
||||||
|
throw new Error('Payment ID is required for 3DS completion test');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In sandbox, 3DS completion will fail without actual 3DS flow
|
||||||
|
// We expect an IyzicoResponseError to be thrown
|
||||||
|
await expect(
|
||||||
|
client.payment.complete3DS({
|
||||||
|
paymentId: initResponse.paymentId,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-3ds-v1-complete-${Date.now()}`,
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should complete 3DS v2 payment', async () => {
|
||||||
|
// First initialize a 3DS payment
|
||||||
|
const initRequest = {
|
||||||
|
...test3DSInitializeRequest,
|
||||||
|
conversationId: `test-3ds-v2-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initResponse = await client.payment.initialize3DS(initRequest);
|
||||||
|
assertSuccessResponse(initResponse);
|
||||||
|
|
||||||
|
if (!initResponse.paymentId) {
|
||||||
|
throw new Error('Payment ID is required for 3DS completion test');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In sandbox, 3DS completion will fail without actual 3DS flow
|
||||||
|
// We expect an IyzicoResponseError to be thrown
|
||||||
|
await expect(
|
||||||
|
client.payment.complete3DSV2({
|
||||||
|
paymentId: initResponse.paymentId,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-3ds-v2-complete-${Date.now()}`,
|
||||||
|
paidPrice: initRequest.paidPrice,
|
||||||
|
basketId: initRequest.basketId!,
|
||||||
|
currency: initRequest.currency || 'TRY',
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge Cases - Invalid Fields', () => {
|
||||||
|
it('should handle missing required fields (price)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-missing-price-${Date.now()}`,
|
||||||
|
price: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing required fields (paidPrice)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-missing-paidprice-${Date.now()}`,
|
||||||
|
paidPrice: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing required fields (paymentCard)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-missing-card-${Date.now()}`,
|
||||||
|
paymentCard: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing required fields (buyer)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-missing-buyer-${Date.now()}`,
|
||||||
|
buyer: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing required fields (billingAddress)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-missing-billing-${Date.now()}`,
|
||||||
|
billingAddress: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing required fields (basketItems)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-missing-basket-${Date.now()}`,
|
||||||
|
basketItems: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty basketItems array', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-empty-basket-${Date.now()}`,
|
||||||
|
basketItems: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid card number (too short)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-card-short-${Date.now()}`,
|
||||||
|
paymentCard: {
|
||||||
|
...testNon3DPaymentRequest.paymentCard,
|
||||||
|
cardNumber: '1234',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid card number (non-numeric)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-card-nonnumeric-${Date.now()}`,
|
||||||
|
paymentCard: {
|
||||||
|
...testNon3DPaymentRequest.paymentCard,
|
||||||
|
cardNumber: 'ABCDEFGHIJKLMNOP',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid expiry month (invalid format)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-expmonth-${Date.now()}`,
|
||||||
|
paymentCard: {
|
||||||
|
...testNon3DPaymentRequest.paymentCard,
|
||||||
|
expireMonth: '13',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid expiry year (invalid format)', async () => {
|
||||||
|
// Note: Sandbox doesn't validate past years, so we test invalid format instead
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-expyear-format-${Date.now()}`,
|
||||||
|
paymentCard: {
|
||||||
|
...testNon3DPaymentRequest.paymentCard,
|
||||||
|
expireYear: 'AB', // Invalid format - non-numeric
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid CVV (too short)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-cvv-short-${Date.now()}`,
|
||||||
|
paymentCard: {
|
||||||
|
...testNon3DPaymentRequest.paymentCard,
|
||||||
|
cvc: '12',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid CVV (non-numeric)', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-cvv-nonnumeric-${Date.now()}`,
|
||||||
|
paymentCard: {
|
||||||
|
...testNon3DPaymentRequest.paymentCard,
|
||||||
|
cvc: 'ABC',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle negative price', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-negative-price-${Date.now()}`,
|
||||||
|
price: -100.0,
|
||||||
|
paidPrice: -100.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle zero price', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-zero-price-${Date.now()}`,
|
||||||
|
price: 0,
|
||||||
|
paidPrice: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid currency', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-currency-${Date.now()}`,
|
||||||
|
currency: 'INVALID' as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid installment value', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-installment-${Date.now()}`,
|
||||||
|
installment: 99 as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid email format', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-invalid-email-${Date.now()}`,
|
||||||
|
buyer: {
|
||||||
|
...testNon3DPaymentRequest.buyer,
|
||||||
|
email: 'invalid-email-format',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle basket item with negative price', async () => {
|
||||||
|
const request = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-negative-basket-price-${Date.now()}`,
|
||||||
|
basketItems: [
|
||||||
|
{
|
||||||
|
...testNon3DPaymentRequest.basketItems[0],
|
||||||
|
price: -10.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing callbackUrl for 3DS initialize', async () => {
|
||||||
|
const request = {
|
||||||
|
...test3DSInitializeRequest,
|
||||||
|
conversationId: `test-missing-callback-${Date.now()}`,
|
||||||
|
callbackUrl: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(client.payment.initialize3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid paymentId for getDetail', async () => {
|
||||||
|
await expect(
|
||||||
|
client.payment.getDetail({
|
||||||
|
paymentId: 'invalid-payment-id-12345',
|
||||||
|
locale: 'tr',
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty paymentId for getDetail', async () => {
|
||||||
|
await expect(
|
||||||
|
client.payment.getDetail({
|
||||||
|
paymentId: '',
|
||||||
|
locale: 'tr',
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing paymentId and paymentConversationId for getDetail', async () => {
|
||||||
|
await expect(
|
||||||
|
client.payment.getDetail({
|
||||||
|
locale: 'tr',
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
338
tests/integration/reporting.test.ts
Normal file
338
tests/integration/reporting.test.ts
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
/**
|
||||||
|
* Reporting integration tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||||
|
import type { ErrorResponse } from '../../src/types';
|
||||||
|
|
||||||
|
describe('Reporting Integration Tests', () => {
|
||||||
|
const client = createTestClient();
|
||||||
|
|
||||||
|
it('should get payment transactions for a date', async () => {
|
||||||
|
// Use today's date
|
||||||
|
const today = new Date();
|
||||||
|
const dateString = today.toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||||
|
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: 1,
|
||||||
|
transactionDate: dateString,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-transactions-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.currentPage).toBe(1);
|
||||||
|
// Transactions might be empty if no transactions exist for the date
|
||||||
|
expect(response.transactions).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get payment details by paymentId', async () => {
|
||||||
|
// First create a payment to get a paymentId
|
||||||
|
const { testNon3DPaymentRequest } = await import('../fixtures/payment');
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId: `test-reporting-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.paymentId) {
|
||||||
|
// Then get payment details
|
||||||
|
const detailsResponse = await client.reporting.getPaymentDetails({
|
||||||
|
paymentId: paymentResponse.paymentId,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-details-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(detailsResponse);
|
||||||
|
expect(detailsResponse.payments).toBeTruthy();
|
||||||
|
if (detailsResponse.payments && detailsResponse.payments.length > 0) {
|
||||||
|
// Convert to string for comparison as API may return number or string
|
||||||
|
const returnedPaymentId = String(detailsResponse.payments[0].paymentId);
|
||||||
|
expect(returnedPaymentId).toBe(String(paymentResponse.paymentId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get payment details by paymentConversationId', async () => {
|
||||||
|
// First create a payment to get a conversationId
|
||||||
|
const { testNon3DPaymentRequest } = await import('../fixtures/payment');
|
||||||
|
const conversationId = `test-reporting-conv-${Date.now()}`;
|
||||||
|
const paymentRequest = {
|
||||||
|
...testNon3DPaymentRequest,
|
||||||
|
conversationId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||||
|
assertSuccessResponse(paymentResponse);
|
||||||
|
|
||||||
|
if (paymentResponse.conversationId) {
|
||||||
|
// Then get payment details using paymentConversationId
|
||||||
|
const detailsResponse = await client.reporting.getPaymentDetails({
|
||||||
|
paymentConversationId: paymentResponse.conversationId,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-details-conv-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(detailsResponse);
|
||||||
|
expect(detailsResponse.payments).toBeTruthy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge Cases - Invalid Fields', () => {
|
||||||
|
it('should handle invalid date format for getPaymentTransactions', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: 1,
|
||||||
|
transactionDate: 'invalid-date-format',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-date-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle future date for getPaymentTransactions', async () => {
|
||||||
|
const futureDate = new Date();
|
||||||
|
futureDate.setFullYear(futureDate.getFullYear() + 1);
|
||||||
|
const dateString = futureDate.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: 1,
|
||||||
|
transactionDate: dateString,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-future-date-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
// Future dates might return empty results or error
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle negative page number', async () => {
|
||||||
|
const today = new Date();
|
||||||
|
const dateString = today.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: -1,
|
||||||
|
transactionDate: dateString,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-negative-page-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle zero page number', async () => {
|
||||||
|
const today = new Date();
|
||||||
|
const dateString = today.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: 0,
|
||||||
|
transactionDate: dateString,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-zero-page-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle very large page number', async () => {
|
||||||
|
const today = new Date();
|
||||||
|
const dateString = today.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: 999999,
|
||||||
|
transactionDate: dateString,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-large-page-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
// Large page numbers might return empty results or error
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing transactionDate', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: 1,
|
||||||
|
transactionDate: undefined as any,
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-missing-date-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid paymentId for getPaymentDetails', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentDetails({
|
||||||
|
paymentId: 'invalid-payment-id-99999',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-paymentid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty paymentId for getPaymentDetails', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentDetails({
|
||||||
|
paymentId: '',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-empty-paymentid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid paymentConversationId for getPaymentDetails', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentDetails({
|
||||||
|
paymentConversationId: 'invalid-conversation-id-99999',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-convid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty paymentConversationId for getPaymentDetails', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentDetails({
|
||||||
|
paymentConversationId: '',
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-empty-convid-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing both paymentId and paymentConversationId', async () => {
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentDetails({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-missing-ids-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid locale', async () => {
|
||||||
|
const today = new Date();
|
||||||
|
const dateString = today.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.reporting.getPaymentTransactions({
|
||||||
|
page: 1,
|
||||||
|
transactionDate: dateString,
|
||||||
|
locale: 'invalid' as any,
|
||||||
|
conversationId: `test-invalid-locale-${Date.now()}`,
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.status).toBeDefined();
|
||||||
|
if (response.status === 'failure') {
|
||||||
|
const errorResponse = response as unknown as ErrorResponse;
|
||||||
|
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
556
tests/integration/subscription.test.ts
Normal file
556
tests/integration/subscription.test.ts
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
/**
|
||||||
|
* Subscription integration tests
|
||||||
|
*
|
||||||
|
* NOTE: These tests require the subscription feature to be enabled in the iyzico merchant panel.
|
||||||
|
* If the feature is not enabled, tests will be skipped automatically.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||||
|
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||||
|
import {
|
||||||
|
testProductCreateRequest,
|
||||||
|
testPricingPlanCreateRequest,
|
||||||
|
testSubscriptionCustomer,
|
||||||
|
testSubscriptionPaymentCard,
|
||||||
|
generateTestProductName,
|
||||||
|
generateTestPlanName,
|
||||||
|
generateTestEmail,
|
||||||
|
} from '../fixtures/subscription';
|
||||||
|
import { IyzicoResponseError } from '../../src/errors';
|
||||||
|
|
||||||
|
describe('Subscription Integration Tests', () => {
|
||||||
|
const client = createTestClient();
|
||||||
|
|
||||||
|
// Flag to track if subscription feature is available
|
||||||
|
let subscriptionFeatureAvailable = true;
|
||||||
|
|
||||||
|
// Shared references for chained tests
|
||||||
|
let createdProductReferenceCode: string | undefined;
|
||||||
|
let createdPricingPlanReferenceCode: string | undefined;
|
||||||
|
let createdSubscriptionReferenceCode: string | undefined;
|
||||||
|
let createdCustomerReferenceCode: string | undefined;
|
||||||
|
|
||||||
|
// Helper to skip test if subscription feature is not available
|
||||||
|
const skipIfUnavailable = () => {
|
||||||
|
if (!subscriptionFeatureAvailable) {
|
||||||
|
console.log('Skipping: Subscription feature not enabled in merchant panel');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Product CRUD Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
describe('Product Operations', () => {
|
||||||
|
it('should create a subscription product', async () => {
|
||||||
|
const request = {
|
||||||
|
...testProductCreateRequest,
|
||||||
|
// conversationId: `test-product-create-${Date.now()}`,
|
||||||
|
// name: generateTestProductName(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.subscription.createProduct(request);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.referenceCode).toBeTruthy();
|
||||||
|
expect(response.data?.name).toBe(request.name);
|
||||||
|
expect(response.data?.status).toBe('ACTIVE');
|
||||||
|
|
||||||
|
// Save for later tests
|
||||||
|
createdProductReferenceCode = response.data?.referenceCode;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Check if subscription feature is not enabled (error code 100001 = system error)
|
||||||
|
if (error instanceof IyzicoResponseError && error.errorResponse?.errorCode === '100001') {
|
||||||
|
subscriptionFeatureAvailable = false;
|
||||||
|
console.log('Subscription feature not enabled in merchant panel. Skipping subscription tests.');
|
||||||
|
return; // Skip this test gracefully
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a subscription product', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no product created
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.getProduct(createdProductReferenceCode);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.referenceCode).toBe(createdProductReferenceCode);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a subscription product', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no product created
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedName = `Updated Product ${Date.now()}`;
|
||||||
|
const response = await client.subscription.updateProduct(createdProductReferenceCode, {
|
||||||
|
name: updatedName,
|
||||||
|
description: 'Updated description',
|
||||||
|
conversationId: `test-product-update-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.name).toBe(updatedName);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list subscription products', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.listProducts({
|
||||||
|
page: 1,
|
||||||
|
count: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.items).toBeInstanceOf(Array);
|
||||||
|
expect(response.data?.totalCount).toBeGreaterThanOrEqual(0);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Pricing Plan CRUD Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
describe('Pricing Plan Operations', () => {
|
||||||
|
it('should create a pricing plan', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no product created
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
...testPricingPlanCreateRequest,
|
||||||
|
conversationId: `test-plan-create-${Date.now()}`,
|
||||||
|
name: generateTestPlanName(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await client.subscription.createPricingPlan(createdProductReferenceCode, request);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.referenceCode).toBeTruthy();
|
||||||
|
expect(response.data?.name).toBe(request.name);
|
||||||
|
expect(response.data?.price).toBe(request.price);
|
||||||
|
expect(response.data?.paymentInterval).toBe(request.paymentInterval);
|
||||||
|
|
||||||
|
// Save for later tests
|
||||||
|
createdPricingPlanReferenceCode = response.data?.referenceCode;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a pricing plan', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no plan created
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.getPricingPlan(createdPricingPlanReferenceCode);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.referenceCode).toBe(createdPricingPlanReferenceCode);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a pricing plan', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no plan created
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedName = `Updated Plan ${Date.now()}`;
|
||||||
|
const response = await client.subscription.updatePricingPlan(createdPricingPlanReferenceCode, {
|
||||||
|
name: updatedName,
|
||||||
|
trialPeriodDays: 14,
|
||||||
|
conversationId: `test-plan-update-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.name).toBe(updatedName);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list pricing plans for a product', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no product created
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.listPricingPlans(createdProductReferenceCode, {
|
||||||
|
page: 1,
|
||||||
|
count: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.items).toBeInstanceOf(Array);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Subscription Lifecycle Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
describe('Subscription Operations', () => {
|
||||||
|
it('should initialize a subscription', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no plan created
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueEmail = generateTestEmail();
|
||||||
|
const response = await client.subscription.initialize({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-sub-init-${Date.now()}`,
|
||||||
|
pricingPlanReferenceCode: createdPricingPlanReferenceCode,
|
||||||
|
subscriptionInitialStatus: 'ACTIVE',
|
||||||
|
customer: {
|
||||||
|
...testSubscriptionCustomer,
|
||||||
|
email: uniqueEmail,
|
||||||
|
},
|
||||||
|
paymentCard: testSubscriptionPaymentCard,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.referenceCode).toBeTruthy();
|
||||||
|
expect(response.data?.customerReferenceCode).toBeTruthy();
|
||||||
|
|
||||||
|
// Save for later tests
|
||||||
|
createdSubscriptionReferenceCode = response.data?.referenceCode;
|
||||||
|
createdCustomerReferenceCode = response.data?.customerReferenceCode;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a subscription', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdSubscriptionReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no subscription created
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.get(createdSubscriptionReferenceCode);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list subscriptions', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.list({
|
||||||
|
page: 1,
|
||||||
|
count: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.items).toBeInstanceOf(Array);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list subscriptions with filters', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.list({
|
||||||
|
subscriptionStatus: 'ACTIVE',
|
||||||
|
page: 1,
|
||||||
|
count: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize subscription with checkout form', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no plan created
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueEmail = generateTestEmail();
|
||||||
|
const response = await client.subscription.initializeCheckoutForm({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-sub-checkout-${Date.now()}`,
|
||||||
|
callbackUrl: 'https://www.merchant.com/callback',
|
||||||
|
pricingPlanReferenceCode: createdPricingPlanReferenceCode,
|
||||||
|
subscriptionInitialStatus: 'ACTIVE',
|
||||||
|
customer: {
|
||||||
|
...testSubscriptionCustomer,
|
||||||
|
email: uniqueEmail,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.token).toBeTruthy();
|
||||||
|
expect(response.checkoutFormContent).toBeTruthy();
|
||||||
|
expect(response.tokenExpireTime).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cancel a subscription', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdSubscriptionReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no subscription created
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.cancel(createdSubscriptionReferenceCode);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Customer Operations Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
describe('Customer Operations', () => {
|
||||||
|
it('should list customers', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.listCustomers({
|
||||||
|
page: 1,
|
||||||
|
count: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.items).toBeInstanceOf(Array);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a customer', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdCustomerReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no customer created
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.getCustomer(createdCustomerReferenceCode);
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.referenceCode).toBe(createdCustomerReferenceCode);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a customer', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdCustomerReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no customer created
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await client.subscription.updateCustomer(createdCustomerReferenceCode, {
|
||||||
|
name: 'Updated John',
|
||||||
|
surname: 'Updated Doe',
|
||||||
|
conversationId: `test-customer-update-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.data).toBeDefined();
|
||||||
|
expect(response.data?.name).toBe('Updated John');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Card Update Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
describe('Card Update Operations', () => {
|
||||||
|
it('should initialize card update checkout form', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdCustomerReferenceCode) {
|
||||||
|
return; // Skip if feature unavailable or no customer created
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.subscription.initializeCardUpdate({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-card-update-${Date.now()}`,
|
||||||
|
callbackUrl: 'https://www.merchant.com/callback',
|
||||||
|
customerReferenceCode: createdCustomerReferenceCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
expect(response.token).toBeTruthy();
|
||||||
|
expect(response.checkoutFormContent).toBeTruthy();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Card update requires an active subscription - may fail if subscription was cancelled
|
||||||
|
if (error instanceof IyzicoResponseError) {
|
||||||
|
const errorMessage = error.errorResponse?.errorMessage || '';
|
||||||
|
if (errorMessage.includes('aktif abonelik bulunamadı')) {
|
||||||
|
console.log('Skipping: No active subscription found for card update test');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Cleanup - Delete created resources
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
describe('Cleanup', () => {
|
||||||
|
it('should delete pricing plan', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||||
|
console.log('No pricing plan to delete');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.subscription.deletePricingPlan(createdPricingPlanReferenceCode);
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
} catch (error) {
|
||||||
|
// Deletion may fail if there are active subscriptions - this is expected
|
||||||
|
if (error instanceof IyzicoResponseError) {
|
||||||
|
console.log(`Could not delete pricing plan: ${error.errorResponse?.errorMessage || error.message}`);
|
||||||
|
return; // Don't fail the test for cleanup issues
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete product', async () => {
|
||||||
|
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||||
|
console.log('No product to delete');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.subscription.deleteProduct(createdProductReferenceCode);
|
||||||
|
assertSuccessResponse(response);
|
||||||
|
} catch (error) {
|
||||||
|
// Deletion may fail if there are active pricing plans/subscriptions - this is expected
|
||||||
|
if (error instanceof IyzicoResponseError) {
|
||||||
|
console.log(`Could not delete product: ${error.errorResponse?.errorMessage || error.message}`);
|
||||||
|
return; // Don't fail the test for cleanup issues
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Edge Cases and Error Handling
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
describe('Edge Cases - Invalid Inputs', () => {
|
||||||
|
it('should handle non-existent product reference code', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.subscription.getProduct('invalid-product-ref-12345')
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle non-existent pricing plan reference code', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.subscription.getPricingPlan('invalid-plan-ref-12345')
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle non-existent subscription reference code', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.subscription.get('invalid-sub-ref-12345')
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle non-existent customer reference code', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.subscription.getCustomer('invalid-customer-ref-12345')
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing required fields in product creation', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.subscription.createProduct({
|
||||||
|
name: '',
|
||||||
|
conversationId: `test-empty-name-${Date.now()}`,
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid pricing plan creation without product', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.subscription.createPricingPlan('invalid-product-ref', {
|
||||||
|
...testPricingPlanCreateRequest,
|
||||||
|
conversationId: `test-invalid-product-${Date.now()}`,
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle subscription initialization with invalid plan', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueEmail = generateTestEmail();
|
||||||
|
await expect(
|
||||||
|
client.subscription.initialize({
|
||||||
|
locale: 'tr',
|
||||||
|
conversationId: `test-invalid-plan-${Date.now()}`,
|
||||||
|
pricingPlanReferenceCode: 'invalid-plan-ref',
|
||||||
|
subscriptionInitialStatus: 'ACTIVE',
|
||||||
|
customer: {
|
||||||
|
...testSubscriptionCustomer,
|
||||||
|
email: uniqueEmail,
|
||||||
|
},
|
||||||
|
paymentCard: testSubscriptionPaymentCard,
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle retry payment with invalid reference', async () => {
|
||||||
|
if (skipIfUnavailable()) {
|
||||||
|
return; // Skip if feature unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.subscription.retryPayment({
|
||||||
|
referenceCode: 'invalid-order-ref-12345',
|
||||||
|
conversationId: `test-invalid-retry-${Date.now()}`,
|
||||||
|
})
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
85
tests/setup.ts
Normal file
85
tests/setup.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Test setup and utilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import { resolve, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { IyzicoClient } from '../src/client';
|
||||||
|
|
||||||
|
// Get the directory of this file (for ESM compatibility)
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
// Load environment variables from .env file
|
||||||
|
// Resolve path relative to project root (two levels up from tests/setup.ts)
|
||||||
|
const envPath = resolve(__dirname, '..', '.env');
|
||||||
|
config({ path: envPath });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an İyzico client for testing using environment variables
|
||||||
|
* @returns Configured IyzicoClient instance
|
||||||
|
*/
|
||||||
|
export function createTestClient(): IyzicoClient {
|
||||||
|
const apiKey = process.env.IYZICO_API_KEY;
|
||||||
|
const secretKey = process.env.IYZICO_API_SECRET;
|
||||||
|
const baseUrl = process.env.IYZICO_BASE_URL || 'https://sandbox-api.iyzipay.com';
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('IYZICO_API_KEY environment variable is required for tests');
|
||||||
|
}
|
||||||
|
if (!secretKey) {
|
||||||
|
throw new Error('IYZICO_API_SECRET environment variable is required for tests');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable retry on rate limit for tests
|
||||||
|
const retryOnRateLimit = process.env.IYZICO_RETRY_ON_RATE_LIMIT !== 'false';
|
||||||
|
const maxRetries = Number.parseInt(process.env.IYZICO_MAX_RETRIES || '3', 10);
|
||||||
|
const retryDelay = Number.parseInt(process.env.IYZICO_RETRY_DELAY_MS || '10000', 10);
|
||||||
|
|
||||||
|
return new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
locale: 'tr',
|
||||||
|
retryOnRateLimit,
|
||||||
|
maxRetries,
|
||||||
|
retryDelay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that a response has a success status
|
||||||
|
*/
|
||||||
|
export function assertSuccessResponse(response: { status: string }): void {
|
||||||
|
if (response.status !== 'success') {
|
||||||
|
throw new Error(`Expected success status, got: ${response.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that a response has a failure status
|
||||||
|
*/
|
||||||
|
export function assertFailureResponse(response: { status: string }): void {
|
||||||
|
if (response.status !== 'failure') {
|
||||||
|
throw new Error(`Expected failure status, got: ${response.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay utility to prevent rate limiting
|
||||||
|
* Adds a small delay between test requests
|
||||||
|
*/
|
||||||
|
export function delay(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default delay between API calls to avoid rate limiting
|
||||||
|
* Can be overridden via IYZICO_TEST_DELAY_MS environment variable
|
||||||
|
*/
|
||||||
|
export const DEFAULT_TEST_DELAY_MS = Number.parseInt(
|
||||||
|
process.env.IYZICO_TEST_DELAY_MS || '200',
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
62
tests/unit/auth.test.ts
Normal file
62
tests/unit/auth.test.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Authentication unit tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { generateAuthorization } from '../../src/auth';
|
||||||
|
|
||||||
|
describe('generateAuthorization', () => {
|
||||||
|
const apiKey = 'sandbox-api-key';
|
||||||
|
const secretKey = 'sandbox-secret-key';
|
||||||
|
const uriPath = '/payment/bin/check';
|
||||||
|
|
||||||
|
it('should generate authorization header with request body', () => {
|
||||||
|
const requestBody = { binNumber: '589004', locale: 'tr' };
|
||||||
|
const result = generateAuthorization(apiKey, secretKey, uriPath, requestBody);
|
||||||
|
|
||||||
|
expect(result.authorization).toMatch(/^IYZWSv2 /);
|
||||||
|
expect(result.randomKey).toBeTruthy();
|
||||||
|
expect(result.randomKey.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate authorization header without request body', () => {
|
||||||
|
const result = generateAuthorization(apiKey, secretKey, uriPath);
|
||||||
|
|
||||||
|
expect(result.authorization).toMatch(/^IYZWSv2 /);
|
||||||
|
expect(result.randomKey).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate different random keys for each call', () => {
|
||||||
|
const result1 = generateAuthorization(apiKey, secretKey, uriPath);
|
||||||
|
const result2 = generateAuthorization(apiKey, secretKey, uriPath);
|
||||||
|
|
||||||
|
expect(result1.randomKey).not.toBe(result2.randomKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use provided random key when given', () => {
|
||||||
|
const customRandomKey = '123456789';
|
||||||
|
const result = generateAuthorization(apiKey, secretKey, uriPath, undefined, customRandomKey);
|
||||||
|
|
||||||
|
expect(result.randomKey).toBe(customRandomKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate valid base64 encoded authorization', () => {
|
||||||
|
const result = generateAuthorization(apiKey, secretKey, uriPath);
|
||||||
|
const base64Part = result.authorization.replace('IYZWSv2 ', '');
|
||||||
|
|
||||||
|
// Base64 should be valid (only contains A-Z, a-z, 0-9, +, /, =)
|
||||||
|
expect(base64Part).toMatch(/^[A-Za-z0-9+/=]+$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include apiKey, randomKey, and signature in authorization string', () => {
|
||||||
|
const customRandomKey = '123456789';
|
||||||
|
const result = generateAuthorization(apiKey, secretKey, uriPath, undefined, customRandomKey);
|
||||||
|
const base64Part = result.authorization.replace('IYZWSv2 ', '');
|
||||||
|
const decoded = Buffer.from(base64Part, 'base64').toString('utf-8');
|
||||||
|
|
||||||
|
expect(decoded).toContain(`apiKey:${apiKey}`);
|
||||||
|
expect(decoded).toContain(`randomKey:${customRandomKey}`);
|
||||||
|
expect(decoded).toContain('signature:');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
97
tests/unit/client.test.ts
Normal file
97
tests/unit/client.test.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Client unit tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { IyzicoClient } from '../../src/client';
|
||||||
|
|
||||||
|
describe('IyzicoClient', () => {
|
||||||
|
const apiKey = 'test-api-key';
|
||||||
|
const secretKey = 'test-secret-key';
|
||||||
|
|
||||||
|
it('should create a client with required config', () => {
|
||||||
|
const client = new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.apiKey).toBe(apiKey);
|
||||||
|
expect(client.secretKey).toBe(secretKey);
|
||||||
|
expect(client.baseUrl).toBe('https://sandbox-api.iyzipay.com');
|
||||||
|
expect(client.locale).toBe('tr');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when apiKey is missing', () => {
|
||||||
|
expect(() => {
|
||||||
|
new IyzicoClient({
|
||||||
|
apiKey: '',
|
||||||
|
secretKey,
|
||||||
|
} as any);
|
||||||
|
}).toThrow('API key is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when secretKey is missing', () => {
|
||||||
|
expect(() => {
|
||||||
|
new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey: '',
|
||||||
|
} as any);
|
||||||
|
}).toThrow('Secret key is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use custom baseUrl when provided', () => {
|
||||||
|
const customUrl = 'https://api.custom.com';
|
||||||
|
const client = new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl: customUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.baseUrl).toBe(customUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use custom locale when provided', () => {
|
||||||
|
const client = new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
locale: 'en',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.locale).toBe('en');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize all services', () => {
|
||||||
|
const client = new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.payment).toBeDefined();
|
||||||
|
expect(client.binCheck).toBeDefined();
|
||||||
|
expect(client.cancelRefund).toBeDefined();
|
||||||
|
expect(client.cardStorage).toBeDefined();
|
||||||
|
expect(client.reporting).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return locale via getLocale()', () => {
|
||||||
|
const client = new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
locale: 'en',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.getLocale()).toBe('en');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return baseUrl via getBaseUrl()', () => {
|
||||||
|
const customUrl = 'https://api.custom.com';
|
||||||
|
const client = new IyzicoClient({
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl: customUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.getBaseUrl()).toBe(customUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
76
tests/unit/errors.test.ts
Normal file
76
tests/unit/errors.test.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Error classes unit tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
IyzicoError,
|
||||||
|
IyzicoRequestError,
|
||||||
|
IyzicoResponseError,
|
||||||
|
} from '../../src/errors';
|
||||||
|
import type { ErrorResponse } from '../../src/types';
|
||||||
|
|
||||||
|
describe('IyzicoError', () => {
|
||||||
|
it('should create error with message', () => {
|
||||||
|
const error = new IyzicoError('Test error');
|
||||||
|
expect(error.message).toBe('Test error');
|
||||||
|
expect(error.name).toBe('IyzicoError');
|
||||||
|
expect(error.code).toBeUndefined();
|
||||||
|
expect(error.originalError).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create error with code and originalError', () => {
|
||||||
|
const originalError = new Error('Original error');
|
||||||
|
const error = new IyzicoError('Test error', 'TEST_CODE', originalError);
|
||||||
|
expect(error.message).toBe('Test error');
|
||||||
|
expect(error.code).toBe('TEST_CODE');
|
||||||
|
expect(error.originalError).toBe(originalError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('IyzicoRequestError', () => {
|
||||||
|
it('should create request error', () => {
|
||||||
|
const error = new IyzicoRequestError('Request failed');
|
||||||
|
expect(error.message).toBe('Request failed');
|
||||||
|
expect(error.name).toBe('IyzicoRequestError');
|
||||||
|
expect(error.code).toBe('REQUEST_ERROR');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create request error with originalError', () => {
|
||||||
|
const originalError = new Error('Validation error');
|
||||||
|
const error = new IyzicoRequestError('Request failed', originalError);
|
||||||
|
expect(error.message).toBe('Request failed');
|
||||||
|
expect(error.code).toBe('REQUEST_ERROR');
|
||||||
|
expect(error.originalError).toBe(originalError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('IyzicoResponseError', () => {
|
||||||
|
it('should create response error', () => {
|
||||||
|
const errorResponse: ErrorResponse = {
|
||||||
|
status: 'failure',
|
||||||
|
errorCode: 'PAYMENT_FAILED',
|
||||||
|
errorMessage: 'Payment failed',
|
||||||
|
};
|
||||||
|
const error = new IyzicoResponseError('Payment failed', errorResponse);
|
||||||
|
expect(error.message).toBe('Payment failed');
|
||||||
|
expect(error.name).toBe('IyzicoResponseError');
|
||||||
|
expect(error.code).toBe('PAYMENT_FAILED');
|
||||||
|
expect(error.errorResponse).toEqual(errorResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create response error with originalError', () => {
|
||||||
|
const errorResponse: ErrorResponse = {
|
||||||
|
status: 'failure',
|
||||||
|
errorCode: 'PAYMENT_FAILED',
|
||||||
|
errorMessage: 'Payment failed',
|
||||||
|
};
|
||||||
|
const originalError = new Error('Network error');
|
||||||
|
const error = new IyzicoResponseError('Payment failed', errorResponse, originalError);
|
||||||
|
expect(error.message).toBe('Payment failed');
|
||||||
|
expect(error.code).toBe('PAYMENT_FAILED');
|
||||||
|
expect(error.errorResponse).toEqual(errorResponse);
|
||||||
|
expect(error.originalError).toBe(originalError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
310
tests/unit/http.test.ts
Normal file
310
tests/unit/http.test.ts
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
/**
|
||||||
|
* HTTP client unit tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { makeRequest } from '../../src/http';
|
||||||
|
import {
|
||||||
|
IyzicoResponseError,
|
||||||
|
IyzicoRequestError,
|
||||||
|
IyzicoError,
|
||||||
|
} from '../../src/errors';
|
||||||
|
|
||||||
|
describe('makeRequest', () => {
|
||||||
|
const apiKey = 'test-api-key';
|
||||||
|
const secretKey = 'test-secret-key';
|
||||||
|
const baseUrl = 'https://api.test.com';
|
||||||
|
|
||||||
|
// Store original fetch to restore it later
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
const mockFetch = vi.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock fetch before each test
|
||||||
|
globalThis.fetch = mockFetch as any;
|
||||||
|
mockFetch.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original fetch after each test
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should make a successful POST request', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
status: 'success',
|
||||||
|
data: 'test data',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => mockResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(mockResponse);
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockFetch.mock.calls[0];
|
||||||
|
expect(callArgs[0]).toBe('https://api.test.com/test');
|
||||||
|
expect(callArgs[1].method).toBe('POST');
|
||||||
|
expect(callArgs[1].headers).toHaveProperty('Authorization');
|
||||||
|
expect(callArgs[1].headers).toHaveProperty('Content-Type', 'application/json');
|
||||||
|
expect(callArgs[1].headers).toHaveProperty('x-iyzi-rnd');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should make a successful GET request without body', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
status: 'success',
|
||||||
|
data: 'test data',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => mockResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await makeRequest({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/test',
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(mockResponse);
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||||
|
const callArgs = mockFetch.mock.calls[0];
|
||||||
|
expect(callArgs[1].body).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw IyzicoResponseError for failure responses', async () => {
|
||||||
|
const errorResponse = {
|
||||||
|
status: 'failure',
|
||||||
|
errorCode: 'TEST_ERROR',
|
||||||
|
errorMessage: 'Test error message',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: false,
|
||||||
|
json: async () => errorResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw IyzicoResponseError for failure status in response', async () => {
|
||||||
|
const errorResponse = {
|
||||||
|
status: 'failure',
|
||||||
|
errorCode: 'TEST_ERROR',
|
||||||
|
errorMessage: 'Test error message',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => errorResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use fallback message when errorMessage is missing', async () => {
|
||||||
|
const errorResponse = {
|
||||||
|
status: 'failure',
|
||||||
|
errorCode: 'TEST_ERROR',
|
||||||
|
// errorMessage is missing
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => errorResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle network errors with fetch in message', async () => {
|
||||||
|
const networkError = new TypeError('Failed to fetch');
|
||||||
|
mockFetch.mockRejectedValueOnce(networkError);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoRequestError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rethrow IyzicoResponseError', async () => {
|
||||||
|
const errorResponse = {
|
||||||
|
status: 'failure',
|
||||||
|
errorCode: 'TEST_ERROR',
|
||||||
|
errorMessage: 'Test error',
|
||||||
|
} as const;
|
||||||
|
const responseError = new IyzicoResponseError('Test error', errorResponse);
|
||||||
|
mockFetch.mockRejectedValueOnce(responseError);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoResponseError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rethrow IyzicoRequestError', async () => {
|
||||||
|
const requestError = new IyzicoRequestError('Request failed');
|
||||||
|
mockFetch.mockRejectedValueOnce(requestError);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoRequestError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rethrow IyzicoError', async () => {
|
||||||
|
const iyzicoError = new IyzicoError('Iyzico error');
|
||||||
|
mockFetch.mockRejectedValueOnce(iyzicoError);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wrap unknown errors in IyzicoError', async () => {
|
||||||
|
const unknownError = new Error('Unknown error');
|
||||||
|
mockFetch.mockRejectedValueOnce(unknownError);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wrap non-Error objects in IyzicoError', async () => {
|
||||||
|
mockFetch.mockRejectedValueOnce('String error');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
makeRequest({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/test',
|
||||||
|
body: { test: 'value' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(IyzicoError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle DELETE request with body', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
status: 'success',
|
||||||
|
data: 'deleted',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => mockResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await makeRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/test',
|
||||||
|
body: { id: '123' },
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(mockResponse);
|
||||||
|
const callArgs = mockFetch.mock.calls[0];
|
||||||
|
expect(callArgs[1].method).toBe('DELETE');
|
||||||
|
expect(callArgs[1].body).toBe(JSON.stringify({ id: '123' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle GET request with query string', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
status: 'success',
|
||||||
|
data: 'test data',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => mockResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await makeRequest({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/test?param=value',
|
||||||
|
apiKey,
|
||||||
|
secretKey,
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(mockResponse);
|
||||||
|
const callArgs = mockFetch.mock.calls[0];
|
||||||
|
expect(callArgs[0]).toBe('https://api.test.com/test?param=value');
|
||||||
|
});
|
||||||
|
});
|
||||||
40
tests/unit/index.test.ts
Normal file
40
tests/unit/index.test.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Index exports test
|
||||||
|
* This test ensures all exports from index.ts are accessible
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('Index exports', () => {
|
||||||
|
it('should export IyzicoClient', async () => {
|
||||||
|
const module = await import('../../src/index');
|
||||||
|
expect(module.IyzicoClient).toBeDefined();
|
||||||
|
expect(typeof module.IyzicoClient).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export all error classes', async () => {
|
||||||
|
const module = await import('../../src/index');
|
||||||
|
expect(module.IyzicoError).toBeDefined();
|
||||||
|
expect(module.IyzicoRequestError).toBeDefined();
|
||||||
|
expect(module.IyzicoResponseError).toBeDefined();
|
||||||
|
expect(typeof module.IyzicoError).toBe('function');
|
||||||
|
expect(typeof module.IyzicoRequestError).toBe('function');
|
||||||
|
expect(typeof module.IyzicoResponseError).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export utility functions', async () => {
|
||||||
|
const module = await import('../../src/index');
|
||||||
|
expect(module.generateRandomNumber).toBeDefined();
|
||||||
|
expect(module.generateRandomKey).toBeDefined();
|
||||||
|
expect(typeof module.generateRandomNumber).toBe('function');
|
||||||
|
expect(typeof module.generateRandomKey).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be importable', async () => {
|
||||||
|
// Just verify the module can be imported (this covers index.ts line 1)
|
||||||
|
const module = await import('../../src/index');
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
expect(Object.keys(module).length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
53
tests/unit/utils.test.ts
Normal file
53
tests/unit/utils.test.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Utility functions unit tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { generateRandomNumber, generateRandomKey } from '../../src/utils';
|
||||||
|
|
||||||
|
describe('generateRandomNumber', () => {
|
||||||
|
it('should generate a number within the specified range', () => {
|
||||||
|
const result = generateRandomNumber(1, 10);
|
||||||
|
expect(result).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(result).toBeLessThanOrEqual(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use default values when no arguments provided', () => {
|
||||||
|
const result = generateRandomNumber();
|
||||||
|
expect(result).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(result).toBeLessThanOrEqual(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when min is greater than max', () => {
|
||||||
|
expect(() => generateRandomNumber(10, 5)).toThrow('Min value cannot be greater than max value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle equal min and max', () => {
|
||||||
|
const result = generateRandomNumber(5, 5);
|
||||||
|
expect(result).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateRandomKey', () => {
|
||||||
|
it('should generate a non-empty string', () => {
|
||||||
|
const result = generateRandomKey();
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
expect(typeof result).toBe('string');
|
||||||
|
expect(result.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate different keys on each call', () => {
|
||||||
|
const key1 = generateRandomKey();
|
||||||
|
const key2 = generateRandomKey();
|
||||||
|
expect(key1).not.toBe(key2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate keys that start with timestamp', () => {
|
||||||
|
const result = generateRandomKey();
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const keyTimestamp = parseInt(result.substring(0, 13));
|
||||||
|
// Should be within 1 second of current timestamp
|
||||||
|
expect(Math.abs(timestamp - keyTimestamp)).toBeLessThan(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"verbatimModuleSyntax": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
||||||
14
tsdown.config.ts
Normal file
14
tsdown.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'tsdown';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm', 'cjs'],
|
||||||
|
dts: true,
|
||||||
|
sourcemap: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
minify: false,
|
||||||
|
target: 'es2022',
|
||||||
|
outDir: 'dist',
|
||||||
|
});
|
||||||
|
|
||||||
31
vitest.config.ts
Normal file
31
vitest.config.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'node',
|
||||||
|
include: ['tests/**/*.test.ts'],
|
||||||
|
setupFiles: ['tests/setup.ts'],
|
||||||
|
testTimeout: 30000, // 30 seconds for integration tests
|
||||||
|
// Run tests sequentially to avoid rate limiting
|
||||||
|
pool: 'forks',
|
||||||
|
poolOptions: {
|
||||||
|
forks: {
|
||||||
|
singleFork: true, // Run all tests in a single process sequentially
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Alternative: use threads with maxConcurrency: 1
|
||||||
|
// maxConcurrency: 1,
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
reporter: ['text', 'json', 'html'],
|
||||||
|
exclude: [
|
||||||
|
'node_modules/',
|
||||||
|
'dist/',
|
||||||
|
'tests/',
|
||||||
|
'*.config.ts',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Reference in New Issue
Block a user