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; } @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 }; } }