feat(redeem): streamline phone redemption confirmation
CI / verify (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsNotEmpty, IsNumber, IsString, Min } from 'class-validator';
|
||||
import type { RedeemPhoneDirectPrepareRequest } from '@dukang/shared-types';
|
||||
|
||||
export class RedeemPhoneSendLookupSmsDto {
|
||||
@IsString()
|
||||
@@ -28,6 +29,17 @@ export class RedeemPhonePrepareDto {
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export class RedeemPhoneDirectPrepareDto implements RedeemPhoneDirectPrepareRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
phone: string;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.01)
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export class RedeemPhoneConfirmDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
import {
|
||||
RedeemPhoneBalanceDto,
|
||||
RedeemPhoneConfirmDto,
|
||||
RedeemPhoneDirectPrepareDto,
|
||||
RedeemPhonePrepareDto,
|
||||
RedeemPhoneSendLookupSmsDto,
|
||||
} from './dto/phone-redeem.dto';
|
||||
@@ -107,6 +108,16 @@ export class ShopRedeemController {
|
||||
);
|
||||
}
|
||||
|
||||
@Post('phone/prepare-direct')
|
||||
phonePrepareDirect(@CurrentUser() user: AuthUser, @Body() body: RedeemPhoneDirectPrepareDto) {
|
||||
return this.redeemService.preparePhoneRedeemDirect(
|
||||
user.actorId,
|
||||
user.storeId!,
|
||||
body.phone,
|
||||
body.amount,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('phone/confirm')
|
||||
phoneConfirm(@CurrentUser() user: AuthUser, @Body() body: RedeemPhoneConfirmDto) {
|
||||
return this.redeemService.confirmPhoneRedeem(
|
||||
|
||||
@@ -311,6 +311,62 @@ export class RedeemService {
|
||||
return session;
|
||||
}
|
||||
|
||||
async preparePhoneRedeemDirect(
|
||||
storeAccountId: bigint,
|
||||
storeId: bigint,
|
||||
phone: string,
|
||||
amount: number,
|
||||
) {
|
||||
const account = await this.loadOpenStoreAccount(storeAccountId, storeId);
|
||||
const normalizedPhone = this.normalizeMobilePhone(phone);
|
||||
const user = await this.resolveUserByPhone(normalizedPhone);
|
||||
const { allocations, totalBalance } = await this.computeDirectAllocations(user.id, amount);
|
||||
const sessionId = randomBytes(16).toString('hex');
|
||||
|
||||
await this.authService.sendSms(normalizedPhone, SmsScene.REDEEM_PHONE_CONFIRM, {
|
||||
clientApp: ClientApp.SHOP_H5,
|
||||
});
|
||||
|
||||
await this.redis.setJson(
|
||||
this.phoneSessionKey(sessionId),
|
||||
{
|
||||
userId: user.id.toString(),
|
||||
phone: normalizedPhone,
|
||||
storeAccountId: storeAccountId.toString(),
|
||||
storeId: account.storeId.toString(),
|
||||
amount,
|
||||
allocations,
|
||||
confirmPrepared: true,
|
||||
} satisfies PhoneRedeemSession,
|
||||
REDEEM_PHONE_SESSION_TTL_SECONDS,
|
||||
);
|
||||
|
||||
this.analyticsService.trackStoreOneSafe(storeAccountId, ClientApp.SHOP_H5, {
|
||||
storeId: account.storeId,
|
||||
eventName: 'store_redeem_phone_prepare',
|
||||
extraJson: {
|
||||
sessionId,
|
||||
amount,
|
||||
phone: this.maskPhoneForStore(normalizedPhone),
|
||||
flow: 'direct',
|
||||
},
|
||||
});
|
||||
|
||||
return serializeBigInt({
|
||||
sessionId,
|
||||
amount,
|
||||
totalBalance,
|
||||
maskedPhone: this.maskPhoneForStore(normalizedPhone),
|
||||
expireInSeconds: REDEEM_PHONE_SESSION_TTL_SECONDS,
|
||||
user: {
|
||||
id: user.id,
|
||||
userNo: user.userNo,
|
||||
nickname: user.nickname,
|
||||
phone: this.maskPhoneForStore(normalizedPhone),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async preparePhoneRedeem(storeAccountId: bigint, storeId: bigint, sessionId: string, amount: number) {
|
||||
const account = await this.loadOpenStoreAccount(storeAccountId, storeId);
|
||||
const session = await this.loadPhoneSession(sessionId, storeAccountId);
|
||||
|
||||
Reference in New Issue
Block a user