البدء
البدء السريع: الدفع نقدًا
امنح عميلك رمز دفع يسدده نقدًا، وحاكِ الدفع في وضع الاختبار، واعرف النتيجة من webhook.
في هذه الصفحة
في الدفع نقدًا، يحصل عميلك على رمز دفع ويسدده نقدًا لدى وكيل دفع شريك، قبل أجل محدد. تنشئ الدفعة تمامًا كما تنشئها للبطاقة؛ طريقة الدفع وحدها تتغير. كل ما هنا يعمل في وضع الاختبار: لا يمكن سداد رمز اختبار لدى وكيل، لذا تحاكي الدفع.
تحتاج إلى حساب في بيئة الاختبار تم التحقق من بريده الإلكتروني. أنشئ حسابًا، ثم أنشئ مفاتيح الاختبار.
1. اضبط مفتاح الاختبار
ضع مفتاحك السري للاختبار (apx_sk_test_…، الذي تنشئه من المطوّرون ← مفاتيح API في
لوحة التحكم) في متغير البيئة APX_SECRET_KEY.
export APX_SECRET_KEY="apx_sk_test_…"2. أنشئ جلسة دفع للدفع نقدًا
أنشئ جلسة دفع (checkout session) تتضمن "cash" في payment_method_types. وأضف "card" أيضًا ليختار
عميلك في الصفحة.
لا تقل الدفعة النقدية عن 100.00 MAD (10000 سنتيم). يُرفض أي مبلغ أصغر بالرمز
amount_too_small، في وضع الاختبار تمامًا كما في الوضع المباشر.
ترويسة Idempotency-Key إلزامية، كما في كل طلب ينشئ دفعة.
curl https://pay-api.adpromax.com/v1/checkout/sessions \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: checkout-order-1043" \
-d '{
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"],
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"metadata": { "order_id": "1043" }
}'const response = await fetch('https://pay-api.adpromax.com/v1/checkout/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'checkout-order-1043',
},
body: JSON.stringify({
amount: 15000,
currency: 'mad',
payment_method_types: ['cash'],
success_url: 'https://shop.example.com/thanks',
cancel_url: 'https://shop.example.com/cart',
metadata: { order_id: '1043' },
}),
})
const session = await response.json()
if (!response.ok)
throw new Error(session.error.message)
console.log(session.payment_intent, session.url)interface CheckoutSession {
id: string
object: 'checkout.session'
amount: number
currency: string
payment_intent: string
status: string
url: string
}
interface ApiError {
error: { type: string, code: string, message: string, request_id: string }
}
const response = await fetch('https://pay-api.adpromax.com/v1/checkout/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'checkout-order-1043',
},
body: JSON.stringify({
amount: 15000,
currency: 'mad',
payment_method_types: ['cash'],
success_url: 'https://shop.example.com/thanks',
cancel_url: 'https://shop.example.com/cart',
metadata: { order_id: '1043' },
}),
})
if (!response.ok) {
const { error } = (await response.json()) as ApiError
throw new Error(`${error.code}: ${error.message}`)
}
const session = (await response.json()) as CheckoutSession
console.log(session.payment_intent, session.url)import os
import requests
response = requests.post(
"https://pay-api.adpromax.com/v1/checkout/sessions",
auth=(os.environ["APX_SECRET_KEY"], ""),
headers={"Idempotency-Key": "checkout-order-1043"},
json={
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"],
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"metadata": {"order_id": "1043"},
},
)
session = response.json()
if not response.ok:
raise RuntimeError(session["error"]["message"])
print(session["payment_intent"], session["url"])<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/checkout/sessions');
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Idempotency-Key: checkout-order-1043',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 15000,
'currency' => 'mad',
'payment_method_types' => ['cash'],
'success_url' => 'https://shop.example.com/thanks',
'cancel_url' => 'https://shop.example.com/cart',
'metadata' => ['order_id' => '1043'],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$session = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
throw new RuntimeException($session['error']['message']);
}
echo $session['payment_intent'], ' ', $session['url'], PHP_EOL;require 'json'
require 'net/http'
uri = URI('https://pay-api.adpromax.com/v1/checkout/sessions')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = 'checkout-order-1043'
request.body = JSON.generate(
amount: 15000,
currency: 'mad',
payment_method_types: ['cash'],
success_url: 'https://shop.example.com/thanks',
cancel_url: 'https://shop.example.com/cart',
metadata: { order_id: '1043' }
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
session = JSON.parse(response.body)
raise session.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)
puts "#{session['payment_intent']} #{session['url']}"package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type CheckoutSession struct {
ID string `json:"id"`
PaymentIntent string `json:"payment_intent"`
URL string `json:"url"`
}
type APIError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func main() {
body, _ := json.Marshal(map[string]any{
"amount": 15000,
"currency": "mad",
"payment_method_types": []string{"cash"},
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"metadata": map[string]string{"order_id": "1043"},
})
req, err := http.NewRequest(http.MethodPost,
"https://pay-api.adpromax.com/v1/checkout/sessions", 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", "checkout-order-1043")
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 session CheckoutSession
if err := json.NewDecoder(res.Body).Decode(&session); err != nil {
log.Fatal(err)
}
fmt.Println(session.PaymentIntent, session.URL)
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Checkout {
public static void main(String[] args) throws Exception {
String body = """
{
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"],
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"metadata": { "order_id": "1043" }
}
""";
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://pay-api.adpromax.com/v1/checkout/sessions"))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", "checkout-order-1043")
.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 session as JSON: read payment_intent and url with your JSON library.
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"));
using var request = new HttpRequestMessage(
HttpMethod.Post, "https://pay-api.adpromax.com/v1/checkout/sessions")
{
Content = JsonContent.Create(new
{
Amount = 15000,
Currency = "mad",
PaymentMethodTypes = new[] { "cash" },
SuccessUrl = "https://shop.example.com/thanks",
CancelUrl = "https://shop.example.com/cart",
Metadata = new Dictionary<string, string> { ["order_id"] = "1043" },
}, options: json),
};
request.Headers.Add("Idempotency-Key", "checkout-order-1043");
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 session = await response.Content.ReadFromJsonAsync<CheckoutSession>(json);
Console.WriteLine($"{session!.PaymentIntent} {session.Url}");
record CheckoutSession(string Id, string PaymentIntent, string Url);
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 CheckoutSession {
payment_intent: String,
url: 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/checkout/sessions")
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.header("Idempotency-Key", "checkout-order-1043")
.json(&json!({
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"],
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"metadata": { "order_id": "1043" },
}))
.send()?;
if !response.status().is_success() {
let failure: ApiError = response.json()?;
return Err(failure.error.message.into());
}
let session: CheckoutSession = response.json()?;
println!("{} {}", session.payment_intent, session.url);
Ok(())
}3. يحصل عميلك على رمز دفع
أرسل عميلك إلى url الجلسة، كما في البطاقة. يدخل في الصفحة اسمه
ومدينته، ثم يحصل على قسيمة:
- الرمز الذي يقدمه لدى الوكيل؛
- المبلغ الواجب دفعه: مبلغك مضافًا إليه رسم طابع صغير يدفعه عميلك (في وضع الاختبار، 0.25 MAD). اعرض دائمًا المبلغ الذي تحدده القسيمة؛ ولا تحسبه بنفسك أبدًا؛
- الأجل: نحو ثلاثة أيام، وتحدده القسيمة.
يمكن طباعة صفحة القسيمة أو حفظها، ويمكن لعميلك العودة إليها. يدفع نقدًا لدى
وكيل دفع شريك، مقدّمًا الرمز، قبل الأجل. وتبقى حالة الدفعة
requires_action إلى ذلك الحين.
4. حاكِ الدفع
في وضع الاختبار، تحمل صفحة القسيمة زر محاكاة الدفع. اضغط عليه: يُسدَّد الرمز كما لو أن عميلك دفع لدى وكيل.
في الاختبارات الآلية، استدعِ أداة الاختبار المساعدة بدل ذلك. تأخذ محاولة الدفعة، أي
latest_charge في نية الدفع (payment intent) الخاصة بها. وتجد النتائج الأخرى (انتهاء الصلاحية، الرفض، مبلغ آخر) في
سيناريوهات اختبار الدفع النقدي.
PI=pi_01k7d4c8n2v6b1m5x9z3q7w4ea # the session's payment_intent
CH=$(curl -s https://pay-api.adpromax.com/v1/payment_intents/$PI \
-u "$APX_SECRET_KEY:" | jq -r .latest_charge)
curl -X POST https://pay-api.adpromax.com/v1/test_helpers/cash_vouchers/$CH/pay \
-u "$APX_SECRET_KEY:"const api = 'https://pay-api.adpromax.com/v1'
const headers = { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` }
const id = 'pi_01k7d4c8n2v6b1m5x9z3q7w4ea' // the session's payment_intent
async function call(method, path) {
const response = await fetch(`${api}${path}`, { method, headers })
const answer = await response.json()
if (!response.ok)
throw new Error(answer.error.message)
return answer
}
const intent = await call('GET', `/payment_intents/${id}`)
const charge = intent.latest_charge
await call('POST', `/test_helpers/cash_vouchers/${charge}/pay`)interface PaymentIntent {
id: string
object: 'payment_intent'
status: string
latest_charge: string | null
}
interface ApiError {
error: { type: string, code: string, message: string, request_id: string }
}
const api = 'https://pay-api.adpromax.com/v1'
const headers = { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` }
const id = 'pi_01k7d4c8n2v6b1m5x9z3q7w4ea' // the session's payment_intent
async function call<T>(method: 'GET' | 'POST', path: string): Promise<T> {
const response = await fetch(`${api}${path}`, { method, headers })
if (!response.ok) {
const { error } = (await response.json()) as ApiError
throw new Error(`${error.code}: ${error.message}`)
}
return (await response.json()) as T
}
const intent = await call<PaymentIntent>('GET', `/payment_intents/${id}`)
const charge = intent.latest_charge
await call('POST', `/test_helpers/cash_vouchers/${charge}/pay`)import os
import requests
API = "https://pay-api.adpromax.com/v1"
AUTH = (os.environ["APX_SECRET_KEY"], "")
intent_id = "pi_01k7d4c8n2v6b1m5x9z3q7w4ea" # the session's payment_intent
def call(method, path):
response = requests.request(method, f"{API}{path}", auth=AUTH)
answer = response.json()
if not response.ok:
raise RuntimeError(answer["error"]["message"])
return answer
intent = call("GET", f"/payment_intents/{intent_id}")
charge = intent["latest_charge"]
call("POST", f"/test_helpers/cash_vouchers/{charge}/pay")<?php
function api(string $method, string $path): array
{
$ch = curl_init('https://pay-api.adpromax.com/v1' . $path);
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
]);
$answer = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
throw new RuntimeException($answer['error']['message']);
}
return $answer;
}
// the session's payment_intent
$intent = api('GET', '/payment_intents/pi_01k7d4c8n2v6b1m5x9z3q7w4ea');
$charge = $intent['latest_charge'];
api('POST', "/test_helpers/cash_vouchers/{$charge}/pay");require 'json'
require 'net/http'
API = 'https://pay-api.adpromax.com/v1'
intent_id = 'pi_01k7d4c8n2v6b1m5x9z3q7w4ea' # the session's payment_intent
def call(request_class, path)
uri = URI("#{API}#{path}")
request = request_class.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
answer = JSON.parse(response.body)
raise answer.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)
answer
end
intent = call(Net::HTTP::Get, "/payment_intents/#{intent_id}")
charge = intent['latest_charge']
call(Net::HTTP::Post, "/test_helpers/cash_vouchers/#{charge}/pay")package main
import (
"encoding/json"
"errors"
"log"
"net/http"
"os"
)
const api = "https://pay-api.adpromax.com/v1"
type PaymentIntent struct {
ID string `json:"id"`
Status string `json:"status"`
LatestCharge string `json:"latest_charge"`
}
type APIError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func call(method, path string, answer any) error {
req, err := http.NewRequest(method, api+path, nil)
if err != nil {
return err
}
req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var apiErr APIError
json.NewDecoder(res.Body).Decode(&apiErr)
return errors.New(apiErr.Error.Message)
}
return json.NewDecoder(res.Body).Decode(answer)
}
func main() {
id := "pi_01k7d4c8n2v6b1m5x9z3q7w4ea" // the session's payment_intent
var intent PaymentIntent
if err := call(http.MethodGet, "/payment_intents/"+id, &intent); err != nil {
log.Fatal(err)
}
pay := "/test_helpers/cash_vouchers/" + intent.LatestCharge + "/pay"
var charge map[string]any
if err := call(http.MethodPost, pay, &charge); err != nil {
log.Fatal(err)
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.regex.Pattern;
public class Simulate {
static final String API = "https://pay-api.adpromax.com/v1";
static final HttpClient HTTP = HttpClient.newHttpClient();
static String call(String method, String path) throws Exception {
HttpRequest request = HttpRequest.newBuilder(URI.create(API + path))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.method(method, HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response =
HTTP.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException(response.body());
}
return response.body();
}
public static void main(String[] args) throws Exception {
String id = "pi_01k7d4c8n2v6b1m5x9z3q7w4ea"; // the session's payment_intent
String intent = call("GET", "/payment_intents/" + id);
// The JDK has no JSON parser: a regex reads latest_charge here.
var charge = Pattern.compile("\"latest_charge\"\\s*:\\s*\"([^\"]+)\"")
.matcher(intent);
if (!charge.find()) {
throw new IllegalStateException("No latest_charge: " + intent);
}
call("POST", "/test_helpers/cash_vouchers/" + charge.group(1) + "/pay");
}
}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 { BaseAddress = new Uri("https://pay-api.adpromax.com/v1/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));
var id = "pi_01k7d4c8n2v6b1m5x9z3q7w4ea"; // the session's payment_intent
using var found = await http.GetAsync($"payment_intents/{id}");
await EnsureSuccess(found);
var intent = await found.Content.ReadFromJsonAsync<PaymentIntent>(json);
using var paid = await http.PostAsync(
$"test_helpers/cash_vouchers/{intent!.LatestCharge}/pay", null);
await EnsureSuccess(paid);
async Task EnsureSuccess(HttpResponseMessage response)
{
if (response.IsSuccessStatusCode)
return;
var failure = await response.Content.ReadFromJsonAsync<ApiError>(json);
throw new InvalidOperationException(failure?.Error.Message);
}
record PaymentIntent(string Id, string Status, string? LatestCharge);
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 reqwest::blocking::{Client, RequestBuilder};
use serde::de::DeserializeOwned;
use serde::Deserialize;
const API: &str = "https://pay-api.adpromax.com/v1";
#[derive(Deserialize)]
struct PaymentIntent {
latest_charge: String,
}
#[derive(Deserialize)]
struct ApiError {
error: ErrorBody,
}
#[derive(Deserialize)]
struct ErrorBody {
message: String,
}
fn send<T: DeserializeOwned>(
request: RequestBuilder,
) -> Result<T, Box<dyn std::error::Error>> {
let key = std::env::var("APX_SECRET_KEY")?;
let response = request.bearer_auth(key).send()?;
if !response.status().is_success() {
let failure: ApiError = response.json()?;
return Err(failure.error.message.into());
}
Ok(response.json()?)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let id = "pi_01k7d4c8n2v6b1m5x9z3q7w4ea"; // the session's payment_intent
let url = format!("{API}/payment_intents/{id}");
let intent: PaymentIntent = send(client.get(url))?;
let charge = intent.latest_charge;
let pay = format!("{API}/test_helpers/cash_vouchers/{charge}/pay");
let _charge: serde_json::Value = send(client.post(pay))?;
Ok(())
}5. استقبل النتيجة
عند تأكيد الدفعة، يرسل AdProMaxPay الحدث payment_intent.succeeded إلى نقطة استقبال الـ webhook لديك:
بعد ثوانٍ قليلة من الدفع المحاكى. وفي الوضع المباشر يصل بعد تأكيد الدفع لدى
الوكيل، خلال دقائق في العادة.
إذا انقضى الأجل دون دفع، تتلقى payment_intent.payment_failed، وتكون قيمة
last_payment_error.code هي voucher_expired. تعود الدفعة إلى
requires_payment_method: يمكن لعميلك الحصول على رمز جديد ما دامت الجلسة مفتوحة.
نقطة الاستقبال والمعالج هما نفساهما في البدء السريع للبطاقة: أنشئ نقطة الاستقبال، ثم عالج الحدث. لا تُسترد الدفعة النقدية نقدًا أبدًا: انظر المبالغ المستردة.
اعرض الرمز في صفحتك الخاصة
يمكنك أيضًا الاستغناء عن الصفحة المستضافة: أنشئ نية دفع، ثم أكّدها بالدفع نقدًا مع
اسم عميلك ومدينته. يتضمن next_action.display_voucher في الاستجابة القسيمة: الرمز
code، والمبلغ amount_to_pay، وexpires_at (بثواني Unix)، ورابط slip_url لوصل قابل للطباعة،
وhosted_voucher_url وهو صفحة القسيمة لدينا، ويمكنك إرساله إلى عميلك بدلًا من ذلك.
يحتاج كل من الطلبين إلى Idempotency-Key خاص به.
PI=$(curl -s https://pay-api.adpromax.com/v1/payment_intents \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1044-create" \
-d '{"amount": 15000, "currency": "mad",
"payment_method_types": ["cash"]}' | jq -r .id)
curl https://pay-api.adpromax.com/v1/payment_intents/$PI/confirm \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1044-confirm" \
-d '{"payment_method_type": "cash",
"payer": {"name": "Salma Idrissi", "city": "Casablanca"}}'const api = 'https://pay-api.adpromax.com/v1'
const auth = `Bearer ${process.env.APX_SECRET_KEY}`
async function post(path, key, body) {
const response = await fetch(`${api}${path}`, {
method: 'POST',
headers: {
'Authorization': auth,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify(body),
})
const answer = await response.json()
if (!response.ok)
throw new Error(answer.error.message)
return answer
}
const intent = await post('/payment_intents', 'order-1044-create', {
amount: 15000,
currency: 'mad',
payment_method_types: ['cash'],
})
const confirmed = await post(`/payment_intents/${intent.id}/confirm`,
'order-1044-confirm', {
payment_method_type: 'cash',
payer: { name: 'Salma Idrissi', city: 'Casablanca' },
})
const voucher = confirmed.next_action.display_voucher
console.log(voucher.code, voucher.amount_to_pay, voucher.expires_at)interface Voucher {
code: string
amount_to_pay: number
currency: string
expires_at: number
slip_url: string
hosted_voucher_url: string
}
interface PaymentIntent {
id: string
object: 'payment_intent'
status: string
next_action: { type: 'display_voucher', display_voucher: Voucher } | null
}
interface ApiError {
error: { type: string, code: string, message: string, request_id: string }
}
const api = 'https://pay-api.adpromax.com/v1'
const auth = `Bearer ${process.env.APX_SECRET_KEY}`
async function post(path: string, key: string, body: object): Promise<PaymentIntent> {
const response = await fetch(`${api}${path}`, {
method: 'POST',
headers: {
'Authorization': auth,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify(body),
})
if (!response.ok) {
const { error } = (await response.json()) as ApiError
throw new Error(`${error.code}: ${error.message}`)
}
return (await response.json()) as PaymentIntent
}
const intent = await post('/payment_intents', 'order-1044-create', {
amount: 15000,
currency: 'mad',
payment_method_types: ['cash'],
})
const confirmed = await post(`/payment_intents/${intent.id}/confirm`,
'order-1044-confirm', {
payment_method_type: 'cash',
payer: { name: 'Salma Idrissi', city: 'Casablanca' },
})
const voucher = confirmed.next_action?.display_voucher
console.log(voucher?.code, voucher?.amount_to_pay, voucher?.expires_at)import os
import requests
API = "https://pay-api.adpromax.com/v1"
AUTH = (os.environ["APX_SECRET_KEY"], "")
def post(path, key, body):
response = requests.post(
f"{API}{path}", auth=AUTH, json=body,
headers={"Idempotency-Key": key},
)
answer = response.json()
if not response.ok:
raise RuntimeError(answer["error"]["message"])
return answer
intent = post("/payment_intents", "order-1044-create", {
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"],
})
confirmed = post(f"/payment_intents/{intent['id']}/confirm",
"order-1044-confirm", {
"payment_method_type": "cash",
"payer": {"name": "Salma Idrissi", "city": "Casablanca"},
})
voucher = confirmed["next_action"]["display_voucher"]
print(voucher["code"], voucher["amount_to_pay"], voucher["expires_at"])<?php
function post(string $path, string $key, array $body): array
{
$ch = curl_init('https://pay-api.adpromax.com/v1' . $path);
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Idempotency-Key: ' . $key,
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true,
]);
$answer = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
throw new RuntimeException($answer['error']['message']);
}
return $answer;
}
$intent = post('/payment_intents', 'order-1044-create', [
'amount' => 15000,
'currency' => 'mad',
'payment_method_types' => ['cash'],
]);
$confirmed = post("/payment_intents/{$intent['id']}/confirm",
'order-1044-confirm', [
'payment_method_type' => 'cash',
'payer' => ['name' => 'Salma Idrissi', 'city' => 'Casablanca'],
]);
$voucher = $confirmed['next_action']['display_voucher'];
echo $voucher['code'], ' ', $voucher['amount_to_pay'], ' ',
$voucher['expires_at'], PHP_EOL;require 'json'
require 'net/http'
API = 'https://pay-api.adpromax.com/v1'
def post(path, key, body)
uri = URI("#{API}#{path}")
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = key
request.body = JSON.generate(body)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
answer = JSON.parse(response.body)
raise answer.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)
answer
end
intent = post('/payment_intents', 'order-1044-create',
amount: 15000, currency: 'mad', payment_method_types: ['cash'])
confirmed = post("/payment_intents/#{intent['id']}/confirm", 'order-1044-confirm',
payment_method_type: 'cash',
payer: { name: 'Salma Idrissi', city: 'Casablanca' })
voucher = confirmed.dig('next_action', 'display_voucher')
puts "#{voucher['code']} #{voucher['amount_to_pay']} #{voucher['expires_at']}"package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
)
const api = "https://pay-api.adpromax.com/v1"
type Voucher struct {
Code string `json:"code"`
AmountToPay int64 `json:"amount_to_pay"`
ExpiresAt int64 `json:"expires_at"`
HostedVoucherURL string `json:"hosted_voucher_url"`
}
type PaymentIntent struct {
ID string `json:"id"`
Status string `json:"status"`
NextAction struct {
DisplayVoucher Voucher `json:"display_voucher"`
} `json:"next_action"`
}
type APIError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func post(path, key string, body any) (*PaymentIntent, error) {
payload, _ := json.Marshal(body)
req, err := http.NewRequest(http.MethodPost, api+path, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", key)
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var apiErr APIError
json.NewDecoder(res.Body).Decode(&apiErr)
return nil, errors.New(apiErr.Error.Message)
}
var intent PaymentIntent
return &intent, json.NewDecoder(res.Body).Decode(&intent)
}
func main() {
intent, err := post("/payment_intents", "order-1044-create", map[string]any{
"amount": 15000,
"currency": "mad",
"payment_method_types": []string{"cash"},
})
if err != nil {
log.Fatal(err)
}
confirmed, err := post("/payment_intents/"+intent.ID+"/confirm",
"order-1044-confirm", map[string]any{
"payment_method_type": "cash",
"payer": map[string]string{
"name": "Salma Idrissi",
"city": "Casablanca",
},
})
if err != nil {
log.Fatal(err)
}
voucher := confirmed.NextAction.DisplayVoucher
fmt.Println(voucher.Code, voucher.AmountToPay, voucher.ExpiresAt)
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.regex.Pattern;
public class Voucher {
static final String API = "https://pay-api.adpromax.com/v1";
static final HttpClient HTTP = HttpClient.newHttpClient();
static String post(String path, String key, String body) throws Exception {
HttpRequest request = HttpRequest.newBuilder(URI.create(API + path))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
HTTP.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException(response.body());
}
return response.body();
}
public static void main(String[] args) throws Exception {
String intent = post("/payment_intents", "order-1044-create", """
{"amount": 15000, "currency": "mad", "payment_method_types": ["cash"]}
""");
// The JDK has no JSON parser: a regex reads the id here.
var id = Pattern.compile("\"id\"\\s*:\\s*\"(pi_[^\"]+)\"").matcher(intent);
if (!id.find()) {
throw new IllegalStateException("No id: " + intent);
}
String confirmed = post("/payment_intents/" + id.group(1) + "/confirm",
"order-1044-confirm", """
{"payment_method_type": "cash",
"payer": {"name": "Salma Idrissi", "city": "Casablanca"}}
""");
// next_action.display_voucher: code, amount_to_pay, expires_at
System.out.println(confirmed);
}
}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 { BaseAddress = new Uri("https://pay-api.adpromax.com/v1/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));
var intent = await Post("payment_intents", "order-1044-create", new
{
Amount = 15000,
Currency = "mad",
PaymentMethodTypes = new[] { "cash" },
});
var confirm = $"payment_intents/{intent.Id}/confirm";
var confirmed = await Post(confirm, "order-1044-confirm", new
{
PaymentMethodType = "cash",
Payer = new { Name = "Salma Idrissi", City = "Casablanca" },
});
var voucher = confirmed.NextAction!.DisplayVoucher;
Console.WriteLine($"{voucher.Code} {voucher.AmountToPay} {voucher.ExpiresAt}");
async Task<PaymentIntent> Post(string path, string key, object body)
{
using var request = new HttpRequestMessage(HttpMethod.Post, path)
{
Content = JsonContent.Create(body, options: json),
};
request.Headers.Add("Idempotency-Key", key);
using var response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var failure = await response.Content.ReadFromJsonAsync<ApiError>(json);
throw new InvalidOperationException(failure?.Error.Message);
}
return (await response.Content.ReadFromJsonAsync<PaymentIntent>(json))!;
}
record PaymentIntent(string Id, string Status, NextAction? NextAction);
record NextAction(string Type, Voucher DisplayVoucher);
record Voucher(string Code, long AmountToPay, long ExpiresAt, string HostedVoucherUrl);
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 reqwest::blocking::Client;
use serde::Deserialize;
use serde_json::{json, Value};
const API: &str = "https://pay-api.adpromax.com/v1";
#[derive(Deserialize)]
struct PaymentIntent {
id: String,
next_action: Option<NextAction>,
}
#[derive(Deserialize)]
struct NextAction {
display_voucher: Voucher,
}
#[derive(Deserialize)]
struct Voucher {
code: String,
amount_to_pay: i64,
expires_at: i64,
}
#[derive(Deserialize)]
struct ApiError {
error: ErrorBody,
}
#[derive(Deserialize)]
struct ErrorBody {
message: String,
}
fn post(
client: &Client,
path: &str,
key: &str,
body: Value,
) -> Result<PaymentIntent, Box<dyn std::error::Error>> {
let response = client
.post(format!("{API}{path}"))
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.header("Idempotency-Key", key)
.json(&body)
.send()?;
if !response.status().is_success() {
let failure: ApiError = response.json()?;
return Err(failure.error.message.into());
}
Ok(response.json()?)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let intent = post(&client, "/payment_intents", "order-1044-create", json!({
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"],
}))?;
let confirm = format!("/payment_intents/{}/confirm", intent.id);
let confirmed = post(&client, &confirm, "order-1044-confirm", json!({
"payment_method_type": "cash",
"payer": { "name": "Salma Idrissi", "city": "Casablanca" },
}))?;
let next_action = confirmed.next_action.ok_or("no voucher")?;
let voucher = next_action.display_voucher;
println!("{} {} {}", voucher.code, voucher.amount_to_pay, voucher.expires_at);
Ok(())
}{
"id": "pi_01k7d5f2k8m4n9b3v7c1x6z2ra",
"object": "payment_intent",
"amount": 15000,
"currency": "mad",
"status": "requires_action",
"payment_method_types": ["cash"],
"latest_charge": "ch_01k7d5f3a1s5d9f2g6h4j8k3nb",
"next_action": {
"type": "display_voucher",
"display_voucher": {
"code": "48210937",
"amount_to_pay": 15025,
"currency": "mad",
"expires_at": 1791112200,
"slip_url": "https://pay.example.com/…",
"hosted_voucher_url": "https://pay.example.com/v/ch_01k7d5f3a1…#…"
}
},
"livemode": false
}الخطوات التالية
- البدء السريع: الدفع بالبطاقة عبر صفحة الدفع المستضافة.
- سيناريوهات اختبار الدفع النقدي: انتهاء الصلاحية، والرفض، والدفع المتأخر، والدفع بمبلغ آخر.
- بيئة الاختبار والوضع المباشر: الفروق: ما يتغير في الدفع نقدًا في الوضع المباشر.