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
6 changes: 6 additions & 0 deletions src/api/hosted-payments/hosted-payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export default class HostedPayments {
*
* Notable optional fields (swagger HostedPaymentsRequest, 2026-06-08):
* - body.authorization_type — e.g. `Estimated`, `Final`.
* - body.3ds.challenge_indicator — four values only (default
* `no_preference`): `no_preference`, `no_challenge_requested`,
* `challenge_requested`, `challenge_requested_mandate`. The exemption
* values (`low_value`, `trusted_listing`, `trusted_listing_prompt`,
* `transaction_risk_assessment`, `data_share`) are accepted only by
* `cko.sessions.request` and are rejected here.
* - body.payment_plan — installment / recurring schedule
* (`amount`, `name`, `start_date` added 2026-05-08).
*
Expand Down
22 changes: 22 additions & 0 deletions src/api/payment-sessions/payment-sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export default class PaymentSessions {
*
* Notable optional fields (swagger CreatePaymentSessionsBaseRequest, 2026-06-08):
* - body.authorization_type — e.g. `Estimated`, `Final`.
* - body.3ds.challenge_indicator — four values only (default
* `no_preference`): `no_preference`, `no_challenge_requested`,
* `challenge_requested`, `challenge_requested_mandate`. The exemption
* values (`low_value`, `trusted_listing`, `trusted_listing_prompt`,
* `transaction_risk_assessment`, `data_share`) are accepted only by
* `cko.sessions.request` and are rejected here.
* - body.payment_plan — installment / recurring schedule. See swagger
* `PaymentSessionPaymentPlanRecurring` for the recurring variant
* (fields: amount, name, start_date — added 2026-05-08).
Expand Down Expand Up @@ -46,6 +52,14 @@ export default class PaymentSessions {
/**
* Submit a payment attempt for a payment session.
*
* Notable optional fields (swagger SubmitPaymentSessionsRequest, 2026-06-08):
* - body.3ds.challenge_indicator — four values only (default
* `no_preference`): `no_preference`, `no_challenge_requested`,
* `challenge_requested`, `challenge_requested_mandate`. The exemption
* values (`low_value`, `trusted_listing`, `trusted_listing_prompt`,
* `transaction_risk_assessment`, `data_share`) are accepted only by
* `cko.sessions.request` and are rejected here.
*
* @memberof PaymentSessions
* @param {string} id The payment session ID.
* @param {object} body PaymentSessions Request body.
Expand All @@ -70,6 +84,14 @@ export default class PaymentSessions {
* Request a Payment Session with Payment.
* Create a payment session and submit a payment attempt for it.
*
* Notable optional fields (swagger CreateAndSubmitPaymentSessionsRequest, 2026-06-08):
* - body.3ds.challenge_indicator — four values only (default
* `no_preference`): `no_preference`, `no_challenge_requested`,
* `challenge_requested`, `challenge_requested_mandate`. The exemption
* values (`low_value`, `trusted_listing`, `trusted_listing_prompt`,
* `transaction_risk_assessment`, `data_share`) are accepted only by
* `cko.sessions.request` and are rejected here.
*
* @memberof PaymentSessions
* @param {object} body PaymentSessions Request body.
* @return {Promise<object>} A promise to payment response (201 processed or 202 action required).
Expand Down
6 changes: 6 additions & 0 deletions src/api/payments-links/payments-links.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export default class PaymentLinks {
*
* Notable optional fields (swagger PaymentLinksRequest, 2026-06-08):
* - body.authorization_type — e.g. `Estimated`, `Final`.
* - body.3ds.challenge_indicator — four values only (default
* `no_preference`): `no_preference`, `no_challenge_requested`,
* `challenge_requested`, `challenge_requested_mandate`. The exemption
* values (`low_value`, `trusted_listing`, `trusted_listing_prompt`,
* `transaction_risk_assessment`, `data_share`) are accepted only by
* `cko.sessions.request` and are rejected here.
* - body.payment_plan — installment / recurring schedule
* (`amount`, `name`, `start_date` added 2026-05-08).
*
Expand Down
6 changes: 6 additions & 0 deletions src/api/payments/payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export default class Payments {
* tracking, surfaced under `processing` (2026-05-07).
* - body.payment_plan, body.authorization_type — present on
* HostedPayments/PaymentLinks/PaymentSessions variants (2026-06-08).
* - body.3ds.challenge_indicator — four values only (default
* `no_preference`): `no_preference`, `no_challenge_requested`,
* `challenge_requested`, `challenge_requested_mandate`. The exemption
* values (`low_value`, `trusted_listing`, `trusted_listing_prompt`,
* `transaction_risk_assessment`, `data_share`) are accepted only by
* `cko.sessions.request` and are rejected here.
*
* Response fields newly available under `processing` (pass-through):
* - scheme_transaction_link_id (Mastercard Transaction Link Identifier, 2026-06-08)
Expand Down
10 changes: 10 additions & 0 deletions src/api/sessions/sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ export default class Sessions {
* Notable optional fields (swagger SessionRequest, 2026-05-07):
* - body.device_information — device-fingerprint block. See swagger
* `DeviceInformation` for shape (browser, ip, screen, timezone, etc.).
* - body.challenge_indicator — this endpoint is the only one that accepts the
* exemption values. Nine values (swagger `ChallengeIndicator`, default
* `no_preference`, max 50 characters): `no_preference`,
* `no_challenge_requested`, `challenge_requested`,
* `challenge_requested_mandate`, plus the exemption requests `low_value`,
* `trusted_listing`, `trusted_listing_prompt`,
* `transaction_risk_assessment`, and `data_share`. If an exemption cannot
* be applied, `no_challenge_requested` is used instead. The
* `3ds.challenge_indicator` field on payments, hosted payments, payment
* links and payment sessions accepts only the first four.
*
* @memberof Sessions
* @param {Object} body Sessions request body.
Expand Down
142 changes: 142 additions & 0 deletions test/sessions/challenge-indicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { Checkout } from '../../src/index.js';
import { expect } from 'chai';
import nock from 'nock';

const ACCESS_HOST = 'https://123456789.access.sandbox.checkout.com';
const API_HOST = 'https://123456789.api.sandbox.checkout.com';

const SECRET =
'2p7YQ37fHiRr8O6lQAikl8enICesB1dvAJrpmE2nZfEOpxzE-J_Gho7wDy0HY9951RfdUr0vSaQCzRKP0-o5Xg';

// The nine values accepted by POST /sessions, per the API Reference ChallengeIndicator schema.
const SESSION_VALUES = [
'no_preference',
'no_challenge_requested',
'challenge_requested',
'challenge_requested_mandate',
'low_value',
'trusted_listing',
'trusted_listing_prompt',
'transaction_risk_assessment',
'data_share',
];

// The four values accepted by the 3ds.challenge_indicator field on the payments family.
const PAYMENT_VALUES = [
'no_preference',
'no_challenge_requested',
'challenge_requested',
'challenge_requested_mandate',
];

const client = () =>
new Checkout(SECRET, {
client: 'ack_vvzhoai466su3j3vbxb47ts5oe',
scope: ['sessions:browser'],
environment: 'sandbox',
subdomain: '123456789',
});

const mockToken = () =>
nock(ACCESS_HOST).post('/connect/token').reply(201, {
access_token: '1234',
expires_in: 3600,
token_type: 'Bearer',
scope: 'sessions:browser',
});

const sessionBody = (challengeIndicator) => ({
source: {
type: 'card',
number: '4485040371536584',
expiry_month: 1,
expiry_year: 2030,
},
amount: 100,
currency: 'USD',
authentication_type: 'regular',
authentication_category: 'payment',
challenge_indicator: challengeIndicator,
reference: 'ORD-5023-4E89',
completion: {
type: 'non_hosted',
callback_url: 'https://example.com/sessions/callback',
},
});

/**
* The SDK is untyped and forwards the request body verbatim, so nothing constrains
* challenge_indicator at the client. These tests capture the body that actually reaches the wire,
* which is what makes them meaningful: they fail if a future change starts validating, rewriting or
* dropping the field — in particular the five exemption values, which only POST /sessions accepts.
*/
describe('Sessions challenge_indicator', () => {
afterEach(() => nock.cleanAll());

SESSION_VALUES.forEach((value) => {
it(`should send challenge_indicator "${value}" to /sessions unchanged`, async () => {
mockToken();

let sent;
nock(API_HOST)
.post('/sessions', (body) => {
sent = body;
return true;
})
.reply(201, { id: 'sid_jlfm4ithpgpefdxgzzdnc3xrc4', status: 'pending' });

await client().sessions.request(sessionBody(value));

expect(sent.challenge_indicator).to.equal(value);
});
});

it('should not strip or rewrite the five exemption values', async () => {
const exemptions = SESSION_VALUES.filter((value) => !PAYMENT_VALUES.includes(value));

expect(exemptions).to.have.lengthOf(5);

for (const value of exemptions) {
mockToken();

let sent;
nock(API_HOST)
.post('/sessions', (body) => {
sent = body;
return true;
})
.reply(201, { id: 'sid_jlfm4ithpgpefdxgzzdnc3xrc4', status: 'pending' });

await client().sessions.request(sessionBody(value));

expect(sent).to.have.property('challenge_indicator', value);
nock.cleanAll();
}
});

PAYMENT_VALUES.forEach((value) => {
it(`should send 3ds.challenge_indicator "${value}" to /payment-sessions unchanged`, async () => {
mockToken();

let sent;
nock(API_HOST)
.post('/payment-sessions', (body) => {
sent = body;
return true;
})
.reply(201, { id: 'ps_2adCU4m0Q1WMlM6Pqu3ilRvUUwr' });

await client().paymentSessions.request({
amount: 100,
currency: 'USD',
reference: 'ORD-123A',
billing: { address: { country: 'GB' } },
success_url: 'https://example.com/success',
failure_url: 'https://example.com/failure',
'3ds': { enabled: true, challenge_indicator: value },
});

expect(sent['3ds'].challenge_indicator).to.equal(value);
});
});
});
Loading