patment part ui complated
This commit is contained in:
@@ -1,22 +0,0 @@
|
|||||||
// ─── Pricing Constants ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export const PRICING = {
|
|
||||||
SERVICE_FEE: 45_000,
|
|
||||||
CERTIFICATE_FEE: 15_000,
|
|
||||||
CURRENCY: 'UZS',
|
|
||||||
// Payme works in tiyin (1 UZS = 100 tiyin)
|
|
||||||
TIYIN_MULTIPLIER: 100,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
// ─── Payme Config ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export const PAYME_CONFIG = {
|
|
||||||
MERCHANT_ID: process.env.NEXT_PUBLIC_PAYME_MERCHANT_ID ?? 'your_merchant_id',
|
|
||||||
BASE_URL: 'https://checkout.paycom.uz',
|
|
||||||
// In development, point to your own backend
|
|
||||||
API_ENDPOINT: '/api/payments/payme/create',
|
|
||||||
RETURN_URL:
|
|
||||||
typeof window !== 'undefined'
|
|
||||||
? `${window.location.origin}/payment/success`
|
|
||||||
: 'https://yourapp.uz/payment/success',
|
|
||||||
} as const;
|
|
||||||
@@ -7,11 +7,6 @@ export interface PaymePaymentRequest {
|
|||||||
returnUrl: string;
|
returnUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymePaymentResponse {
|
|
||||||
redirectUrl: string;
|
|
||||||
transactionId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PaymentStatus = 'idle' | 'loading' | 'success' | 'error';
|
export type PaymentStatus = 'idle' | 'loading' | 'success' | 'error';
|
||||||
|
|
||||||
// ─── Component Props ───────────────────────────────────────────────────────────
|
// ─── Component Props ───────────────────────────────────────────────────────────
|
||||||
@@ -29,10 +24,3 @@ export interface PaymentModalProps {
|
|||||||
onConfirmPayment: () => void;
|
onConfirmPayment: () => void;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymeButtonProps {
|
|
||||||
amount: number;
|
|
||||||
orderId: string;
|
|
||||||
onSuccess?: (response: PaymePaymentResponse) => void;
|
|
||||||
onError?: (error: Error) => void;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import { useState, useCallback } from 'react';
|
|
||||||
import { PaymentStatus, PaymePaymentResponse } from './types';
|
|
||||||
import {
|
|
||||||
calculateTotal,
|
|
||||||
createPaymePayment,
|
|
||||||
generateOrderId,
|
|
||||||
redirectToPayme,
|
|
||||||
toTiyin,
|
|
||||||
} from './utils';
|
|
||||||
import { PAYME_CONFIG } from './constant';
|
|
||||||
|
|
||||||
interface UsePaymentOptions {
|
|
||||||
hasCertificate: boolean;
|
|
||||||
onSuccess?: (response: PaymePaymentResponse) => void;
|
|
||||||
onError?: (error: Error) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UsePaymentReturn {
|
|
||||||
status: PaymentStatus;
|
|
||||||
error: string | null;
|
|
||||||
totalAmount: number;
|
|
||||||
handlePaymePayment: () => Promise<void>;
|
|
||||||
resetError: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePayment = ({
|
|
||||||
hasCertificate,
|
|
||||||
onSuccess,
|
|
||||||
onError,
|
|
||||||
}: UsePaymentOptions): UsePaymentReturn => {
|
|
||||||
const [status, setStatus] = useState<PaymentStatus>('idle');
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const totalAmount = calculateTotal(hasCertificate);
|
|
||||||
|
|
||||||
const handlePaymePayment = useCallback(async () => {
|
|
||||||
setStatus('loading');
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
const orderId = generateOrderId();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await createPaymePayment({
|
|
||||||
amount: toTiyin(totalAmount),
|
|
||||||
orderId,
|
|
||||||
description: `Service fee${hasCertificate ? ' + Certificate' : ''}`,
|
|
||||||
returnUrl: PAYME_CONFIG.RETURN_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
setStatus('success');
|
|
||||||
onSuccess?.(response);
|
|
||||||
redirectToPayme(response.redirectUrl);
|
|
||||||
} catch (err) {
|
|
||||||
const paymentError =
|
|
||||||
err instanceof Error
|
|
||||||
? err
|
|
||||||
: new Error('Payment failed. Please try again.');
|
|
||||||
setStatus('error');
|
|
||||||
setError(paymentError.message);
|
|
||||||
onError?.(paymentError);
|
|
||||||
}
|
|
||||||
}, [totalAmount, hasCertificate, onSuccess, onError]);
|
|
||||||
|
|
||||||
const resetError = useCallback(() => {
|
|
||||||
setError(null);
|
|
||||||
setStatus('idle');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { status, error, totalAmount, handlePaymePayment, resetError };
|
|
||||||
};
|
|
||||||
@@ -2,14 +2,6 @@
|
|||||||
export const formatPrice = (amount: number, currency: string): string =>
|
export const formatPrice = (amount: number, currency: string): string =>
|
||||||
`${amount.toLocaleString('uz-UZ')} ${currency}`;
|
`${amount.toLocaleString('uz-UZ')} ${currency}`;
|
||||||
|
|
||||||
// ─── Order ID Generator ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export const generateOrderId = (): string => {
|
|
||||||
const timestamp = Date.now();
|
|
||||||
const random = Math.random().toString(36).slice(2, 8).toUpperCase();
|
|
||||||
return `ORDER-${timestamp}-${random}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─── Payme API ─────────────────────────────────────────────────────────────────
|
// ─── Payme API ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,38 +34,6 @@ const CloseButton: React.FC<{ onClick: () => void }> = ({ onClick }) => (
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
// ─── Error Banner ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// const ErrorBanner: React.FC<{ message: string; onDismiss: () => void }> = ({
|
|
||||||
// message,
|
|
||||||
// onDismiss,
|
|
||||||
// }) => (
|
|
||||||
// <div
|
|
||||||
// role="alert"
|
|
||||||
// className="flex items-start gap-3 rounded-lg bg-red-50 border border-red-200 px-4 py-3"
|
|
||||||
// >
|
|
||||||
// <svg
|
|
||||||
// className="shrink-0 mt-0.5 text-red-500"
|
|
||||||
// width="16"
|
|
||||||
// height="16"
|
|
||||||
// viewBox="0 0 24 24"
|
|
||||||
// fill="currentColor"
|
|
||||||
// >
|
|
||||||
// <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
|
|
||||||
// </svg>
|
|
||||||
// <p className="text-sm text-red-700 flex-1">{message}</p>
|
|
||||||
// <button
|
|
||||||
// onClick={onDismiss}
|
|
||||||
// aria-label="Dismiss error"
|
|
||||||
// className="text-red-400 hover:text-red-600 transition-colors"
|
|
||||||
// >
|
|
||||||
// <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
||||||
// <line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
||||||
// </svg>
|
|
||||||
// </button>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
|
|
||||||
// ─── Security Badge ────────────────────────────────────────────────────────────
|
// ─── Security Badge ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const SecurityBadge: React.FC<{ securityText: string }> = ({
|
const SecurityBadge: React.FC<{ securityText: string }> = ({
|
||||||
|
|||||||
Reference in New Issue
Block a user