Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

A subscriber resumes a cancelled subscription

Source: subscriptions/tests/test_resume_subscription.rs L128

  • When: the user initializes a subscription authority
  • When: the merchant creates plan 1
  • When: the subscriber subscribes
  • When: the subscriber cancels
  • Then: the cancelled subscription has an expiry ✓
  • When: the subscriber resumes
  • Then: the expiry is cleared ✓
  • Then: period start preserved ✓
  • Then: amount pulled preserved ✓

Result: ✓ success — 1926 CU · claims 4/4 ✓

sequenceDiagram
    participant p0 as alice
    participant p1 as Subscriptions
    p0->>p1: resumeSubscription
    activate p1
    p1->>p1: emit:subscriptionResumedEvent
    activate p1
    p1-->>p1: ✓ 137cu
    deactivate p1
    p1-->>p0: ✓ 1926cu
    deactivate p1

Authority

flowchart LR
    Subscriptions["Subscriptions"]:::program
    alice(["alice"]):::signer
    subscriptionplanmerchant1alice[("subscription(plan(merchant, 1), alice)")]:::state
    eventAuthority(["eventAuthority"]):::signer
    alice -->|signs| Subscriptions
    Subscriptions -->|writes| subscriptionplanmerchant1alice
    eventAuthority -->|signs| Subscriptions
    classDef program fill:#dae8fc,stroke:#6c8ebf;
    classDef signer fill:#d5e8d4,stroke:#82b366;
    classDef state fill:#ffe6cc,stroke:#d79b00;

Ownership

flowchart LR
    system["system"]:::program
    alice[("alice")]:::state
    Subscriptions["Subscriptions"]:::program
    subscriptionplanmerchant1alice[("subscription(plan(merchant, 1), alice)")]:::state
    system -->|owns| alice
    Subscriptions -->|owns| subscriptionplanmerchant1alice
    classDef program fill:#dae8fc,stroke:#6c8ebf;
    classDef signer fill:#d5e8d4,stroke:#82b366;
    classDef state fill:#ffe6cc,stroke:#d79b00;

Accounts

AccountSignerWritableOwner
alicesystem
plan(merchant, 1)··Subscriptions
subscription(plan(merchant, 1), alice)·Subscriptions
SA(alice, Mint)··Subscriptions
eventAuthority··system
Subscriptions··BPFL…
Call tree and logs
A subscriber resumes a cancelled subscription
alice (1926cu)
└─ Subscriptions::resumeSubscription ✓ 1926cu
   └─ Subscriptions::emit:subscriptionResumedEvent ✓ 137cu
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 invoke [1]
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 invoke [2]
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 consumed 137 of 198234 compute units
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 success
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 consumed 1926 of 200000 compute units
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 success

Integration

What an integrator writes: the derivations and the instruction, byte-identical to the transaction this page witnessed. Addresses are this scenario’s; supply your own.

#![allow(unused)]
fn main() {
use solana_instruction::{AccountMeta, Instruction};
use solana_pubkey::{pubkey, Pubkey};

// --- resumeSubscription ---
let program_id: Pubkey = pubkey!("De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44");
let subscriber: Pubkey = pubkey!("2rj1EEgg32bupe12mMYnPFaKQCPPyYBhKYmDFZ8cXxpT"); // alice: this scenario's value; supply your own
let plan_pda: Pubkey = pubkey!("2PQuEytmHE7C2a9ynpmsRfYK7UgXNtCqSR4qnNNgeZwp"); // plan(merchant, 1): this scenario's value; supply your own
let subscription_pda: Pubkey = pubkey!("6pQDdNgFksFRPMJQFvuiMheurG16kVi12So7UutPzWjH"); // subscription(plan(merchant, 1), alice): this scenario's value; supply your own
let subscription_authority: Pubkey = pubkey!("BXjfuP23wCLiPPwdhYow6hVtETMW4gzosqTbJarvxN8U"); // SA(alice, Mint): this scenario's value; supply your own
let event_authority = Pubkey::find_program_address(&[b"event_authority"], &program_id).0;
let ix = Instruction {
    program_id,
    accounts: vec![
        AccountMeta::new_readonly(subscriber, true), // subscriber
        AccountMeta::new_readonly(plan_pda, false), // planPda
        AccountMeta::new(subscription_pda, false), // subscriptionPda
        AccountMeta::new_readonly(subscription_authority, false), // subscriptionAuthority
        AccountMeta::new_readonly(event_authority, false), // eventAuthority
        AccountMeta::new_readonly(program_id, false), // selfProgram
    ],
    // discriminator ++ borsh()
    data: vec![0x0d],
};
}

The same call through the Codama-generated TypeScript client (the async builder derives every PDA the resolver derived here, so only the free inputs appear):

import { address } from "@solana/kit";
import { getResumeSubscriptionInstructionAsync } from "your-generated-client";

const ix = await getResumeSubscriptionInstructionAsync({
  subscriber: subscriberSigner, // alice (a TransactionSigner)
  planPda: address("2PQuEytmHE7C2a9ynpmsRfYK7UgXNtCqSR4qnNNgeZwp"), // plan(merchant, 1)
  subscriptionPda: address("6pQDdNgFksFRPMJQFvuiMheurG16kVi12So7UutPzWjH"), // subscription(plan(merchant, 1), alice)
  subscriptionAuthority: address("BXjfuP23wCLiPPwdhYow6hVtETMW4gzosqTbJarvxN8U"), // SA(alice, Mint)
});
How this page verifies itself: the test that ran it

The narrative above is this test; the page and the code cannot drift.

#![allow(unused)]
fn main() {
#[test]
fn resume_subscription_happy_path() {
    let mut story = Story::load(SO, IDL);
    let s = story.stage_subscription();

    story
        .cancel_subscription(&s.alice, s.plan_pda, s.subscription_pda)
        .expect_success();
    let period_start = field_i64(&story, &s.subscription_pda, "currentPeriodStartTs");
    let amount_pulled = field_u64(&story, &s.subscription_pda, "amountPulledInPeriod");
    let expires_at = field_i64(&story, &s.subscription_pda, "expiresAtTs");
    story.then("the cancelled subscription has an expiry", move |_| {
        expires_at != 0
    });

    let outcome = story
        .resume_subscription(&s.alice, s.plan_pda, s.subscription_pda, s.mint)
        .expect_success();

    let expires_after = field_i64(&story, &s.subscription_pda, "expiresAtTs");
    story.then("the expiry is cleared", move |_| expires_after == 0);
    let period_start_after = field_i64(&story, &s.subscription_pda, "currentPeriodStartTs");
    story.then("period start preserved", move |_| {
        period_start_after == period_start
    });
    let amount_pulled_after = field_u64(&story, &s.subscription_pda, "amountPulledInPeriod");
    story.then("amount pulled preserved", move |_| {
        amount_pulled_after == amount_pulled
    });
    emit_scenario(
        &story,
        &outcome,
        "A subscriber resumes a cancelled subscription",
    );
}
}