mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 01:26:37 +08:00
132 lines
3.3 KiB
TypeScript
132 lines
3.3 KiB
TypeScript
import { FactoryProvider, Injectable, Logger } from '@nestjs/common';
|
|
import Stripe from 'stripe';
|
|
|
|
import { Config, Mutex, OnEvent } from '../../base';
|
|
import { ServerFeature, ServerService } from '../../core';
|
|
import {
|
|
decodeLookupKey,
|
|
DEFAULT_PRICES,
|
|
SubscriptionRecurring,
|
|
} from './types';
|
|
|
|
@Injectable()
|
|
export class StripeFactory {
|
|
#stripe!: Stripe;
|
|
readonly #logger = new Logger(StripeFactory.name);
|
|
|
|
constructor(
|
|
private readonly config: Config,
|
|
private readonly mutex: Mutex,
|
|
private readonly server: ServerService
|
|
) {}
|
|
|
|
get stripe() {
|
|
return this.#stripe;
|
|
}
|
|
|
|
@OnEvent('config.init')
|
|
async onConfigInit() {
|
|
this.setup();
|
|
await this.initStripeProducts();
|
|
}
|
|
|
|
@OnEvent('config.changed')
|
|
async onConfigChanged(event: Events['config.changed']) {
|
|
if ('payment' in event.updates) {
|
|
this.setup();
|
|
}
|
|
}
|
|
|
|
setup() {
|
|
const {
|
|
apiKey,
|
|
webhookKey: _,
|
|
...config
|
|
} = this.config.payment.stripe || {};
|
|
// NOTE:
|
|
// we always fake a key if not set because `new Stripe` will complain if it's empty string
|
|
// this will make code cleaner than providing `Stripe` instance as optional one.
|
|
const stripeApiKey = apiKey || 'stripe-api-key';
|
|
|
|
// TODO@(@darkskygit): use per-requests api key injection
|
|
this.#stripe = new Stripe(stripeApiKey, config);
|
|
if (this.config.payment.enabled) {
|
|
this.server.enableFeature(ServerFeature.Payment);
|
|
} else {
|
|
this.server.disableFeature(ServerFeature.Payment);
|
|
}
|
|
}
|
|
|
|
private async initStripeProducts() {
|
|
// only init stripe products in dev mode or canary deployment
|
|
if (!this.config.payment.enabled && !env.namespaces.canary) {
|
|
return;
|
|
}
|
|
|
|
await using lock = await this.mutex.acquire('init stripe prices');
|
|
|
|
if (!lock) {
|
|
return;
|
|
}
|
|
|
|
const keys = new Set<string>();
|
|
try {
|
|
await this.stripe.prices
|
|
.list({
|
|
active: true,
|
|
limit: 100,
|
|
})
|
|
.autoPagingEach(item => {
|
|
if (item.lookup_key) {
|
|
keys.add(item.lookup_key);
|
|
}
|
|
});
|
|
} catch {
|
|
this.#logger.warn('Failed to list stripe prices, skip auto init.');
|
|
return;
|
|
}
|
|
|
|
for (const [key, setting] of DEFAULT_PRICES) {
|
|
if (keys.has(key)) {
|
|
continue;
|
|
}
|
|
|
|
const lookupKey = decodeLookupKey(key);
|
|
|
|
try {
|
|
await this.stripe.prices.create({
|
|
product_data: {
|
|
name: setting.product,
|
|
},
|
|
billing_scheme: 'per_unit',
|
|
unit_amount: setting.price,
|
|
currency: 'usd',
|
|
lookup_key: key,
|
|
tax_behavior: 'inclusive',
|
|
recurring:
|
|
lookupKey.recurring === SubscriptionRecurring.Lifetime
|
|
? undefined
|
|
: {
|
|
interval:
|
|
lookupKey.recurring === SubscriptionRecurring.Monthly
|
|
? 'month'
|
|
: 'year',
|
|
interval_count: 1,
|
|
usage_type: 'licensed',
|
|
},
|
|
});
|
|
} catch (e) {
|
|
this.#logger.error('Failed to create stripe price.', e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export const StripeProvider: FactoryProvider = {
|
|
provide: Stripe,
|
|
useFactory: (provider: StripeFactory) => {
|
|
return provider.stripe;
|
|
},
|
|
inject: [StripeFactory],
|
|
};
|