Skip to content

Commit abce354

Browse files
committed
feat: added add and remove coupons methods to subscription resource
1 parent a96dcc8 commit abce354

11 files changed

Lines changed: 182 additions & 27 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
sidebar_position: 14
3+
---
4+
5+
# Add a coupon to a subscription
6+
7+
Adds the specified coupon to the subscription. Adding coupons do not trigger immediate adjustments and are applied in the following billing cycle.
8+
9+
## Code Sample
10+
11+
```typescript
12+
import { Salable } from '@salable/node-sdk';
13+
14+
const salable = new Salable('{{API_KEY}}', 'v2');
15+
16+
await salable.subscriptions.addCoupon('d18642b3-6dc0-40c4-aaa5-6315ed37c744', { couponUuid: '4c064ace-57c4-4618-bd79-a0e8029f9904' });
17+
```
18+
19+
## Parameters
20+
21+
#### subscriptionUuid (_required_)
22+
23+
_Type:_ `string`
24+
25+
The UUID of the Subscription
26+
27+
#### Options (_required_)
28+
29+
_Type:_ `{ couponUuid: string }`
30+
31+
| Option | Type | Description | Required |
32+
| ---------- | ------ | ---------------------- | -------- |
33+
| couponUuid | string | The UUID of the coupon ||
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
sidebar_position: 15
3+
---
4+
5+
# Remove a coupon from a subscription
6+
7+
Removes the specified coupon from the subscription. Removing coupons do not trigger immediate adjustments and are applied in the following billing cycle.
8+
9+
## Code Sample
10+
11+
```typescript
12+
import { Salable } from '@salable/node-sdk';
13+
14+
const salable = new Salable('{{API_KEY}}', 'v2');
15+
16+
await salable.subscriptions.removeCoupon('d18642b3-6dc0-40c4-aaa5-6315ed37c744', { couponUuid: '4c064ace-57c4-4618-bd79-a0e8029f9904' });
17+
```
18+
19+
## Parameters
20+
21+
#### subscriptionUuid (_required_)
22+
23+
_Type:_ `string`
24+
25+
The UUID of the Subscription
26+
27+
#### Options (_required_)
28+
29+
_Type:_ `{ couponUuid: string }`
30+
31+
| Option | Type | Description | Required |
32+
| ---------- | ------ | ---------------------- | -------- |
33+
| couponUuid | string | The UUID of the coupon ||

package-lock.json

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prisma/schema.prisma

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -648,24 +648,26 @@ enum CouponStatus {
648648
}
649649

650650
model Coupon {
651-
uuid String @id @default(uuid())
652-
name String
653-
productUuid String
654-
product Product @relation(fields: [productUuid], references: [uuid], onDelete: Cascade)
655-
appliesTo CouponsOnPlans[]
656-
promotionCodes PromotionCodes[]
657-
discountType DiscountType
658-
currencies CouponCurrency[]
659-
percentOff Float?
660-
duration CouponDuration
661-
durationInMonths Int?
662-
expiresAt DateTime?
663-
maxRedemptions Int?
664-
status CouponStatus @default(ACTIVE)
665-
isTest Boolean @default(false)
666-
createdAt DateTime @default(now())
667-
updatedAt DateTime @updatedAt
668-
couponsOnSubscriptions CouponsOnSubscriptions[]
651+
uuid String @id @default(uuid())
652+
name String
653+
productUuid String
654+
product Product @relation(fields: [productUuid], references: [uuid], onDelete: Cascade)
655+
appliesTo CouponsOnPlans[]
656+
promotionCodes PromotionCodes[]
657+
discountType DiscountType
658+
currencies CouponCurrency[]
659+
percentOff Float?
660+
duration CouponDuration
661+
durationInMonths Int?
662+
expiresAt DateTime?
663+
maxRedemptions Int?
664+
paymentIntegrationCouponId String?
665+
isRestricted Boolean @default(false)
666+
status CouponStatus @default(ACTIVE)
667+
isTest Boolean @default(false)
668+
createdAt DateTime @default(now())
669+
updatedAt DateTime @updatedAt
670+
couponsOnSubscriptions CouponsOnSubscriptions[]
669671
}
670672

671673
model CouponsOnPlans {
@@ -698,4 +700,4 @@ model CouponsOnSubscriptions {
698700
@@id([couponUuid, subscriptionUuid])
699701
@@index([couponUuid])
700702
@@index([subscriptionUuid])
701-
}
703+
}

