fix: redirect link (#15615)

#### PR Dependency Tree


* **PR #15615** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Checkout success links are now safely normalized before being sent to
the payment provider.
  - Relative callback paths are converted into valid absolute URLs.
- Empty or missing success callbacks now return customers to the default
application page.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-09-19 00:50:41 +08:00
committed by GitHub
parent 17a9c3c4c5
commit 368e62d895
2 changed files with 10 additions and 4 deletions
@@ -7,6 +7,7 @@ import {
SameSubscriptionRecurring,
SubscriptionAlreadyExists,
SubscriptionNotExists,
URLHelper,
} from '../../base';
import { BackendRuntimeProvider } from '../../core/backend-runtime';
import { ServerFeature, ServerService } from '../../core/config';
@@ -26,10 +27,12 @@ ava(
const runtime = Sinon.createStubInstance(BackendRuntimeProvider);
const command = runtime.executePaymentCommandV1 as Sinon.SinonStub;
const server = Sinon.createStubInstance(ServerService);
const url = Sinon.createStubInstance(URLHelper);
url.safeLink.callsFake(path => new URL(path, 'https://app.example').href);
const config = {
payment: { enabled: false, showLifetimePrice: false },
} as Config;
const service = new SubscriptionService(runtime, config, server);
const service = new SubscriptionService(runtime, config, server, url);
service.onConfigInit();
t.true(server.disableFeature.calledWith(ServerFeature.Payment));
@@ -95,7 +98,7 @@ ava(
{
plan: SubscriptionPlan.Pro,
recurring: SubscriptionRecurring.Monthly,
successCallbackLink: 'https://app.example/success',
successCallbackLink: '/success',
idempotencyKey: 'intent-1',
},
{ user: { id: 'user-1', email: 'user@example.com' } }
@@ -107,6 +110,7 @@ ava(
actorUserId: 'user-1',
targetType: 'user',
targetId: 'user-1',
successUrl: 'https://app.example/success',
intentId: 'intent-1',
});
@@ -18,6 +18,7 @@ import {
SubscriptionHasNotBeenCanceled,
SubscriptionNotExists,
SubscriptionPlanNotFound,
URLHelper,
UserNotFound,
} from '../../base';
import { CurrentUser } from '../../core/auth';
@@ -93,7 +94,8 @@ export class SubscriptionService {
constructor(
private readonly runtime: BackendRuntimeProvider,
private readonly config: Config,
private readonly server: ServerService
private readonly server: ServerService,
private readonly url: URLHelper
) {}
@OnEvent('config.init')
@@ -162,7 +164,7 @@ export class SubscriptionService {
variant: params.variant,
coupon: params.coupon,
quantity: args.quantity ?? params.quantity ?? undefined,
successUrl: params.successCallbackLink,
successUrl: this.url.safeLink(params.successCallbackLink || '/'),
intentId: params.idempotencyKey ?? randomUUID(),
});
return { id: result.sessionId, url: result.url };