feat(structures): add Subscription structure (#11399)

* feat(structure): update barrel exports for new structure

* chore(structure): add new symbols for the Subscription structure

* feat(structure): add Subscription structure

* docs(structure): correct typos

* chore(structure): add default attributes on class params

* fix(structures): correctly expose [..]Ids properties and update docs

* fix(structures): add canceled_at to DataTemplate

* docs(structures): update doc clarity on canceledAt getter - @almeidx

This was a suggestion by Almedia.

Co-authored-by: Almeida <github@almeidx.dev>

* style: fix

---------

Co-authored-by: Almeida <github@almeidx.dev>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Asad
2026-01-27 22:56:06 +00:00
committed by GitHub
parent 7a7fecbe3c
commit fc5ba6be70
4 changed files with 183 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ export * from './polls/index.js';
export * from './stickers/index.js'; export * from './stickers/index.js';
export * from './users/index.js'; export * from './users/index.js';
export * from './Structure.js'; export * from './Structure.js';
export * from './subscriptions/index.js';
export * from './Mixin.js'; export * from './Mixin.js';
export * from './utils/optimization.js'; export * from './utils/optimization.js';
export type * from './utils/types.js'; export type * from './utils/types.js';

View File

@@ -0,0 +1,177 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import type { APISubscription, SubscriptionStatus } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import {
kData,
kCurrentPeriodStartTimestamp,
kCurrentPeriodEndTimestamp,
kCanceledTimestamp,
} from '../utils/symbols.js';
import { isIdSet } from '../utils/type-guards.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents any subscription on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
*/
export class Subscription<
Omitted extends keyof APISubscription | '' = 'canceled_at' | 'current_period_end' | 'current_period_start',
> extends Structure<APISubscription, Omitted> {
/**
* The template used for removing data from the raw data stored for each subscription
*/
public static override readonly DataTemplate: Partial<APISubscription> = {
set current_period_start(_: string) {},
set current_period_end(_: string) {},
set canceled_at(_: string) {},
};
protected [kCurrentPeriodStartTimestamp]: number | null = null;
protected [kCurrentPeriodEndTimestamp]: number | null = null;
protected [kCanceledTimestamp]: number | null = null;
/**
* @param data - The raw data received from the API for the subscription
*/
public constructor(data: Partialize<APISubscription, Omitted>) {
super(data);
this.optimizeData(data);
}
/**
* {@inheritDoc Structure.optimizeData}
*/
protected override optimizeData(data: Partial<APISubscription>) {
const currentPeriodStartTimestamp = data.current_period_start;
const currentPeriodEndTimestamp = data.current_period_end;
const canceledTimestamp = data.canceled_at;
if (currentPeriodStartTimestamp) {
this[kCurrentPeriodStartTimestamp] = Date.parse(currentPeriodStartTimestamp);
}
if (currentPeriodEndTimestamp) {
this[kCurrentPeriodEndTimestamp] = Date.parse(currentPeriodEndTimestamp);
}
if (canceledTimestamp) {
this[kCanceledTimestamp] = Date.parse(canceledTimestamp);
}
}
/**
* The subscription's id
*
* @remarks The start of a subscription is determined by its id. When the subscription renews, its current period is updated.
*/
public get id() {
return this[kData].id;
}
/**
* Id of the user who is subscribed
*/
public get userId() {
return this[kData].user_id;
}
/**
* List of SKUs subscribed to
*/
public get skuIds() {
return this[kData].sku_ids;
}
/**
* List of entitlements granted for this subscription
*/
public get entitlementIds() {
return this[kData].entitlement_ids;
}
/**
* List of SKUs that this user will be subscribed to at renewal
*/
public get renewalSkuIds() {
return this[kData].renewal_sku_ids;
}
/**
* Timestamp of start of the current subscription period
*/
public get currentPeriodStartTimestamp() {
return this[kCurrentPeriodStartTimestamp];
}
/**
* The time at which the current subscription period will start
*/
public get currentPeriodStartAt() {
const startTimestamp = this.currentPeriodStartTimestamp;
return startTimestamp ? new Date(startTimestamp) : null;
}
/**
* Timestamp of end of the current subscription period
*/
public get currentPeriodEndTimestamp() {
return this[kCurrentPeriodEndTimestamp];
}
/**
* The time at which the current subscription period will end
*/
public get currentPeriodEndsAt() {
const endTimestamp = this.currentPeriodEndTimestamp;
return endTimestamp ? new Date(endTimestamp) : null;
}
/**
* The {@link SubscriptionStatus} of the current subscription
*/
public get status() {
return this[kData].status;
}
/**
* Timestamp when the subscription was canceled
*/
public get canceledTimestamp() {
return this[kCanceledTimestamp];
}
/**
* The time when the subscription was canceled
*
* @remarks This is populated when the {@link Subscription#status} transitions to {@link SubscriptionStatus.Ending}.
*/
public get canceledAt() {
const canceledTimestamp = this.canceledTimestamp;
return canceledTimestamp ? new Date(canceledTimestamp) : null;
}
/**
* ISO3166-1 alpha-2 country code of the payment source used to purchase the subscription. Missing unless queried with a private OAuth scope.
*/
public get country() {
return this[kData].country;
}
/**
* The timestamp the subscription was created at
*/
public get createdTimestamp() {
return isIdSet(this.id) ? DiscordSnowflake.timestampFrom(this.id) : null;
}
/**
* The time the subscription was created at
*/
public get createdAt() {
const createdTimestamp = this.createdTimestamp;
return createdTimestamp ? new Date(createdTimestamp) : null;
}
}

View File

@@ -0,0 +1 @@
export * from './Subscription.js';

View File

@@ -10,6 +10,10 @@ export const kArchiveTimestamp = Symbol.for('djs.structures.archiveTimestamp');
export const kStartsTimestamp = Symbol.for('djs.structures.startsTimestamp'); export const kStartsTimestamp = Symbol.for('djs.structures.startsTimestamp');
export const kEndsTimestamp = Symbol.for('djs.structures.endsTimestamp'); export const kEndsTimestamp = Symbol.for('djs.structures.endsTimestamp');
export const kCurrentPeriodStartTimestamp = Symbol.for('djs.structures.currentPeriodStartTimestamp');
export const kCurrentPeriodEndTimestamp = Symbol.for('djs.structures.currentPeriodEndTimestamp');
export const kCanceledTimestamp = Symbol.for('djs.structures.canceledTimestamp');
export const kAllow = Symbol.for('djs.structures.allow'); export const kAllow = Symbol.for('djs.structures.allow');
export const kDeny = Symbol.for('djs.structures.deny'); export const kDeny = Symbol.for('djs.structures.deny');