Karar Servisiniz
Chargeless, yayımladığınız politika çerçevesinde iade ve iptal yapar: iade süresi, tutar üst sınırı, kabul edilen gerekçeler, müşteri başına ve saatlik sınırlar. Ancak bazı talepler yalnızca sizin sistemlerinizin bildiği bilgilere bağlıdır: krediler çoktan harcanmıştır, hesap inceleme altındadır, sipariş bu sabah kargoya verilmiştir.
Karar servisi, politikanızdaki bir kural gerektirdiğinde Chargeless'ın iade ya da iptal yapmadan önce sorduğu, size ait tek bir HTTPS uç noktasıdır. O talebin sonucunu yanıtınız belirler: onay, ret ya da ekibinize devir. Her ödeme sağlayıcısıyla çalışır: Stripe kullanan bir çalışma alanı da, faturalamayı kendi sisteminde yürüten bir çalışma alanı da servisi aynı şekilde bağlar.
Bu sayfa, sözleşmeyi örneklerle anlatır. Şemalar, sözleşme dosyalarında
schemas/merchant-decision/v1/ altındadır (request.json, response.json); bu sayfayla şemalar
arasında fark varsa şemalar esas alınır.
Size Ne Zaman Sorulur
Yalnızca yayımlanmış politikanızdaki bir kural bunu istediğinde ve yalnızca diğer tüm adımlar geçildikten sonra.
- Chargeless talebi politikanıza göre, sabit bir sırayla kontrol eder: durdurmalar, kimlik, ödemenin durumu, gerekçe, iade süresi, tutar üst sınırı, iade edilebilir kalan tutar, müşteri başına ve saatlik sınırlar, devre kesici.
- Ardından kurallarınız sırayla çalışır; eşleşen ilk kural kararı verir. Bir kuralın sonucu Reddet, Ekibinize devret ya da Sisteminize sor olabilir.
- Servisinize yalnızca eşleşen bir Sisteminize sor kuralı istek gönderir.
Bu nedenle servisinize, politikanın zaten reddedeceği bir talep hiçbir zaman sorulmaz; onayınız da politikanın izin verdiğinin ötesine asla geçemez: başka bir tutara, başka bir ödemeye ya da bir sınırın ötesine izin vermez. Kurallarınızın servise yönlendirmediği talepler servise sorulmadan karara bağlanır.
Diğer kurallarınız da karar vermeye devam eder. Reddeden ya da ekibinize devreden bir kural, listede hangi sırada olursa olsun, eşleştiği her durumda kararı verir: servisinize yalnızca bu kuralların hiçbiri eşleşmediğinde sorulur. Dolayısıyla onayınız, kendi kurallarınızdan birinin durduracağı bir talebi asla geçirmez. Soran birden fazla kural eşleşirse servisinize yalnızca ilk kural için, bir kez sorulur.
Bir kural her talepte ("Her talep" koşulu) ya da yalnızca bazı taleplerde sorabilir: belirli bir tutarın üzerindeki iadeler, belirli bir ürün türü, daha önce iade almış müşteriler, yalnızca iptaller. İadeler (müşterinin istediği ya da erken dolandırıcılık uyarısının ardından yapılan) ve abonelik iptalleri kurallardan geçer; itirazlar asla geçmez.
Panelde politika dışı onayla (override) iade yapan bir ekip üyesi kurallarınızı atlar; bu durumda servisinize sorulmaz. Politika dışı onay kullanılmadığında ekip üyesinin işlemi için de diğer talepler gibi servisinize sorulur.
Her Yanıtın Sonucu
| Yanıtınız | Ne olur | Müşteri ne görür |
|---|---|---|
approve |
Chargeless işlemi, politikanın izin verdiği şekliyle aynen gerçekleştirir. | Her zamanki gibi iade ya da iptal. |
deny |
Talep reddedilir ve vaka kapatılır. | Otomatik olarak onaylanamayan talepler için hazırladığınız ret metni. |
handoff |
Talep otomatik olarak reddedilir ve ekibinize devredilir: paneldeki talep kuyruğuna ve devir webhook'unuza. Kararı ekibinizden biri verir. | Talebin ekip tarafından inceleneceği. |
| süresi içinde geçerli yanıt gelmemesi | handoff ile aynı. |
handoff ile aynı. |
"Beklemede" diye bir yanıt yoktur. Sizin tarafınızda birinin incelemesi gerekiyorsa handoff
yanıtını verin: talep, diğer tüm devirler gibi ekibinize ulaşır ve gerekçe kodunuz yanında görünür.
reason_code ve reference değerleriniz ekibinize gösterilir (vaka, İşlem Günlüğü, "Nasıl Ele
Alındı"). Müşteriye hiçbir zaman gösterilmez, asistana da hiçbir zaman verilmez.
Bağlantıyı Kurma
Ayarlar → Karar Servisi. URL'yi girin, kaydedin ve imza anahtarını kopyalayın: anahtar yalnızca
bir kez gösterilir (srmd_…). Test İsteği Gönder, "test": true ve yapay referanslarla imzalı
tek bir çağrı yapar; geçerli her yanıt testi geçer. Ardından servisi açın.
Sisteminize sor kuralı içeren bir politikayı yayımlamak için servisin açık olması ve son testini geçmiş olması gerekir. Servisi sonradan kapatmak ya da kaldırmak yayımlanmış hiçbir şeyi geri çekmez: soran kurallar bu durumda taleplerini ekibinize gönderir ve politika sayfasında bu belirtilir.
URL, 443 numaralı portta https kullanmalı, Chargeless'ın internet üzerinden erişebileceği bir
adreste olmalı, bir yol (path) içermeli ve sorgu dizesi (query string) içermemelidir.
Yönlendirmeler izlenmez. URL'yi değiştirmek son test sonucunu siler: kuralları yeniden
yayımlamadan önce yeni adresi test edin.
Servisi Kendi API'nizin Arkasında Tutma
Servis API ağ geçidinizin (API gateway) arkasındaysa, ağ geçidinin istediği bilgileri aynı
bölümdeki API'nizin Kimlik Bilgileri alanından Chargeless'a verin: bir erişim token'ı
(Authorization: Bearer <token> olarak gönderilir) ve en fazla üç kendi başlığınız (örneğin
X-Api-Key). Bu bilgiler şifrelenerek saklanır, bir daha gösterilmez, her istekte imzayla birlikte
ve yalnızca girildikleri URL'ye gönderilir: URL'yi değiştirdiğinizde bunları yeniden girin ya da
kaldırın.
İmza Anahtarını Yenileme
- Yeni Anahtar ile yeni bir anahtar hazırlayın: yalnızca bir kez gösterilir; Chargeless bu sırada mevcut anahtarla imzalamaya devam eder. Doğrulayıcınızın iki anahtarı da kabul etmesini sağlayın ve yayına alın.
- Yeni Anahtarı Etkinleştir: Chargeless hazırlanan anahtarla imzalanmış bir test isteği gönderir ve bu anahtarı yalnızca servisiniz teste geçerli bir yanıt verirse geçerli anahtar yapar. Eski anahtarı on dakika daha kabul edin, ardından kullanımdan kaldırın.
- Hemen Yenile, anahtar sızdıysa: yeni anahtar hemen imzalamaya başlar. Siz yeni anahtarı yayına alana kadar servisiniz Chargeless'ın isteklerini reddeder ve kurallarınızın sorduğu talepler ekibinize gider.
İstek
POST /chargeless/decide HTTP/1.1
Host: decide.example.com
Accept: application/json
Content-Type: application/json
User-Agent: Chargeless (merchant-decision/v1)
X-SupportRoute-Timestamp: 1794484800
X-SupportRoute-Signature: v1=dce746d25963a23f20f55d82243cb102fb5cccd67b053b3a8ef101fe7d608898
Idempotency-Key: 01999999-0000-7000-8000-000000000001:decision-test:019a7d2e-8000-7000-8000-000000000000
X-SR-Request-Id: 019a7d2e-8000-7000-8000-000000000001
X-SR-Correlation-Id: 019a7d2e-8000-7000-8000-0000000000aa
X-SR-Channel: chat
{"contract":"merchant-decision/1.0","decision_id":"019a7d2e-8000-7000-8000-000000000000",…}- İmza, başında
v1=bulunan ve imza anahtarınızla şu metin üzerinden hesaplanan HMAC-SHA256 değerinin küçük harfli hex gösterimidir:{timestamp}\n{METHOD}\n{host}\n{path}\n{idempotency_key}\n{body}. Metnin parçaları sırasıyla: gönderildiği haliyle zaman damgası başlığı,POST, gönderildiği haliyle host (küçük harfle; port 443 ise port yazılmaz), gönderildiği haliyle yol (path), gönderildiği haliyleIdempotency-Keybaşlığı ve ham gövde baytları. Bu, işletmenin barındırdığı sağlayıcıların (merchant-provider) imzasıyla aynıdır; tek bir doğrulayıcı ikisinde de kullanılabilir.schemas/merchant-decision/examples/v1/signature.jsonbir test vektörüdür:srmd_test_5b8e2c7a1f9dile imzalanmış test isteği. - Eşleşmeyen bir imzayı ya da beş dakikadan fazla sapan bir zaman damgasını
401ile reddedin. İmzaları sabit zamanlı (constant time) karşılaştırın. - Yanıt süresi gövdedeki alanda yer alır (
deadline): bir kişinin ya da bir API istemcisinin yanıt beklediği yerlerde (sohbet, müşteri portalı, panel, formunuz)asked_atanından 4 saniye, arka plandaki bir iş akışında (e-posta talebi, erken dolandırıcılık uyarısı) 10 saniye sonrasıdır. Bu süreden sonra gelen yanıt okunmaz. Yeniden deneme yapılmaz: hızlı yanıt verin, emin olamadığınızdahandoffyanıtını verin. - Aynı
decision_idaynı yanıtı alır.Idempotency-Keydenemeyi değil, talebin amacını (intent) adlandırır ({workspace}:{the action's key});decision_idbu anahtardan, hedeften ve sorulan tutardan türetilir. Chargeless aynı amaç için yeniden sorarsa (kendi tarafındaki bir çökmeden sonra, kararı kaydetmeden önce)decision_iddeğişmez: ilk seferde nasıl yanıt verdiyseniz öyle yanıtlayın. Aynı anahtarla farklı bir tutar sorulursadecision_idde farklı olur: bu yeni bir sorudur. Önbelleğidecision_iddeğerine göre tutun, asla yalnızca anahtara göre tutmayın. Chargeless kaydettiği bir talep için asla yeniden sormaz ve yanıtınızı yalnızca sorduğu tutara uygular. X-SR-Correlation-Id(varsa talebin ilgili olduğu vaka) veX-SR-Channel(chat,ticket,staffya dasystem) günlük kayıtlarınız içindir; hiçbir işlem bunlara bağlı değildir.- İletişim bilgisi yoktur. İstek yalnızca kimlikler, tutarlar, kodlar ve zamanlar içerir: ödeme sağlayıcınızın ödemeye ya da aboneliğe ve müşteriye verdiği kimlikler. Ad, e-posta, adres ya da müşterinin yazdığı herhangi bir şey bulunmaz.
Bir İade (request.refund.json)
{
"contract": "merchant-decision/1.0",
"decision_id": "019a7d2e-4c1b-7a3e-9d21-4f6a8b0c2d4f",
"tenant_id": "01999999-0000-7000-8000-000000000001",
"test": false,
"policy_version": "v12",
"asked_at": "2026-11-12T10:00:00Z",
"deadline": "2026-11-12T10:00:04Z",
"rule": { "id": "big-refunds-ask-us", "reason": "large_refund" },
"action": {
"type": "refund",
"source": "chat",
"reason_code": "not_as_described",
"amount": { "amount_minor": 8900, "currency": "USD" },
"partial": false
},
"target": {
"type": "transaction",
"provider": "stripe",
"connection_id": "01999999-0000-7000-8000-0000000000c1",
"ref": "ch_3QxY2bL8",
"amount": { "amount_minor": 8900, "currency": "USD" },
"created_at": "2026-11-02T15:21:09Z"
},
"customer": { "kind": "account", "ref": "cus_R2k9aZ", "verified_by": "email_otp" }
}rule: soran kural, politikada yazdığınız haliyle.action.source: politikanın gözünden talebin kaynağı:chat(asistan ve müşteri portalı),ticket(e-posta ya da yardım masası talebi, formunuz),fraud_alert(erken dolandırıcılık uyarısı) ya damerchant_user(politika dışı onay kullanmayan ekibiniz).action.amount: iade edilecek tutar; ödemenin kendi para biriminde, asla dönüştürülmeden.partial, bu tutarın iade edilebilir kalandan az olduğunu belirtir.target.ref: ödeme sağlayıcınızın ödemeye verdiği kimlik (Stripe'tach_…ya dapy_…; kendi sağlayıcınızda sizin referansınız).target.providerdeğeristripe,merchant(kendi sisteminiz),paddle,fastspring,adyen,dlocal,ecommpay,paypalya danuveiolabilir; tanımadığınız bir değeri de kabul edin.test_workspace: istek bir test çalışma alanından geliyorsatrueolur (aksi halde alan hiç bulunmaz); böylece tek bir servis bu istekleri kendi test ortamına (sandbox) yönlendirebilir.customer.verified_by: müşterinin kimliğini nasıl kanıtladığı:signed_token,email_otp,email_link,identifier_matchya danone(dolandırıcılık uyarısı, ekibiniz). Misafir ödemesinde ("kind": "guest")refbulunmaz; kayıtlı müşterisi olmayan bir ödemedecustomeralanı hiç bulunmaz.
Bir İptal (request.cancel.json)
{
"contract": "merchant-decision/1.0",
"decision_id": "019a7d2e-6f00-7b11-8c22-0a1b2c3d4e5f",
"tenant_id": "01999999-0000-7000-8000-000000000001",
"test": false,
"policy_version": "v12",
"asked_at": "2026-11-12T10:05:00Z",
"deadline": "2026-11-12T10:05:04Z",
"rule": { "id": "every-cancellation-asks-us", "reason": "cancellation_check" },
"action": { "type": "cancel_subscription", "source": "chat", "reason_code": "forgot_to_cancel" },
"target": {
"type": "subscription",
"provider": "merchant",
"connection_id": "01999999-0000-7000-8000-0000000000c1",
"ref": "sub-88213",
"created_at": "2026-05-01T09:00:00Z"
},
"customer": { "kind": "account", "ref": "cust-4410", "verified_by": "signed_token" }
}İptalle Birlikte Gelen İade (request.followup.json)
Politikanız, seçtiğiniz bir gerekçeyle iptal edilen bir aboneliğin son ödemesini iade ediyorsa
(refund_last_charge_if), bu iade ayrı bir taleptir. Bir kural bu iade için de soruyorsa iade için
ayrı bir istek gönderilir: iptal isteğiyle aynı anda, kendi anahtarıyla ve aboneliği belirten
action.followup_of alanıyla:
{
"contract": "merchant-decision/1.0",
"decision_id": "019a7d2e-6f00-7b11-8c22-0a1b2c3d4e60",
"tenant_id": "01999999-0000-7000-8000-000000000001",
"test": false,
"policy_version": "v12",
"asked_at": "2026-11-12T10:05:00Z",
"deadline": "2026-11-12T10:05:04Z",
"rule": { "id": "big-refunds-ask-us", "reason": "large_refund" },
"action": {
"type": "refund",
"source": "chat",
"reason_code": "forgot_to_cancel",
"amount": { "amount_minor": 5900, "currency": "EUR" },
"partial": false,
"followup_of": { "type": "cancel_subscription", "target_ref": "sub-88213" }
},
"target": {
"type": "transaction",
"provider": "merchant",
"connection_id": "01999999-0000-7000-8000-0000000000c1",
"ref": "txn-77120",
"amount": { "amount_minor": 5900, "currency": "EUR" },
"created_at": "2026-11-10T08:00:00Z",
"charge_type": "renewal"
},
"customer": { "kind": "account", "ref": "cust-4410", "verified_by": "signed_token" }
}Her yanıt yalnızca kendi talebini belirler. İadeyi reddetmeniz iptali etkilemez; iptal kendi yanıtına göre sonuçlanır. İptali reddederseniz, iade için ne yanıt vermiş olursanız olun iade yapılmaz.
Test İsteği (request.test.json)
"test": true, yapay referanslar (ch_test, cus_test), test kuralı. Hiçbir karar verilmez;
geçerli her yanıt testi geçer.
Yanıt
Yanıt, son süre dolmadan HTTP 200 ile, Content-Type: application/json olarak ve en fazla 4 KiB
boyutunda verilir:
{ "decision_id": "019a7d2e-4c1b-7a3e-9d21-4f6a8b0c2d4f", "decision": "approve" }{
"decision_id": "019a7d2e-4c1b-7a3e-9d21-4f6a8b0c2d4f",
"decision": "deny",
"reason_code": "item_used",
"reference": "dec_8812"
}{
"decision_id": "019a7d2e-6f00-7b11-8c22-0a1b2c3d4e5f",
"decision": "handoff",
"reason_code": "account_review"
}decision_id: istekteki değerin aynısı.decision:approve,denyya dahandoff.reason_code(isteğe bağlı): size ait bir kod,^[a-z0-9_]{1,64}$.reference(isteğe bağlı): kararı kendi günlüklerinizde bulmanız için size ait kimlik; en fazla 128 yazdırılabilir ASCII karakter, kart numarasına benzeyen hiçbir şey içeremez. Harf, rakam ve. _ : -karakterlerinden oluşan bir kimlik kullanın: başka karakter (boşluk,@) ya da on iki veya daha fazla ardışık rakam içeren bir kimlik kabul edilir ama saklanmaz.
Yanıt katı kurallara tabidir. Bunların dışındaki her yanıt, yanıt verilmemiş sayılır ve talep
ekibinize gider: başka bir durum kodu (401, 500), yönlendirme, zaman aşımı, 4 KiB'ı aşan bir
gövde, JSON olmayan bir gövde, bu sayfada adı geçmeyen bir alan, istekteki değerle eşleşmeyen bir
decision_id, kart numarasına benzeyen bir reference. Paneldeki son çağrılar hangisi olduğunu
gösterir (timeout, http_status, schema, decision_id, card_number, …).
Sınırlar
- Yanıtınızla karar arasında iade tutarı değişirse (bu sırada aynı ödemenin başka bir iadesi sonuçlanır ya da başarısız olursa), yanıtınız kullanılmaz ve talep ekibinize gider.
- Her Chargeless sunucusunda, bir çalışma alanı için servisinize aynı anda en fazla 16 istek açık
olabilir (bir sunucuda toplam en fazla 256). Bu sınırı aşan istek gönderilmez ve ekibinize gider
(
merchant_decision_busy). - Servisin sayfası (Ayarlar → Karar Servisi) şu durumlarda uyarı gösterir: son 15 dakikada en az
beş isteğin %20'sinden fazlası geçerli yanıt alamadıysa, servisiniz Chargeless'ı reddettiyse
(
401,403) ya da sözleşmeye uymayan yanıtlar verdiyse.
Örnek Bir Uç Nokta
İmzayı doğrulayan ve size ait bir kural uygulayan, Node ile yazılmış minimal bir servis:
import crypto from 'node:crypto';
import express from 'express';
const SECRET = process.env.CHARGELESS_DECISION_SECRET; // srmd_…
const app = express();
app.use(express.raw({ type: 'application/json', limit: '64kb' }));
function verified(req) {
const ts = req.get('X-SupportRoute-Timestamp') ?? '';
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const signed = [ts, 'POST', req.get('Host'), req.originalUrl, req.get('Idempotency-Key') ?? '', req.body].join('\n');
const want = 'v1=' + crypto.createHmac('sha256', SECRET).update(signed).digest('hex');
const got = req.get('X-SupportRoute-Signature') ?? '';
return got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}
app.post('/chargeless/decide', async (req, res) => {
if (!verified(req)) return res.status(401).end();
const q = JSON.parse(req.body.toString('utf8'));
if (q.test) return res.json({ decision_id: q.decision_id, decision: 'approve' });
// Your own records, found by the provider's ids.
const account = await accounts.byProviderCustomer(q.customer?.ref);
if (account?.underReview) {
return res.json({ decision_id: q.decision_id, decision: 'handoff', reason_code: 'account_review' });
}
if (q.action.type === 'refund' && (await usage.creditsSpentOn(q.target.ref)) > 0) {
return res.json({ decision_id: q.decision_id, decision: 'deny', reason_code: 'credits_used' });
}
return res.json({ decision_id: q.decision_id, decision: 'approve' });
});Gövde, ayrıştırılmadan önce imza için ham bayt olarak okunur. Aynı decision_id için hep aynı
yanıtı verin; bu değere göre tutulan, bir gün saklanan bir önbellek yeterlidir (asla yalnızca
Idempotency-Key değerine göre tutmayın: başka bir tutar için yeniden sorulan aynı anahtar başka
bir decision_id taşır ve yeni bir sorudur).
Kontrol Listesi
- Uç nokta 443 numaralı portta
httpskullanır, internetten erişilebilir ve 4 saniyenin çok altında yanıt verir. - İmza ve zaman damgası kontrol edilir; hatalı olanlar
401alır. - Aynı
decision_idaynı yanıtı alır (önbellek yalnızca anahtara göre değil, bu değere göre tutulur). -
decision_idaynen geri gönderilir; dört alan dışında hiçbir şey döndürülmez. - Emin olunamadığında yanıt, yavaş bir
approvedeğil,handoffolur. - Test isteği panelde geçer, servis açılır, ardından kurallar yayımlanır.