Skip to content

Commit 93ee70e

Browse files
committed
feat: add trigger-renewals testnet endpoint for subscription testing
Allows manually running process_renewals job to test past_due and cancel-at-period-end logic without waiting for the hourly cron. Testnet-only, requires authentication.
1 parent 36fa156 commit 93ee70e

3 files changed

Lines changed: 47 additions & 1 deletion

File tree

ROADMAP.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ Privacy-preserving Zcash payment gateway. Non-custodial, shielded-only.
9898
- [ ] **Embeddable widget** (JS snippet for any website)
9999
- `<script src="https://pay.cipherpay.app/widget.js">`
100100
- Drop-in payment button with modal checkout
101-
- [ ] Email notifications (payment confirmations, invoice reminders, subscription renewals — privacy-conscious: no PII in body)
102101
- [ ] **Blog** (`/blog`) — product updates, integration guides, campaign spotlights, privacy commentary. MDX-based, built into Next.js app.
103102
- [ ] **Developer changelog** (`/changelog`) — running log of features, fixes, and updates. Signals active development.
104103
- [ ] **Use cases page** (`/use-cases`) — e-commerce, donations, ticketing, AI agents, subscriptions. Conversion page for new visitors.

src/api/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
115115
"/subscriptions/{id}/simulate-period-end",
116116
web::post().to(subscriptions::simulate_period_end),
117117
)
118+
.route(
119+
"/subscriptions/trigger-renewals",
120+
web::post().to(subscriptions::trigger_renewals),
121+
)
118122
// Payment links (merchant auth)
119123
.route("/payment-links", web::post().to(payment_links::create))
120124
.route("/payment-links", web::get().to(payment_links::list))

src/api/subscriptions.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,46 @@ pub async fn simulate_period_end(
210210
"hint": "Use with_payment: true to simulate a confirmed renewal payment"
211211
}))
212212
}
213+
214+
/// Testnet-only: trigger the process_renewals job manually
215+
/// POST /api/subscriptions/trigger-renewals
216+
pub async fn trigger_renewals(
217+
req: HttpRequest,
218+
pool: web::Data<SqlitePool>,
219+
config: web::Data<Config>,
220+
) -> HttpResponse {
221+
if !config.is_testnet() {
222+
return HttpResponse::Forbidden().json(serde_json::json!({
223+
"error": "Simulation endpoints are only available on testnet"
224+
}));
225+
}
226+
227+
// Require authentication (any merchant)
228+
if super::auth::require_merchant_or_session(&req, pool.get_ref()).await.is_err() {
229+
return HttpResponse::Unauthorized().json(serde_json::json!({
230+
"error": "Authentication required"
231+
}));
232+
}
233+
234+
let http = reqwest::Client::new();
235+
// Empty UFVKs map — renewal invoice creation will skip address generation
236+
// but past_due and cancel logic will still work for testing
237+
let empty_ufvks = std::collections::HashMap::new();
238+
match subscriptions::process_renewals(
239+
pool.get_ref(),
240+
&http,
241+
&config.encryption_key,
242+
&empty_ufvks,
243+
None,
244+
)
245+
.await
246+
{
247+
Ok(actions) => HttpResponse::Ok().json(serde_json::json!({
248+
"message": "process_renewals completed",
249+
"actions": actions,
250+
})),
251+
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
252+
"error": e.to_string()
253+
})),
254+
}
255+
}

0 commit comments

Comments
 (0)