Files
lehuo/apps/api/src/content-notifications/wechat-subscription.adapter.ts
T
2026-08-12 14:35:12 +08:00

65 lines
2.5 KiB
TypeScript

import { Inject, Injectable } from '@nestjs/common';
import type { NotificationType } from '@lehuo/contracts';
import { AppConfigService } from '../config/app-config.service';
export interface SubscriptionMessage {
openid: string;
type: NotificationType;
title: string;
body: string;
resourcePublicId: string | null;
}
export interface SubscriptionSendResult {
providerMessageId: string | null;
}
export abstract class WechatSubscriptionAdapter {
abstract get configured(): boolean;
abstract send(message: SubscriptionMessage): Promise<SubscriptionSendResult>;
}
@Injectable()
export class HttpWechatSubscriptionAdapter extends WechatSubscriptionAdapter {
constructor(@Inject(AppConfigService) private readonly config: AppConfigService) {
super();
}
get configured() {
return this.config.wechatNotificationConfigured;
}
async send(message: SubscriptionMessage) {
if (!this.config.wechatAppId || !this.config.wechatAppSecret)
throw new Error('WECHAT_NOT_CONFIGURED');
const templateId = this.config.wechatNotificationTemplates[message.type];
if (!templateId) throw new Error('WECHAT_TEMPLATE_NOT_CONFIGURED');
const tokenResponse = await fetch(
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${encodeURIComponent(this.config.wechatAppId)}&secret=${encodeURIComponent(this.config.wechatAppSecret)}`,
);
const token = (await tokenResponse.json()) as { access_token?: string; errcode?: number };
if (!tokenResponse.ok || !token.access_token)
throw new Error(`WECHAT_TOKEN_${token.errcode ?? 'FAILED'}`);
const response = await fetch(
`https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${encodeURIComponent(token.access_token)}`,
{
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
touser: message.openid,
template_id: templateId,
page: message.resourcePublicId
? `pages/index/index?resource=${message.resourcePublicId}`
: 'pages/index/index',
data: {
thing1: { value: message.title.slice(0, 20) },
thing2: { value: message.body.slice(0, 20) },
},
}),
},
);
const result = (await response.json()) as {
errcode?: number;
errmsg?: string;
msgid?: string | number;
};
if (!response.ok || result.errcode !== 0)
throw new Error(`WECHAT_SEND_${result.errcode ?? 'FAILED'}`);
return { providerMessageId: result.msgid?.toString() ?? null };
}
}