-
Notifications
You must be signed in to change notification settings - Fork 21
Feature/new endpoint payment setups #419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
armando-rodriguez-cko
merged 7 commits into
master
from
feature/new-endpoint-payment-setups
Jan 28, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
de82b86
Payment Setups initial development, lacks tests
david-ruiz-cko fc16b2c
Import fix for payment setups endpoint
david-ruiz-cko 8de2c8c
Payment setups unit tests
david-ruiz-cko 36cb88f
Payment Setups integration test
david-ruiz-cko 81f08a2
Fixing hardcoded files upload purpose bug, updated the tests with som…
david-ruiz-cko 8ef6375
Merge branch 'master' into feature/new-endpoint-payment-setups
robert-goheen-cko ec4aa26
Merge branch 'master' into feature/new-endpoint-payment-setups
robert-goheen-cko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.'); | ||
| } | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.