البدء
اختبر تكاملك
كل مسار قد يسلكه عملاؤك، وكل حالة فشل، ومعالجتك لإشعارات webhook، مختبرة من البداية إلى النهاية في بيئة الاختبار قبل الانتقال إلى الوضع المباشر.
في هذه الصفحة
يشغّل وضع الاختبار الكود نفسه الذي يشغّله الوضع المباشر: التحقق من صحة البيانات نفسه، والحالات نفسها، والأحداث نفسها، وإشعارات الـ webhook نفسها (انظر بيئة الاختبار والوضع المباشر: الفروق). ما يعمل هنا يعمل في الوضع المباشر. اختبر أولًا المسار الذي يسلكه عملاؤك، ثم كل طريقة قد تفشل بها دفعة، ثم ما يفعله خادمك بإشعارات الـ webhook التي يتلقاها.
قبل أن تبدأ
- مفتاح سري للاختبار في
APX_SECRET_KEY: انظر مفاتيح API الخاصة بك. - نقطة استقبال webhook في وضع الاختبار، مع سرّها في
APX_WEBHOOK_SECRET. ولاستقبال الأحداث على حاسوبك، اجعل خادمك متاحًا عبر نفق HTTPS: انظر البدء السريع للبطاقة. - لوحة التحكم، في وضع الاختبار، لمتابعة ما يحدث:
- تعرض المطوّرون ← سجلات الطلبات كل استدعاء لـ API مع حالته و
Request-Idالخاص به؛ - تعرض المطوّرون ← الأحداث كل حدث، مع عمليات تسليمه إلى كل نقطة استقبال؛
- تعرض صفحة نقطة الاستقبال في المطوّرون ← Webhooks كل عملية تسليم وإجاباتك عنها.
- تعرض المطوّرون ← سجلات الطلبات كل استدعاء لـ API مع حالته و
الدفع بالبطاقة
ادفع في كل سيناريو على الصفحة المستضافة باستخدام بطاقة الاختبار المذكورة، أو دون متصفح عبر أدوات الاختبار المساعدة.
ينفّذ الطلبية مرة واحدة، عند payment_intent.succeeded.
يُظهر أن الدفعة فشلت ويتيح لعميلك المحاولة من جديد. تبقى الطلبية مفتوحة.
مرة بخيار موافقة ومرة بخيار فشل
عند الموافقة: كما في النجاح. عند الفشل: كما في الرفض، مع authentication_failed.
يعود عميلك إلى success_url قبل تأكيد الدفعة: اعرض «نحن بصدد تأكيد دفعتك»، وسلّم الطلبية عند وصول الـ webhook، بعد نحو 60 ثانية.
تنفيذ واحد: بريد إلكتروني واحد، وحركة مخزون واحدة.
لا يسلّم أبدًا: لا تنجح الدفعة، وتنتهي صلاحية محاولتها بعد نحو 30 دقيقة.
الدفع نقدًا
تصرّف في كل رمز اختبار بأزرار صفحة القسيمة، أو عبر أدوات الاختبار المساعدة. وتجد ما تفعله كل نتيجة في سيناريوهات اختبار الدفع النقدي.
محاكاة الدفع، أو pay
ينفّذ الطلبية عند payment_intent.succeeded.
expire
يحرّر الطلبية أو المخزون، ويتيح لعميلك الحصول على رمز جديد.
pay مع amount مختلف
لا يسلّم شيئًا: تحصل الدفعة على review ولا يُضاف شيء إلى الرصيد. وينتظر.
expire، ثم pay
الأمر نفسه: review، ولا إضافة إلى الرصيد، ولا تسليم.
دفعة نقدية بمبلغ 5000
يعالج الرفض، amount_too_small: اعرض البطاقة فقط للمبالغ الصغيرة.
الاسترداد
استردّ جزءًا من دفعة ناجحة بالبطاقة
يسجّل ما تم استرداده؛ ويزداد amount_refunded في نية الدفع (payment intent).
استردّ الباقي
يضع على الطلبية علامة «مستردة» عند نجاح الاسترداد.
حاول استرداد دفعة نقدية
يعالج الرفض، refund_unsupported_payment_method: يتولى الأمر إجراؤك الخاص بالدفع نقدًا. انظر المبالغ المستردة.
إشعارات webhook
اختر إعادة الإرسال لعملية تسليم من لوحة التحكم
يعالجه مرة واحدة: id الحدث معروف مسبقًا. انظر إزالة التكرار.
أوقف خادمك، وادفع، وشغّله من جديد، ثم اختر إعادة الإرسال أو انتظر إعادة المحاولة
ينفّذ الطلبية عندما تنجح عملية التسليم.
الطلبات
أرسل طلب إنشاء مرتين بالقيمة نفسها لـ Idempotency-Key
دفعة واحدة؛ وتحمل الاستجابة الثانية Idempotent-Replayed: true. انظر عدم التكرار (idempotency).
في اختباراتك الخاصة، أعِد إلى برنامجك الرد 429
ينتظر مدة Retry-After، ثم يعيد المحاولة بالمفتاح نفسه. انظر حدود المعدل.
مفتاح اختبار ينقصه حرف واحد
يعالج 401، api_key_invalid: تنبيه لك، لا إعادة محاولة أبدًا.
اختبار آلي: رمز دفع نقدي تنتهي صلاحيته
يمكن لمجموعة اختباراتك تشغيل أي من هذه السيناريوهات على بيئة الاختبار. ينشئ هذا الاختبار دفعة
نقدية، ويؤكدها باسم عميلك ومدينته، وينهي صلاحية رمزها بأداة الاختبار المساعدة، ثم
ينتظر حتى تعود الدفعة إلى requires_payment_method مع voucher_expired.
يحمل كل طلب إنشاء وكل طلب تأكيد Idempotency-Key خاصًا به، يُنشأ من جديد في كل تشغيل.
وتذكر استجابة التأكيد المحاولة في latest_charge: وعليها تعمل الأداة المساعدة.
API=https://pay-api.adpromax.com/v1
RUN=$(date +%s)
PI=$(curl -s $API/payment_intents -u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: test-$RUN-create" \
-d '{"amount": 15000, "currency": "mad",
"payment_method_types": ["cash"]}' | jq -r .id)
CH=$(curl -s $API/payment_intents/$PI/confirm -u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: test-$RUN-confirm" \
-d '{"payment_method_type": "cash",
"payer": {"name": "Salma Idrissi", "city": "Casablanca"}}' \
| jq -r .latest_charge)
curl -s -X POST $API/test_helpers/cash_vouchers/$CH/expire \
-u "$APX_SECRET_KEY:" > /dev/null
for i in 1 2 3 4 5 6 7 8 9 10; do
INTENT=$(curl -s $API/payment_intents/$PI -u "$APX_SECRET_KEY:")
STATUS=$(echo "$INTENT" | jq -r .status)
[ "$STATUS" = requires_payment_method ] && break
sleep 1
done
echo "$PI $STATUS $(echo "$INTENT" | jq -r .last_payment_error.code)"import { randomUUID } from 'node:crypto'
const api = 'https://pay-api.adpromax.com/v1'
const auth = `Bearer ${process.env.APX_SECRET_KEY}`
const run = randomUUID()
async function call(method, path, body, key) {
const headers = { Authorization: auth }
if (body !== undefined)
headers['Content-Type'] = 'application/json'
if (key !== undefined)
headers['Idempotency-Key'] = `${run}-${key}`
const response = await fetch(`${api}${path}`, {
method,
headers,
body: body === undefined ? undefined : JSON.stringify(body),
})
const answer = await response.json()
if (!response.ok)
throw new Error(answer.error.message)
return answer
}
const intent = await call('POST', '/payment_intents', {
amount: 15000,
currency: 'mad',
payment_method_types: ['cash'],
}, 'create')
const confirmed = await call('POST', `/payment_intents/${intent.id}/confirm`, {
payment_method_type: 'cash',
payer: { name: 'Salma Idrissi', city: 'Casablanca' },
}, 'confirm')
const charge = confirmed.latest_charge
await call('POST', `/test_helpers/cash_vouchers/${charge}/expire`)
let latest
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 1000))
latest = await call('GET', `/payment_intents/${intent.id}`)
if (latest.status === 'requires_payment_method')
break
}
// Expected: requires_payment_method voucher_expired
console.log(intent.id, latest.status, latest.last_payment_error?.code)import { randomUUID } from 'node:crypto'
interface PaymentIntent {
id: string
status: string
latest_charge: string | null
last_payment_error: { code: string, message: string } | 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}`
const run = randomUUID()
async function call<T>(
method: string,
path: string,
body?: object,
key?: string,
): Promise<T> {
const headers: Record<string, string> = { Authorization: auth }
if (body !== undefined)
headers['Content-Type'] = 'application/json'
if (key !== undefined)
headers['Idempotency-Key'] = `${run}-${key}`
const response = await fetch(`${api}${path}`, {
method,
headers,
body: body === undefined ? undefined : 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 T
}
const intent = await call<PaymentIntent>('POST', '/payment_intents', {
amount: 15000,
currency: 'mad',
payment_method_types: ['cash'],
}, 'create')
const confirm = `/payment_intents/${intent.id}/confirm`
const confirmed = await call<PaymentIntent>('POST', confirm, {
payment_method_type: 'cash',
payer: { name: 'Salma Idrissi', city: 'Casablanca' },
}, 'confirm')
const charge = confirmed.latest_charge
await call('POST', `/test_helpers/cash_vouchers/${charge}/expire`)
let latest = confirmed
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 1000))
latest = await call<PaymentIntent>('GET', `/payment_intents/${intent.id}`)
if (latest.status === 'requires_payment_method')
break
}
// Expected: requires_payment_method voucher_expired
console.log(intent.id, latest.status, latest.last_payment_error?.code)import os
import time
import uuid
import requests
API = "https://pay-api.adpromax.com/v1"
AUTH = (os.environ["APX_SECRET_KEY"], "")
RUN = uuid.uuid4().hex
def call(method, path, body=None, key=None):
headers = {"Idempotency-Key": f"{RUN}-{key}"} if key else {}
response = requests.request(
method, f"{API}{path}", auth=AUTH, json=body, headers=headers
)
answer = response.json()
response.raise_for_status()
return answer
intent = call("POST", "/payment_intents", {
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"],
}, "create")
confirmed = call("POST", f"/payment_intents/{intent['id']}/confirm", {
"payment_method_type": "cash",
"payer": {"name": "Salma Idrissi", "city": "Casablanca"},
}, "confirm")
charge = confirmed["latest_charge"]
call("POST", f"/test_helpers/cash_vouchers/{charge}/expire")
latest = None
for _ in range(10):
time.sleep(1)
latest = call("GET", f"/payment_intents/{intent['id']}")
if latest["status"] == "requires_payment_method":
break
# Expected: requires_payment_method voucher_expired
error = latest["last_payment_error"] or {}
print(intent["id"], latest["status"], error.get("code"))<?php
function call(
string $method,
string $path,
?array $body = null,
?string $key = null
): array {
$headers = ['Content-Type: application/json'];
if ($key !== null) {
$headers[] = "Idempotency-Key: {$key}";
}
$ch = curl_init('https://pay-api.adpromax.com/v1' . $path);
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$answer = json_decode(curl_exec($ch), true);
return $answer;
}
$run = bin2hex(random_bytes(8));
$intent = call('POST', '/payment_intents', [
'amount' => 15000,
'currency' => 'mad',
'payment_method_types' => ['cash'],
], "{$run}-create");
$confirmed = call('POST', "/payment_intents/{$intent['id']}/confirm", [
'payment_method_type' => 'cash',
'payer' => ['name' => 'Salma Idrissi', 'city' => 'Casablanca'],
], "{$run}-confirm");
$charge = $confirmed['latest_charge'];
call('POST', "/test_helpers/cash_vouchers/{$charge}/expire");
for ($i = 0; $i < 10; $i++) {
sleep(1);
$latest = call('GET', "/payment_intents/{$intent['id']}");
if ($latest['status'] === 'requires_payment_method') {
break;
}
}
// Expected: requires_payment_method voucher_expired
echo $intent['id'], ' ', $latest['status'], ' ',
$latest['last_payment_error']['code'] ?? '', PHP_EOL;require 'json'
require 'minitest/autorun'
require 'net/http'
require 'securerandom'
class CashExpiryTest < Minitest::Test
API = 'https://pay-api.adpromax.com/v1'.freeze
RUN = SecureRandom.uuid
def call(verb, path, body = nil, key = nil)
uri = URI("#{API}#{path}")
request = Net::HTTP.const_get(verb).new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = "#{RUN}-#{key}" if key
request.body = JSON.generate(body) if 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
def test_an_expired_code_reopens_the_payment
intent = call(:Post, '/payment_intents', {
amount: 15000,
currency: 'mad',
payment_method_types: ['cash']
}, 'create')
confirmed = call(:Post, "/payment_intents/#{intent['id']}/confirm", {
payment_method_type: 'cash',
payer: { name: 'Salma Idrissi', city: 'Casablanca' }
}, 'confirm')
charge = confirmed['latest_charge']
call(:Post, "/test_helpers/cash_vouchers/#{charge}/expire")
latest = nil
10.times do
sleep 1
latest = call(:Get, "/payment_intents/#{intent['id']}")
break if latest['status'] == 'requires_payment_method'
end
assert_equal 'requires_payment_method', latest['status']
assert_equal 'voucher_expired', latest.dig('last_payment_error', 'code')
end
endpackage main
import (
"bytes"
"crypto/rand"
"encoding/json"
"net/http"
"os"
"testing"
"time"
)
const api = "https://pay-api.adpromax.com/v1"
// A fresh run, so that every run's Idempotency-Key is new.
var run = rand.Text()
type paymentError struct {
Code string `json:"code"`
}
type paymentIntent struct {
ID string `json:"id"`
Status string `json:"status"`
LatestCharge string `json:"latest_charge"`
LastPaymentError *paymentError `json:"last_payment_error"`
}
func call(
t *testing.T, method, path string, body any, key string,
) paymentIntent {
t.Helper()
var payload bytes.Buffer
if body != nil {
json.NewEncoder(&payload).Encode(body)
}
req, err := http.NewRequest(method, api+path, &payload)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
req.Header.Set("Content-Type", "application/json")
if key != "" {
req.Header.Set("Idempotency-Key", run+"-"+key)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode >= 400 {
t.Fatalf("%s %s: %s", method, path, res.Status)
}
var answer paymentIntent
json.NewDecoder(res.Body).Decode(&answer)
return answer
}
func TestAnExpiredCodeReopensThePayment(t *testing.T) {
intent := call(t, http.MethodPost, "/payment_intents", map[string]any{
"amount": 15000,
"currency": "mad",
"payment_method_types": []string{"cash"},
}, "create")
payer := map[string]string{"name": "Salma Idrissi", "city": "Casablanca"}
confirmed := call(t, http.MethodPost,
"/payment_intents/"+intent.ID+"/confirm", map[string]any{
"payment_method_type": "cash",
"payer": payer,
}, "confirm")
expire := "/test_helpers/cash_vouchers/" + confirmed.LatestCharge + "/expire"
call(t, http.MethodPost, expire, nil, "")
var latest paymentIntent
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
latest = call(t, http.MethodGet, "/payment_intents/"+intent.ID, nil, "")
if latest.Status == "requires_payment_method" {
break
}
}
code := ""
if latest.LastPaymentError != nil {
code = latest.LastPaymentError.Code
}
if latest.Status != "requires_payment_method" || code != "voucher_expired" {
t.Fatalf("%s: %s %s", intent.ID, latest.Status, code)
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CashExpiryTest {
static final String API = "https://pay-api.adpromax.com/v1";
static final String RUN = UUID.randomUUID().toString();
static final HttpClient HTTP = HttpClient.newHttpClient();
static String call(String method, String path, String body, String key)
throws Exception {
HttpRequest.Builder request = HttpRequest
.newBuilder(URI.create(API + path))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.header("Content-Type", "application/json")
.method(method, body == null
? BodyPublishers.noBody() : BodyPublishers.ofString(body));
if (key != null) {
request.header("Idempotency-Key", RUN + "-" + key);
}
HttpResponse<String> response = HTTP.send(
request.build(), HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException(response.body());
}
return response.body();
}
// The JDK has no JSON parser: a small regex reads one text field, the
// first with that name after `after`.
static String field(String json, String after, String name) {
Matcher match = Pattern
.compile("\"" + name + "\"\\s*:\\s*\"([^\"]*)\"")
.matcher(json);
int from = Math.max(json.indexOf(after), 0);
return match.find(from) ? match.group(1) : "";
}
public static void main(String[] args) throws Exception {
String created = call("POST", "/payment_intents", """
{
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"]
}
""", "create");
String intent = field(created, "", "id");
String confirm = "/payment_intents/" + intent + "/confirm";
String confirmed = call("POST", confirm, """
{
"payment_method_type": "cash",
"payer": { "name": "Salma Idrissi", "city": "Casablanca" }
}
""", "confirm");
String charge = field(confirmed, "", "latest_charge");
String expire = "/test_helpers/cash_vouchers/" + charge + "/expire";
call("POST", expire, null, null);
String status = "";
String code = "";
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
String latest = call("GET", "/payment_intents/" + intent, null, null);
status = field(latest, "", "status");
code = field(latest, "\"last_payment_error\"", "code");
if (status.equals("requires_payment_method")) {
break;
}
}
// Expected: requires_payment_method voucher_expired
boolean reopened = status.equals("requires_payment_method");
if (!reopened || !code.equals("voucher_expired")) {
throw new AssertionError(intent + " " + status + " " + code);
}
System.out.println(intent + " " + status + " " + code);
}
}using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
const string Api = "https://pay-api.adpromax.com/v1";
var run = Guid.NewGuid().ToString();
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));
async Task<JsonElement> Call(
HttpMethod method, string path, object? body = null, string? key = null)
{
using var request = new HttpRequestMessage(method, Api + path);
if (body is not null)
request.Content = JsonContent.Create(body);
if (key is not null)
request.Headers.Add("Idempotency-Key", $"{run}-{key}");
using var response = await http.SendAsync(request);
var answer = await response.Content.ReadFromJsonAsync<JsonElement>();
if (!response.IsSuccessStatusCode)
throw new InvalidOperationException(
answer.GetProperty("error").GetProperty("message").GetString());
return answer;
}
var intent = await Call(HttpMethod.Post, "/payment_intents", new
{
amount = 15000,
currency = "mad",
payment_method_types = new[] { "cash" },
}, "create");
var id = intent.GetProperty("id").GetString();
var confirmed = await Call(HttpMethod.Post, $"/payment_intents/{id}/confirm", new
{
payment_method_type = "cash",
payer = new { name = "Salma Idrissi", city = "Casablanca" },
}, "confirm");
var charge = confirmed.GetProperty("latest_charge").GetString();
await Call(HttpMethod.Post, $"/test_helpers/cash_vouchers/{charge}/expire");
var latest = confirmed;
for (var i = 0; i < 10; i++)
{
await Task.Delay(1000);
latest = await Call(HttpMethod.Get, $"/payment_intents/{id}");
if (latest.GetProperty("status").GetString() == "requires_payment_method")
break;
}
var status = latest.GetProperty("status").GetString();
var error = latest.GetProperty("last_payment_error");
var code = error.ValueKind == JsonValueKind.Object
? error.GetProperty("code").GetString()
: null;
// Expected: requires_payment_method voucher_expired
if (status != "requires_payment_method" || code != "voucher_expired")
throw new InvalidOperationException($"{id} {status} {code}");
Console.WriteLine($"{id} {status} {code}");// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde_json = "1"
// uuid = { version = "1", features = ["v4"] }
// Run it with `cargo test`.
use std::{thread::sleep, time::Duration};
use reqwest::{blocking::Client, Method};
use serde_json::{json, Value};
const API: &str = "https://pay-api.adpromax.com/v1";
fn call(
method: Method,
path: &str,
body: Option<Value>,
key: Option<String>,
) -> Value {
let mut request = Client::new()
.request(method, format!("{API}{path}"))
.bearer_auth(std::env::var("APX_SECRET_KEY").unwrap());
if let Some(body) = body {
request = request.json(&body);
}
if let Some(key) = key {
request = request.header("Idempotency-Key", key);
}
let response = request.send().unwrap();
let ok = response.status().is_success();
let answer: Value = response.json().unwrap();
assert!(ok, "{}", answer["error"]["message"]);
answer
}
fn main() {}
#[test]
fn an_expired_code_reopens_the_payment() {
// A fresh run, so that every run's Idempotency-Key is new.
let run = uuid::Uuid::new_v4();
let intent = call(
Method::POST,
"/payment_intents",
Some(json!({
"amount": 15000,
"currency": "mad",
"payment_method_types": ["cash"]
})),
Some(format!("{run}-create")),
);
let id = intent["id"].as_str().unwrap();
let confirmed = call(
Method::POST,
&format!("/payment_intents/{id}/confirm"),
Some(json!({
"payment_method_type": "cash",
"payer": { "name": "Salma Idrissi", "city": "Casablanca" }
})),
Some(format!("{run}-confirm")),
);
let charge = confirmed["latest_charge"].as_str().unwrap();
let expire = format!("/test_helpers/cash_vouchers/{charge}/expire");
call(Method::POST, &expire, None, None);
let mut latest = confirmed.clone();
for _ in 0..10 {
sleep(Duration::from_secs(1));
latest = call(Method::GET, &format!("/payment_intents/{id}"), None, None);
if latest["status"] == "requires_payment_method" {
break;
}
}
assert_eq!(latest["status"], "requires_payment_method");
assert_eq!(latest["last_payment_error"]["code"], "voucher_expired");
}ما يراه عملاؤك
يدفع عملاؤك في صفحاتنا، وغالبًا من الهاتف. انظر إليها كما سيراها عملاؤك:
- افتح
urlجلسة دفع (checkout session) على هاتف، بالفرنسية والإنجليزية والعربية (تتبع الصفحة لغة المتصفح). ادفع بالبطاقة، ثم اطلب رمز دفع نقدي. - في صفحة القسيمة، اطبع القسيمة وشاركها: يجب أن يُقرأ الرمز والمبلغ الواجب دفعه والأجل بوضوح.
- غادر الصفحة إلى متجرك، ثم افتح
urlالجلسة من جديد: العميل الذي غادر يستطيع العودة إلى جلسة ما زالت مفتوحة.
دع لوحة التحكم تساعدك
- بطاقة التكامل في الصفحة الرئيسية تؤشّر بنودها تلقائيًا كلما وصلت مدفوعات الاختبار وإشعارات الـ webhook ورموز الدفع النقدي.
- في وضع الاختبار، يملأ إنشاء مدفوعات نموذجية في الصفحة الرئيسية الشاشات بمدفوعات وعملاء نموذجيين، لترى ما تعرضه كل صفحة.
- حذف كل بيانات الاختبار، في الإعدادات ← المؤسسة، يعيدك إلى بيئة اختبار نظيفة. وتبقى مفاتيحك وفريقك.
الخطوات التالية
عندما ينجح كل سيناريو في هذه الصفحة، اقرأ قائمة التحقق قبل الوضع المباشر.