diff --git a/src/binders/orders/parameters.ts b/src/binders/orders/parameters.ts index 39b59061..fb98229c 100644 --- a/src/binders/orders/parameters.ts +++ b/src/binders/orders/parameters.ts @@ -6,7 +6,7 @@ import { type CreateParameters as PaymentCreateParameters } from '../payments/pa import type PickOptional from '../../types/PickOptional'; export type CreateParameters = Pick & - PickOptional & { + PickOptional & { /** * All order lines must have the same currency as the order. You cannot mix currencies within a single order. * @@ -89,7 +89,7 @@ export interface GetParameters { embed?: OrderEmbed[]; } -export type UpdateParameters = PickOptional & { +export type UpdateParameters = PickOptional & { orderNumber?: string; testmode?: boolean; }; diff --git a/src/binders/payments/parameters.ts b/src/binders/payments/parameters.ts index 09599c13..54c4b029 100644 --- a/src/binders/payments/parameters.ts +++ b/src/binders/payments/parameters.ts @@ -4,7 +4,7 @@ import { type PaymentData, type PaymentEmbed, type PaymentInclude } from '../../ import { type IdempotencyParameter, type PaginationParameters, type ThrottlingParameter } from '../../types/parameters'; import type PickOptional from '../../types/PickOptional'; -export type CreateParameters = Pick & +export type CreateParameters = Pick & PickOptional & { /** * Normally, a payment method screen is shown. However, when using this parameter, you can choose a specific payment method and your customer will skip the selection screen and is sent directly to @@ -174,7 +174,7 @@ export type PageParameters = PaginationParameters & { export type IterateParameters = Omit & ThrottlingParameter; -export type UpdateParameters = Pick & +export type UpdateParameters = Pick & PickOptional & { /** * For digital goods in most jurisdictions, you must apply the VAT rate from your customer's country. Choose the VAT rates you have used for the order to ensure your customer's country matches the diff --git a/src/data/orders/data.ts b/src/data/orders/data.ts index b6373128..b94acf22 100644 --- a/src/data/orders/data.ts +++ b/src/data/orders/data.ts @@ -114,6 +114,18 @@ export interface OrderData extends Model<'order'> { * @see https://docs.mollie.com/reference/v2/orders-api/get-order?path=redirectUrl#response */ redirectUrl: Nullable; + /** + * The optional redirect URL you provided during payment creation. Consumer that explicitly cancel the order will be redirected to this URL if provided, or otherwise to the `redirectUrl` instead — + * see above. + * + * Mollie will always give you status updates via webhooks, including for the `canceled` status. This parameter is therefore entirely optional, but can be useful when implementing a dedicated + * consumer-facing flow to handle order cancellations. + * + * The URL will be `null` for recurring orders. + * + * @see https://docs.mollie.com/reference/v2/orders-api/get-order?path=cancelUrl#response + */ + cancelUrl: Nullable; /** * An array of order line objects. Each object will have the properties listed below. * diff --git a/src/data/payments/data.ts b/src/data/payments/data.ts index e1564fdd..c7b5df0f 100644 --- a/src/data/payments/data.ts +++ b/src/data/payments/data.ts @@ -112,6 +112,18 @@ export interface PaymentData extends Model<'payment'> { * @see https://docs.mollie.com/reference/v2/payments-api/get-payment?path=redirectUrl#response */ redirectUrl?: string; + /** + * The optional redirect URL you provided during payment creation. Consumer that explicitly cancel the payment will be redirected to this URL if provided, or otherwise to the `redirectUrl` instead — + * see above. + * + * Mollie will always give you status updates via webhooks, including for the `canceled` status. This parameter is therefore entirely optional, but can be useful when implementing a dedicated + * consumer-facing flow to handle payment cancellations. + * + * The URL will be `null` for recurring payments. + * + * @see https://docs.mollie.com/reference/v2/payments-api/get-payment?path=cancelUrl#response + */ + cancelUrl?: string; /** * The URL Mollie will call as soon an important status change takes place. * diff --git a/tests/unit/models/order.test.ts b/tests/unit/models/order.test.ts index d9063a1e..579056e6 100644 --- a/tests/unit/models/order.test.ts +++ b/tests/unit/models/order.test.ts @@ -53,6 +53,7 @@ async function getOrder(status, additionalLinks?: object) { method: 'klarnapaylater', isCancelable: true, redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', lines: [ { diff --git a/tests/unit/models/payment.test.ts b/tests/unit/models/payment.test.ts index dd9335ae..edc50ca2 100644 --- a/tests/unit/models/payment.test.ts +++ b/tests/unit/models/payment.test.ts @@ -37,6 +37,7 @@ async function getPayment(status, additionalProperties?: object, additionalLinks profileId: 'pfl_2A1gacu42V', sequenceType: 'oneoff', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', settlementAmount: { value: '20.00', diff --git a/tests/unit/resources/customers/payments.test.ts b/tests/unit/resources/customers/payments.test.ts index 93a60a57..003cd87b 100644 --- a/tests/unit/resources/customers/payments.test.ts +++ b/tests/unit/resources/customers/payments.test.ts @@ -24,6 +24,7 @@ test('createCustomerPayment', async () => { profileId: 'pfl_2A1gacu42V', sequenceType: 'oneoff', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', _links: { self: { @@ -53,6 +54,7 @@ test('createCustomerPayment', async () => { customerId: 'cst_FhQJRw4s2n', description: 'My first API payment', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', metadata: { order_id: '1234', @@ -75,6 +77,7 @@ test('createCustomerPayment', async () => { expect(payment.profileId).toBe('pfl_2A1gacu42V'); expect(payment.sequenceType).toBe('oneoff'); expect(payment.redirectUrl).toBe('https://example.org/redirect'); + expect(payment.cancelUrl).toBe('https://example.org/cancel'); expect(payment.webhookUrl).toBe('https://example.org/webhook'); expect(payment._links.self).toEqual({ href: 'https://api.mollie.com/v2/payments/tr_44aKxzEbr8', type: 'application/hal+json' }); diff --git a/tests/unit/resources/orders.test.ts b/tests/unit/resources/orders.test.ts index 1474a9e1..d152585b 100644 --- a/tests/unit/resources/orders.test.ts +++ b/tests/unit/resources/orders.test.ts @@ -50,6 +50,7 @@ function composeOrderResponse(orderId, orderStatus = 'created', orderNumber = '1 method: 'klarnapaylater', isCancelable: true, redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', lines: [ { @@ -182,6 +183,7 @@ function testOrder(order, orderId, orderStatus = 'created', orderNumber = '1337' expect(order.locale).toBe('nl_NL'); expect(order.redirectUrl).toBe('https://example.org/redirect'); + expect(order.cancelUrl).toBe('https://example.org/cancel'); expect(order.webhookUrl).toBe('https://example.org/webhook'); expect(order._links.self).toEqual({ @@ -275,6 +277,7 @@ test('createOrder', async () => { locale: 'nl_NL', orderNumber: '1337', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', method: 'klarnapaylater', lines: [ @@ -380,6 +383,7 @@ test('getOrderIncludingPayments', async () => { email: 'luke@skywalker.com', }, redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', lines: [ { resource: 'orderline', @@ -515,6 +519,7 @@ test('getOrderIncludingPayments', async () => { orderId: 'ord_kEn1PlbGa', sequenceType: 'oneoff', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', _links: { self: { href: 'https://api.mollie.com/v2/payments/tr_ncaPcAhuUV', @@ -568,6 +573,7 @@ test('getOrderIncludingPayments', async () => { expect(payment.orderId).toBe('ord_kEn1PlbGa'); expect(payment.sequenceType).toBe('oneoff'); expect(payment.redirectUrl).toBe('https://example.org/redirect'); + expect(payment.cancelUrl).toBe('https://example.org/cancel'); expect(payment._links.self).toEqual({ href: 'https://api.mollie.com/v2/payments/tr_ncaPcAhuUV', type: 'application/hal+json', diff --git a/tests/unit/resources/payments.test.ts b/tests/unit/resources/payments.test.ts index 09e395fc..ee0ef5a3 100644 --- a/tests/unit/resources/payments.test.ts +++ b/tests/unit/resources/payments.test.ts @@ -24,6 +24,7 @@ test('createPayment', async () => { profileId: 'pfl_2A1gacu42V', sequenceType: 'oneoff', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', _links: { self: { @@ -48,6 +49,7 @@ test('createPayment', async () => { }, description: 'My first API payment', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', metadata: { order_id: '1234', @@ -70,6 +72,7 @@ test('createPayment', async () => { expect(payment.profileId).toBe('pfl_2A1gacu42V'); expect(payment.sequenceType).toBe('oneoff'); expect(payment.redirectUrl).toBe('https://example.org/redirect'); + expect(payment.cancelUrl).toBe('https://example.org/cancel'); expect(payment.webhookUrl).toBe('https://example.org/webhook'); expect(payment._links.self).toEqual({ href: 'https://api.mollie.com/v2/payments/tr_44aKxzEbr8', type: 'application/hal+json' }); @@ -190,6 +193,7 @@ test('getPayment', async () => { profileId: 'pfl_2A1gacu42V', sequenceType: 'oneoff', redirectUrl: 'https://example.org/redirect', + cancelUrl: 'https://example.org/cancel', webhookUrl: 'https://example.org/webhook', settlementAmount: { value: '20.00', @@ -226,6 +230,7 @@ test('getPayment', async () => { expect(payment.profileId).toBe('pfl_2A1gacu42V'); expect(payment.sequenceType).toBe('oneoff'); expect(payment.redirectUrl).toBe('https://example.org/redirect'); + expect(payment.cancelUrl).toBe('https://example.org/cancel'); expect(payment.webhookUrl).toBe('https://example.org/webhook'); expect(payment._links.self).toEqual({ href: 'https://api.mollie.com/v2/payments/tr_44aKxzEbr8', type: 'application/hal+json' }); diff --git a/tests/unit/resources/subscriptions/payments.test.ts b/tests/unit/resources/subscriptions/payments.test.ts index bc792c09..231106ba 100644 --- a/tests/unit/resources/subscriptions/payments.test.ts +++ b/tests/unit/resources/subscriptions/payments.test.ts @@ -28,6 +28,7 @@ test('listSubscriptionPayments', async () => { subscriptionId: 'sub_8JfGzs6v3K', sequenceType: 'recurring', redirectUrl: null, + cancelUrl: null, webhookUrl: 'https://example.org/webhook', settlementAmount: { value: '10.00', @@ -84,6 +85,7 @@ test('listSubscriptionPayments', async () => { subscriptionId: 'sub_8JfGzs6v3K', sequenceType: 'recurring', redirectUrl: null, + cancelUrl: null, webhookUrl: 'https://example.org/webhook', settlementAmount: { value: '10.00',