@@ -11,6 +11,7 @@ import {
|
||||
calcBenefitAmount,
|
||||
generateOrderNo,
|
||||
orderTabToStatuses,
|
||||
toMinSaleQuantity,
|
||||
validateMinPurchase,
|
||||
} from '@dukang/domain';
|
||||
import { loadAppConfig, ClientApp, WECHAT_AUTH_REQUIRED } from '@dukang/shared-types';
|
||||
@@ -37,6 +38,7 @@ import { AlertService } from '../../common/alert/alert.service';
|
||||
import { PayRedeemAnomalyService } from '../../common/alert/pay-redeem-anomaly.service';
|
||||
import { WecomMessagePushService } from '../../integrations/wecom/wecom-message-push.service';
|
||||
import type { Request } from 'express';
|
||||
import type { CommonProductSku } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class TradeService {
|
||||
@@ -62,22 +64,54 @@ export class TradeService {
|
||||
|
||||
private readonly logger = new Logger(TradeService.name);
|
||||
|
||||
private overlayProductWithSku(
|
||||
productDto: Record<string, unknown>,
|
||||
sku: CommonProductSku,
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
...productDto,
|
||||
skuCode: sku.skuCode,
|
||||
spec: sku.specText || productDto.spec,
|
||||
price: Number(sku.price),
|
||||
benefitAmount: Number(sku.benefitAmount ?? sku.price),
|
||||
benefitDisplay: Number(sku.benefitAmount ?? sku.price),
|
||||
allowOnSitePickup: sku.allowOnSitePickup,
|
||||
allowOnlinePurchase: sku.allowOnlinePurchase,
|
||||
allowCrossCityDelivery: sku.allowCrossCityDelivery,
|
||||
saleUnit: sku.saleUnit,
|
||||
bottlesPerUnit: sku.bottlesPerUnit,
|
||||
selectedSkuId: sku.id.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
async preview(
|
||||
userId: bigint,
|
||||
body: { productId: string; quantity: number; addressId?: string; onSitePickup?: boolean },
|
||||
body: {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
addressId?: string;
|
||||
onSitePickup?: boolean;
|
||||
skuId?: string;
|
||||
},
|
||||
) {
|
||||
const viewerPhone = await this.catalogService.resolveUserPhone(userId);
|
||||
const product = await this.catalogService.getProduct(BigInt(body.productId), { phone: viewerPhone });
|
||||
if (!product || product.status !== 'ON_SALE') {
|
||||
const { product: spu, sku } = await this.catalogService.assertPurchasable(
|
||||
BigInt(body.productId),
|
||||
viewerPhone,
|
||||
body.skuId,
|
||||
);
|
||||
const productDto = await this.catalogService.getProduct(spu.id, { phone: viewerPhone });
|
||||
if (!productDto || productDto.status !== 'ON_SALE') {
|
||||
throw new BadRequestException('商品不可购买');
|
||||
}
|
||||
const product = this.overlayProductWithSku(productDto as Record<string, unknown>, sku);
|
||||
|
||||
const city = await this.prisma.commonCity.findFirst({ where: { status: 'ACTIVE' } });
|
||||
if (!city) throw new BadRequestException('暂无开城城市');
|
||||
|
||||
const onSitePickup = !!body.onSitePickup;
|
||||
if (onSitePickup && !product.allowOnSitePickup) {
|
||||
throw new BadRequestException('该商品不支持现场取货');
|
||||
if (onSitePickup && !sku.allowOnSitePickup) {
|
||||
throw new BadRequestException('该规格不支持现场取货');
|
||||
}
|
||||
|
||||
let deliveryType: 'LOCAL' | 'CROSS_CITY' | 'ON_SITE_PICKUP' = onSitePickup
|
||||
@@ -95,43 +129,46 @@ export class TradeService {
|
||||
let addressOk = true;
|
||||
let addressMessage: string | null = null;
|
||||
if (!onSitePickup) {
|
||||
const allowOnline = product.allowOnlinePurchase !== false;
|
||||
const allowCross = product.allowCrossCityDelivery !== false;
|
||||
const allowOnline = sku.allowOnlinePurchase !== false;
|
||||
const allowCross = sku.allowCrossCityDelivery !== false;
|
||||
if (deliveryType === 'LOCAL' && !allowOnline) {
|
||||
addressOk = false;
|
||||
addressMessage = '该商品不支持线上购买';
|
||||
addressMessage = '该规格不支持线上购买';
|
||||
} else if (deliveryType === 'CROSS_CITY') {
|
||||
if (!allowOnline) {
|
||||
addressOk = false;
|
||||
addressMessage = '该商品不支持线上购买';
|
||||
addressMessage = '该规格不支持线上购买';
|
||||
} else if (!allowCross) {
|
||||
addressOk = false;
|
||||
addressMessage = '该商品不支持跨城配送,请更换为开城城市内的收货地址';
|
||||
addressMessage = '该规格不支持跨城配送,请更换为开城城市内的收货地址';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bottlesPerUnit = sku.bottlesPerUnit > 0 ? sku.bottlesPerUnit : 1;
|
||||
const check = validateMinPurchase(
|
||||
deliveryType,
|
||||
body.quantity,
|
||||
city.localMinQty,
|
||||
city.crossMinQty,
|
||||
{ bottlesPerUnit, saleUnit: sku.saleUnit },
|
||||
);
|
||||
|
||||
const unitPrice = Number(product.price);
|
||||
const unitPrice = Number(sku.price);
|
||||
const productAmount = unitPrice * body.quantity;
|
||||
const benefitPerUnit = calcBenefitAmount({
|
||||
price: unitPrice,
|
||||
benefitAmount: product.benefitAmount ? Number(product.benefitAmount) : null,
|
||||
benefitAmount: sku.benefitAmount != null ? Number(sku.benefitAmount) : null,
|
||||
});
|
||||
|
||||
const freightPayType: FreightPayType | null = deliveryType === 'CROSS_CITY' ? 'COD' : null;
|
||||
const minQty =
|
||||
const minBottleQty =
|
||||
deliveryType === 'ON_SITE_PICKUP'
|
||||
? city.localMinQty
|
||||
: deliveryType === 'LOCAL'
|
||||
? city.localMinQty
|
||||
: city.crossMinQty;
|
||||
const minQty = toMinSaleQuantity(minBottleQty, bottlesPerUnit);
|
||||
|
||||
return {
|
||||
product,
|
||||
@@ -143,16 +180,18 @@ export class TradeService {
|
||||
payAmount: productAmount,
|
||||
benefitAmount: benefitPerUnit * body.quantity,
|
||||
city: serializeBigInt(city),
|
||||
/** 起购未满足时仍返回预览,供确认页改数量;下单接口仍会硬校验 */
|
||||
quantityOk: check.ok,
|
||||
quantityMessage: check.ok ? null : (check.message ?? null),
|
||||
/** 地址/履约未满足时仍返回预览,供确认页提示换地址;下单接口仍会硬校验 */
|
||||
addressOk,
|
||||
addressMessage,
|
||||
minQty,
|
||||
onSitePickup,
|
||||
allowCrossCityDelivery: product.allowCrossCityDelivery !== false,
|
||||
allowOnlinePurchase: product.allowOnlinePurchase !== false,
|
||||
allowCrossCityDelivery: sku.allowCrossCityDelivery !== false,
|
||||
allowOnlinePurchase: sku.allowOnlinePurchase !== false,
|
||||
skuId: sku.id.toString(),
|
||||
saleUnit: sku.saleUnit,
|
||||
bottlesPerUnit,
|
||||
bottleQuantity: body.quantity * bottlesPerUnit,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -164,6 +203,7 @@ export class TradeService {
|
||||
addressId?: string;
|
||||
onSitePickup?: boolean;
|
||||
clientLocation?: unknown;
|
||||
skuId?: string;
|
||||
},
|
||||
req: Request,
|
||||
) {
|
||||
@@ -207,6 +247,9 @@ export class TradeService {
|
||||
const product = await this.prisma.commonProductItem.findUniqueOrThrow({
|
||||
where: { id: BigInt(body.productId) },
|
||||
});
|
||||
const sku = await this.prisma.commonProductSku.findUniqueOrThrow({
|
||||
where: { id: BigInt(preview.skuId) },
|
||||
});
|
||||
const city = await this.prisma.commonCity.findFirstOrThrow({ where: { status: 'ACTIVE' } });
|
||||
const orderNo = generateOrderNo();
|
||||
const payExpireAt = new Date(Date.now() + 30 * 60 * 1000);
|
||||
@@ -239,12 +282,15 @@ export class TradeService {
|
||||
payStatus: 'UNPAID',
|
||||
deliveryType: preview.deliveryType as 'LOCAL' | 'CROSS_CITY' | 'ON_SITE_PICKUP',
|
||||
productId: product.id,
|
||||
barcode69: product.barcode69,
|
||||
skuId: sku.id,
|
||||
barcode69: sku.barcode69,
|
||||
productName: product.name,
|
||||
productSpec: product.spec,
|
||||
productSpec: sku.specText || product.spec,
|
||||
imageResourceId: product.coverResourceId,
|
||||
quantity: body.quantity,
|
||||
listUnitPrice: product.price,
|
||||
saleUnit: sku.saleUnit,
|
||||
bottlesPerUnit: preview.bottlesPerUnit,
|
||||
listUnitPrice: sku.price,
|
||||
listAmount: preview.productAmount,
|
||||
productAmount: preview.productAmount,
|
||||
receiverName,
|
||||
@@ -300,6 +346,7 @@ export class TradeService {
|
||||
extraJson: {
|
||||
orderId: order.id.toString(),
|
||||
productId: body.productId,
|
||||
skuId: sku.id.toString(),
|
||||
quantity: body.quantity,
|
||||
onSitePickup,
|
||||
},
|
||||
@@ -1503,18 +1550,28 @@ export class TradeService {
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const details = await Promise.all(
|
||||
products.map((p) =>
|
||||
this.catalogService.getProduct(BigInt(String(p.id)), { phone: primary.phone }),
|
||||
),
|
||||
);
|
||||
return serializeBigInt({
|
||||
products: products.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
spec: p.spec,
|
||||
price: Number(p.price),
|
||||
benefitAmount: p.benefitAmount != null ? Number(p.benefitAmount) : null,
|
||||
coverUrl: (p as { mainImageUrl?: string | null }).mainImageUrl ?? null,
|
||||
allowOnSitePickup: !!(p as { allowOnSitePickup?: boolean }).allowOnSitePickup,
|
||||
allowOnlinePurchase: (p as { allowOnlinePurchase?: boolean }).allowOnlinePurchase !== false,
|
||||
products: details.filter(Boolean).map((p) => ({
|
||||
id: p!.id,
|
||||
name: p!.name,
|
||||
spec: p!.spec,
|
||||
price: Number(p!.price),
|
||||
benefitAmount: p!.benefitAmount != null ? Number(p!.benefitAmount) : null,
|
||||
coverUrl: (p as { mainImageUrl?: string | null } | null)?.mainImageUrl ?? null,
|
||||
allowOnSitePickup: !!(p as { allowOnSitePickup?: boolean } | null)?.allowOnSitePickup,
|
||||
allowOnlinePurchase: (p as { allowOnlinePurchase?: boolean } | null)?.allowOnlinePurchase !== false,
|
||||
allowCrossCityDelivery:
|
||||
(p as { allowCrossCityDelivery?: boolean }).allowCrossCityDelivery !== false,
|
||||
(p as { allowCrossCityDelivery?: boolean } | null)?.allowCrossCityDelivery !== false,
|
||||
specEnabled: !!(p as { specEnabled?: boolean } | null)?.specEnabled,
|
||||
saleUnit: (p as { saleUnit?: string } | null)?.saleUnit,
|
||||
skus: (p as { skus?: unknown[] } | null)?.skus ?? [],
|
||||
defaultSkuId: (p as { defaultSkuId?: string } | null)?.defaultSkuId,
|
||||
specAttrs: (p as { specAttrs?: unknown[] } | null)?.specAttrs ?? [],
|
||||
})),
|
||||
promoCodes,
|
||||
stores: stores.map((s) => ({
|
||||
@@ -1537,13 +1594,25 @@ export class TradeService {
|
||||
storeId?: string;
|
||||
receiverCity?: string;
|
||||
receiverDistrict?: string;
|
||||
skuId?: string;
|
||||
},
|
||||
viewer?: { phone?: string | null; bypassWhitelist?: boolean },
|
||||
) {
|
||||
const product = await this.catalogService.getProduct(BigInt(body.productId), viewer ?? {});
|
||||
if (!product || product.status !== 'ON_SALE') {
|
||||
const { product: spu, sku } = await this.catalogService.assertPurchasable(
|
||||
BigInt(body.productId),
|
||||
viewer?.phone,
|
||||
body.skuId,
|
||||
{ bypassWhitelist: !!viewer?.bypassWhitelist },
|
||||
);
|
||||
if (spu.status !== 'ON_SALE') {
|
||||
throw new BadRequestException('商品不可购买');
|
||||
}
|
||||
// 非 bypass 时再走可见性(与 getProduct 一致)
|
||||
if (!viewer?.bypassWhitelist) {
|
||||
const dto = await this.catalogService.getProduct(spu.id, viewer ?? {});
|
||||
if (!dto) throw new BadRequestException('商品不可购买');
|
||||
}
|
||||
|
||||
const city = await this.prisma.commonCity.findFirst({ where: { status: 'ACTIVE' } });
|
||||
if (!city) throw new BadRequestException('暂无开城城市');
|
||||
|
||||
@@ -1551,8 +1620,8 @@ export class TradeService {
|
||||
let deliveryType: 'LOCAL' | 'CROSS_CITY' | 'ON_SITE_PICKUP' = 'LOCAL';
|
||||
|
||||
if (deliveryMode === 'ON_SITE_PICKUP') {
|
||||
if (!product.allowOnSitePickup) {
|
||||
throw new BadRequestException('该商品不支持现场提货');
|
||||
if (!sku.allowOnSitePickup) {
|
||||
throw new BadRequestException('该规格不支持现场提货');
|
||||
}
|
||||
deliveryType = 'ON_SITE_PICKUP';
|
||||
} else {
|
||||
@@ -1560,34 +1629,36 @@ export class TradeService {
|
||||
if (receiverCity && receiverCity !== city.name && receiverCity !== '郑州市') {
|
||||
deliveryType = 'CROSS_CITY';
|
||||
}
|
||||
const allowOnline = product.allowOnlinePurchase !== false;
|
||||
const allowCross = product.allowCrossCityDelivery !== false;
|
||||
const allowOnline = sku.allowOnlinePurchase !== false;
|
||||
const allowCross = sku.allowCrossCityDelivery !== false;
|
||||
if (deliveryType === 'LOCAL' && !allowOnline) {
|
||||
throw new BadRequestException('该商品不支持线上购买');
|
||||
throw new BadRequestException('该规格不支持线上购买');
|
||||
}
|
||||
if (deliveryType === 'CROSS_CITY') {
|
||||
if (!allowOnline) {
|
||||
throw new BadRequestException('该商品不支持线上购买');
|
||||
throw new BadRequestException('该规格不支持线上购买');
|
||||
}
|
||||
if (!allowCross) {
|
||||
throw new BadRequestException('该商品不支持跨城配送');
|
||||
throw new BadRequestException('该规格不支持跨城配送');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bottlesPerUnit = sku.bottlesPerUnit > 0 ? sku.bottlesPerUnit : 1;
|
||||
const check = validateMinPurchase(
|
||||
deliveryType === 'CROSS_CITY' ? 'CROSS_CITY' : 'LOCAL',
|
||||
deliveryType === 'CROSS_CITY' ? 'CROSS_CITY' : deliveryType === 'ON_SITE_PICKUP' ? 'ON_SITE_PICKUP' : 'LOCAL',
|
||||
body.quantity,
|
||||
city.localMinQty,
|
||||
city.crossMinQty,
|
||||
{ bottlesPerUnit, saleUnit: sku.saleUnit },
|
||||
);
|
||||
if (!check.ok) throw new BadRequestException(check.message);
|
||||
|
||||
const unitPrice = Number(product.price);
|
||||
const unitPrice = Number(sku.price);
|
||||
const productAmount = unitPrice * body.quantity;
|
||||
const benefitPerUnit = calcBenefitAmount({
|
||||
price: unitPrice,
|
||||
benefitAmount: product.benefitAmount ? Number(product.benefitAmount) : null,
|
||||
benefitAmount: sku.benefitAmount != null ? Number(sku.benefitAmount) : null,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -1596,6 +1667,14 @@ export class TradeService {
|
||||
benefitAmount: benefitPerUnit * body.quantity,
|
||||
deliveryType,
|
||||
unitPrice,
|
||||
skuId: sku.id.toString(),
|
||||
saleUnit: sku.saleUnit,
|
||||
bottlesPerUnit,
|
||||
bottleQuantity: body.quantity * bottlesPerUnit,
|
||||
minQuantity: toMinSaleQuantity(
|
||||
deliveryType === 'CROSS_CITY' ? city.crossMinQty : city.localMinQty,
|
||||
bottlesPerUnit,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1609,6 +1688,7 @@ export class TradeService {
|
||||
storeId?: string;
|
||||
receiverCity?: string;
|
||||
receiverDistrict?: string;
|
||||
skuId?: string;
|
||||
},
|
||||
) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
@@ -1630,6 +1710,7 @@ export class TradeService {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
promoCodeId?: string;
|
||||
skuId?: string;
|
||||
},
|
||||
req: Request,
|
||||
) {
|
||||
@@ -1666,6 +1747,7 @@ export class TradeService {
|
||||
storeId: body.storeId,
|
||||
receiverCity: body.city,
|
||||
receiverDistrict: body.district,
|
||||
skuId: body.skuId,
|
||||
},
|
||||
{ phone: primary.phone },
|
||||
);
|
||||
@@ -1673,6 +1755,9 @@ export class TradeService {
|
||||
const product = await this.prisma.commonProductItem.findUniqueOrThrow({
|
||||
where: { id: BigInt(body.productId) },
|
||||
});
|
||||
const sku = await this.prisma.commonProductSku.findUniqueOrThrow({
|
||||
where: { id: BigInt(preview.skuId) },
|
||||
});
|
||||
const city = await this.prisma.commonCity.findFirstOrThrow({ where: { status: 'ACTIVE' } });
|
||||
|
||||
let receiverName = body.receiverName?.trim() || `用户${normalizedPhone.slice(-4)}`;
|
||||
@@ -1730,12 +1815,15 @@ export class TradeService {
|
||||
channelSource: 'PROXY_ONLINE',
|
||||
promoCodeId,
|
||||
productId: product.id,
|
||||
barcode69: product.barcode69,
|
||||
skuId: sku.id,
|
||||
barcode69: sku.barcode69,
|
||||
productName: product.name,
|
||||
productSpec: product.spec,
|
||||
productSpec: sku.specText || product.spec,
|
||||
imageResourceId: product.coverResourceId,
|
||||
quantity: body.quantity,
|
||||
listUnitPrice: product.price,
|
||||
saleUnit: sku.saleUnit,
|
||||
bottlesPerUnit: preview.bottlesPerUnit,
|
||||
listUnitPrice: sku.price,
|
||||
listAmount: preview.productAmount,
|
||||
productAmount: preview.productAmount,
|
||||
payAmount: preview.payAmount,
|
||||
@@ -1993,18 +2081,28 @@ export class TradeService {
|
||||
this.catalogService.listProducts(undefined, undefined, { bypassWhitelist: true }),
|
||||
this.promoCodeService.listActiveOptions(),
|
||||
]);
|
||||
const details = await Promise.all(
|
||||
products.map((p) =>
|
||||
this.catalogService.getProduct(BigInt(String(p.id)), { bypassWhitelist: true }),
|
||||
),
|
||||
);
|
||||
return serializeBigInt({
|
||||
products: products.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
spec: p.spec,
|
||||
price: Number(p.price),
|
||||
benefitAmount: p.benefitAmount != null ? Number(p.benefitAmount) : null,
|
||||
coverUrl: (p as { mainImageUrl?: string | null }).mainImageUrl ?? null,
|
||||
allowOnSitePickup: !!(p as { allowOnSitePickup?: boolean }).allowOnSitePickup,
|
||||
allowOnlinePurchase: (p as { allowOnlinePurchase?: boolean }).allowOnlinePurchase !== false,
|
||||
products: details.filter(Boolean).map((p) => ({
|
||||
id: p!.id,
|
||||
name: p!.name,
|
||||
spec: p!.spec,
|
||||
price: Number(p!.price),
|
||||
benefitAmount: p!.benefitAmount != null ? Number(p!.benefitAmount) : null,
|
||||
coverUrl: (p as { mainImageUrl?: string | null } | null)?.mainImageUrl ?? null,
|
||||
allowOnSitePickup: !!(p as { allowOnSitePickup?: boolean } | null)?.allowOnSitePickup,
|
||||
allowOnlinePurchase: (p as { allowOnlinePurchase?: boolean } | null)?.allowOnlinePurchase !== false,
|
||||
allowCrossCityDelivery:
|
||||
(p as { allowCrossCityDelivery?: boolean }).allowCrossCityDelivery !== false,
|
||||
(p as { allowCrossCityDelivery?: boolean } | null)?.allowCrossCityDelivery !== false,
|
||||
specEnabled: !!(p as { specEnabled?: boolean } | null)?.specEnabled,
|
||||
saleUnit: (p as { saleUnit?: string } | null)?.saleUnit,
|
||||
skus: (p as { skus?: unknown[] } | null)?.skus ?? [],
|
||||
defaultSkuId: (p as { defaultSkuId?: string } | null)?.defaultSkuId,
|
||||
specAttrs: (p as { specAttrs?: unknown[] } | null)?.specAttrs ?? [],
|
||||
})),
|
||||
promoCodes,
|
||||
stores: [],
|
||||
@@ -2025,6 +2123,7 @@ export class TradeService {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
promoCodeId?: string;
|
||||
skuId?: string;
|
||||
},
|
||||
req: Request,
|
||||
) {
|
||||
@@ -2064,6 +2163,7 @@ export class TradeService {
|
||||
deliveryMode: body.deliveryMode,
|
||||
receiverCity: body.city,
|
||||
receiverDistrict: body.district,
|
||||
skuId: body.skuId,
|
||||
},
|
||||
{ bypassWhitelist: true },
|
||||
);
|
||||
@@ -2071,6 +2171,9 @@ export class TradeService {
|
||||
const product = await this.prisma.commonProductItem.findUniqueOrThrow({
|
||||
where: { id: BigInt(body.productId) },
|
||||
});
|
||||
const sku = await this.prisma.commonProductSku.findUniqueOrThrow({
|
||||
where: { id: BigInt(preview.skuId) },
|
||||
});
|
||||
const city = await this.prisma.commonCity.findFirstOrThrow({ where: { status: 'ACTIVE' } });
|
||||
|
||||
let receiverName = body.receiverName?.trim() || `用户${normalizedPhone.slice(-4)}`;
|
||||
@@ -2128,12 +2231,15 @@ export class TradeService {
|
||||
channelSource: 'PROXY_ONLINE',
|
||||
promoCodeId,
|
||||
productId: product.id,
|
||||
barcode69: product.barcode69,
|
||||
skuId: sku.id,
|
||||
barcode69: sku.barcode69,
|
||||
productName: product.name,
|
||||
productSpec: product.spec,
|
||||
productSpec: sku.specText || product.spec,
|
||||
imageResourceId: product.coverResourceId,
|
||||
quantity: body.quantity,
|
||||
listUnitPrice: product.price,
|
||||
saleUnit: sku.saleUnit,
|
||||
bottlesPerUnit: preview.bottlesPerUnit,
|
||||
listUnitPrice: sku.price,
|
||||
listAmount: preview.productAmount,
|
||||
productAmount: preview.productAmount,
|
||||
payAmount: preview.payAmount,
|
||||
|
||||
Reference in New Issue
Block a user