Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export default class Checkout {
this.issuing = new ENDPOINTS.Issuing(this.config);
this.paymentContexts = new ENDPOINTS.PaymentContexts(this.config);
this.paymentSessions = new ENDPOINTS.PaymentSessions(this.config);
this.paymentSetups = new ENDPOINTS.PaymentSetups(this.config);
this.forward = new ENDPOINTS.Forward(this.config);
}
}
117 changes: 117 additions & 0 deletions src/api/payment-setups/payment-setups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { determineError } from '../../services/errors.js';
import { get, post, put } from '../../services/http.js';
import { validatePayment } from '../../services/validation.js';

/**
* Class dealing with the /payment-setups endpoint
*
* @export
* @class PaymentSetups
*/
export default class PaymentSetups {
constructor(config) {
this.config = config;
}

/**
* Create a Payment Setup
* [BETA]
* Creates a Payment Setup.
* To maximize the amount of information the payment setup can use, we recommend that you create a payment setup as early
* as possible in the customer's journey. For example, the first time they land on the basket page.
* @method createAPaymentSetup
* @param {Object} body - Request body
* @returns {Promise<Object>} A promise to the Create a Payment Setup response
*/
async createAPaymentSetup(body) {
try {
validatePayment(body);
const url = `${this.config.host}/payments/setups`;
const response = await post(
this.config.httpClient,
url,
this.config,
this.config.sk,
body
);
return await response.json;
} catch (error) {
throw await determineError(error);
}
}

/**
* Update a Payment Setup
* [BETA]
* Updates a Payment Setup.
* You should update the payment setup whenever there are significant changes in the data relevant to the customer's
* transaction. For example, when the customer makes a change that impacts the total payment amount.
* @method updateAPaymentSetup
* @param {string} id - The unique identifier of the Payment Setup to update.
* @param {Object} body - Request body
* @returns {Promise<Object>} A promise to the Update a Payment Setup response
*/
async updateAPaymentSetup(id, body) {
try {
validatePayment(body);
const url = `${this.config.host}/payments/setups/${id}`;
const response = await put(
this.config.httpClient,
url,
this.config,
this.config.sk,
body
);
return await response.json;
} catch (error) {
throw await determineError(error);
}
}

/**
* Get a Payment Setup
* [BETA]
* Retrieves a Payment Setup.
* @method getAPaymentSetup
* @param {string} id - The unique identifier of the Payment Setup to retrieve.
* @returns {Promise<Object>} A promise to the Get a Payment Setup response
*/
async getAPaymentSetup(id) {
try {
const url = `${this.config.host}/payments/setups/${id}`;
const response = await get(
this.config.httpClient,
url,
this.config,
this.config.sk,
);
return await response.json;
} catch (error) {
throw await determineError(error);
}
}

/**
* Confirm a Payment Setup
* [BETA]
* Confirm a Payment Setup to begin processing the payment request with your chosen payment method option.
* @method confirmAPaymentSetup
* @param {string} id - The unique identifier of the Payment Setup.
* @param {string} payment_method_option_id - The unique identifier of the payment option to process the payment with.
* @returns {Promise<Object>} A promise to the Confirm a Payment Setup response
*/
async confirmAPaymentSetup(id, payment_method_option_id) {
try {
const url = `${this.config.host}/payments/setups/${id}/confirm/${payment_method_option_id}`;
const response = await post(
this.config.httpClient,
url,
this.config,
this.config.sk,
);
return await response.json;
} catch (error) {
throw await determineError(error);
}
}
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export { default as Financial } from './api/financial/financial.js';
export { default as Issuing } from './api/issuing/issuing.js';
export { default as PaymentContexts } from './api/payment-contexts/payment-contexts.js';
export { default as PaymentSessions } from './api/payment-sessions/payment-sessions.js';
export { default as PaymentSetups } from './api/payment-setups/payment-setups.js';
export { default as Forward } from './api/forward/forward.js';
export { default as Checkout } from './Checkout.js';
export { default } from './Checkout.js';
224 changes: 224 additions & 0 deletions test/payment-setups/payment-setups-it.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { expect } from "chai";
import nock from "nock";
import Checkout from '../../src/Checkout.js'
import { NotFoundError, ValidationError, AuthenticationError, ActionNotAllowed } from "../../src/services/errors.js";

afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});

const cko = new Checkout(process.env.CHECKOUT_DEFAULT_SECRET_KEY);
const processingChannelId = process.env.CHECKOUT_PROCESSING_CHANNEL_ID;

