mirror of
https://github.com/duhanbalci/iyzico.git
synced 2026-03-03 20:29:18 +00:00
557 lines
18 KiB
TypeScript
557 lines
18 KiB
TypeScript
/**
|
||
* 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);
|
||
});
|
||
});
|
||
});
|