1use anchor_lang::prelude::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
8#[repr(u8)]
9pub enum MerchantTier {
10 Free = 0,
12 Pro = 1,
14 Enterprise = 2,
16}
17
18impl MerchantTier {
19 #[must_use]
21 pub const fn fee_bps(self) -> u16 {
22 match self {
23 Self::Free => 200, Self::Pro => 150, Self::Enterprise => 100, }
27 }
28
29 #[must_use]
31 pub const fn from_discriminant(value: u8) -> Option<Self> {
32 match value {
33 0 => Some(Self::Free),
34 1 => Some(Self::Pro),
35 2 => Some(Self::Enterprise),
36 _ => None,
37 }
38 }
39}
40
41#[derive(
44 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
45)]
46pub struct Merchant {
47 pub authority: Pubkey,
49 pub usdc_mint: Pubkey,
51 pub treasury_ata: Pubkey,
53 pub platform_fee_bps: u16,
55 pub tier: u8,
57 pub bump: u8,
59}
60
61#[derive(
64 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
65)]
66pub struct Plan {
67 pub merchant: Pubkey,
69 pub plan_id: [u8; 32],
71 pub price_usdc: u64,
73 pub period_secs: u64,
75 pub grace_secs: u64,
77 pub name: [u8; 32],
79 pub active: bool,
81}
82
83#[derive(
86 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
87)]
88pub struct Subscription {
89 pub plan: Pubkey,
91 pub subscriber: Pubkey,
93 pub next_renewal_ts: i64,
95 pub active: bool,
97 pub renewals: u32,
104 pub created_ts: i64,
106 pub last_amount: u64,
108 pub last_renewed_ts: i64,
110 pub trial_ends_at: Option<i64>,
116 pub in_trial: bool,
121 pub bump: u8,
123}
124
125#[derive(
127 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
128)]
129pub struct InitMerchantArgs {
130 pub usdc_mint: Pubkey,
132 pub treasury_ata: Pubkey,
134 pub platform_fee_bps: u16,
136}
137
138#[derive(
140 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
141)]
142pub struct CreatePlanArgs {
143 pub plan_id: String,
145 pub plan_id_bytes: [u8; 32],
147 pub price_usdc: u64,
149 pub period_secs: u64,
151 pub grace_secs: u64,
153 pub name: String,
155}
156
157#[derive(
159 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
160)]
161pub struct StartSubscriptionArgs {
162 pub allowance_periods: u8,
164}
165
166#[derive(
168 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
169)]
170pub struct RenewSubscriptionArgs {
171 }
173
174#[derive(
176 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
177)]
178pub struct UpdatePlanArgs {
179 pub name: Option<String>,
181 pub active: Option<bool>,
183 pub price_usdc: Option<u64>,
185 pub period_secs: Option<u64>,
187 pub grace_secs: Option<u64>,
189}
190
191#[derive(
193 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
194)]
195pub struct CancelSubscriptionArgs;
196
197#[derive(
199 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
200)]
201pub struct AdminWithdrawFeesArgs {
202 pub amount: u64,
204}
205
206#[derive(
209 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
210)]
211pub struct Config {
212 pub platform_authority: Pubkey,
214 pub pending_authority: Option<Pubkey>,
216 pub max_platform_fee_bps: u16,
218 pub min_platform_fee_bps: u16,
220 pub min_period_seconds: u64,
222 pub default_allowance_periods: u8,
224 pub allowed_mint: Pubkey,
227 pub max_withdrawal_amount: u64,
230 pub max_grace_period_seconds: u64,
233 pub paused: bool,
236 pub keeper_fee_bps: u16,
240 pub bump: u8,
242}
243
244#[derive(
246 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
247)]
248pub struct InitConfigArgs {
249 pub platform_authority: Pubkey,
251 pub max_platform_fee_bps: u16,
253 pub fee_basis_points_divisor: u16,
255 pub min_period_seconds: u64,
257 pub default_allowance_periods: u8,
259}
260
261impl Plan {
262 #[must_use]
264 pub fn plan_id_str(&self) -> String {
265 String::from_utf8_lossy(&self.plan_id)
266 .trim_end_matches('\0')
267 .to_string()
268 }
269
270 #[must_use]
272 pub fn name_str(&self) -> String {
273 String::from_utf8_lossy(&self.name)
274 .trim_end_matches('\0')
275 .to_string()
276 }
277
278 #[must_use]
282 pub fn plan_id_string(&self) -> String {
283 self.plan_id_str()
284 }
285
286 #[must_use]
288 pub fn name_string(&self) -> String {
289 self.name_str()
290 }
291
292 #[must_use]
294 #[allow(clippy::cast_precision_loss)]
295 pub fn price_usdc_formatted(&self) -> f64 {
296 self.price_usdc as f64 / 1_000_000.0
297 }
298
299 #[must_use]
301 pub fn period_formatted(&self) -> String {
302 let days = self.period_secs / 86400;
303 if days == 1 {
304 "1 day".to_string()
305 } else if days == 7 {
306 "1 week".to_string()
307 } else if days == 30 {
308 "1 month".to_string()
309 } else if days == 365 {
310 "1 year".to_string()
311 } else {
312 format!("{days} days")
313 }
314 }
315}
316
317impl CreatePlanArgs {
318 #[must_use]
320 pub fn plan_id_bytes(&self) -> [u8; 32] {
321 let mut bytes = [0u8; 32];
322 let id_bytes = self.plan_id.as_bytes();
323 let len = id_bytes.len().min(32);
324 bytes[..len].copy_from_slice(&id_bytes[..len]);
325 bytes
326 }
327
328 #[must_use]
330 pub fn name_bytes(&self) -> [u8; 32] {
331 let mut bytes = [0u8; 32];
332 let name_bytes = self.name.as_bytes();
333 let len = name_bytes.len().min(32);
334 bytes[..len].copy_from_slice(&name_bytes[..len]);
335 bytes
336 }
337}
338
339impl UpdatePlanArgs {
340 #[must_use]
342 pub const fn new() -> Self {
343 Self {
344 name: None,
345 active: None,
346 price_usdc: None,
347 period_secs: None,
348 grace_secs: None,
349 }
350 }
351
352 #[must_use]
354 pub fn with_name(mut self, name: String) -> Self {
355 self.name = Some(name);
356 self
357 }
358
359 #[must_use]
361 pub const fn with_active(mut self, active: bool) -> Self {
362 self.active = Some(active);
363 self
364 }
365
366 #[must_use]
368 pub const fn with_price_usdc(mut self, price_usdc: u64) -> Self {
369 self.price_usdc = Some(price_usdc);
370 self
371 }
372
373 #[must_use]
375 pub const fn with_period_secs(mut self, period_secs: u64) -> Self {
376 self.period_secs = Some(period_secs);
377 self
378 }
379
380 #[must_use]
382 pub const fn with_grace_secs(mut self, grace_secs: u64) -> Self {
383 self.grace_secs = Some(grace_secs);
384 self
385 }
386
387 #[must_use]
389 pub const fn has_updates(&self) -> bool {
390 self.name.is_some()
391 || self.active.is_some()
392 || self.price_usdc.is_some()
393 || self.period_secs.is_some()
394 || self.grace_secs.is_some()
395 }
396
397 #[must_use]
399 pub fn name_bytes(&self) -> Option<[u8; 32]> {
400 self.name.as_ref().map(|name| {
401 let mut bytes = [0u8; 32];
402 let name_bytes = name.as_bytes();
403 let len = name_bytes.len().min(32);
404 bytes[..len].copy_from_slice(&name_bytes[..len]);
405 bytes
406 })
407 }
408}
409
410impl Default for UpdatePlanArgs {
411 fn default() -> Self {
412 Self::new()
413 }
414}
415
416#[derive(
418 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
419)]
420pub struct CloseSubscriptionArgs {
421 }
423
424#[derive(
426 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
427)]
428pub struct TransferAuthorityArgs {
429 pub new_authority: Pubkey,
431}
432
433#[derive(
435 Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
436)]
437pub struct AcceptAuthorityArgs {
438 }
440
441#[derive(
443 Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
444)]
445pub struct CancelAuthorityTransferArgs {
446 }
448
449#[derive(
451 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
452)]
453pub struct PauseArgs {}
454
455#[derive(
457 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
458)]
459pub struct UnpauseArgs {}
460
461#[derive(
463 Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
464)]
465pub struct UpdateConfigArgs {
466 pub keeper_fee_bps: Option<u16>,
468 pub max_withdrawal_amount: Option<u64>,
470 pub max_grace_period_seconds: Option<u64>,
472 pub min_platform_fee_bps: Option<u16>,
474 pub max_platform_fee_bps: Option<u16>,
476 pub min_period_seconds: Option<u64>,
478 pub default_allowance_periods: Option<u8>,
480}
481
482#[derive(
484 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
485)]
486pub struct UpdateMerchantTierArgs {
487 pub new_tier: u8,
489}
490
491#[derive(
496 Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize,
497)]
498pub struct UpdatePlanTermsArgs {
499 pub price_usdc: Option<u64>,
502 pub period_secs: Option<u64>,
505 pub grace_secs: Option<u64>,
508 pub name: Option<String>,
511}
512
513impl UpdatePlanTermsArgs {
514 #[must_use]
516 pub const fn new() -> Self {
517 Self {
518 price_usdc: None,
519 period_secs: None,
520 grace_secs: None,
521 name: None,
522 }
523 }
524
525 #[must_use]
527 pub const fn with_price_usdc(mut self, price_usdc: u64) -> Self {
528 self.price_usdc = Some(price_usdc);
529 self
530 }
531
532 #[must_use]
534 pub const fn with_period_secs(mut self, period_secs: u64) -> Self {
535 self.period_secs = Some(period_secs);
536 self
537 }
538
539 #[must_use]
541 pub const fn with_grace_secs(mut self, grace_secs: u64) -> Self {
542 self.grace_secs = Some(grace_secs);
543 self
544 }
545
546 #[must_use]
548 pub fn with_name(mut self, name: String) -> Self {
549 self.name = Some(name);
550 self
551 }
552
553 #[must_use]
555 pub const fn has_updates(&self) -> bool {
556 self.price_usdc.is_some()
557 || self.period_secs.is_some()
558 || self.grace_secs.is_some()
559 || self.name.is_some()
560 }
561
562 #[must_use]
564 pub fn name_bytes(&self) -> Option<[u8; 32]> {
565 self.name.as_ref().map(|name| {
566 let mut bytes = [0u8; 32];
567 let name_bytes = name.as_bytes();
568 let len = name_bytes.len().min(32);
569 bytes[..len].copy_from_slice(&name_bytes[..len]);
570 bytes
571 })
572 }
573}