المدفوعات
الدفع بالبطاقة
كيف تعمل الدفعة بالبطاقة، و3-D Secure، وحالات الرفض وإعادة المحاولة، وكيف ترسل عميلك إلى صفحة البطاقة من صفحتك الخاصة.
في هذه الصفحة
يكتب عميلك بيانات بطاقته في صفحة البطاقة الآمنة لشريكنا في البطاقات، لا في موقعك ولا في موقعنا أبدًا. لا ترى رقم بطاقة ولا ترسله ولا تخزّنه أبدًا، وترفض واجهة API أي نص يشبهه. في وضع الاختبار، تلك الصفحة هي محاكاتنا التي تحمل علامة TEST، ولا تقبل إلا بطاقات الاختبار.
هناك طريقتان لإيصال عميلك إليها:
- صفحة الدفع المستضافة (موصى بها): تعرض صفحتنا البطاقة، والدفع نقدًا إن أردت، وتنفّذ عنك كل الخطوات أدناه. انظر صفحة الدفع المستضافة.
- صفحتك الخاصة: تؤكد نية الدفع (payment intent) بنفسك وترسل متصفح عميلك إلى صفحة البطاقة، كما تبيّن هذه الصفحة.
نموذج بطاقة مضمَّن في صفحتك الخاصة مخطط له لاحقًا. سنعلن عنه في سجل التغييرات.
ما يحدث أثناء الدفع بالبطاقة
- تُؤكَّد نية دفع للبطاقة: تبدأ محاولة (
ch_…)، وتصبح نية الدفعrequires_action. - يصل متصفح عميلك إلى صفحة البطاقة، فيكتب بيانات بطاقته.
- إذا طلب بنكه ذلك، يؤكد عبر 3-D Secure في صفحة تابعة لبنكه. لا شيء
عليك فعله: يجري ذلك في صفحة البطاقة. وفشل التحقق ينهي المحاولة بالرمز
authentication_failed. - يؤكد لنا شريك البطاقات الدفعة. تصبح نية الدفع
succeededوتتلقىpayment_intent.succeeded.
تبقى صفحة البطاقة صالحة للاستخدام نحو 30 دقيقة؛ بعدها تنتهي المحاولة بحالة expired وتعود
نية الدفع إلى requires_payment_method. لا تقل الدفعة بالبطاقة عن 1.00 MAD
(100).
من صفحتك الخاصة
أنشئ نية دفع للبطاقة، ثم أكّدها مع:
payment_method_type:card؛return_url: العنوان الذي يعود إليه عميلك بعد صفحة البطاقة، بـhttps. ضع فيه مرجع طلبيتك، مثلhttps://shop.example.com/orders/1042/return: لا تحمل العودة أي دليل على الدفع.
يحتاج كل من الطلبين إلى Idempotency-Key خاص به. الاستجابة هي نية الدفع، في
requires_action، مع next_action من النوع form_post: عنوان (url)، وmethod،
وfields يجب أن يرسلها متصفح عميلك للوصول إلى صفحة البطاقة. تتغير الحقول من
دفعة إلى أخرى: أرسلها كلها كما هي.
PI=$(curl -s https://pay-api.adpromax.com/v1/payment_intents \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-create" \
-d '{"amount": 25000, "currency": "mad",
"payment_method_types": ["card"]}' | 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-1042-confirm" \
-d '{"payment_method_type": "card",
"return_url": "https://shop.example.com/orders/1042/return"}'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-1042-create', {
amount: 25000,
currency: 'mad',
payment_method_types: ['card'],
})
const confirmed = await post(`/payment_intents/${intent.id}/confirm`,
'order-1042-confirm', {
payment_method_type: 'card',
return_url: 'https://shop.example.com/orders/1042/return',
})
const formPost = confirmed.next_action.form_postinterface FormPost {
url: string
method: string
fields: Record<string, string>
}
interface PaymentIntent {
id: string
object: 'payment_intent'
status: string
next_action: { type: 'form_post', form_post: FormPost } | 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-1042-create', {
amount: 25000,
currency: 'mad',
payment_method_types: ['card'],
})
const confirmed = await post(`/payment_intents/${intent.id}/confirm`,
'order-1042-confirm', {
payment_method_type: 'card',
return_url: 'https://shop.example.com/orders/1042/return',
})
const formPost = confirmed.next_action?.form_postimport 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()
response.raise_for_status()
return answer
intent = post("/payment_intents", "order-1042-create", {
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card"],
})
confirmed = post(f"/payment_intents/{intent['id']}/confirm",
"order-1042-confirm", {
"payment_method_type": "card",
"return_url": "https://shop.example.com/orders/1042/return",
})
form_post = confirmed["next_action"]["form_post"]<?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);
return $answer;
}
$intent = post('/payment_intents', 'order-1042-create', [
'amount' => 25000,
'currency' => 'mad',
'payment_method_types' => ['card'],
]);
$confirmed = post("/payment_intents/{$intent['id']}/confirm",
'order-1042-confirm', [
'payment_method_type' => 'card',
'return_url' => 'https://shop.example.com/orders/1042/return',
]);
$formPost = $confirmed['next_action']['form_post'];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-1042-create',
amount: 25000, currency: 'mad', payment_method_types: ['card'])
confirmed = post("/payment_intents/#{intent['id']}/confirm", 'order-1042-confirm',
payment_method_type: 'card',
return_url: 'https://shop.example.com/orders/1042/return')
form_post = confirmed.dig('next_action', 'form_post')package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
)
const api = "https://pay-api.adpromax.com/v1"
type FormPost struct {
URL string `json:"url"`
Method string `json:"method"`
Fields map[string]string `json:"fields"`
}
type PaymentIntent struct {
ID string `json:"id"`
Status string `json:"status"`
NextAction struct {
FormPost FormPost `json:"form_post"`
} `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-1042-create", map[string]any{
"amount": 25000,
"currency": "mad",
"payment_method_types": []string{"card"},
})
if err != nil {
log.Fatal(err)
}
confirmed, err := post("/payment_intents/"+intent.ID+"/confirm",
"order-1042-confirm", map[string]any{
"payment_method_type": "card",
"return_url": "https://shop.example.com/orders/1042/return",
})
if err != nil {
log.Fatal(err)
}
formPost := confirmed.NextAction.FormPost
fmt.Println(formPost.Method, formPost.URL)
}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 Card {
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-1042-create", """
{"amount": 25000, "currency": "mad", "payment_method_types": ["card"]}
""");
// 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-1042-confirm", """
{"payment_method_type": "card",
"return_url": "https://shop.example.com/orders/1042/return"}
""");
// next_action.form_post: url, method, fields
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-1042-create", new
{
amount = 25000,
currency = "mad",
payment_method_types = new[] { "card" },
});
var confirm = $"payment_intents/{intent.Id}/confirm";
var confirmed = await Post(confirm, "order-1042-confirm", new
{
payment_method_type = "card",
return_url = "https://shop.example.com/orders/1042/return",
});
var formPost = confirmed.NextAction!.FormPost;
Console.WriteLine($"{formPost.Method} {formPost.Url}");
async Task<PaymentIntent> Post(string path, string key, object body)
{
using var request = new HttpRequestMessage(HttpMethod.Post, path)
{
Content = JsonContent.Create(body),
};
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, FormPost FormPost);
record FormPost(string Url, string Method, Dictionary<string, string> Fields);
record ApiError(ApiErrorBody Error);
record ApiErrorBody(string Code, string Message);// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
use std::collections::HashMap;
use 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 {
form_post: FormPost,
}
#[derive(Deserialize)]
struct FormPost {
url: String,
method: String,
fields: HashMap<String, String>,
}
#[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 create = json!({
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card"],
});
let intent = post(&client, "/payment_intents", "order-1042-create", create)?;
let confirm = json!({
"payment_method_type": "card",
"return_url": "https://shop.example.com/orders/1042/return",
});
let path = format!("/payment_intents/{}/confirm", intent.id);
let confirmed = post(&client, &path, "order-1042-confirm", confirm)?;
let next_action = confirmed.next_action.ok_or("no form to post")?;
let form_post = next_action.form_post;
let fields = form_post.fields.len();
println!("{} {} ({fields} fields)", form_post.method, form_post.url);
Ok(())
}{
"id": "pi_01k7e5r2t8w4y6b9c3n7m1x5qa",
"object": "payment_intent",
"amount": 25000,
"currency": "mad",
"status": "requires_action",
"payment_method_types": ["card"],
"latest_charge": "ch_01k7e5r3a6s9d2f5g8h1j4k7mb",
"next_action": {
"type": "form_post",
"form_post": {
"url": "https://pay.example.com/…",
"method": "POST",
"fields": { "…": "…" }
}
},
"livemode": false
}أرسل عميلك إلى صفحة البطاقة
أجب متصفح عميلك بصفحة فيها نموذج مبني من next_action.form_post: حقل
إدخال مخفي لكل حقل، يُرسل إلى url. يرسل النموذج نفسه فورًا؛ وزر متابعة الظاهر
هو البديل عند حظر السكربتات. هرِّب (escape) كل قيمة تكتبها في الصفحة.
إذا أعاد عميلك تحميل الصفحة قبل الدفع، فاقرأ نية الدفع من جديد وأرسل
form_post نفسه: يبقى صالحًا ما دامت المحاولة في الانتظار.
# Shows what the page must post: the form_post of the confirmed payment intent.
curl -s https://pay-api.adpromax.com/v1/payment_intents/pi_01k7e5r2t8w4y6b9c3n7m1x5qa \
-u "$APX_SECRET_KEY:" | jq .next_action.form_postconst escape = value => String(value)
.replace(/[&<>"']/g, character => `&#${character.charCodeAt(0)};`)
// formPost is next_action.form_post of the confirmed payment intent.
export function cardForm(formPost) {
const inputs = Object.entries(formPost.fields)
.map(([name, value]) =>
`<input type="hidden" name="${escape(name)}" value="${escape(value)}">`)
.join('\n')
return `<!doctype html>
<form id="card" method="${escape(formPost.method)}" action="${escape(formPost.url)}">
${inputs}
<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>`
}
// Express: res.send(cardForm(confirmed.next_action.form_post))import type { Response } from 'express'
interface FormPost {
url: string
method: string
fields: Record<string, string>
}
const escape = (value: string): string => value
.replace(/[&<>"']/g, character => `&#${character.charCodeAt(0)};`)
// formPost is next_action.form_post of the confirmed payment intent.
export function cardForm(formPost: FormPost): string {
const inputs = Object.entries(formPost.fields)
.map(([name, value]) =>
`<input type="hidden" name="${escape(name)}" value="${escape(value)}">`)
.join('\n')
return `<!doctype html>
<form id="card" method="${escape(formPost.method)}" action="${escape(formPost.url)}">
${inputs}
<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>`
}
// In an Express route: sendCardForm(res, confirmed.next_action.form_post)
export function sendCardForm(res: Response, formPost: FormPost): void {
res.type('html').send(cardForm(formPost))
}from flask import render_template_string
CARD_FORM = """<!doctype html>
<form id="card" method="{{ form.method }}" action="{{ form.url }}">
{% for name, value in form.fields.items() %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
{% endfor %}
<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>"""
def card_form(form_post):
# Jinja escapes every value.
return render_template_string(CARD_FORM, form=form_post)<?php
// $formPost is next_action.form_post of the confirmed payment intent.
$e = fn ($value) => htmlspecialchars((string) $value, ENT_QUOTES);
?>
<!doctype html>
<form id="card" method="<?= $e($formPost['method']) ?>" action="<?= $e($formPost['url']) ?>">
<?php foreach ($formPost['fields'] as $name => $value): ?>
<input type="hidden" name="<?= $e($name) ?>" value="<?= $e($value) ?>">
<?php endforeach; ?>
<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>require 'sinatra'
CARD_FORM = <<~HTML
<!doctype html>
<form id="card" method="<%= h form['method'] %>" action="<%= h form['url'] %>">
<% form['fields'].each do |name, value| %>
<input type="hidden" name="<%= h name %>" value="<%= h value %>">
<% end %>
<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>
HTML
helpers do
def h(value)
Rack::Utils.escape_html(value.to_s)
end
# form_post is next_action.form_post of the confirmed payment intent.
def card_form(form_post)
erb CARD_FORM, locals: { form: form_post }
end
endpackage main
import (
"html/template"
"net/http"
)
type FormPost struct {
URL string `json:"url"`
Method string `json:"method"`
Fields map[string]string `json:"fields"`
}
// html/template escapes every value.
var cardFormPage = template.Must(template.New("card").Parse(`<!doctype html>
<form id="card" method="{{.Method}}" action="{{.URL}}">
{{range $name, $value := .Fields}}
<input type="hidden" name="{{$name}}" value="{{$value}}">
{{end}}
<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>`))
// formPost is next_action.form_post of the confirmed payment intent.
func cardForm(w http.ResponseWriter, formPost FormPost) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
cardFormPage.Execute(w, formPost)
}import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.HtmlUtils;
public class CardFormController {
record FormPost(String url, String method, Map<String, String> fields) {}
// formPost is next_action.form_post of the confirmed payment intent.
static ResponseEntity<String> cardForm(FormPost formPost) {
StringBuilder inputs = new StringBuilder();
formPost.fields().forEach((name, value) -> inputs.append(
"<input type=\"hidden\" name=\"%s\" value=\"%s\">\n"
.formatted(escape(name), escape(value))));
String page = """
<!doctype html>
<form id="card" method="%s" action="%s">
%s<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>
""".formatted(escape(formPost.method()), escape(formPost.url()), inputs);
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(page);
}
static String escape(String value) {
return HtmlUtils.htmlEscape(value);
}
}using System.Net;
using System.Text;
// formPost is next_action.form_post of the confirmed payment intent.
// In a route: return CardForm(confirmed.NextAction.FormPost);
static IResult CardForm(FormPost formPost)
{
var inputs = new StringBuilder();
foreach (var (name, value) in formPost.Fields)
{
inputs.AppendLine(
$"<input type=\"hidden\" name=\"{E(name)}\" value=\"{E(value)}\">");
}
var page = $"""
<!doctype html>
<form id="card" method="{E(formPost.Method)}" action="{E(formPost.Url)}">
{inputs}<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>
""";
return Results.Content(page, "text/html");
}
static string E(string value) => WebUtility.HtmlEncode(value);
record FormPost(string Url, string Method, Dictionary<string, string> Fields);// Cargo.toml: axum = "0.8", serde = { version = "1", features = ["derive"] }
use std::collections::BTreeMap;
use axum::response::Html;
use serde::Deserialize;
#[derive(Deserialize)]
struct FormPost {
url: String,
method: String,
fields: BTreeMap<String, String>,
}
fn escape(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
// form_post is next_action.form_post of the confirmed payment intent.
fn card_form(form_post: &FormPost) -> Html<String> {
let inputs: String = form_post
.fields
.iter()
.map(|(name, value)| {
format!(
"<input type=\"hidden\" name=\"{}\" value=\"{}\">\n",
escape(name),
escape(value)
)
})
.collect();
Html(format!(
r#"<!doctype html>
<form id="card" method="{}" action="{}">
{}<button type="submit">Continue</button>
</form>
<script>document.getElementById('card').submit()</script>"#,
escape(&form_post.method),
escape(&form_post.url),
inputs
))
}عندما يعود عميلك
بعد صفحة البطاقة، يعود متصفح عميلك إلى return_url مهما حدث.
هذه العودة لا تثبت شيئًا: اعرض «نحن بصدد تأكيد دفعتك»، ثم اقرأ نية الدفع
بمفتاحك السري، وسلّم الطلبية عند وصول إشعار الـ webhook payment_intent.succeeded. قد يصل التأكيد
بعد عودة عميلك: انظر كيف تعمل المدفوعات.
الرفض وإعادة المحاولة
المحاولة المرفوضة تعيد نية الدفع إلى requires_payment_method، مع السبب في
last_payment_error، وتتلقى payment_intent.payment_failed.
last_payment_error.code | ما حدث | ما يستطيع عميلك فعله |
|---|---|---|
card_declined | رفض البنك. وقد يوضح decline_code أكثر، مثل insufficient_funds. | تجربة بطاقة أخرى. |
expired_card | انتهت صلاحية البطاقة. | تجربة بطاقة أخرى. |
authentication_failed | لم ينجح التحقق عبر 3-D Secure. | المحاولة من جديد، أو تجربة بطاقة أخرى. |
processing_error | تعذّرت معالجة الدفعة. | المحاولة من جديد. |
يمكن لعميلك أن يحاول من جديد على نية الدفع نفسها: كل تجربة محاولة جديدة، وتبقى نية الدفع تنجح مرة واحدة.
- مع صفحة الدفع المستضافة، تعرض الصفحة جرّب بطاقة أخرى، وادفع نقدًا بدلًا من ذلك إن كنت عرضت الدفع نقدًا.
- مع صفحتك الخاصة، أكّد نية الدفع من جديد بقيمة جديدة لـ
Idempotency-Key، وأرسل عميلك إلىform_postالجديد.
اعرض لعميلك جملة واضحة، لا الرمز. وتجد الأرقام التي تُحدث كل حالة في وضع الاختبار في بطاقات الاختبار.
الاسترداد
يمكن استرداد الدفعة الناجحة بالبطاقة، كليًا أو جزئيًا: انظر المبالغ المستردة.