src/licenses/v2/licenses-v2.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const noSubLicenseThreeUuid = uuidv4();
1919
const subscriptionUuid = uuidv4();
2020
const testPurchaser = 'tester@testing.com';
2121
const testGrantee = '123456';
22+
const owner = 'subscription-owner'
2223

2324
describe('Licenses V2 Tests', () => {
2425
const salable = new Salable(testUuids.devApiKeyV2, version);
@@ -574,7 +575,7 @@ const generateTestData = async () => {
574575
email: 'tester@testing.com',
575576
type: 'salable',
576577
status: 'ACTIVE',
577-
owner: 'owner_12345',
578+
owner,
578579
organisation: testUuids.organisationId,
579580
license: { connect: [{ uuid: licenseUuid }, { uuid: licenseTwoUuid }, { uuid: licenseThreeUuid }] },
580581
product: { connect: { uuid: testUuids.productUuid } },

src/sessions/v2/sessions-v2.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const stripeEnvs = JSON.parse(process.env.stripEnvs || '');
1010
const licenseUuid = uuidv4();
1111
const subscriptionUuid = uuidv4();
1212
const testGrantee = '123456';
13+
const owner = 'subscription-owner'
1314

1415
describe('Sessions V2 Tests', () => {
1516
const apiKey = testUuids.devApiKeyV2;
@@ -102,7 +103,6 @@ const generateTestData = async () => {
102103
paymentIntegrationSubscriptionId: stripeEnvs.basicSubscriptionFourId,
103104
uuid: subscriptionUuid,
104105
email: 'tester@testing.com',
105-
owner: 'owner_12345',
106106
type: 'salable',
107107
status: 'ACTIVE',
108108
organisation: testUuids.organisationId,
@@ -112,6 +112,7 @@ const generateTestData = async () => {
112112
createdAt: new Date(),
113113
updatedAt: new Date(),
114114
expiryDate: new Date(Date.now() + 31536000000),
115+
owner,
115116
},
116117
});
117118
};

src/subscriptions/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,38 @@ export type SubscriptionVersions = {
255255
proration?: string;
256256
},
257257
) => Promise<SubscriptionSeat>;
258+
259+
/**
260+
* Applies the specified coupon to the subscription.
261+
*
262+
* @param {string} subscriptionUuid - The UUID of the subscription
263+
*
264+
* Docs - https://docs.salable.app/api/v2#tag/Subscriptions/operation/addCoupon
265+
*
266+
* @returns {Promise<void>}
267+
*/
268+
addCoupon: (
269+
subscriptionUuid: string,
270+
options: {
271+
couponUuid: string
272+
},
273+
) => Promise<void>;
274+
275+
/**
276+
* Removes the specified coupon from the subscription.
277+
*
278+
* @param {string} subscriptionUuid - The UUID of the subscription
279+
*
280+
* Docs - https://docs.salable.app/api/v2#tag/Subscriptions/operation/removeCoupon
281+
*
282+
* @returns {Promise<void>}
283+
*/
284+
removeCoupon: (
285+
subscriptionUuid: string,
286+
options: {
287+
couponUuid: string
288+
},
289+
) => Promise<void>;
258290
};
259291
};
260292

src/subscriptions/v2/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ export const v2SubscriptionMethods = (request: ApiRequest): SubscriptionVersions
2828
method: 'PUT',
2929
body: JSON.stringify(options),
3030
}),
31+
addCoupon: (uuid, options) => request(`${baseUrl}/${uuid}/coupons`, { method: 'POST', body: JSON.stringify(options) }),
32+
removeCoupon: (uuid, options) => request(`${baseUrl}/${uuid}/coupons`, { method: 'PUT', body: JSON.stringify(options) }),
3133
});

src/subscriptions/v2/subscriptions-v2.test.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const perSeatSubscriptionUuid = uuidv4();
1414
const licenseUuid = uuidv4();
1515
const licenseTwoUuid = uuidv4();
1616
const licenseThreeUuid = uuidv4();
17+
const couponUuid = uuidv4();
1718
const perSeatBasicLicenseUuids = [uuidv4(), uuidv4(), uuidv4(), uuidv4(), uuidv4(), uuidv4()];
1819
const testGrantee = '123456';
1920
const testEmail = 'tester@domain.com';
@@ -231,6 +232,18 @@ describe('Subscriptions V2 Tests', () => {
231232
expect(data).toEqual({ ...subscriptionSchema, owner: 'updated-owner' });
232233
});
233234

