1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Entitlement operations for DiscordUser.
//!
//! Endpoints under `/applications/{application.id}/entitlements` plus the
//! one-shot consume endpoint.
use std::borrow::Cow;
use crate::{
context::DiscordContext,
error::Result,
route::Route,
types::{monetization::Entitlement, requests::CreateTestEntitlementRequest},
};
impl<T: DiscordContext + Send + Sync> EntitlementOps for T {}
/// Optional query parameters for [`EntitlementOps::list_entitlements`].
///
/// Mirrors the documented filter set on
/// `GET /applications/{application.id}/entitlements`.
#[derive(Debug, Default, Clone)]
pub struct EntitlementListQuery {
/// Filter to a specific user's entitlements.
pub user_id: Option<u64>,
/// Comma-separated list of SKU snowflake IDs to filter by.
pub sku_ids: Option<String>,
/// Return entitlements before this snowflake (newer-than cursor).
pub before: Option<u64>,
/// Return entitlements after this snowflake (older-than cursor).
pub after: Option<u64>,
/// Page size (1-100, default 100).
pub limit: Option<u32>,
/// Filter to a specific guild's entitlements.
pub guild_id: Option<u64>,
/// When `true`, omits entitlements whose subscription period has ended.
pub exclude_ended: Option<bool>,
/// When `true`, omits entitlements that have been deleted.
pub exclude_deleted: Option<bool>,
}
/// Extension trait providing entitlement CRUD plus the test-mode helpers
/// (`create_test_entitlement` / `delete_test_entitlement`) and the
/// consumable-entitlement consume endpoint.
#[allow(async_fn_in_trait)]
pub trait EntitlementOps: DiscordContext {
/// List entitlements for an application, optionally filtered.
///
/// Targets `GET /applications/{application.id}/entitlements`.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn list_entitlements(&self, application_id: u64, query: Option<EntitlementListQuery>) -> Result<Vec<Entitlement>> {
let q = query.unwrap_or_default();
self.http()
.get(Route::ApplicationEntitlements {
application_id,
user_id: q.user_id,
sku_ids: q.sku_ids.map(Cow::Owned),
before: q.before,
after: q.after,
limit: q.limit,
guild_id: q.guild_id,
exclude_ended: q.exclude_ended,
exclude_deleted: q.exclude_deleted,
})
.await
}
/// Fetch a single entitlement by ID.
///
/// Targets `GET /applications/{application.id}/entitlements/{entitlement.id}`.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure or if the entitlement is
/// not found.
async fn get_entitlement(&self, application_id: u64, entitlement_id: u64) -> Result<Entitlement> {
self.http().get(Route::ApplicationEntitlement { application_id, entitlement_id }).await
}
/// Create a test-mode entitlement that grants access without an actual
/// purchase. Useful during development.
///
/// Targets `POST /applications/{application.id}/entitlements`.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn create_test_entitlement(&self, application_id: u64, body: CreateTestEntitlementRequest) -> Result<Entitlement> {
self.http()
.post(
Route::ApplicationEntitlements {
application_id,
user_id: None,
sku_ids: None,
before: None,
after: None,
limit: None,
guild_id: None,
exclude_ended: None,
exclude_deleted: None,
},
body,
)
.await
}
/// Delete a test-mode entitlement created via
/// [`create_test_entitlement`](Self::create_test_entitlement).
///
/// Targets `DELETE /applications/{application.id}/entitlements/{entitlement.id}`.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn delete_test_entitlement(&self, application_id: u64, entitlement_id: u64) -> Result<()> {
self.http().delete(Route::ApplicationEntitlement { application_id, entitlement_id }).await
}
/// Mark a one-time-purchase (consumable) entitlement as consumed.
///
/// Targets `POST /applications/{application.id}/entitlements/{entitlement.id}/consume`.
/// Discord returns 204 No Content on success.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn consume_entitlement(&self, application_id: u64, entitlement_id: u64) -> Result<()> {
self.http()
.post_no_response(Route::ConsumeEntitlement { application_id, entitlement_id }, serde_json::Value::Null)
.await
}
}