feat: implement payment result handling; refactor payment components and enhance terminal payment logic
This commit is contained in:
+2
-3
@@ -1,4 +1,3 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
|
||||||
# ---------- Build stage ----------
|
# ---------- Build stage ----------
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
@@ -7,11 +6,11 @@ ARG DIST_DIR=tis
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm config set registry "https://hub.megan.ir/npm/"
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
RUN pnpm install --frozen-lockfile
|
||||||
pnpm config set store-dir /pnpm/store && pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,41 @@
|
|||||||
<div class="w-full min-h-[60svh] p-4 md:p-6">
|
@defer {
|
||||||
<section class="mx-auto max-w-3xl overflow-hidden rounded-2xl border border-surface-200 bg-white">
|
<div class="w-full min-h-[60svh] p-4 md:p-6">
|
||||||
<div class="bg-gradient-to-l from-emerald-50 via-teal-50 to-cyan-50 p-5 md:p-7">
|
<section class="mx-auto max-w-3xl overflow-hidden rounded-2xl border border-surface-200 bg-white">
|
||||||
<p class="mb-2 inline-flex items-center rounded-full bg-white px-3 py-1 text-xs font-semibold text-emerald-700">
|
<div class="bg-gradient-to-l from-emerald-50 via-teal-50 to-cyan-50 p-5 md:p-7">
|
||||||
{{ brandingInfo.title }}
|
<p class="mb-2 inline-flex items-center rounded-full bg-white px-3 py-1 text-xs font-semibold text-emerald-700">
|
||||||
</p>
|
{{ brandingInfo.title }}
|
||||||
<span class="block mb-4 text-xl font-bold text-surface-900 md:text-3xl">{{ brandingInfo.fullTitle }}</span>
|
</p>
|
||||||
<p class="mt-3 max-w-2xl text-sm leading-7 text-surface-700 md:text-base">
|
<span class="block mb-4 text-xl font-bold text-surface-900 md:text-3xl">{{ brandingInfo.fullTitle }}</span>
|
||||||
این نسخه برای استفاده روزانه روی دستگاه های PSP بهینه شده است تا عملیات ثبت و صدور صورتحسابهای مالیاتی با سرعت
|
<p class="mt-3 max-w-2xl text-sm leading-7 text-surface-700 md:text-base">
|
||||||
بالاتر، پایداری بیشتر و تجربه کاربری ساده تر انجام شود.
|
این نسخه برای استفاده روزانه روی دستگاه های PSP بهینه شده است تا عملیات ثبت و صدور صورتحسابهای مالیاتی با
|
||||||
</p>
|
سرعت بالاتر، پایداری بیشتر و تجربه کاربری ساده تر انجام شود.
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-3 p-4 md:grid-cols-3 md:gap-4 md:p-6">
|
<div class="grid gap-3 p-4 md:grid-cols-3 md:gap-4 md:p-6">
|
||||||
<!-- <article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
<!-- <article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
||||||
<h2 class="mb-2 text-sm font-bold text-surface-900">پایداری در وب ویو</h2>
|
<h2 class="mb-2 text-sm font-bold text-surface-900">پایداری در وب ویو</h2>
|
||||||
<p class="text-sm leading-6 text-surface-700">
|
<p class="text-sm leading-6 text-surface-700">
|
||||||
رفتار رابط کاربری برای محیط WebView تنظیم شده تا روی سخت افزارهای ضعیف تر هم اجرای روان تری داشته باشد.
|
رفتار رابط کاربری برای محیط WebView تنظیم شده تا روی سخت افزارهای ضعیف تر هم اجرای روان تری داشته باشد.
|
||||||
</p>
|
</p>
|
||||||
</article> -->
|
</article> -->
|
||||||
|
|
||||||
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
||||||
<span class="block mb-4 text-xl font-bold text-surface-900">تمرکز بر سرعت عملیات</span>
|
<span class="block mb-4 text-xl font-bold text-surface-900">تمرکز بر سرعت عملیات</span>
|
||||||
<p class="text-sm leading-6 text-surface-700">
|
<p class="text-sm leading-6 text-surface-700">
|
||||||
مسیرهای کلیدی مانند انتخاب کالا، ورود مقادیر و ثبت نهایی برای کاهش تاخیر و افزایش سرعت کاربر بهینه شده اند.
|
مسیرهای کلیدی مانند انتخاب کالا، ورود مقادیر و ثبت نهایی برای کاهش تاخیر و افزایش سرعت کاربر بهینه شده اند.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
||||||
<span class="block mb-4 text-xl font-bold text-surface-900">سازگار با سناریوهای فروش</span>
|
<span class="block mb-4 text-xl font-bold text-surface-900">سازگار با سناریوهای فروش</span>
|
||||||
<p class="text-sm leading-6 text-surface-700">
|
<p class="text-sm leading-6 text-surface-700">
|
||||||
طراحی این نسخه بر مبنای سناریوهای واقعی فروشگاه انجام شده تا اپراتور با کمترین کلیک فرآیند را تکمیل کند.
|
طراحی این نسخه بر مبنای سناریوهای واقعی فروشگاه انجام شده تا اپراتور با کمترین کلیک فرآیند را تکمیل کند.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="mx-4 mb-4 rounded-xl border border-surface-200 bg-surface-50 p-4 md:mx-6">
|
<!-- <div class="mx-4 mb-4 rounded-xl border border-surface-200 bg-surface-50 p-4 md:mx-6">
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
<div class="text-sm text-surface-700">
|
<div class="text-sm text-surface-700">
|
||||||
نسخه برنامه:
|
نسخه برنامه:
|
||||||
@@ -52,10 +53,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<div class="border-t border-surface-200 bg-surface-50 px-4 py-4 md:px-6">
|
<div class="border-t border-surface-200 bg-surface-50 px-4 py-4 md:px-6">
|
||||||
<p class="text-xs leading-6 text-surface-600 md:text-sm">
|
<p class="text-xs leading-6 text-surface-600 md:text-sm">
|
||||||
در صورت نیاز به راهنمایی بیشتر، به صفحهی راهنمای استفاده مراجعه کنید یا با پشتیبانی تماس بگیرید.
|
در صورت نیاز به راهنمایی بیشتر، به صفحهی راهنمای استفاده مراجعه کنید یا با پشتیبانی تماس بگیرید.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export class PosConfigPrintFormComponent extends AbstractForm<
|
|||||||
override async ngOnInit() {
|
override async ngOnInit() {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
const initialValues = await this.service.get();
|
const initialValues = await this.service.get();
|
||||||
console.log('initialValues', initialValues);
|
|
||||||
|
|
||||||
this.form.patchValue(initialValues);
|
this.form.patchValue(initialValues);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<uikit-field label="تعداد مراحل پرداخت با پایانه" name="pay_by_terminal_steps">
|
<uikit-field label="تعداد مراحل پرداخت با پایانه" name="pay_by_terminal_steps">
|
||||||
<p-select
|
<p-select
|
||||||
[options]="payByTerminalSteps"
|
[options]="payByTerminalSteps()"
|
||||||
[ngModel]="selectedPayByTerminalStep()"
|
[ngModel]="selectedPayByTerminalStep()"
|
||||||
[ngModelOptions]="{ standalone: true }"
|
[ngModelOptions]="{ standalone: true }"
|
||||||
optionLabel="label"
|
optionLabel="label"
|
||||||
@@ -33,13 +33,14 @@
|
|||||||
[label]="$index === 0 ? 'پرداخت با پایانه' : 'پرداخت با پایانه - مرحله ' + ($index + 1)"
|
[label]="$index === 0 ? 'پرداخت با پایانه' : 'پرداخت با پایانه - مرحله ' + ($index + 1)"
|
||||||
type="price"
|
type="price"
|
||||||
[max]="terminalsMax()[$index]"
|
[max]="terminalsMax()[$index]"
|
||||||
|
[disabled]="payByTerminalSteps()[$index].payed"
|
||||||
>
|
>
|
||||||
<ng-template #suffixTemp>
|
<ng-template #suffixTemp>
|
||||||
<button
|
<button
|
||||||
pButton
|
pButton
|
||||||
size="small"
|
size="small"
|
||||||
type="button"
|
type="button"
|
||||||
[disabled]="!remainedAmount()"
|
[disabled]="!remainedAmount() || payByTerminalSteps()[$index].payed"
|
||||||
(click)="fillTerminalRemained($index)"
|
(click)="fillTerminalRemained($index)"
|
||||||
>
|
>
|
||||||
تمامی بدهی باقیمانده
|
تمامی بدهی باقیمانده
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angu
|
|||||||
import { ButtonDirective } from 'primeng/button';
|
import { ButtonDirective } from 'primeng/button';
|
||||||
import { Select } from 'primeng/select';
|
import { Select } from 'primeng/select';
|
||||||
import { catchError, throwError } from 'rxjs';
|
import { catchError, throwError } from 'rxjs';
|
||||||
import { IPayment, IPosOrderResponse, TOrderPaymentTypes } from '../../models';
|
import {
|
||||||
|
IPayment,
|
||||||
|
IPosOrderResponse,
|
||||||
|
PosPaymentResult,
|
||||||
|
TerminalSuccessPaymentPayload,
|
||||||
|
TOrderPaymentTypes,
|
||||||
|
} from '../../models';
|
||||||
import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract';
|
import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract';
|
||||||
import { PosPaymentBridgeService } from '../../services/payment-bridge.service';
|
import { PosPaymentBridgeService } from '../../services/payment-bridge.service';
|
||||||
import { PosLandingStore } from '../../store/main.store';
|
import { PosLandingStore } from '../../store/main.store';
|
||||||
@@ -48,25 +54,57 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
|
|
||||||
submitOrderLoading = computed(() => this.store.submitOrderLoading());
|
submitOrderLoading = computed(() => this.store.submitOrderLoading());
|
||||||
|
|
||||||
payByTerminalSteps = Array.from({ length: 10 }, (_, i) => ({
|
payByTerminalSteps = signal(
|
||||||
value: i + 1,
|
Array.from({ length: 10 }, (_, i) => ({
|
||||||
label: `${i + 1} مرحلهای`,
|
value: i + 1,
|
||||||
}));
|
payed: false,
|
||||||
|
info: {} as TerminalSuccessPaymentPayload,
|
||||||
|
label: `${i + 1} مرحلهای`,
|
||||||
|
amount: 0,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
selectedPayByTerminalStep = signal(1);
|
selectedPayByTerminalStep = signal(1);
|
||||||
payedInTerminal = signal<Array<number>>([]);
|
|
||||||
|
|
||||||
private restorePaymentCallback: Maybe<() => void> = null;
|
private restorePaymentCallback: Maybe<() => void> = null;
|
||||||
|
|
||||||
private readonly injectedPaymentResultHandler = (payload: unknown) => {
|
private readonly injectedPaymentResultHandler = (payload: PosPaymentResult) => {
|
||||||
alert('پرداخت با موفقیت انجام شد');
|
if (payload.status === 'SUCCESS') {
|
||||||
this.toastServices.success({ text: 'شد شد شد شد شد' });
|
const paidStepId = Number(payload.id || 0);
|
||||||
// this.handlePaymentResult(payload);
|
this.payByTerminalSteps.update((prev) =>
|
||||||
|
prev.map((step, index) => {
|
||||||
|
if (step.value === paidStepId) {
|
||||||
|
return {
|
||||||
|
...step,
|
||||||
|
payed: true,
|
||||||
|
amount: this.terminalControls[step.value - 1]?.value || 0,
|
||||||
|
info: {
|
||||||
|
customer_card_no: payload.data?.customerCardNO || '',
|
||||||
|
description: payload.message || '',
|
||||||
|
rrn: payload.data?.rrn || '',
|
||||||
|
stan: payload.data?.stan || '',
|
||||||
|
terminal_id: payload.data?.terminalId || '',
|
||||||
|
transaction_date_time: new Date(payload.data?.transactionDateTime || ''),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return step;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const terminalControl = this.terminalControls[paidStepId - 1];
|
||||||
|
if (terminalControl) {
|
||||||
|
terminalControl.disable({ onlySelf: true, emitEvent: false });
|
||||||
|
}
|
||||||
|
const amount = Number(
|
||||||
|
terminalControl?.value || this.extractAmountFromPaymentResult(payload) || 0,
|
||||||
|
);
|
||||||
|
this.toastServices.success({ text: `پرداخت با موفقیت انجام شد. مبلغ: ${amount}` });
|
||||||
|
} else {
|
||||||
|
this.toastServices.error({ text: payload.message || 'پرداخت با دستگاه انجام نشد.' });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
console.log('ngOnViewInit');
|
|
||||||
|
|
||||||
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener(
|
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener(
|
||||||
this.injectedPaymentResultHandler,
|
this.injectedPaymentResultHandler,
|
||||||
);
|
);
|
||||||
@@ -118,7 +156,11 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
this.terminalsMax.update((max) => {
|
this.terminalsMax.update((max) => {
|
||||||
const terminalMax = remainedAmount + (terminal.value || 0);
|
const terminalMax = remainedAmount + (terminal.value || 0);
|
||||||
terminal.addValidators([Validators.max(terminalMax)]);
|
terminal.addValidators([Validators.max(terminalMax)]);
|
||||||
if (terminalMax) {
|
if (this.isTerminalStepPaid(i + 1)) {
|
||||||
|
terminal.disable({
|
||||||
|
onlySelf: true,
|
||||||
|
});
|
||||||
|
} else if (terminalMax) {
|
||||||
terminal.enable({
|
terminal.enable({
|
||||||
onlySelf: true,
|
onlySelf: true,
|
||||||
});
|
});
|
||||||
@@ -145,7 +187,13 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
}
|
}
|
||||||
|
|
||||||
changePayByTerminalSteps(steps: number) {
|
changePayByTerminalSteps(steps: number) {
|
||||||
const normalizedSteps = Math.max(1, Number(steps) || 1);
|
const minSteps = this.getMinAllowedSteps();
|
||||||
|
const normalizedSteps = Math.max(minSteps, Number(steps) || 1);
|
||||||
|
if ((Number(steps) || 1) < minSteps) {
|
||||||
|
this.toastServices.warn({
|
||||||
|
text: `حداقل تعداد مراحل به دلیل پرداخت های انجام شده ${minSteps} است.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.selectedPayByTerminalStep.set(normalizedSteps);
|
this.selectedPayByTerminalStep.set(normalizedSteps);
|
||||||
|
|
||||||
const terminals = this.form.controls.terminals;
|
const terminals = this.form.controls.terminals;
|
||||||
@@ -219,10 +267,24 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rawPayment = this.form.getRawValue();
|
const rawPayment = this.form.getRawValue();
|
||||||
|
|
||||||
|
const terminalPayments = this.payByTerminalSteps()
|
||||||
|
.slice(0, this.selectedPayByTerminalStep())
|
||||||
|
.filter((step) => step.payed)
|
||||||
|
.map((step) => ({
|
||||||
|
amount: Number(this.terminalControls[step.value - 1]?.value || step.amount || 0),
|
||||||
|
terminal_id: step.info.terminal_id || '',
|
||||||
|
stan: step.info.stan || '',
|
||||||
|
rrn: step.info.rrn || '',
|
||||||
|
response_code: '00',
|
||||||
|
customer_card_no: step.info.customer_card_no || '',
|
||||||
|
transaction_date_time: step.info.transaction_date_time || new Date(),
|
||||||
|
description: step.info.description || '',
|
||||||
|
}));
|
||||||
const payment: IPayment = {
|
const payment: IPayment = {
|
||||||
cash: Number(rawPayment.cash || 0),
|
cash: Number(rawPayment.cash || 0),
|
||||||
set_off: Number(rawPayment.set_off || 0),
|
set_off: Number(rawPayment.set_off || 0),
|
||||||
terminals: (rawPayment.terminals || []).map((value) => Number(value || 0)),
|
terminals: terminalPayments,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.store.setPayment(payment);
|
this.store.setPayment(payment);
|
||||||
@@ -230,6 +292,9 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
const terminalPaymentIsDone = this.checkTerminalPayments();
|
const terminalPaymentIsDone = this.checkTerminalPayments();
|
||||||
|
|
||||||
if (terminalPaymentIsDone) {
|
if (terminalPaymentIsDone) {
|
||||||
|
this.toastService.info({
|
||||||
|
text: `terminalPayments[0].customer_card_no: ${payment.terminals[0]?.customer_card_no || ''}`,
|
||||||
|
});
|
||||||
this.store
|
this.store
|
||||||
.submitOrder()
|
.submitOrder()
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -245,23 +310,15 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private checkTerminalPayments(): boolean {
|
private checkTerminalPayments(): boolean {
|
||||||
this.form.controls.terminals.controls = this.form.controls.terminals.controls.filter(
|
const pendingTerminalPayments = this.getPendingTerminalPayments();
|
||||||
(control) => control.value,
|
if (pendingTerminalPayments.length) {
|
||||||
);
|
|
||||||
const terminalPayments = this.form.controls.terminals.controls.map(
|
|
||||||
(terminal) => terminal.value!,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.selectedPayByTerminalStep.set(terminalPayments.length || 1);
|
|
||||||
|
|
||||||
if (terminalPayments.length) {
|
|
||||||
if (this.nativeBridgeService.isEnabled()) {
|
if (this.nativeBridgeService.isEnabled()) {
|
||||||
for (let i in terminalPayments) {
|
const nextPending = pendingTerminalPayments[0];
|
||||||
const terminal = terminalPayments[i];
|
this.payByTerminal(nextPending.stepId, nextPending.amount);
|
||||||
this.toastServices.info({ text: 'در حال پردازش پرداخت با دستگاه...', life: 3000 });
|
this.toastServices.info({
|
||||||
this.payByTerminal(parseInt(i), terminal);
|
text: 'پس از تایید پرداخت هر مرحله، برای مرحله بعد دوباره روی ثبت پرداخت بزنید.',
|
||||||
break;
|
life: 3500,
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
this.toastServices.error({
|
this.toastServices.error({
|
||||||
text: 'امکان ارتباط با دستگاه پرداخت میسر نیست.',
|
text: 'امکان ارتباط با دستگاه پرداخت میسر نیست.',
|
||||||
@@ -278,7 +335,7 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
private payByTerminal(id: number, amount: number) {
|
private payByTerminal(id: number, amount: number) {
|
||||||
const payResult = this.nativeBridgeService.pay({
|
const payResult = this.nativeBridgeService.pay({
|
||||||
amount,
|
amount,
|
||||||
id: id.toString(),
|
id: String(id),
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (!payResult.success) {
|
// if (!payResult.success) {
|
||||||
@@ -288,6 +345,27 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isTerminalStepPaid(stepId: number): boolean {
|
||||||
|
return this.payByTerminalSteps().some((step) => step.value === stepId && step.payed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMinAllowedSteps() {
|
||||||
|
const paidSteps = this.payByTerminalSteps()
|
||||||
|
.slice(0, this.selectedPayByTerminalStep())
|
||||||
|
.filter((step) => step.payed)
|
||||||
|
.map((step) => step.value);
|
||||||
|
return paidSteps.length ? Math.max(...paidSteps) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPendingTerminalPayments() {
|
||||||
|
return this.terminalControls
|
||||||
|
.map((control, index) => ({
|
||||||
|
stepId: index + 1,
|
||||||
|
amount: Number(control.value || 0),
|
||||||
|
}))
|
||||||
|
.filter(({ stepId, amount }) => amount > 0 && !this.isTerminalStepPaid(stepId));
|
||||||
|
}
|
||||||
|
|
||||||
override onSuccess(response: IPosOrderResponse): void {}
|
override onSuccess(response: IPosOrderResponse): void {}
|
||||||
|
|
||||||
private extractAmountFromPaymentResult(payload: unknown): number | null {
|
private extractAmountFromPaymentResult(payload: unknown): number | null {
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export * from './customer';
|
|||||||
export * from './io';
|
export * from './io';
|
||||||
export * from './payload';
|
export * from './payload';
|
||||||
export * from './payment';
|
export * from './payment';
|
||||||
|
export * from './posPaymentResult';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface IPayment {
|
export interface IPayment {
|
||||||
terminals: number[];
|
terminals: IPaymentTerminal[];
|
||||||
cash: number;
|
cash: number;
|
||||||
set_off: number;
|
set_off: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export interface PosPaymentResult {
|
||||||
|
id?: string;
|
||||||
|
status: 'SUCCESS' | 'FAILURE';
|
||||||
|
message?: string;
|
||||||
|
data?: {
|
||||||
|
terminalId: string;
|
||||||
|
stan: string;
|
||||||
|
rrn: string;
|
||||||
|
responseCode: string;
|
||||||
|
customerCardNO: string;
|
||||||
|
transactionDateTime: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TerminalSuccessPaymentPayload {
|
||||||
|
terminal_id: string;
|
||||||
|
stan: string;
|
||||||
|
rrn: string;
|
||||||
|
transaction_date_time: Date;
|
||||||
|
customer_card_no: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { PosPaymentResult } from '../models';
|
||||||
|
|
||||||
export abstract class PosPaymentBridgeAbstract {
|
export abstract class PosPaymentBridgeAbstract {
|
||||||
abstract registerPaymentResultListener(handler: (payload: unknown) => void): () => void;
|
abstract registerPaymentResultListener(handler: (payload: PosPaymentResult) => void): () => void;
|
||||||
abstract emitPaymentResultForTest(payload: unknown): void;
|
abstract emitPaymentResultForTest(payload: unknown): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,51 @@
|
|||||||
import { ToastService } from '@/core/services/toast.service';
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { PosPaymentResult } from '../models';
|
||||||
import { PosPaymentBridgeAbstract } from './payment-bridge.abstract';
|
import { PosPaymentBridgeAbstract } from './payment-bridge.abstract';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PosPaymentBridgeService extends PosPaymentBridgeAbstract {
|
export class PosPaymentBridgeService extends PosPaymentBridgeAbstract {
|
||||||
private readonly toastServices = inject(ToastService);
|
private readonly toastServices = inject(ToastService);
|
||||||
registerPaymentResultListener(handler: (payload: unknown) => void): () => void {
|
registerPaymentResultListener(handler: (payload: PosPaymentResult) => void): () => void {
|
||||||
// const w = window as unknown as {
|
|
||||||
// WebV?: {
|
|
||||||
// onPaymentResult?: (payload: unknown) => void;
|
|
||||||
// AndroidBridge?: {
|
|
||||||
// onPaymentResult?: (payload: unknown) => void;
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
this.toastServices.error({ text: '@@@@@@@@@@@@@' });
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.WebV = {
|
window.WebV = {
|
||||||
onPaymentResult: () => {
|
onPaymentResult: (payload: PosPaymentResult) => {
|
||||||
this.toastServices.error({ text: 'asdasdsadassadasdas' });
|
try {
|
||||||
|
// const parsedPayload: PosPaymentResult = JSON.parse(payload);
|
||||||
|
// this.toastServices.info({ text: payload.data?.customerCardNO || '', life: 10000 });
|
||||||
|
// this.toastServices.info({
|
||||||
|
// text: payload.data?.responseCode || 'No response code',
|
||||||
|
// life: 10000,
|
||||||
|
// });
|
||||||
|
// this.toastServices.info({ text: payload.data?.rrn || 'No response code', life: 10000 });
|
||||||
|
// this.toastServices.info({ text: payload.data?.stan || 'No response code', life: 10000 });
|
||||||
|
// this.toastServices.info({
|
||||||
|
// text: payload.data?.terminalId || 'No response code',
|
||||||
|
// life: 10000,
|
||||||
|
// });
|
||||||
|
// this.toastServices.info({
|
||||||
|
// text: payload.data?.transactionDateTime || 'No response code',
|
||||||
|
// life: 10000,
|
||||||
|
// });
|
||||||
|
handler(payload);
|
||||||
|
} catch (e) {
|
||||||
|
this.toastServices.error({ text: 'Failed to parse payment result', life: 10000 });
|
||||||
|
this.toastServices.error({
|
||||||
|
text: e instanceof Error ? e.message : String(e),
|
||||||
|
life: 10000,
|
||||||
|
});
|
||||||
|
this.toastServices.error({ text: typeof payload, life: 10000 });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
emitPaymentResultForTest(payload: unknown): void {
|
emitPaymentResultForTest(payload: PosPaymentResult): void {
|
||||||
const w = window as unknown as { WebV?: { onPaymentResult?: (payload: unknown) => void } };
|
const w = window as unknown as {
|
||||||
|
WebV?: { onPaymentResult?: (payload: PosPaymentResult) => void };
|
||||||
|
};
|
||||||
w.WebV?.onPaymentResult?.(payload);
|
w.WebV?.onPaymentResult?.(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
// import { ICustomerResponse } from '@/modules/customers/models';
|
// import { ICustomerResponse } from '@/modules/customers/models';
|
||||||
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
|
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
|
||||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||||
import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils';
|
import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils';
|
||||||
@@ -55,6 +56,7 @@ export const INITIAL_POS_STATE: IPosLandingState = {
|
|||||||
@Injectable({ providedIn: 'any' })
|
@Injectable({ providedIn: 'any' })
|
||||||
export class PosLandingStore {
|
export class PosLandingStore {
|
||||||
private readonly service = inject(PosService);
|
private readonly service = inject(PosService);
|
||||||
|
private readonly toastServices = inject(ToastService);
|
||||||
|
|
||||||
private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE });
|
private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE });
|
||||||
|
|
||||||
@@ -238,6 +240,10 @@ export class PosLandingStore {
|
|||||||
total_amount: this.orderPricingInfo().totalAmount,
|
total_amount: this.orderPricingInfo().totalAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.toastServices.info({
|
||||||
|
text: `payment submitted: ${this.payments().terminals[0]?.customer_card_no || ''}`,
|
||||||
|
});
|
||||||
|
|
||||||
let res: Maybe<IPosOrderResponse> = null;
|
let res: Maybe<IPosOrderResponse> = null;
|
||||||
|
|
||||||
return this.service.submitOrder(orderPayload).pipe(
|
return this.service.submitOrder(orderPayload).pipe(
|
||||||
|
|||||||
@@ -1,49 +1,51 @@
|
|||||||
<div class="w-full min-h-[60svh] p-4 md:p-6">
|
@defer {
|
||||||
<section class="mx-auto max-w-3xl overflow-hidden rounded-2xl border border-surface-200 bg-white">
|
<div class="w-full min-h-[60svh] p-4 md:p-6">
|
||||||
<div class="bg-gradient-to-l from-sky-50 via-cyan-50 to-teal-50 p-5 md:p-7">
|
<section class="mx-auto max-w-3xl overflow-hidden rounded-2xl border border-surface-200 bg-white">
|
||||||
<span class="block mb-4 text-xl font-bold text-surface-900 md:text-3xl">پشتیبانی {{ brandingInfo.title }}</span>
|
<div class="bg-gradient-to-l from-sky-50 via-cyan-50 to-teal-50 p-5 md:p-7">
|
||||||
<p class="mt-3 max-w-2xl text-sm leading-7 text-surface-700 md:text-base">
|
<span class="block mb-4 text-xl font-bold text-surface-900 md:text-3xl">پشتیبانی {{ brandingInfo.title }}</span>
|
||||||
در صورت بروز مشکل در ثبت سفارش، صدور صورتحساب یا تنظیمات دستگاه، تیم پشتیبانی آماده پاسخگویی است. لطفا هنگام
|
<p class="mt-3 max-w-2xl text-sm leading-7 text-surface-700 md:text-base">
|
||||||
تماس، نام پایانه و نام کاربری را همراه داشته باشید.
|
در صورت بروز مشکل در ثبت سفارش، صدور صورتحساب یا تنظیمات دستگاه، تیم پشتیبانی آماده پاسخگویی است. لطفا هنگام
|
||||||
</p>
|
تماس، نام پایانه و نام کاربری را همراه داشته باشید.
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-3 p-4 md:grid-cols-2 md:gap-4 md:p-6">
|
<div class="grid gap-3 p-4 md:grid-cols-2 md:gap-4 md:p-6">
|
||||||
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
||||||
<span class="block mb-4 text-xl font-bold text-surface-900">شماره های تماس مستقیم</span>
|
<span class="block mb-4 text-xl font-bold text-surface-900">شماره های تماس مستقیم</span>
|
||||||
@if (brandingInfo!.support.phones?.length) {
|
@if (brandingInfo!.support.phones?.length) {
|
||||||
<ul class="space-y-2 text-sm text-surface-700">
|
<ul class="space-y-2 text-sm text-surface-700">
|
||||||
@for (phone of brandingInfo.support.phones!; track $index) {
|
@for (phone of brandingInfo.support.phones!; track $index) {
|
||||||
<li class="flex items-start justify-between gap-2">
|
<li class="flex items-start justify-between gap-2">
|
||||||
<span>{{ phone.title }}</span>
|
<span>{{ phone.title }}</span>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@for (item of phone.phones; track $index) {
|
@for (item of phone.phones; track $index) {
|
||||||
<a class="font-semibold text-primary" href="tel:{{ item }}" dir="ltr">{{ item }}</a>
|
<a class="font-semibold text-primary" href="tel:{{ item }}" dir="ltr">{{ item }}</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
</ul>
|
||||||
|
} @else {
|
||||||
|
<p class="text-sm text-surface-700">در حال حاضر شماره تماس مستقیمی برای پشتیبانی اعلام نشده است.</p>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
||||||
|
<span class="block mb-4 text-xl font-bold text-surface-900">راهنمای سریع قبل از تماس</span>
|
||||||
|
<ul class="space-y-2 text-sm leading-6 text-surface-700">
|
||||||
|
<li>اتصال اینترنت دستگاه را بررسی کنید.</li>
|
||||||
|
<li>یک بار برنامه را ببندید و دوباره اجرا کنید.</li>
|
||||||
|
<li>در صفحه درباره نرم افزار، نسخه برنامه را اعلام کنید.</li>
|
||||||
|
<li>در صورت خطا، متن خطا یا تصویر آن را برای پشتیبانی ارسال کنید.</li>
|
||||||
</ul>
|
</ul>
|
||||||
} @else {
|
</article>
|
||||||
<p class="text-sm text-surface-700">در حال حاضر شماره تماس مستقیمی برای پشتیبانی اعلام نشده است.</p>
|
</div>
|
||||||
}
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
|
<div class="border-t border-surface-200 bg-surface-50 px-4 py-4 md:px-6">
|
||||||
<span class="block mb-4 text-xl font-bold text-surface-900">راهنمای سریع قبل از تماس</span>
|
<p class="text-xs leading-6 text-surface-600 md:text-sm">
|
||||||
<ul class="space-y-2 text-sm leading-6 text-surface-700">
|
ساعات پاسخگویی: شنبه تا چهارشنبه 8:30 تا 17:30 و پنجشنبه 8:30 تا 13:00
|
||||||
<li>اتصال اینترنت دستگاه را بررسی کنید.</li>
|
</p>
|
||||||
<li>یک بار برنامه را ببندید و دوباره اجرا کنید.</li>
|
</div>
|
||||||
<li>در صفحه درباره نرم افزار، نسخه برنامه را اعلام کنید.</li>
|
</section>
|
||||||
<li>در صورت خطا، متن خطا یا تصویر آن را برای پشتیبانی ارسال کنید.</li>
|
</div>
|
||||||
</ul>
|
}
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="border-t border-surface-200 bg-surface-50 px-4 py-4 md:px-6">
|
|
||||||
<p class="text-xs leading-6 text-surface-600 md:text-sm">
|
|
||||||
ساعات پاسخگویی: شنبه تا چهارشنبه 8:30 تا 17:30 و پنجشنبه 8:30 تا 13:00
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// TIS tenant environment configuration
|
// TIS tenant environment configuration
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
// apiBaseUrl: 'https://psp-api.shift-am.ir',
|
apiBaseUrl: 'https://psp-api.shift-am.ir',
|
||||||
apiBaseUrl: 'http://192.168.128.73:5002',
|
// apiBaseUrl: 'http://192.168.128.73:5002',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 5000,
|
port: 5000,
|
||||||
enableLogging: false,
|
enableLogging: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user