المدفوعات
المبالغ المستردة
استرداد دفعة بالبطاقة كليًا أو جزئيًا، والمدة التي يستغرقها الاسترداد، ولماذا لا تُسترد الدفعة النقدية نقدًا.
في هذه الصفحة
الاسترداد يعيد المال إلى عميلك عن دفعة نجحت. يمكن استرداد المدفوعات بالبطاقة، كليًا أو جزئيًا. أما المدفوعات النقدية فلا: انظر المدفوعات النقدية أدناه.
- لا تُسترد إلا دفعة حالتها
succeeded. - يمكنك إجراء عدة عمليات استرداد جزئية، ما دام مجموعها لا يتجاوز
amount_receivedللدفعة. يُرفض أي استرداد يتجاوز المتبقي بالرمزamount_too_large. - يُرفض استرداد الدفعة النقدية بالرمز
refund_unsupported_payment_method، في وضع الاختبار كما في الوضع المباشر.
استرداد دفعة بالبطاقة
أنشئ عملية استرداد عبر POST /v1/refunds. ترويسة Idempotency-Key إلزامية، كما في كل
طلب يحرّك أموالًا: الاسترداد الذي يُرسل مرتين بالمفتاح نفسه يُنفَّذ مرة واحدة.
| المعامل | ما يحمله |
|---|---|
payment_intent | الدفعة المراد استردادها (pi_…). أو أرسل charge، أي محاولتها الناجحة (ch_…). |
amount | اختياري. بالسنتيم. من دونه، كل ما لم يُسترد بعد. |
reason | اختياري. requested_by_customer أو duplicate أو fraudulent. |
metadata | اختياري. مراجعك الخاصة، مثل رقم الإرجاع: انظر البيانات الوصفية. |
الاستجابة هي عملية الاسترداد، وتكون pending في البداية.
curl https://pay-api.adpromax.com/v1/refunds \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-order-1042-1" \
-d '{
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" }
}'const response = await fetch('https://pay-api.adpromax.com/v1/refunds', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'refund-order-1042-1',
},
body: JSON.stringify({
payment_intent: 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
amount: 10000,
reason: 'requested_by_customer',
metadata: { return_id: 'R-311' },
}),
})
const refund = await response.json()
if (!response.ok)
throw new Error(refund.error.message)
console.log(refund.id, refund.status)interface Refund {
id: string
object: 'refund'
amount: number
currency: string
payment_intent: string
reason: string | null
status: string
metadata: Record<string, string>
}
interface ApiError {
error: { type: string, code: string, message: string, request_id: string }
}
const response = await fetch('https://pay-api.adpromax.com/v1/refunds', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'refund-order-1042-1',
},
body: JSON.stringify({
payment_intent: 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
amount: 10000,
reason: 'requested_by_customer',
metadata: { return_id: 'R-311' },
}),
})
if (!response.ok) {
const { error } = (await response.json()) as ApiError
throw new Error(`${error.code}: ${error.message}`)
}
const refund = (await response.json()) as Refund
console.log(refund.id, refund.status)import os
import requests
response = requests.post(
"https://pay-api.adpromax.com/v1/refunds",
auth=(os.environ["APX_SECRET_KEY"], ""),
headers={"Idempotency-Key": "refund-order-1042-1"},
json={
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": {"return_id": "R-311"},
},
)
refund = response.json()
response.raise_for_status()
print(refund["id"], refund["status"])<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/refunds');
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Idempotency-Key: refund-order-1042-1',
],
CURLOPT_POSTFIELDS => json_encode([
'payment_intent' => 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
'amount' => 10000,
'reason' => 'requested_by_customer',
'metadata' => ['return_id' => 'R-311'],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$refund = json_decode(curl_exec($ch), true);
echo $refund['id'], ' ', $refund['status'], PHP_EOL;require 'json'
require 'net/http'
uri = URI('https://pay-api.adpromax.com/v1/refunds')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = 'refund-order-1042-1'
request.body = JSON.generate(
payment_intent: 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
amount: 10000,
reason: 'requested_by_customer',
metadata: { return_id: 'R-311' }
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
refund = JSON.parse(response.body)
raise refund.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)
puts "#{refund['id']} #{refund['status']}"package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type Refund struct {
ID string `json:"id"`
Amount int64 `json:"amount"`
PaymentIntent string `json:"payment_intent"`
Status string `json:"status"`
}
type APIError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func main() {
body, err := json.Marshal(map[string]any{
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": map[string]string{"return_id": "R-311"},
})
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest(http.MethodPost,
"https://pay-api.adpromax.com/v1/refunds", bytes.NewReader(body))
if err != nil {
log.Fatal(err)
}
req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "refund-order-1042-1")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var apiErr APIError
json.NewDecoder(res.Body).Decode(&apiErr)
log.Fatal(apiErr.Error.Message)
}
var refund Refund
if err := json.NewDecoder(res.Body).Decode(&refund); err != nil {
log.Fatal(err)
}
fmt.Println(refund.ID, refund.Status)
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Refund {
public static void main(String[] args) throws Exception {
String body = """
{
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" }
}
""";
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://pay-api.adpromax.com/v1/refunds"))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", "refund-order-1042-1")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException(response.body());
}
// The refund as JSON: read its id and status with a JSON library.
System.out.println(response.statusCode() + " " + response.body());
}
}using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
var json = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));
using var request = new HttpRequestMessage(
HttpMethod.Post, "https://pay-api.adpromax.com/v1/refunds");
request.Headers.Add("Idempotency-Key", "refund-order-1042-1");
request.Content = JsonContent.Create(new
{
payment_intent = "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
amount = 10000,
reason = "requested_by_customer",
metadata = new { return_id = "R-311" },
});
using var response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var failure = await response.Content.ReadFromJsonAsync<ApiError>(json);
throw new InvalidOperationException(failure?.Error.Message);
}
var refund = await response.Content.ReadFromJsonAsync<Refund>(json);
Console.WriteLine($"{refund!.Id} {refund.Status}");
record Refund(string Id, long Amount, string PaymentIntent, string Status);
record ApiError(ApiErrorBody Error);
record ApiErrorBody(string Code, string Message);// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
struct Refund {
id: String,
status: String,
}
#[derive(Deserialize)]
struct ApiError {
error: ErrorBody,
}
#[derive(Deserialize)]
struct ErrorBody {
message: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::blocking::Client::new()
.post("https://pay-api.adpromax.com/v1/refunds")
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.header("Idempotency-Key", "refund-order-1042-1")
.json(&json!({
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" }
}))
.send()?;
if !response.status().is_success() {
let failure: ApiError = response.json()?;
return Err(failure.error.message.into());
}
let refund: Refund = response.json()?;
println!("{} {}", refund.id, refund.status);
Ok(())
}{
"id": "re_01k7e7w3y6a9c2e5g8j1m4p7rt",
"object": "refund",
"amount": 10000,
"currency": "mad",
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"charge": "ch_01k7e7v3c6e9g2j5m8p1r4t7wb",
"status": "pending",
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" },
"created": 1790863800,
"livemode": false
}حياة عملية الاسترداد
- pending → succeeded: نُفّذت
- pending → failed: رُفضت
- pending → canceled: تلغيها أنت
تبقى عملية الاسترداد pending إلى أن تُنفَّذ، ثم تصبح succeeded. تنتهي بحالة failed إذا رفضتها شبكة
البطاقات، وبحالة canceled إذا ألغيتها قبل ذلك.
- في وضع الاختبار، ينجح الاسترداد في ثوانٍ.
- في الوضع المباشر، قد يُعالَج استرداد الدفعة بالبطاقة معالجة يدوية: ينفّذه فريقنا مع
شبكة البطاقات. يبقى
pendingإلى أن يتم ذلك، وقد يستغرق بضعة أيام عمل. وبعد أن يصبحsucceeded، يأخذ بنك عميلك وقته الخاص لإظهار المال في بطاقته.
الحالات هي نفسها في الوضعين، فلا يتغير كودك بين وضع وآخر.
إلغاء عملية استرداد
يمكن إلغاء عملية استرداد pending لم تُنفَّذ بعد عبر
POST /v1/refunds/{id}/cancel. تصبح canceled، ويمكن استرداد مبلغها من جديد لاحقًا.
curl -X POST https://pay-api.adpromax.com/v1/refunds/re_01k7e7w3y6a9c2e5g8j1m4p7rt/cancel \
-u "$APX_SECRET_KEY:"const id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt'
const response = await fetch(`https://pay-api.adpromax.com/v1/refunds/${id}/cancel`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const refund = await response.json()
console.log(refund.status) // canceledinterface Refund {
id: string
object: 'refund'
status: string
}
const id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt'
const response = await fetch(`https://pay-api.adpromax.com/v1/refunds/${id}/cancel`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const refund = (await response.json()) as Refund
console.log(refund.status) // canceledimport os
import requests
refund_id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt"
response = requests.post(
f"https://pay-api.adpromax.com/v1/refunds/{refund_id}/cancel",
auth=(os.environ["APX_SECRET_KEY"], ""),
)
print(response.json()["status"]) # canceled<?php
$id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt';
$ch = curl_init("https://pay-api.adpromax.com/v1/refunds/{$id}/cancel");
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
]);
$refund = json_decode(curl_exec($ch), true);
echo $refund['status'], PHP_EOL; // canceledrequire 'json'
require 'net/http'
id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt'
uri = URI("https://pay-api.adpromax.com/v1/refunds/#{id}/cancel")
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)['status'] # canceledpackage main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type Refund struct {
ID string `json:"id"`
Status string `json:"status"`
}
func main() {
id := "re_01k7e7w3y6a9c2e5g8j1m4p7rt"
req, err := http.NewRequest(http.MethodPost,
"https://pay-api.adpromax.com/v1/refunds/"+id+"/cancel", nil)
if err != nil {
log.Fatal(err)
}
req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var refund Refund
if err := json.NewDecoder(res.Body).Decode(&refund); err != nil {
log.Fatal(err)
}
fmt.Println(refund.Status) // canceled
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CancelRefund {
public static void main(String[] args) throws Exception {
String id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt";
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://pay-api.adpromax.com/v1/refunds/" + id + "/cancel"))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// The refund as JSON: its status is now "canceled".
System.out.println(response.body());
}
}using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
var json = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));
var id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt";
using var response = await http.PostAsync(
$"https://pay-api.adpromax.com/v1/refunds/{id}/cancel", null);
var refund = await response.Content.ReadFromJsonAsync<Refund>(json);
Console.WriteLine(refund!.Status); // canceled
record Refund(string Id, string Status);// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde = { version = "1", features = ["derive"] }
use serde::Deserialize;
#[derive(Deserialize)]
struct Refund {
status: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt";
let refund: Refund = reqwest::blocking::Client::new()
.post(format!("https://pay-api.adpromax.com/v1/refunds/{id}/cancel"))
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.send()?
.json()?;
println!("{}", refund.status); // canceled
Ok(())
}الأحداث والأرصدة
| الحدث | متى |
|---|---|
refund.created | أُنشئت عملية استرداد. |
refund.updated | تغيّرت حالة عملية استرداد، مثلًا إلى succeeded. |
refund.failed | فشلت عملية استرداد. |
charge.refunded | استُردّت محاولة، كليًا أو جزئيًا. |
- يزداد
amount_refundedفي نية الدفع (payment intent) عندما ينجح استرداد. تبقى نية الدفعsucceeded، حتى لو استُردّت كليًا. - الاسترداد الفاشل أو الملغى يحرّر مبلغه: يمكنك استرداده من جديد.
- يُخصم الاسترداد الناجح من رصيدك؛ ويظهر كسطر استرداد في الأرصدة. لا تُعاد رسومنا على الدفعة: الدفعة المستردة كليًا تترك رصيدك أقل بمقدار تلك الرسوم.
من لوحة التحكم
في صفحة الدفعة، يفتح استرداد نموذجًا:
- المبلغ، معبأ مسبقًا بما بقي للاسترداد، مع خياري كامل وجزئي؛
- السبب: مكرر، أو احتيالي، أو بطلب من العميل، أو غير ذلك مع ملاحظة؛
- في الوضع المباشر، تأكيد أخير قبل إرسال الاسترداد.
تعرض صفحة المبالغ المستردة كل عملية استرداد مع حالتها ودفعتها ومن أجراها.
من يستطيع الاسترداد في لوحة التحكم يتوقف على الدور: المالك والمسؤول والمالية والدعم يستطيعون. يستطيع دور الدعم الاسترداد حتى سقف لكل عملية، 1,000.00 MAD افتراضيًا، ويعرضه النموذج قبل أن تكتب. في الأدوار الأخرى يكون الزر معطّلًا ويذكر السبب. انظر الفريق والأدوار. مفتاح API لا تقيّده الأدوار: يتصرف باسم المؤسسة كلها.
المدفوعات النقدية
الدفعة النقدية الناجحة لا يمكن استردادها عبر AdProMaxPay: لا نقدًا، ولا إلى بطاقة.
السبب. سلّم عميلك المال نقدًا لدى وكيل. لا توجد بطاقة ولا حساب يُعاد إليه.
ما يحدث. تبقى الدفعة succeeded ويبقى مبلغها في رصيدك. في
لوحة التحكم، يكون استرداد معطّلًا في الدفعة النقدية ويذكر السبب؛ وتجيب واجهة API بـ
refund_unsupported_payment_method، في وضع الاختبار كما في الوضع المباشر.
ما تفعله. سوِّ الأمر مع عميلك مباشرة: تحويل بنكي، أو رصيد في متجرك، أو نقدًا في محلّك. سجّل ذلك في دفاترك. وأخبر عملاءك بسياستك قبل أن يختاروا الدفع نقدًا.
رمز الدفع النقدي الذي لم يُدفع بعد لا يُسترد بل يُلغى: انظر إلغاء رمز لم تعد تريده.