describe('Integration::Payment-Setups', () => {
const createRequest = {
processing_channel_id: processingChannelId,
amount: 1000,
currency: "GBP",
payment_type: "Regular",
reference: `TEST-REF-${Date.now()}`,
description: "Integration test payment setup",
settings: {
success_url: "https://example.com/success",
failure_url: "https://example.com/failure"
},
customer: {
name: "John Smith",
email: {
address: `john.smith+${Date.now()}@example.com`,
verified: true
},
phone: {
country_code: "+44",
number: "207 946 0000"
},
device: {
locale: "en_GB"
}
},
payment_methods: {
klarna: {
initialization: "disabled",
account_holder: {
billing_address: {
address_line1: "123 High Street",
city: "London",
zip: "SW1A 1AA",
country: "GB"
}
}
}
}
};

const updateRequest = {
processing_channel_id: processingChannelId,
amount: 1500,
currency: "GBP",
payment_type: "Regular",
reference: `TEST-REF-UPDATED-${Date.now()}`,
description: "Updated integration test payment setup",
settings: {
success_url: "https://example.com/success-updated",
failure_url: "https://example.com/failure-updated"
},
customer: {
name: "John Smith Updated",
email: {
address: `john.smith.updated+${Date.now()}@example.com`,
verified: true
},
phone: {
country_code: "+44",
number: "207 946 0001"
},
device: {
locale: "en_US"
}
},
payment_methods: {
klarna: {
initialization: "disabled",
account_holder: {
billing_address: {
address_line1: "456 Updated Street",
city: "Manchester",
zip: "M1 2AB",
country: "GB"
}
}
}
}
};

describe('Create and manage Payment Setup lifecycle', () => {

it('should create a payment setup', async () => {
const response = await cko.paymentSetups.createAPaymentSetup(createRequest);

expect(response.id).not.to.be.null;
expect(response.amount).to.equal(1000);
expect(response.currency).to.equal('GBP');
expect(response.customer.email.address).to.include('john.smith+');
});

it('should get a payment setup', async () => {
// First create a payment setup
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);

// Then retrieve it
const response = await cko.paymentSetups.getAPaymentSetup(createResponse.id);

expect(response.id).to.equal(createResponse.id);
expect(response.amount).to.equal(1000);
expect(response.currency).to.equal('GBP');
expect(response.customer.email.address).to.include('john.smith+');
});

it('should update a payment setup', async () => {
// First create a payment setup
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);

// Then update it
const response = await cko.paymentSetups.updateAPaymentSetup(createResponse.id, updateRequest);

expect(response.id).to.equal(createResponse.id);
expect(response.amount).to.equal(1500);
expect(response.customer.name).to.equal('John Smith Updated');
});

it('should confirm a payment setup with a payment method option', async () => {
try {
// First create a payment setup
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);

// Get the payment setup to find available payment method options
const getResponse = await cko.paymentSetups.getAPaymentSetup(createResponse.id);

// Extract first available payment method option ID (if available)
const paymentMethodOptions = getResponse.payment_method_options || [];
if (paymentMethodOptions.length > 0) {
const paymentMethodOptionId = paymentMethodOptions[0].id;

// Confirm the payment setup
const response = await cko.paymentSetups.confirmAPaymentSetup(createResponse.id, paymentMethodOptionId);

expect(response.id).not.to.be.null;
expect(response.status).not.to.be.null;
expect(response.approved).to.be.a('boolean');
} else {
// If no payment method options available, skip this test
console.log('Skipping confirm test - no payment method options available');
}
} catch (error) {
// Payment setups might not be fully configured in test environment
// so we expect certain validation errors
expect(error).to.be.instanceOf(ValidationError);
}
});
});

describe('Error handling', () => {
it('should throw NotFoundError when getting non-existent payment setup', async () => {
try {
await cko.paymentSetups.getAPaymentSetup('psu_not_found');
} catch (err) {
expect(err).to.be.instanceOf(NotFoundError);
}
});

it('should throw NotFoundError when updating non-existent payment setup', async () => {
const updateRequest = {
amount: 1500,
currency: "GBP"
};

try {
await cko.paymentSetups.updateAPaymentSetup('psu_not_found', updateRequest);
} catch (err) {
expect(err).to.be.instanceOf(NotFoundError);
}
});

it('should throw NotFoundError when confirming non-existent payment setup', async () => {
try {
await cko.paymentSetups.confirmAPaymentSetup('psu_not_found', 'pmo_not_found');
} catch (err) {
expect(err).to.be.instanceOf(NotFoundError);
}
});

it('should throw ValidationError when creating payment setup with invalid data', async () => {
const invalidRequest = {
// Missing required fields
amount: 'invalid_amount',
currency: 'INVALID'
};

try {
await cko.paymentSetups.createAPaymentSetup(invalidRequest);
} catch (err) {
expect(err.name).to.equal('ValueError');
expect(err.body).to.equal('The currency value is not valid.');
}
});

it('should throw ValidationError when updating payment setup with invalid data', async () => {
// First create a valid payment setup
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);

const invalidUpdateRequest = {
amount: 'invalid_amount',
currency: 'INVALID'
};

try {
await cko.paymentSetups.updateAPaymentSetup(createResponse.id, invalidUpdateRequest);
} catch (err) {
expect(err.name).to.equal('ValueError');
expect(err.body).to.equal('The currency value is not valid.');
}
});
});
});
Loading