This commit is contained in:
Duhan BALCI
2026-01-01 18:30:21 +03:00
commit c65195d26d
44 changed files with 8715 additions and 0 deletions

View 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);
}
});
});
});

View 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);
}
}
}
});
});
});

View 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);
}
}
});
});
});

View 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);
});
});
});

View 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);
}
});
});
});

View 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);
});
});
});