المدفوعات
البيانات الوصفية
أرفق مراجعك الخاصة بالمدفوعات والجلسات والعملاء وعمليات الاسترداد، واعثر عليها من جديد في إشعارات webhook وفي لوحة التحكم.
في هذه الصفحة
البيانات الوصفية (metadata) مجموعة من مفاتيحك وقيمك الخاصة على كائن: رقم طلبية، أو معرّف سلة، أو المتجر أو القناة التي جاء منها البيع. يخزّنها AdProMaxPay ويعيدها كما هي أينما ظهر الكائن. ولا تغيّر أبدًا طريقة عمل الدفعة.
أضف بيانات وصفية إلى دفعة
أرسل metadata ككائن من السلاسل النصية عند إنشاء نية الدفع (payment intent). تعود مراجعك عندئذ
في استجابات API وفي كل حدث يخص هذه الدفعة: عندما يتلقى الـ webhook لديك
payment_intent.succeeded، يذكر data.object.metadata.order_id الطلبية المراد تنفيذها.
curl https://pay-api.adpromax.com/v1/payment_intents \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1045-create" \
-d '{
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": { "order_id": "1045", "channel": "whatsapp" }
}'const response = await fetch('https://pay-api.adpromax.com/v1/payment_intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-1045-create',
},
body: JSON.stringify({
amount: 25000,
currency: 'mad',
payment_method_types: ['card', 'cash'],
metadata: { order_id: '1045', channel: 'whatsapp' },
}),
})
const intent = await response.json()
if (!response.ok)
throw new Error(intent.error.message)
console.log(intent.id, intent.metadata.order_id)interface PaymentIntent {
id: string
object: 'payment_intent'
amount: number
currency: string
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/payment_intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-1045-create',
},
body: JSON.stringify({
amount: 25000,
currency: 'mad',
payment_method_types: ['card', 'cash'],
metadata: { order_id: '1045', channel: 'whatsapp' },
}),
})
if (!response.ok) {
const { error } = (await response.json()) as ApiError
throw new Error(`${error.code}: ${error.message}`)
}
const intent = (await response.json()) as PaymentIntent
console.log(intent.id, intent.metadata.order_id)import os
import requests
response = requests.post(
"https://pay-api.adpromax.com/v1/payment_intents",
auth=(os.environ["APX_SECRET_KEY"], ""),
headers={"Idempotency-Key": "order-1045-create"},
json={
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": {"order_id": "1045", "channel": "whatsapp"},
},
)
intent = response.json()
response.raise_for_status()
print(intent["id"], intent["metadata"]["order_id"])<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/payment_intents');
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Idempotency-Key: order-1045-create',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 25000,
'currency' => 'mad',
'payment_method_types' => ['card', 'cash'],
'metadata' => ['order_id' => '1045', 'channel' => 'whatsapp'],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$intent = json_decode(curl_exec($ch), true);
echo $intent['id'], ' ', $intent['metadata']['order_id'], PHP_EOL;require 'json'
require 'net/http'
uri = URI('https://pay-api.adpromax.com/v1/payment_intents')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = 'order-1045-create'
request.body = JSON.generate(
amount: 25000,
currency: 'mad',
payment_method_types: %w[card cash],
metadata: { order_id: '1045', channel: 'whatsapp' }
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
intent = JSON.parse(response.body)
raise intent.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)
puts "#{intent['id']} #{intent['metadata']['order_id']}"package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type PaymentIntent struct {
ID string `json:"id"`
Status string `json:"status"`
Metadata map[string]string `json:"metadata"`
}
type APIError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func main() {
metadata := map[string]string{"order_id": "1045", "channel": "whatsapp"}
body, err := json.Marshal(map[string]any{
"amount": 25000,
"currency": "mad",
"payment_method_types": []string{"card", "cash"},
"metadata": metadata,
})
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest(http.MethodPost,
"https://pay-api.adpromax.com/v1/payment_intents", 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", "order-1045-create")
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 intent PaymentIntent
if err := json.NewDecoder(res.Body).Decode(&intent); err != nil {
log.Fatal(err)
}
fmt.Println(intent.ID, intent.Metadata["order_id"])
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Metadata {
public static void main(String[] args) throws Exception {
// Your own references, as strings.
String metadata = """
{ "order_id": "1045", "channel": "whatsapp" }""";
String body = """
{
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": %s
}
""".formatted(metadata);
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://pay-api.adpromax.com/v1/payment_intents"))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-1045-create")
.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 payment intent as JSON: its metadata comes back as you sent it.
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/payment_intents");
request.Headers.Add("Idempotency-Key", "order-1045-create");
request.Content = JsonContent.Create(new
{
amount = 25000,
currency = "mad",
payment_method_types = new[] { "card", "cash" },
metadata = new { order_id = "1045", channel = "whatsapp" },
});
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 intent = await response.Content.ReadFromJsonAsync<PaymentIntent>(json);
Console.WriteLine($"{intent!.Id} {intent.Metadata["order_id"]}");
record PaymentIntent(string Id, string Status, Dictionary<string, string> Metadata);
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 std::collections::HashMap;
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
struct PaymentIntent {
id: String,
metadata: HashMap<String, 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/payment_intents")
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.header("Idempotency-Key", "order-1045-create")
.json(&json!({
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": { "order_id": "1045", "channel": "whatsapp" }
}))
.send()?;
if !response.status().is_success() {
let failure: ApiError = response.json()?;
return Err(failure.error.message.into());
}
let intent: PaymentIntent = response.json()?;
println!("{} {}", intent.id, intent.metadata["order_id"]);
Ok(())
}الحدود
- حتى 50 مفتاحًا في الكائن الواحد.
- المفتاح حتى 40 حرفًا؛ والقيمة حتى 500 حرف.
- القيم سلاسل نصية فقط. أرسل الرقم كنص:
"1045"لا1045.
يُرفض الطلب الذي يتجاوز هذه الحدود بـ 400، parameter_invalid، مع param يذكر المفتاح
المخالف، مثل metadata.order_id. ولا يُنشأ شيء. اقرأ param لتعرف المفتاح: فحقل
message جملة عامة لا تذكر أي حد جرى تجاوزه. في هذه الاستجابة، المفتاح
أطول من 40 حرفًا.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"param": "metadata.customer_reference_from_our_previous_system",
"message": "This value is not valid.",
"doc_url": "https://pay-docs.adpromax.com/errors#parameter_invalid",
"request_id": "req_01k7e5s3d7f1g5h9j3k7m1n5pz"
}
}الكائنات التي تقبل البيانات الوصفية
| الكائن | التعيين | التغيير |
|---|---|---|
| نية الدفع | POST /v1/payment_intents | POST /v1/payment_intents/{id}، ما دامت الدفعة requires_payment_method |
| جلسة الدفع | POST /v1/checkout/sessions | — |
| العميل | POST /v1/customers | POST /v1/customers/{id}، في أي وقت |
| الاسترداد | POST /v1/refunds | — |
| نقطة استقبال الـ webhook | POST /v1/webhook_endpoints | POST /v1/webhook_endpoints/{id} |
تغيير البيانات الوصفية لنقطة استقبال webhook يُدمج مع ما تحمله: تُعيَّن المفاتيح التي ترسلها،
وتبقى الأخرى، ويُحذف المفتاح المرسل بقيمة فارغة ("").
تبقى البيانات الوصفية حيث وضعتها
تنتمي البيانات الوصفية إلى الكائن الذي وضعتها عليه، ولا تُنسخ أبدًا إلى كائن آخر. تكون
metadata جلسة الدفع (checkout session) على الجلسة وفي أحداثها checkout.session.*؛ ولا تحملها نية الدفع التي
تنشئها الجلسة.
مع صفحة الدفع المستضافة، احتفظ بمعرّف نية الدفع مع طلبيتك، كما يفعل
البدء السريع للبطاقة، أو اقرأ الجلسة عند وصول
checkout.session.completed.
أين تراها
- في API: كل استجابة تحمل الكائن.
- في إشعارات الـ webhook: يحمل
data.objectفي الحدث البيانات الوصفية للكائن كما كانت عند وقوع الحدث. - في لوحة التحكم: تعرض صفحة الدفعة بياناتها الوصفية، للقراءة فقط، ويمكن تصفية قائمة المدفوعات حسب البيانات الوصفية. وتُعدَّل البيانات الوصفية للعميل في صفحة العميل.
ما لا يوضع أبدًا في البيانات الوصفية
البيانات الوصفية للمراجع، لا للأسرار. يراها فريقك كله في لوحة التحكم، وتتلقاها نقاط استقبال الـ webhook لديك.
- لا أرقام بطاقات. لا تمر أرقام البطاقات عبر API أبدًا: تُرفض أي قيمة تشبه رقم
بطاقة بالرمز
parameter_invalid. - لا كلمات مرور ولا مفاتيح ولا رموز وصول.
- لا بيانات شخصية حساسة، مثل أرقام الهوية أو المعلومات الصحية. لتعرف من دفع، استخدم عميلًا واحتفظ بالباقي في أنظمتك، تحت معرّفك.