235+
it('addCoupon: Should successfully add the specified coupon to the subscription', async () => {
236+
const data = await salable.subscriptions.addCoupon(subscriptionUuid, { couponUuid });
237+
238+
expect(data).toBeUndefined();
239+
});
240+
241+
it('removeCoupon: Should successfully remove the specified coupon from the subscription', async () => {
242+
const data = await salable.subscriptions.removeCoupon(subscriptionUuid, { couponUuid });
243+
244+
expect(data).toBeUndefined();
245+
});
246+
234247
it('cancel: Should successfully cancel the subscription', async () => {
235248
const data = await salable.subscriptions.cancel(subscriptionUuid, { when: 'now' });
236249

@@ -340,8 +353,8 @@ const invoiceSchema: Invoice = {
340353
account_tax_ids: expect.toBeOneOf([expect.toBeArray(), null]),
341354
amount_due: expect.any(Number),
342355
amount_paid: expect.any(Number),
343-
amount_remaining: expect.any(Number),
344356
amount_overpaid: expect.any(Number),
357+
amount_remaining: expect.any(Number),
345358
amount_shipping: expect.any(Number),
346359
application: expect.toBeOneOf([expect.any(String), null]),
347360
application_fee_amount: expect.toBeOneOf([expect.any(Number), null]),
@@ -388,6 +401,7 @@ const invoiceSchema: Invoice = {
388401
on_behalf_of: expect.toBeOneOf([expect.any(String), null]),
389402
paid: expect.any(Boolean),
390403
paid_out_of_band: expect.any(Boolean),
404+
parent: expect.toBeObject(),
391405
payment_intent: expect.any(String),
392406
payment_settings: expect.toBeObject(),
393407
period_end: expect.any(Number),
@@ -410,7 +424,6 @@ const invoiceSchema: Invoice = {
410424
subtotal_excluding_tax: expect.any(Number),
411425
tax: expect.toBeOneOf([expect.any(Number), null]),
412426
test_clock: expect.toBeOneOf([expect.any(String), null]),
413-
parent: expect.toBeObject(),
414427
total: expect.any(Number),
415428
total_discount_amounts: expect.toBeOneOf([expect.toBeArray(), null]),
416429
total_excluding_tax: expect.any(Number),
@@ -476,6 +489,7 @@ const stripePaymentMethodSchema = {
476489

477490
const deleteTestData = async () => {
478491
await prismaClient.license.deleteMany({});
492+
await prismaClient.couponsOnSubscriptions.deleteMany({});
479493
await prismaClient.subscription.deleteMany({});
480494
};
481495

@@ -700,4 +714,32 @@ const generateTestData = async () => {
700714
quantity: 2,
701715
},
702716
});
717+
718+
await prismaClient.coupon.create({
719+
data: {
720+
uuid: couponUuid,
721+
paymentIntegrationCouponId: stripeEnvs.couponId,
722+
name: 'Percentage Coupon',
723+
duration: 'ONCE',
724+
discountType: 'PERCENTAGE',
725+
percentOff: 10,
726+
expiresAt: null,
727+
maxRedemptions: null,
728+
isTest: false,
729+
durationInMonths: 1,
730+
status: 'ACTIVE',
731+
product: {
732+
connect: {
733+
uuid: testUuids.productUuid,
734+
},
735+
},
736+
appliesTo: {
737+
create: {
738+
plan: {
739+
connect: { uuid: testUuids.paidPlanTwoUuid },
740+
},
741+
},
742+
},
743+
},
744+
});
703745
};

src/usage/v2/usage-v2.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const stripeEnvs = JSON.parse(process.env.stripEnvs || '');
1111
const meteredLicenseUuid = uuidv4();
1212
const usageSubscriptionUuid = uuidv4();
1313
const testGrantee = 'userId_metered';
14+
const owner = 'subscription-owner'
1415

1516
describe('Usage V2 Tests', () => {
1617
const salable = new Salable(testUuids.devApiKeyV2, version);
@@ -98,7 +99,6 @@ const generateTestData = async () => {
9899
paymentIntegrationSubscriptionId: stripeEnvs.usageBasicSubscriptionId,
99100
uuid: usageSubscriptionUuid,
100101
email: 'tester@testing.com',
101-
owner: 'owner_12345',
102102
type: 'salable',
103103
status: 'ACTIVE',
104104
organisation: testUuids.organisationId,
@@ -143,6 +143,7 @@ const generateTestData = async () => {
143143
endTime: new Date(),
144144
},
145145
},
146+
owner,
146147
product: { connect: { uuid: testUuids.productTwoUuid } },
147148
plan: { connect: { uuid: testUuids.usageBasicMonthlyPlanUuid } },
148149
createdAt: new Date(),

0 commit comments

Comments
 (0)