Skip to content

Commit cb5d9ac

Browse files
Feature/new endpoint payment setups (#419)
* Payment Setups initial development, lacks tests * Import fix for payment setups endpoint * Payment setups unit tests * Payment Setups integration test * Fixing hardcoded files upload purpose bug, updated the tests with some to extend the checks (#420) --------- Co-authored-by: Robert Goheen <robert.goheen@checkout.com>
1 parent 9d3d705 commit cb5d9ac

8 files changed

Lines changed: 3398 additions & 0 deletions

File tree

src/Checkout.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export default class Checkout {
201201
this.issuing = new ENDPOINTS.Issuing(this.config);
202202
this.paymentContexts = new ENDPOINTS.PaymentContexts(this.config);
203203
this.paymentSessions = new ENDPOINTS.PaymentSessions(this.config);
204+
this.paymentSetups = new ENDPOINTS.PaymentSetups(this.config);
204205
this.forward = new ENDPOINTS.Forward(this.config);
205206
}
206207
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { determineError } from '../../services/errors.js';
2+
import { get, post, put } from '../../services/http.js';
3+
import { validatePayment } from '../../services/validation.js';
4+
5+
/**
6+
* Class dealing with the /payment-setups endpoint
7+
*
8+
* @export
9+
* @class PaymentSetups
10+
*/
11+
export default class PaymentSetups {
12+
constructor(config) {
13+
this.config = config;
14+
}
15+
16+
/**
17+
* Create a Payment Setup
18+
* [BETA]
19+
* Creates a Payment Setup.
20+
* To maximize the amount of information the payment setup can use, we recommend that you create a payment setup as early
21+
* as possible in the customer's journey. For example, the first time they land on the basket page.
22+
* @method createAPaymentSetup
23+
* @param {Object} body - Request body
24+
* @returns {Promise&lt;Object&gt;} A promise to the Create a Payment Setup response
25+
*/
26+
async createAPaymentSetup(body) {
27+
try {
28+
validatePayment(body);
29+
const url = `${this.config.host}/payments/setups`;
30+
const response = await post(
31+
this.config.httpClient,
32+
url,
33+
this.config,
34+
this.config.sk,
35+
body
36+
);
37+
return await response.json;
38+
} catch (error) {
39+
throw await determineError(error);
40+
}
41+
}
42+
43+
/**
44+
* Update a Payment Setup
45+
* [BETA]
46+
* Updates a Payment Setup.
47+
* You should update the payment setup whenever there are significant changes in the data relevant to the customer's
48+
* transaction. For example, when the customer makes a change that impacts the total payment amount.
49+
* @method updateAPaymentSetup
50+
* @param {string} id - The unique identifier of the Payment Setup to update.
51+
* @param {Object} body - Request body
52+
* @returns {Promise&lt;Object&gt;} A promise to the Update a Payment Setup response
53+
*/
54+
async updateAPaymentSetup(id, body) {
55+
try {
56+
validatePayment(body);
57+
const url = `${this.config.host}/payments/setups/${id}`;
58+
const response = await put(
59+
this.config.httpClient,
60+
url,
61+
this.config,
62+
this.config.sk,
63+
body
64+
);
65+
return await response.json;
66+
} catch (error) {
67+
throw await determineError(error);
68+
}
69+
}
70+
71+
/**
72+
* Get a Payment Setup
73+
* [BETA]
74+
* Retrieves a Payment Setup.
75+
* @method getAPaymentSetup
76+
* @param {string} id - The unique identifier of the Payment Setup to retrieve.
77+
* @returns {Promise&lt;Object&gt;} A promise to the Get a Payment Setup response
78+
*/
79+
async getAPaymentSetup(id) {
80+
try {
81+
const url = `${this.config.host}/payments/setups/${id}`;
82+
const response = await get(
83+
this.config.httpClient,
84+
url,
85+
this.config,
86+
this.config.sk,
87+
);
88+
return await response.json;
89+
} catch (error) {
90+
throw await determineError(error);
91+
}
92+
}
93+
94+
/**
95+
* Confirm a Payment Setup
96+
* [BETA]
97+
* Confirm a Payment Setup to begin processing the payment request with your chosen payment method option.
98+
* @method confirmAPaymentSetup
99+
* @param {string} id - The unique identifier of the Payment Setup.
100+
* @param {string} payment_method_option_id - The unique identifier of the payment option to process the payment with.
101+
* @returns {Promise&lt;Object&gt;} A promise to the Confirm a Payment Setup response
102+
*/
103+
async confirmAPaymentSetup(id, payment_method_option_id) {
104+
try {
105+
const url = `${this.config.host}/payments/setups/${id}/confirm/${payment_method_option_id}`;
106+
const response = await post(
107+
this.config.httpClient,
108+
url,
109+
this.config,
110+
this.config.sk,
111+
);
112+
return await response.json;
113+
} catch (error) {
114+
throw await determineError(error);
115+
}
116+
}
117+
}

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export { default as Financial } from './api/financial/financial.js';
3535
export { default as Issuing } from './api/issuing/issuing.js';
3636
export { default as PaymentContexts } from './api/payment-contexts/payment-contexts.js';
3737
export { default as PaymentSessions } from './api/payment-sessions/payment-sessions.js';
38+
export { default as PaymentSetups } from './api/payment-setups/payment-setups.js';
3839
export { default as Forward } from './api/forward/forward.js';
3940
export { default as Checkout } from './Checkout.js';
4041
export { default } from './Checkout.js';
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { expect } from "chai";
2+
import nock from "nock";
3+
import Checkout from '../../src/Checkout.js'
4+
import { NotFoundError, ValidationError, AuthenticationError, ActionNotAllowed } from "../../src/services/errors.js";
5+
6+
afterEach(() => {
7+
nock.cleanAll();
8+
nock.enableNetConnect();
9+
});
10+
11+
const cko = new Checkout(process.env.CHECKOUT_DEFAULT_SECRET_KEY);
12+
const processingChannelId = process.env.CHECKOUT_PROCESSING_CHANNEL_ID;
13+
14+
describe('Integration::Payment-Setups', () => {
15+
const createRequest = {
16+
processing_channel_id: processingChannelId,
17+
amount: 1000,
18+
currency: "GBP",
19+
payment_type: "Regular",
20+
reference: `TEST-REF-${Date.now()}`,
21+
description: "Integration test payment setup",
22+
settings: {
23+
success_url: "https://example.com/success",
24+
failure_url: "https://example.com/failure"
25+
},
26+
customer: {
27+
name: "John Smith",
28+
email: {
29+
address: `john.smith+${Date.now()}@example.com`,
30+
verified: true
31+
},
32+
phone: {
33+
country_code: "+44",
34+
number: "207 946 0000"
35+
},
36+
device: {
37+
locale: "en_GB"
38+
}
39+
},
40+
payment_methods: {
41+
klarna: {
42+
initialization: "disabled",
43+
account_holder: {
44+
billing_address: {
45+
address_line1: "123 High Street",
46+
city: "London",
47+
zip: "SW1A 1AA",
48+
country: "GB"
49+
}
50+
}
51+
}
52+
}
53+
};
54+
55+
const updateRequest = {
56+
processing_channel_id: processingChannelId,
57+
amount: 1500,
58+
currency: "GBP",
59+
payment_type: "Regular",
60+
reference: `TEST-REF-UPDATED-${Date.now()}`,
61+
description: "Updated integration test payment setup",
62+
settings: {
63+
success_url: "https://example.com/success-updated",
64+
failure_url: "https://example.com/failure-updated"
65+
},
66+
customer: {
67+
name: "John Smith Updated",
68+
email: {
69+
address: `john.smith.updated+${Date.now()}@example.com`,
70+
verified: true
71+
},
72+
phone: {
73+
country_code: "+44",
74+
number: "207 946 0001"
75+
},
76+
device: {
77+
locale: "en_US"
78+
}
79+
},
80+
payment_methods: {
81+
klarna: {
82+
initialization: "disabled",
83+
account_holder: {
84+
billing_address: {
85+
address_line1: "456 Updated Street",
86+
city: "Manchester",
87+
zip: "M1 2AB",
88+
country: "GB"
89+
}
90+
}
91+
}
92+
}
93+
};
94+
95+
describe('Create and manage Payment Setup lifecycle', () => {
96+
97+
it('should create a payment setup', async () => {
98+
const response = await cko.paymentSetups.createAPaymentSetup(createRequest);
99+
100+
expect(response.id).not.to.be.null;
101+
expect(response.amount).to.equal(1000);
102+
expect(response.currency).to.equal('GBP');
103+
expect(response.customer.email.address).to.include('john.smith+');
104+
});
105+
106+
it('should get a payment setup', async () => {
107+
// First create a payment setup
108+
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);
109+
110+
// Then retrieve it
111+
const response = await cko.paymentSetups.getAPaymentSetup(createResponse.id);
112+
113+
expect(response.id).to.equal(createResponse.id);
114+
expect(response.amount).to.equal(1000);
115+
expect(response.currency).to.equal('GBP');
116+
expect(response.customer.email.address).to.include('john.smith+');
117+
});
118+
119+
it('should update a payment setup', async () => {
120+
// First create a payment setup
121+
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);
122+
123+
// Then update it
124+
const response = await cko.paymentSetups.updateAPaymentSetup(createResponse.id, updateRequest);
125+
126+
expect(response.id).to.equal(createResponse.id);
127+
expect(response.amount).to.equal(1500);
128+
expect(response.customer.name).to.equal('John Smith Updated');
129+
});
130+
131+
it('should confirm a payment setup with a payment method option', async () => {
132+
try {
133+
// First create a payment setup
134+
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);
135+
136+
// Get the payment setup to find available payment method options
137+
const getResponse = await cko.paymentSetups.getAPaymentSetup(createResponse.id);
138+
139+
// Extract first available payment method option ID (if available)
140+
const paymentMethodOptions = getResponse.payment_method_options || [];
141+
if (paymentMethodOptions.length > 0) {
142+
const paymentMethodOptionId = paymentMethodOptions[0].id;
143+
144+
// Confirm the payment setup
145+
const response = await cko.paymentSetups.confirmAPaymentSetup(createResponse.id, paymentMethodOptionId);
146+
147+
expect(response.id).not.to.be.null;
148+
expect(response.status).not.to.be.null;
149+
expect(response.approved).to.be.a('boolean');
150+
} else {
151+
// If no payment method options available, skip this test
152+
console.log('Skipping confirm test - no payment method options available');
153+
}
154+
} catch (error) {
155+
// Payment setups might not be fully configured in test environment
156+
// so we expect certain validation errors
157+
expect(error).to.be.instanceOf(ValidationError);
158+
}
159+
});
160+
});
161+
162+
describe('Error handling', () => {
163+
it('should throw NotFoundError when getting non-existent payment setup', async () => {
164+
try {
165+
await cko.paymentSetups.getAPaymentSetup('psu_not_found');
166+
} catch (err) {
167+
expect(err).to.be.instanceOf(NotFoundError);
168+
}
169+
});
170+
171+
it('should throw NotFoundError when updating non-existent payment setup', async () => {
172+
const updateRequest = {
173+
amount: 1500,
174+
currency: "GBP"
175+
};
176+
177+
try {
178+
await cko.paymentSetups.updateAPaymentSetup('psu_not_found', updateRequest);
179+
} catch (err) {
180+
expect(err).to.be.instanceOf(NotFoundError);
181+
}
182+
});
183+
184+
it('should throw NotFoundError when confirming non-existent payment setup', async () => {
185+
try {
186+
await cko.paymentSetups.confirmAPaymentSetup('psu_not_found', 'pmo_not_found');
187+
} catch (err) {
188+
expect(err).to.be.instanceOf(NotFoundError);
189+
}
190+
});
191+
192+
it('should throw ValidationError when creating payment setup with invalid data', async () => {
193+
const invalidRequest = {
194+
// Missing required fields
195+
amount: 'invalid_amount',
196+
currency: 'INVALID'
197+
};
198+
199+
try {
200+
await cko.paymentSetups.createAPaymentSetup(invalidRequest);
201+
} catch (err) {
202+
expect(err.name).to.equal('ValueError');
203+
expect(err.body).to.equal('The currency value is not valid.');
204+
}
205+
});
206+
207+
it('should throw ValidationError when updating payment setup with invalid data', async () => {
208+
// First create a valid payment setup
209+
const createResponse = await cko.paymentSetups.createAPaymentSetup(createRequest);
210+
211+
const invalidUpdateRequest = {
212+
amount: 'invalid_amount',
213+
currency: 'INVALID'
214+
};
215+
216+
try {
217+
await cko.paymentSetups.updateAPaymentSetup(createResponse.id, invalidUpdateRequest);
218+
} catch (err) {
219+
expect(err.name).to.equal('ValueError');
220+
expect(err.body).to.equal('The currency value is not valid.');
221+
}
222+
});
223+
});
224+
});

0 commit comments

Comments
 (0)