Ödeme doğrulama ve sipariş takibi
Bu kılavuz, Ethereum, BNB Smart Chain ve TRON'da Volet.com akıllı sözleşmeleriyle yapılan emanetçi olmayan ödemelerin nasıl doğrulanacağını açıklar.
İşlem hash'i (txid) ödeme kanıtı değildir. Bir siparişi ödendi olarak işaretlemeden önce arka ucunuz yürütmeyi, kesinliği ve ödeme olaylarını bağımsız olarak kontrol etmelidir. Volet.com bu entegrasyonda Hosted Checkout'taki gibi Status URL bildirimleri göndermez.
Ödeme doğrulama nasıl çalışır
- Sipariş oluşturun. Beklenen ağı, varlığı, alıcı cüzdanı (
seller), işlemci sözleşmesini, üye işyerine gitmesi beklenen tutarı ve gerekiyorsa alıcının cüzdanını kaydedin. - Ödeme denemesini kaydedin. Ödemenin
txiddeğerini siparişle ilişkilendirin. USDTapprove()işlemlerinin hash'lerini ayrı saklayın. - Yürütmeyi ve kesinliği kontrol edin. Blokzincir verilerini arka uçtaki bir RPC bağlantısıyla veya barındırılan bir blokzincir hizmetiyle alın.
- Ödeme olaylarını doğrulayın. Onaylı işlemcinin yayınladığı
PaymentProcessedolayını bulun. USDT için ayrıca token'ın üye işyerine yapılan gerçekTransferişlemini doğrulayın. - Siparişle eşleştirin. Olaydaki üye işyeri adresini, alıcıyı (gerekiyorsa) ve net tutarı arka uçtaki değiştirilemez sipariş bilgileriyle karşılaştırın.
- Siparişi bir kez tamamlayın. Doğrulanmış ödemeyi atomik olarak kaydedin ve idempotent teslimatı başlatın.
Barındırılan bir sağlayıcı kullanabilirsiniz. Kendi blokzincir düğümünüzü çalıştırmanız gerekmez.
Ödeme olayları ve tutarlar
Belgelenmiş Volet.com işlemcileri, üye işyerine ödemeyi yaptıktan sonra PaymentProcessed olayını yayınlar:
event PaymentProcessed(
address indexed buyer,
address indexed seller,
uint256 commission,
uint256 totalPay
);
Bu dağıtımlarda üye işyerinin aldığı net tutar şöyle hesaplanır:
receivedAmount = totalPay - commission
Her iki değer de varlığın en küçük birimleriyle ifade edilir. Hesaplama, buyerPaysCommission ayarının her iki değeri için de geçerlidir.
| Varlık | Gereken zincir üstü kanıt |
|---|---|
| ETH, BNB | Başarılı ve kesinleşmiş yürütme ile onaylı yerel varlık işlemcisinin yayınladığı eşleşen bir PaymentProcessed kaydı |
| USDT ERC-20, BEP-20 | İşlemcinin eşleşen PaymentProcessed kaydı ve onaylı token sözleşmesinden USDT Transfer kaydı: receivedAmount tutarında işlemci → üye işyeri |
| TRX | Başarılı ve solidified yürütme ile onaylı TRX işlemcisinin yayınladığı eşleşen bir PaymentProcessed kaydı |
| USDT TRC-20 | İşlemcinin eşleşen PaymentProcessed kaydı ve onaylı token sözleşmesinden USDT TRC-20 Transfer kaydı: receivedAmount tutarında işlemci → üye işyeri |
Her olayı yayınlayan adresi her zaman kontrol edin. Olay adı doğru olsa bile ilgisiz bir sözleşmenin yayınladığı kayıt ödeme kanıtı değildir. Cüzdandan cüzdana doğrudan transferler, işlemci tabanlı bu ödeme akışının kapsamı dışındadır.
İşlemci entegrasyon belgelerine göre, üye işyerine aktarım başarısız olursa yerel ödemeler geri alınır; bu nedenle başarılı ve kesinleşmiş bir işlemdeki gerçek PaymentProcessed olayı yerel ödemenin kanıtıdır. Blok gezginlerindeki iç transfer görünümleri incelemede işe yarar, ancak burada açıklanan doğrulama yöntemi için gerekli değildir.
Bu olay kontrolleri, burada açıklanan dağıtılmış işlemci uygulamaları için belirleyicidir. ABI'yi ve işlemci adreslerini kullandığınız dağıtımla karşılaştırarak doğrulayın.
Kesinlik gereksinimleri
| Ağ | Tamamlanma koşulu |
|---|---|
| Ethereum | İşlem kesinleşmiş bir blokta; başarılı makbuz ve eşleşen ödeme olayları |
| BNB Smart Chain | İşlem, yapılandırılan ve sağlayıcının desteklediği kesinlik politikasını karşılıyor; başarılı makbuz ve eşleşen ödeme olayları |
| TRON | İşlem bir SolidityNode'dan alınabiliyor, makbuz SUCCESS bildiriyor ve ödeme olayları eşleşiyor |
Geliştirici örnekleri EVM ağlarında 15 onay kullanır. Bu bir onay sayısı politikasıdır, Ethereum kesinliği değildir. Aşağıdaki örnekler kesinleşmiş blok kontrolünü korur; ağınızın gerektirdiği kesinlik anlamını destekleyen bir sağlayıcı kullanın. Sağlayıcı bu bilgiyi veremiyorsa ödemeyi kesin saymak yerine inceleme durumunda tutun.
Bir sağlayıcı kullanılamıyorsa veya makbuz henüz görünmüyorsa yeniden deneyin. Boş bir yanıt, ödemenin başarısız olduğunu kanıtlamaz.
Sipariş yaşam döngüsü
| Durum | Anlamı | Teslim edilsin mi? |
|---|---|---|
created | Sipariş kaydedildi, ödeme gönderilmedi | Hayır |
approval_pending | USDT onay işlemi gönderildi | Hayır |
pending | Ödeme txid değeri kaydedildi; bloğa eklenme veya kesinlik bekleniyor | Hayır |
verifying | Yürütme, kesinlik ve olaylar kontrol ediliyor | Hayır |
paid | Doğrulanmış ödeme siparişle eşleşiyor | Evet, bir kez |
failed | Doğrulanmış yürütme hatası veya kesin doğrulama başarısızlığı | Hayır |
expired | Ödeme süresi doldu; gönderilen ödemelerin mutabakatı yine de gerekli | Mutabakat yapılana kadar hayır |
review | Eksik, belirsiz veya çelişkili doğrulama verileri | Hayır |
Sayfanın yenilenmesi, ödeme süresinin dolması veya tarayıcı sekmesinin kapanması arka uçtaki doğrulama işleyicisini durdurmamalıdır.
Beklenen ödemeyi kaydedin
Alıcıdan işlemi göndermesini istemeden önce ödeme kaydını oluşturun. Örnek:
function createPendingPaymentRecord(order) {
return {
orderId: order.orderId,
chain: order.chain, // e.g. 'ethereum', 'bsc', 'tron'
chainId: order.chainId ?? null,
asset: order.asset,
tokenAddress: order.tokenAddress ?? null,
processorAddress: order.processorAddress,
sellerAddress: order.sellerAddress,
buyerAddress: order.buyerAddress ?? null,
amountBaseUnits: order.amountBaseUnits.toString(),
expectedSellerAmount: order.expectedSellerAmount.toString(),
buyerPaysCommission: order.buyerPaysCommission,
approvalTxids: [],
paymentAttempts: [],
status: 'created'
};
}
amountBaseUnits, siparişin talep edilen tutarıdır. expectedSellerAmount, en küçük birimlerle üye işyerinin alması gereken net tutardır. Bu tutarı, sunucuda doğrulanmış sipariş ve onaylı komisyon yapılandırmasından, işlemcinin previewPayment() sonuçlarını kullanarak belirleyin ve kaydedin. Tarayıcıdan gelen rastgele verilerden veya koda sabitlenmiş bir komisyon yüzdesinden türetmeyin.
USDT için Ethereum ve TRON'da 6 ondalık basamak, BNB Smart Chain'de 18 ondalık basamak kullanın. ETH ve BNB 18, TRX ise 6 (SUN) ondalık basamak kullanır. Token tutarlarını hesaplarken asla kayan noktalı aritmetik kullanmayın.
Onaylı ağ, token ve işlemci adreslerini arka uçtaki bir izin listesinde saklayın. transfer() fonksiyonunda orderId parametresi yoktur; bu nedenle ödeme denemelerini siparişlere uygulamanız bağlamalıdır.
Benzersiz ödeme denemesi kayıtları kullanın ve zincir üstü bir ödeme olayının birden fazla siparişe atanamayacağını garanti edin. Bir olay (chain, txid, logIndex) ile tanımlanır. Sipariş teslimatını da idempotent hale getirin. Müşterinin yeniden denemesi, önceki bekleyen hash'in üzerine yazmak yerine yeni bir deneme oluşturmalıdır.
Ethereum ve BNB Smart Chain doğrulaması
Arka uçta ethers v6 kullanın. Aşağıdaki örnek yürütmeyi ve kesinliği doğrular, ardından gerçek bir PaymentProcessed kaydını kontrol eder. USDT için ayrıca gerçek token transferini kontrol eder.
order bağımsız değişkeni arka uçtan gelen güvenilir değerler içermelidir. order.chainId ile aynı ağın provider nesnesini iletin.
import { ethers } from 'ethers';
const events = new ethers.Interface([
'event PaymentProcessed(address indexed buyer, address indexed seller, uint256 commission, uint256 totalPay)',
'event Transfer(address indexed from, address indexed to, uint256 value)'
]);
function sameEvmAddress(a, b) {
return ethers.getAddress(a) === ethers.getAddress(b);
}
async function verifyEvmPayment(provider, txid, order) {
const chainId = (await provider.getNetwork()).chainId;
if (chainId !== BigInt(order.chainId)) {
return { state: 'review', reason: 'Unexpected network' };
}
const receipt = await provider.getTransactionReceipt(txid);
if (!receipt) return { state: 'pending' };
if (receipt.status !== 1) return { state: 'failed', reason: 'Execution reverted' };
let finalized;
try {
finalized = await provider.getBlock('finalized');
} catch {
return { state: 'review', reason: 'Finality data unavailable' };
}
if (!finalized || receipt.blockNumber > finalized.number) {
return { state: 'pending' };
}
// Make sure the receipt still belongs to the canonical block.
const block = await provider.getBlock(receipt.blockNumber);
if (!block || block.hash !== receipt.blockHash) {
return { state: 'review', reason: 'Block mismatch' };
}
const processor = ethers.getAddress(order.processorAddress);
const seller = ethers.getAddress(order.sellerAddress);
const token = order.tokenAddress
? ethers.getAddress(order.tokenAddress)
: null;
const expectedNet = BigInt(order.expectedSellerAmount);
for (const log of receipt.logs) {
if (!sameEvmAddress(log.address, processor)) continue;
let event;
try { event = events.parseLog(log); } catch { continue; }
if (event?.name !== 'PaymentProcessed') continue;
if (!sameEvmAddress(event.args.seller, seller)) continue;
if (order.buyerAddress &&
!sameEvmAddress(event.args.buyer, order.buyerAddress)) continue;
const net = event.args.totalPay - event.args.commission;
if (net < 0n || net !== expectedNet) continue;
if (token) {
const tokenPaid = receipt.logs.some((otherLog) => {
if (!sameEvmAddress(otherLog.address, token)) return false;
let transfer;
try { transfer = events.parseLog(otherLog); } catch { return false; }
return transfer?.name === 'Transfer' &&
sameEvmAddress(transfer.args.from, processor) &&
sameEvmAddress(transfer.args.to, seller) &&
transfer.args.value === net;
});
if (!tokenPaid) continue;
}
return {
state: 'matched',
buyer: event.args.buyer,
sellerReceived: net.toString(),
eventId: `${order.chain}:${txid.toLowerCase()}:${log.index}`
};
}
return { state: 'review', reason: 'No matching payment event' };
}
Bu sonucu nasıl kullanmalı: matched, ödeme kanıtının bu fonksiyonun kontrollerinden geçtiği anlamına gelir. Siparişi paid durumuna geçirmeden önce eventId değerini veritabanınızda benzersiz olarak sahiplenin ve siparişi sunucu tarafında idempotent bir işlemle tamamlayın.
Kod, işlem calldata'sına güvenmek yerine bilerek makbuzdaki kimliği doğrulanmış kayıtları eşleştirir. Üye işyerine doğrudan yapılan bir işlem veya ilgisiz bir Transfer olayı geçerli sayılmaz.
Yerel ETH ve BNB ödemelerinde işlemcinin kimliği doğrulanmış PaymentProcessed olayı, belgelenmiş ödeme kanıtıdır. USDT'de ek Transfer olayı onaylı token sözleşmesi tarafından yayınlanmalı ve token'ları işlemciden satıcıya aktarmalıdır.
Üretimde kendi güvenilir RPC uç noktalarınızı yapılandırın ve bu fonksiyonun çevresinde sağlayıcı hatalarını ve yeniden denemeleri yönetin.
TRON doğrulaması
TRON'da SolidityNode uç noktası üzerinden solidified işlem makbuzunu sorgulayın:
POST https://api.trongrid.io/walletsolidity/gettransactioninfobyid
Makbuz, ham olay kayıtlarını içerir. Aşağıdaki uygulama bu kayıtları doğrudan okur; böylece bir dizinleyicinin olay listesinin mevcut olmasına veya adres biçimine bağımlı kalmaz. EVM uyumlu olay verilerini çözümlemek için ethers v6, TRON adreslerini normalleştirmek için tronweb kullanır.
import { ethers } from 'ethers';
import { TronWeb } from 'tronweb';
const tronEvents = new ethers.Interface([
'event PaymentProcessed(address indexed buyer, address indexed seller, uint256 commission, uint256 totalPay)',
'event Transfer(address indexed from, address indexed to, uint256 value)'
]);
function tronAddress20(address) {
// Base58 addresses use 41 + 20-byte account; EVM event addresses use 20 bytes.
if (typeof address !== 'string') throw new Error('Invalid address');
if (TronWeb.isAddress(address)) {
return TronWeb.address.toHex(address).slice(-40).toLowerCase();
}
const hex = address.replace(/^0x/, '').toLowerCase();
if (/^[0-9a-f]{40}$/.test(hex)) return hex;
if (/^41[0-9a-f]{40}$/.test(hex)) return hex.slice(2);
throw new Error('Invalid TRON address format');
}
function decodeTronLog(log) {
return tronEvents.parseLog({
topics: (log.topics || []).map((topic) => `0x${topic.replace(/^0x/, '')}`),
data: `0x${(log.data || '').replace(/^0x/, '')}`
});
}
async function verifyTronPayment(txid, order, tronGridApiKey) {
if (!/^[0-9a-fA-F]{64}$/.test(txid)) {
throw new Error('Invalid TRON transaction ID');
}
const response = await fetch(
'https://api.trongrid.io/walletsolidity/gettransactioninfobyid',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'TRON-PRO-API-KEY': tronGridApiKey
},
body: JSON.stringify({ value: txid })
}
);
if (!response.ok) throw new Error(`TronGrid HTTP ${response.status}`);
const info = await response.json();
if (info.Error) throw new Error(`TronGrid: ${info.Error}`);
if (!info.id) return { state: 'pending' };
if (info.id.toLowerCase() !== txid.toLowerCase()) {
return { state: 'review', reason: 'Transaction ID mismatch' };
}
if (!info.receipt?.result) {
return { state: 'review', reason: 'Receipt status unavailable' };
}
if (info.receipt.result !== 'SUCCESS') {
return { state: 'failed', reason: info.receipt.result };
}
const processor = tronAddress20(order.processorAddress);
const seller = tronAddress20(order.sellerAddress);
const token = order.tokenAddress ? tronAddress20(order.tokenAddress) : null;
const expectedNet = BigInt(order.expectedSellerAmount);
const logs = info.log || [];
for (const [index, log] of logs.entries()) {
if (tronAddress20(log.address) !== processor) continue;
let event;
try { event = decodeTronLog(log); } catch { continue; }
if (event?.name !== 'PaymentProcessed') continue;
if (tronAddress20(event.args.seller) !== seller) continue;
if (order.buyerAddress &&
tronAddress20(event.args.buyer) !== tronAddress20(order.buyerAddress)) continue;
const net = event.args.totalPay - event.args.commission;
if (net < 0n || net !== expectedNet) continue;
if (token) {
const tokenPaid = logs.some((other) => {
if (tronAddress20(other.address) !== token) return false;
let transfer;
try { transfer = decodeTronLog(other); } catch { return false; }
return transfer?.name === 'Transfer' &&
tronAddress20(transfer.args.from) === processor &&
tronAddress20(transfer.args.to) === seller &&
transfer.args.value === net;
});
if (!tokenPaid) continue;
}
return {
state: 'matched',
buyer: TronWeb.address.fromHex(`41${tronAddress20(event.args.buyer)}`),
sellerReceived: net.toString(),
eventId: `tron:${txid.toLowerCase()}:${index}`
};
}
return { state: 'review', reason: 'No matching payment event' };
}
Fonksiyon solidified yürütme makbuzunu, gerçek PaymentProcessed olayını ve USDT için eşleşen token Transfer olayını doğrular. Solidified makbuzun olmaması ödemenin beklemede olduğunu gösterir, başarısız olduğunu değil. Hatalarda veya eksik olay bilgilerinde ödemeyi inceleme durumunda tutun ve yeniden deneyin.
Akıllı sözleşme referansı sayfasındaki onaylı TRON ana ağ işlemci ve token adreslerini kullanın. Tarayıcıdan gelen farklı bir ağı veya işlemci adresini kabul etmeyin.
SolidityNode kesinliği hakkında ayrıntılar için TRON confirmation semantics sayfasına bakın.
Ödemeleri siparişlerle eşleştirin ve tekrarları önleyin
transfer() fonksiyonu orderId içermez. Siparişlerle ödemeler arasında güvenilir bir ilişki kurmak arka ucunuzun sorumluluğundadır.
- Ödeme sayfası bir ödeme gönderdiğinde:
txiddeğerini mevcut siparişe kaydedin ve değiştirilemez bir ödeme denemesi oluşturun. - Bir ödeme olayını işlerken: Olaydaki satıcının, isteğe bağlı alıcının ve net tutarın beklenen siparişle eşleştiğini kontrol edin.
- İdempotentlik için: Her ödeme olayı için benzersiz bir
(chain, txid, logIndex)anahtarı kullanın. Aynı olayın birden fazla siparişi tamamlamasını önleyin. Doğrulama işleyicisi yeniden denese bile bir siparişi iki kez teslim etmeyin. - Yeniden denemeler ve geç ödemeler için: Önceki ödeme denemelerini saklayın ve ödeme süresi dolduktan sonra kesinleşen işlemlerin mutabakatını yapın.
Birden fazla ödemede satıcı ve tutar aynı olabilir. Yalnızca satıcı, tutar veya zaman aralığı eşleşmesine güvenmeyin ve benzersiz fatura tutarları üretmek için son ondalık basamakları değiştirmeyin. Ödeme sayfanızın işlemi gönderdiği her yerde işlem ile sipariş arasında doğrudan bir ilişki tutun.
Yeniden denemeler ve operasyonel hatalar
| Durum | İşlem |
|---|---|
| İşlem henüz madenlenmedi veya solidified değil | Denemeyi beklemede tutun ve yeniden deneyin |
| RPC zaman aşımı, 429 veya geçici ağ hatası | Artan bekleme süreleriyle yeniden deneyin; ödemenin başarısız olduğu sonucuna varmayın |
| Onay işlemi başarılı | Onayı kaydedin, ardından ayrı ödeme işlemini bekleyin |
| Kesinleşmiş işlem geri alındı | Denemeyi başarısız olarak işaretleyin; teslim etmeyin |
| Makbuz başarılı ama olay eşleşmiyor | İncelemede tutun; işlemciyi, token'ı, satıcıyı ve beklenen tutarı kontrol edin |
| Tarayıcı kapandı veya ödeme süresi doldu | Arka uçta mutabakata devam edin |
| Ödeme olayı zaten bir siparişe atanmış | Başka bir siparişe atamayın |
Tarayıcıdaki bir onay sayacı, blok gezginindeki durum rozeti veya müşterinin ekran görüntüsü bir siparişi tamamlamak için yeterli değildir.
Manuel doğrulama
Bir ödemeyi incelerken Etherscan, BscScan veya TronScan kullanın. İşlemi doğru ağda kontrol edin: başarılı yürütme, onaylı işlemcinin PaymentProcessed olayı, üye işyerinin adresi ve net tutar. USDT için ayrıca işlemciden üye işyerine yapılan gerçek token Transfer işlemini inceleyin.
Manuel inceleme, destek ekiplerinin ödeme mutabakatı yapmasına yardımcı olabilir; ancak üretimdeki bir ödeme sayfası otomatik, sunucu tarafı doğrulama uygulamalıdır.
Yayına alma kontrol listesi
- Onaylı ana ağ işlemci ve USDT token adresleri sunucu tarafında yapılandırıldı.
-
PaymentProcessedolay ABI'si dağıtılmış işlemci sözleşmeleriyle eşleşiyor. - Varlık hassasiyeti doğru: ETH/BNB 18, Ethereum/TRON USDT 6, BSC USDT 18, TRX 6.
- Siparişler ve satıcıya gitmesi beklenen net tutarlar ödeme gönderilmeden önce kaydediliyor.
- Token onay hash'leri ödeme hash'lerinden ayrı tutuluyor.
- Makbuz durumu ve ağa özgü kesinlik kontrol ediliyor.
-
PaymentProcessed, yayınlayan işlemci adresine, satıcıya ve beklenen net tutara göre doğrulanıyor. - USDT
Transfer, yayınlayan token adresine, işlemciye, satıcıya ve tutara göre doğrulanıyor. - Ödeme sayfası gerektiriyorsa sabit alıcı cüzdanı eşleştiriliyor.
- Her olay benzersiz olarak sahipleniliyor; teslimat idempotent.
- Başarısız, mükerrer, eksik ve yanlış alıcıya yapılan ödemeler test edildi.
- RPC kesintileri, eksik kayıtlar, geç onaylar ve süresi dolan ödemeler yönetiliyor.
- Doğrulama, desteklenen her varlık için bilinen başarılı ve başarısız işlemlerle test edildi.
İlgili sayfalar
- Akıllı sözleşme entegrasyonu — emanetçi olmayan ödeme entegrasyonuna genel bakış.
- Ethereum ve BNB Smart Chain entegrasyonu — ETH, BNB ve USDT ödemeleri gönderin.
- TRON entegrasyonu — TRX ve USDT TRC-20 ödemeleri gönderin.
- Akıllı sözleşme referansı — işlemci adresleri, token ondalık basamakları ve olay arayüzü.
- Sorun giderme — sık karşılaşılan cüzdan ve ödeme hataları.