البدء

البدء السريع: الدفع نقدًا

امنح عميلك رمز دفع يسدده نقدًا، وحاكِ الدفع في وضع الاختبار، واعرف النتيجة من webhook.

فتح بصيغة Markdown
في هذه الصفحة
  1. 1. اضبط مفتاح الاختبار
  2. 2. أنشئ جلسة دفع للدفع نقدًا
  3. 3. يحصل عميلك على رمز دفع
  4. 4. حاكِ الدفع
  5. 5. استقبل النتيجة
  6. اعرض الرمز في صفحتك الخاصة
  7. الخطوات التالية

في الدفع نقدًا، يحصل عميلك على رمز دفع ويسدده نقدًا لدى وكيل دفع شريك، قبل أجل محدد. تنشئ الدفعة تمامًا كما تنشئها للبطاقة؛ طريقة الدفع وحدها تتغير. كل ما هنا يعمل في وضع الاختبار: لا يمكن سداد رمز اختبار لدى وكيل، لذا تحاكي الدفع.

تحتاج إلى حساب في بيئة الاختبار تم التحقق من بريده الإلكتروني. أنشئ حسابًا، ثم أنشئ مفاتيح الاختبار.

1. اضبط مفتاح الاختبار

ضع مفتاحك السري للاختبار (apx_sk_test_…، الذي تنشئه من المطوّرون ← مفاتيح API في لوحة التحكم) في متغير البيئة APX_SECRET_KEY.

Terminal
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
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
checkout.mjscheckout.tscheckout.pycheckout.phpcheckout.rbcheckout.goCheckout.javaCheckout.csmain.rs
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) الخاصة بها. وتجد النتائج الأخرى (انتهاء الصلاحية، الرفض، مبلغ آخر) في سيناريوهات اختبار الدفع النقدي.

  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
simulate.mjssimulate.tssimulate.pysimulate.phpsimulate.rbsimulate.goSimulate.javaSimulate.csmain.rs
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 خاص به.

  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
voucher.mjsvoucher.tsvoucher.pyvoucher.phpvoucher.rbvoucher.goVoucher.javaVoucher.csmain.rs
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(())
}
الاستجابة200 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
}

الخطوات التالية