fix: correct Cryptomus signature generation to use base64 encoding

- Updated signature algorithm to match Cryptomus API requirements (MD5(base64(JSON) + API_KEY))
- Removed unnecessary key sorting that was incompatible with Cryptomus specification
- Simplified payment flow to redirect immediately to payment URL instead of showing intermediate success state
This commit is contained in:
Yusuf İpek
2025-11-17 17:32:58 +03:00
parent ea792a8a7f
commit ce4d0a6e3a
3 changed files with 76 additions and 140 deletions
+4 -15
View File
@@ -25,23 +25,12 @@ interface CryptomusResponse {
} }
function generateSign(data: any, apiKey: string): string { function generateSign(data: any, apiKey: string): string {
// Convert data to JSON and sort keys for consistent signature // Cryptomus signature: MD5(base64(JSON) + API_KEY)
const sortedData = Object.keys(data) const jsonString = JSON.stringify(data)
.sort() const base64Data = Buffer.from(jsonString).toString('base64')
.reduce((result: any, key: string) => {
result[key] = data[key]
return result
}, {})
const jsonString = JSON.stringify(sortedData)
// Create MD5 hash using Web Crypto API
const encoder = new TextEncoder()
const dataBuffer = encoder.encode(jsonString + apiKey)
// For server-side, we'll use Node.js crypto
const crypto = require('crypto') const crypto = require('crypto')
return crypto.createHash('md5').update(jsonString + apiKey).digest('hex') return crypto.createHash('md5').update(base64Data + apiKey).digest('hex')
} }
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
+4 -10
View File
@@ -15,19 +15,13 @@ function verifySign(data: any, apiKey: string): boolean {
// Extract sign from data and create a copy without it // Extract sign from data and create a copy without it
const { sign: receivedSign, ...dataToVerify } = data const { sign: receivedSign, ...dataToVerify } = data
// Sort keys and create JSON string // Cryptomus signature: MD5(base64(JSON) + API_KEY)
const sortedData = Object.keys(dataToVerify) const jsonString = JSON.stringify(dataToVerify)
.sort() const base64Data = Buffer.from(jsonString).toString('base64')
.reduce((result: any, key: string) => {
result[key] = dataToVerify[key]
return result
}, {})
const jsonString = JSON.stringify(sortedData)
// Generate expected signature // Generate expected signature
const crypto = require('crypto') const crypto = require('crypto')
const expectedSign = crypto.createHash('md5').update(jsonString + apiKey).digest('hex') const expectedSign = crypto.createHash('md5').update(base64Data + apiKey).digest('hex')
return receivedSign === expectedSign return receivedSign === expectedSign
} }
+5 -52
View File
@@ -7,24 +7,18 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { useLocale } from '@/contexts/LocaleContext' import { useLocale } from '@/contexts/LocaleContext'
import { Loader2, Heart, AlertCircle, CheckCircle } from 'lucide-react' import { Loader2, Heart, AlertCircle } from 'lucide-react'
interface SupportModalProps { interface SupportModalProps {
isOpen: boolean isOpen: boolean
onClose: () => void onClose: () => void
} }
interface PaymentData {
payment_url: string
payment_id: string
}
export function SupportModal({ isOpen, onClose }: SupportModalProps) { export function SupportModal({ isOpen, onClose }: SupportModalProps) {
const [amount, setAmount] = useState('') const [amount, setAmount] = useState('')
const [currency, setCurrency] = useState('USDT') const [currency, setCurrency] = useState('USDT')
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [payment, setPayment] = useState<PaymentData | null>(null)
const [error, setError] = useState('') const [error, setError] = useState('')
const { t } = useLocale() const { t } = useLocale()
@@ -41,7 +35,6 @@ export function SupportModal({ isOpen, onClose }: SupportModalProps) {
e.preventDefault() e.preventDefault()
setLoading(true) setLoading(true)
setError('') setError('')
setPayment(null)
try { try {
const response = await fetch('/api/support/create-payment', { const response = await fetch('/api/support/create-payment', {
@@ -64,7 +57,10 @@ export function SupportModal({ isOpen, onClose }: SupportModalProps) {
throw new Error(data.error || 'Payment creation failed') throw new Error(data.error || 'Payment creation failed')
} }
setPayment(data) // Redirect immediately to payment URL
window.open(data.payment_url, '_blank')
resetForm()
onClose()
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error') setError(err instanceof Error ? err.message : 'Unknown error')
} finally { } finally {
@@ -76,7 +72,6 @@ export function SupportModal({ isOpen, onClose }: SupportModalProps) {
setAmount('') setAmount('')
setCurrency('USDT') setCurrency('USDT')
setEmail('') setEmail('')
setPayment(null)
setError('') setError('')
} }
@@ -108,7 +103,6 @@ export function SupportModal({ isOpen, onClose }: SupportModalProps) {
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{!payment ? (
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="amount">{t('support.amount')}</Label> <Label htmlFor="amount">{t('support.amount')}</Label>
@@ -173,47 +167,6 @@ export function SupportModal({ isOpen, onClose }: SupportModalProps) {
)} )}
</Button> </Button>
</form> </form>
) : (
<div className="space-y-4">
<div className="flex items-center space-x-2 text-green-600 bg-green-50 p-3 rounded-lg">
<CheckCircle className="h-4 w-4" />
<span className="text-sm">{t('support.payment_created')}</span>
</div>
<div className="space-y-2">
<Label>{t('support.payment_id')}</Label>
<div className="p-2 bg-muted rounded font-mono text-sm">
{payment.payment_id}
</div>
</div>
<div className="space-y-2">
<Label>{t('support.amount_to_pay')}</Label>
<div className="p-2 bg-muted rounded text-sm">
{amount} {currency}
</div>
</div>
<Button
onClick={() => window.open(payment.payment_url, '_blank')}
className="w-full"
>
{t('support.pay_now')}
</Button>
<div className="text-xs text-muted-foreground text-center">
{t('support.payment_note')}
</div>
<Button
variant="outline"
onClick={resetForm}
className="w-full"
>
{t('support.create_another')}
</Button>
</div>
)}
<div className="text-xs text-muted-foreground space-y-1"> <div className="text-xs text-muted-foreground space-y-1">
<p>{t('support.secure_payment')}</p> <p>{t('support.secure_payment')}</p>