feat: 完善微信登录
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# H5 开发环境接口
|
||||
VITE_H5_API_BASE_URL=http://localhost:3000
|
||||
|
||||
# 小程序开发环境接口
|
||||
VITE_MP_API_BASE_URL=http://localhost:3000
|
||||
# 小程序开发环境接口(真机调试使用局域网IP)
|
||||
VITE_MP_API_BASE_URL=http://192.168.0.111:3000
|
||||
|
||||
@@ -84,7 +84,7 @@ function handleTap(key: string) {
|
||||
}
|
||||
|
||||
.tool-label {
|
||||
font-size: $font-xs;
|
||||
font-size: $font-base;
|
||||
color: $text-secondary;
|
||||
font-weight: $font-medium;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"appid": "wx6b2d69c900f8f93a",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<view class="page-cashbacks">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar">
|
||||
<view class="navbar-content">
|
||||
<view class="navbar-left" @tap="goBack">
|
||||
<u-icon name="arrow-left" :size="20" color="#1A1A1A" />
|
||||
</view>
|
||||
<text class="navbar-title">返现明细</text>
|
||||
<view class="navbar-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 顶部统计卡片 -->
|
||||
<view class="stats-header">
|
||||
<view class="stats-main">
|
||||
@@ -319,46 +308,6 @@ function goBack() {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ========== 自定义导航栏 ========== */
|
||||
.custom-navbar {
|
||||
background: #ffffff;
|
||||
padding: 0 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbar-left,
|
||||
.navbar-right {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
justify-content: flex-start;
|
||||
padding: 12rpx;
|
||||
margin-left: -12rpx;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
/* ========== 顶部统计卡片 ========== */
|
||||
.stats-header {
|
||||
margin: 24rpx 24rpx 16rpx;
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<view class="page-records">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar">
|
||||
<view class="navbar-content">
|
||||
<view class="navbar-left" @tap="goBack">
|
||||
<u-icon name="arrow-left" :size="20" color="#1A1A1A" />
|
||||
</view>
|
||||
<text class="navbar-title">我的邀请</text>
|
||||
<view class="navbar-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 顶部统计卡片 -->
|
||||
<view class="stats-banner">
|
||||
<view class="stats-item">
|
||||
@@ -279,46 +268,6 @@ function goBack() {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ========== 自定义导航栏 ========== */
|
||||
.custom-navbar {
|
||||
background: #ffffff;
|
||||
padding: 0 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbar-left,
|
||||
.navbar-right {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
justify-content: flex-start;
|
||||
padding: 12rpx;
|
||||
margin-left: -12rpx;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
/* ========== 顶部统计横幅 ========== */
|
||||
.stats-banner {
|
||||
margin: 24rpx 24rpx 16rpx;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<!-- 一键登录按钮(微信获取手机号) -->
|
||||
<button
|
||||
v-if="isWechatMiniProgram"
|
||||
v-if="isWechatMiniProgram && agreed"
|
||||
class="quick-login-button"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="handleQuickLogin"
|
||||
@@ -31,6 +31,16 @@
|
||||
一键登录
|
||||
</button>
|
||||
|
||||
<!-- 未同意协议时显示的普通按钮 -->
|
||||
<button
|
||||
v-if="isWechatMiniProgram && !agreed"
|
||||
class="quick-login-button"
|
||||
@tap="handleQuickLoginCheck"
|
||||
>
|
||||
<u-icon name="phone-fill" :size="24" color="#fff" style="margin-right: 12rpx;" />
|
||||
一键登录
|
||||
</button>
|
||||
|
||||
<!-- 手机号安全登录按钮 -->
|
||||
<button class="phone-login-button" @tap="handleShowPhoneModal">
|
||||
<u-icon name="lock-fill" :size="24" color="#fff" style="margin-right: 12rpx;" />
|
||||
@@ -76,7 +86,23 @@
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button class="modal-button cancel" @tap="closeAgreementModal">取消</button>
|
||||
<button class="modal-button confirm" @tap="confirmAgreement">确定</button>
|
||||
<!-- 如果是微信一键登录触发的弹窗,确定按钮带微信授权 -->
|
||||
<button
|
||||
v-if="isWechatMiniProgram && pendingLoginType === 'wechat'"
|
||||
class="modal-button confirm"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="handleQuickLoginAfterAgreement"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
<!-- 其他情况的普通确定按钮 -->
|
||||
<button
|
||||
v-else
|
||||
class="modal-button confirm"
|
||||
@tap="confirmAgreement"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -147,6 +173,7 @@ const agreed = ref(false);
|
||||
const isWechatMiniProgram = ref(false);
|
||||
const showAgreementModal = ref(false);
|
||||
const showPhoneModal = ref(false);
|
||||
const pendingLoginType = ref<'wechat' | 'phone' | null>(null);
|
||||
const userStore = useUserStore();
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
@@ -176,6 +203,7 @@ function checkAgreement(action: () => void) {
|
||||
function closeAgreementModal() {
|
||||
showAgreementModal.value = false;
|
||||
pendingAction = null;
|
||||
pendingLoginType.value = null;
|
||||
}
|
||||
|
||||
// 确认协议
|
||||
@@ -186,54 +214,82 @@ function confirmAgreement() {
|
||||
pendingAction();
|
||||
pendingAction = null;
|
||||
}
|
||||
pendingLoginType.value = null;
|
||||
}
|
||||
|
||||
// 一键登录检查协议
|
||||
function handleQuickLoginCheck() {
|
||||
if (!agreed.value) {
|
||||
pendingLoginType.value = 'wechat';
|
||||
showAgreementModal.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 同意协议后的微信一键登录
|
||||
async function handleQuickLoginAfterAgreement(e: any) {
|
||||
agreed.value = true;
|
||||
showAgreementModal.value = false;
|
||||
pendingLoginType.value = null;
|
||||
await handleQuickLogin(e);
|
||||
}
|
||||
|
||||
// 一键登录(微信获取手机号)
|
||||
async function handleQuickLogin(e: any) {
|
||||
const action = async () => {
|
||||
try {
|
||||
const { code, encryptedData, iv } = e.detail;
|
||||
try {
|
||||
const { code: phoneCode, encryptedData, iv } = e.detail;
|
||||
|
||||
if (!code) {
|
||||
uni.showToast({ title: '获取手机号失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
console.log('微信手机号授权返回:', { phoneCode, encryptedData, iv });
|
||||
|
||||
uni.showLoading({ title: '登录中...' });
|
||||
|
||||
// 获取待绑定的邀请码
|
||||
const inviteCode = uni.getStorageSync('pendingInviteCode');
|
||||
|
||||
// 调用后端接口,使用微信手机号登录
|
||||
const res = await loginByWechatPhone(code, encryptedData, iv, inviteCode);
|
||||
|
||||
// 登录成功后清除邀请码
|
||||
if (inviteCode) {
|
||||
uni.removeStorageSync('pendingInviteCode');
|
||||
}
|
||||
|
||||
userStore.setLogin(res.data);
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '登录成功', icon: 'success' });
|
||||
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/index/index' });
|
||||
}, 500);
|
||||
} catch (error: any) {
|
||||
uni.hideLoading();
|
||||
console.error('一键登录失败', error);
|
||||
uni.showToast({
|
||||
title: error.message || '登录失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
if (!phoneCode) {
|
||||
uni.showToast({ title: '获取手机号失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (!checkAgreement(action)) {
|
||||
return;
|
||||
uni.showLoading({ title: '登录中...' });
|
||||
|
||||
// 先调用 wx.login 获取登录 code
|
||||
const loginRes = await new Promise<any>((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
|
||||
if (!loginRes.code) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '获取登录凭证失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('wx.login 返回的 code:', loginRes.code);
|
||||
|
||||
// 获取待绑定的邀请码
|
||||
const inviteCode = uni.getStorageSync('pendingInviteCode');
|
||||
|
||||
// 调用后端接口,使用微信手机号登录
|
||||
const res = await loginByWechatPhone(loginRes.code, encryptedData, iv, inviteCode);
|
||||
|
||||
// 登录成功后清除邀请码
|
||||
if (inviteCode) {
|
||||
uni.removeStorageSync('pendingInviteCode');
|
||||
}
|
||||
|
||||
userStore.setLogin(res.data);
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '登录成功', icon: 'success' });
|
||||
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/index/index' });
|
||||
}, 500);
|
||||
} catch (error: any) {
|
||||
uni.hideLoading();
|
||||
console.error('一键登录失败', error);
|
||||
uni.showToast({
|
||||
title: error.message || '登录失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
await action();
|
||||
}
|
||||
|
||||
// 显示手机号登录弹窗
|
||||
@@ -243,6 +299,7 @@ function handleShowPhoneModal() {
|
||||
};
|
||||
|
||||
if (!checkAgreement(action)) {
|
||||
pendingLoginType.value = 'phone';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -630,32 +687,37 @@ async function handlePhoneLogin() {
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
gap: 24rpx;
|
||||
padding: 0 32rpx 32rpx;
|
||||
|
||||
.modal-button {
|
||||
flex: 1;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
background: transparent;
|
||||
transition: all 0.2s;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.cancel {
|
||||
color: #999;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
background: #f7f8fa;
|
||||
color: #666;
|
||||
|
||||
&:active {
|
||||
background: #f7f8fa;
|
||||
background: #e8e9eb;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
&:active {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.4);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
/>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 商家服务 -->
|
||||
<view class="menu-section">
|
||||
<menu-list
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
<!-- 搜索头部 -->
|
||||
<view class="search-header">
|
||||
<view class="search-bar">
|
||||
<view class="back-icon" @tap="goBack">
|
||||
<u-icon name="arrow-left" size="20" color="#333" />
|
||||
</view>
|
||||
<view class="input-wrapper">
|
||||
<u-icon name="search" size="18" color="#999" />
|
||||
<input
|
||||
@@ -238,15 +235,6 @@ function formatMerchant(merchant: any) {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
<template>
|
||||
<view class="page-withdrawals">
|
||||
<!-- 导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="navbar-left" @tap="goBack">
|
||||
<u-icon name="arrow-left" :size="20" color="#1A1A1A" />
|
||||
</view>
|
||||
<text class="navbar-title">提现记录</text>
|
||||
<view class="navbar-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<view
|
||||
@@ -212,43 +203,6 @@ function goBack() {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
.navbar {
|
||||
background: #ffffff;
|
||||
padding: 0 24rpx;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
.navbar-left,
|
||||
.navbar-right {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
justify-content: flex-start;
|
||||
padding: 12rpx;
|
||||
margin-left: -12rpx;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
background: #ffffff;
|
||||
|
||||
@@ -2,21 +2,17 @@
|
||||
function getBaseURL(): string {
|
||||
// 判断当前运行平台
|
||||
// #ifdef H5
|
||||
return import.meta.env.VITE_H5_API_BASE_URL || 'http://localhost:3000';
|
||||
return 'http://localhost:3000';
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
return import.meta.env.VITE_MP_API_BASE_URL || 'http://localhost:3000';
|
||||
return 'http://192.168.0.111:3000';
|
||||
// #endif
|
||||
|
||||
// 其他平台默认值
|
||||
// #ifndef H5 || MP-WEIXIN
|
||||
return import.meta.env.VITE_MP_API_BASE_URL || 'http://localhost:3000';
|
||||
// #endif
|
||||
return 'http://192.168.0.111:3000';
|
||||
}
|
||||
|
||||
const BASE_URL = getBaseURL();
|
||||
|
||||
interface RequestOptions {
|
||||
url: string;
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
@@ -54,6 +50,9 @@ export function request<T = any>(options: RequestOptions): Promise<ApiResponse<T
|
||||
header['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 动态获取 BASE_URL
|
||||
const BASE_URL = getBaseURL();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${BASE_URL}${url}`,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ScheduleModule } from '@nestjs/schedule';
|
||||
import databaseConfig from './config/database.config';
|
||||
import jwtConfig from './config/jwt.config';
|
||||
import redisConfig from './config/redis.config';
|
||||
import wechatConfig from './config/wechat.config';
|
||||
import { ScheduleModule as TaskScheduleModule } from './schedule/schedule.module';
|
||||
|
||||
// 新的模块结构 - 按端分类
|
||||
@@ -20,7 +21,7 @@ import { WebsiteModule } from './modules/website/website.module';
|
||||
TaskScheduleModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [databaseConfig, jwtConfig, redisConfig],
|
||||
load: [databaseConfig, jwtConfig, redisConfig, wechatConfig],
|
||||
envFilePath: ['.env.local', '.env'],
|
||||
}),
|
||||
TypeOrmModule.forRootAsync({
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('wechat', () => ({
|
||||
appId: process.env.WECHAT_APPID,
|
||||
appSecret: process.env.WECHAT_SECRET,
|
||||
}));
|
||||
@@ -239,18 +239,7 @@ export class AuthService {
|
||||
session_key: string;
|
||||
unionid?: string;
|
||||
}> {
|
||||
// 开发模式:返回模拟数据
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
if (isDev) {
|
||||
this.logger.log(`[DEV] 模拟微信登录: code=${code}`);
|
||||
return {
|
||||
openid: `dev_openid_${Date.now()}`,
|
||||
session_key: 'dev_session_key_1234567890abcdef',
|
||||
unionid: `dev_unionid_${Date.now()}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 生产模式:调用微信API
|
||||
// 调用微信API
|
||||
const appId = this.configService.get<string>('wechat.appId');
|
||||
const appSecret = this.configService.get<string>('wechat.appSecret');
|
||||
|
||||
@@ -261,12 +250,17 @@ export class AuthService {
|
||||
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`;
|
||||
|
||||
try {
|
||||
this.logger.log(`调用微信API: appId=${appId}, code=${code.substring(0, 10)}...`);
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
this.logger.log(`微信API响应: ${JSON.stringify(data)}`);
|
||||
|
||||
if (data.errcode) {
|
||||
this.logger.error(`微信API错误: ${data.errmsg}`);
|
||||
throw new BadRequestException('微信授权失败');
|
||||
const errorMsg = this.getWechatErrorMessage(data.errcode, data.errmsg);
|
||||
this.logger.error(`微信API错误 [${data.errcode}]: ${errorMsg}`);
|
||||
this.logger.error(`微信API错误 data : ${data}`);
|
||||
throw new BadRequestException(errorMsg);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -275,29 +269,35 @@ export class AuthService {
|
||||
unionid: data.unionid,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
this.logger.error('调用微信API失败', error);
|
||||
throw new BadRequestException('微信授权失败');
|
||||
throw new BadRequestException('微信授权失败,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
private getWechatErrorMessage(errcode: number, errmsg: string): string {
|
||||
const errorMap: Record<number, string> = {
|
||||
40029: '微信登录code无效,请重新授权',
|
||||
45011: '微信登录频率限制,请稍后再试',
|
||||
40163: 'code已被使用,请重新授权',
|
||||
};
|
||||
|
||||
if (errcode === -1) {
|
||||
return '微信系统繁忙,请稍后再试';
|
||||
}
|
||||
|
||||
return errorMap[errcode] || `微信授权失败: ${errmsg}`;
|
||||
}
|
||||
|
||||
private decryptWechatData(
|
||||
encryptedData: string,
|
||||
iv: string,
|
||||
sessionKey: string,
|
||||
): any {
|
||||
try {
|
||||
// 开发模式:返回模拟数据
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
if (isDev) {
|
||||
this.logger.log('[DEV] 模拟解密微信手机号');
|
||||
return {
|
||||
phoneNumber: '13800138000',
|
||||
purePhoneNumber: '13800138000',
|
||||
countryCode: '86',
|
||||
};
|
||||
}
|
||||
|
||||
// 生产模式:解密数据
|
||||
// 解密数据
|
||||
const sessionKeyBuffer = Buffer.from(sessionKey, 'base64');
|
||||
const encryptedDataBuffer = Buffer.from(encryptedData, 'base64');
|
||||
const ivBuffer = Buffer.from(iv, 'base64');
|
||||
|
||||
Reference in New Issue
Block a user