نظرة عامة#
تريز يعطي عملاءك نقاطاً على مشترياتهم، وكل 100 نقطة تزرع شجرة محلية حقيقية موثّقة بالـGPS. فيه طريقتين ترسل لنا فيها الطلبات — اختر اللي يناسب متجرك:
سلة، زد، ووكومرس، شوبيفاي: اربط من البوابة بدون برمجة.
أي موقع أو تطبيق أو نظام كاشير ثاني: طلب HTTPS واحد لكل طلب.
الرابط الأساسي: https://api.treezplus.com/v1. البيانات بصيغة JSON، والحقول بنمط camelCase، والأوقات بصيغة ISO-8601 بتوقيت UTC. القوائم تستخدم ?limit=&cursor= وترجع { items, nextCursor }.
المصادقة#
أنشئ مفاتيح API من بوابة المتجر في المطوّرون ← مفاتيح API. أرسل المفتاح كـ Bearer من خادمك:
Authorization: Bearer sk_live_4f8a…| المفتاح | وش يسوي |
|---|---|
sk_test_… | الطلبات تُسجَّل بـ livemode: false وتطلق الويب هوك، بس ما تلمس ميزانيتك ولا محافظ العملاء. متاح وقت ما متجرك بانتظار الموافقة. |
sk_live_… | طلبات حقيقية ونقاط حقيقية. يحتاج متجراً معتمداً. |
واجهة الطلبات#
أرسل الطلب لما ينْدفع (أو يكتمل — حسب اختيارك في الإعدادات ← قواعد النقاط). إرسال نفس externalOrderId مرة ثانية يحدّث الطلب بدل ما يكرره.
/v1/orderscurl -X POST https://api.treezplus.com/v1/orders \
-H "Authorization: Bearer sk_test_…" \
-H "Idempotency-Key: order-1001" \
-H "Content-Type: application/json" \
-d '{
"externalOrderId": "1001",
"amount": 12.5,
"currency": "OMR",
"status": "paid",
"customer": { "email": "salim@example.com", "name": "Salim", "phone": "+96890000001" },
"occurredAt": "2026-10-24T09:30:00Z"
}'حقول الطلب
externalOrderIdstring | رقم الطلب عندك. فريد لكل متجر. |
amountnumber | إجمالي الطلب بالوحدة الكبيرة (12.5 = 12.500 ر.ع). |
currencystring | OMR · SAR · AED · KWD · QAR · BHD · USD |
status"paid" | "completed" | حالة الطلب الحالية. |
customerobject | email (أرسله دائماً — الحقل الوحيد اللي نعرف فيه عضو تريز)، name، phone (اختياري، يُحفظ لسجلاتك وما يُستخدم للمطابقة). شوف مطابقة العملاء. |
occurredAtتاريخ ISO، اختياري | وقت الطلب. افتراضياً الآن. |
الاستجابة
{
"id": "6f1c2a7e-0b7d-4c55-9a43-3f0d2a9b1e10",
"source": "api",
"externalOrderId": "1001",
"livemode": true,
"amount": 12.5,
"currency": "OMR",
"status": "paid",
"customer": {
"email": "salim@example.com",
"phone": "+96890000001",
"name": "Salim",
"trees": { "matched": true, "firstName": "Salim" }
},
"points": 125,
"pointsStatus": "pending",
"skipReason": null,
"confirmAt": "2026-11-07T09:30:00.000Z",
"confirmedAt": null,
"reversedAt": null,
"createdAt": "2026-10-24T09:30:00.000Z",
"updatedAt": "2026-10-24T09:30:00.000Z"
}النقاط = floor(amount × pointsPerUnit) حسب عملة الطلب، بحد أقصى حسب إعدادك. ما فيه قاعدة للعملة أو المبلغ أقل من الحد الأدنى ← points: 0 مع skipReason.
دورة الحياة
pendingالنقاط محجوزة من ميزانيتك؛ confirmAt = وقت الطلب + أيام الانتظار.
confirmedانتهت فترة الانتظار والعميل في تريز ← انضافت النقاط.
reversedانلغى أو استُرجع قبل التأكيد ← رجعت الميزانية.
العميل لسه مو في تريز؟ النقاط تنتظر لحد 90 يوم وتنضاف تلقائياً لما يدخل تطبيق تريز بنفس البريد.
تحديث الحالة
/v1/orders/:externalOrderId/statuscurl -X POST https://api.treezplus.com/v1/orders/1001/status \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{ "status": "refunded" }'الحالات: completed، cancelled، refunded. الاسترجاع بعد التأكيد ينسجّل، بس النقاط ما تنسحب.
القائمة والتفاصيل
/v1/orders?pointsStatus&source&limit&cursor/v1/orders/:idcurl "https://api.treezplus.com/v1/orders?pointsStatus=pending&limit=20" \
-H "Authorization: Bearer sk_live_…"مطابقة العملاء#
النقاط تروح لحساب تريز اللي بريده يطابق customer.email في الطلب. العملاء والمزارعون يدخلون تطبيق تريز برمز لمرة وحدة يوصلهم على البريد، فالبريد هو الهوية اللي نعتمد عليها. أرسل customer.email دائماً.
customer.emailنص · مطلوب للنقاط | يُطابق بدون حساسية لحالة الأحرف (بعد حذف المسافات وتحويله لأحرف صغيرة) مع حسابات العملاء والمزارعين. |
customer.phoneنص · اختياري | يُحفظ ويظهر لك في البوابة. ما يُستخدم للمطابقة أبداً — أرقام الجوال في تريز غير موثّقة. |
customer.nameنص · اختياري | يظهر في بوابتك. |
customer.email ينسجّل بـ pointsStatus: "skipped" و skipReason: "NO_CUSTOMER_CONTACT" — هالنقاط ما أحد يقدر يستلمها، فما ينحجز شي من ميزانيتك. إرسال رقم الجوال بس يُعتبر بدون بريد.العميل لسه مو في تريز
إذا ما فيه حساب بهالبريد، الاستجابة تبيّن trees.matched: false والنقاط تنتظر لحد 90 يوم. تنضاف أول ما أحد يدخل تطبيق تريز بنفس البريد (وتتأكد فوراً إذا انتهت فترة الانتظار). غير كذا تنتهي بحالة unclaimed_expired وترجع لميزانيتك. طلبات وضع التجربة ما تُطابق مع عملاء حقيقيين أبداً.
أسباب التخطي
| skipReason | المعنى |
|---|---|
NO_CUSTOMER_CONTACT | ما فيه بريد للعميل في الطلب، فما فيه أحد نعطيه النقاط. |
NO_RULE_FOR_CURRENCY | ما فيه قاعدة نقاط لهذه العملة. |
BELOW_MINIMUM | أقل من الحد الأدنى للطلب. |
ZERO_POINTS | مبلغ الطلب صغير وما يكسب نقطة كاملة. |
INSUFFICIENT_BUDGET | ما كانت الميزانية كافية لما وصل الطلب. |
AWAITING_STATUS | بانتظار اكتمال الطلب (قاعدة النقاط عندك تعطي عند الاكتمال). |
CANCELLED_BEFORE_AWARD | انلغى قبل ما يكسب نقاط. |
MERCHANT_NOT_ACTIVE | متجرك ما كان معتمد لما وصل الطلب. |
عدم التكرار#
الشبكات تفشل أحياناً. أرسل ترويسة Idempotency-Key (مثلاً order-1001) وأعد المحاولة بأمان: نفس المفتاح يرجع نفس الاستجابة مع Idempotent-Replayed: true، وما يضيف نقاط مرتين. استخدام نفس المفتاح مع محتوى مختلف يرجع 409 IDEMPOTENCY_KEY_REUSED.
حتى بدون الترويسة، الطلبات تُحدَّث حسب (المتجر، المصدر، externalOrderId).
الأخطاء#
الأخطاء تستخدم حالة HTTP ورمزاً ثابتاً:
{
"error": {
"code": "MERCHANT_NOT_ACTIVE",
"message": "Your store is not approved yet",
"details": {}
}
}| الرمز | HTTP | المعنى |
|---|---|---|
VALIDATION_ERROR | 400 | حقل ناقص أو غير صحيح — شوف details.issues. |
INVALID_API_KEY | 401 | المفتاح غلط أو ملغى أو مو موجود. |
UNAUTHORIZED | 401 | ما انرسلت بيانات دخول. |
MERCHANT_NOT_ACTIVE | 403 | متجرك لسه ما انعتمد (المفاتيح الحقيقية والنقاط الحقيقية). |
TEST_MODE_UNSUPPORTED | 403 | مفتاح تجربة استدعى نقطة تشتغل بالوضع الحقيقي فقط. |
ORDER_NOT_FOUND | 404 | ما فيه طلب بهذا المعرّف لمتجرك. |
IDEMPOTENCY_KEY_REUSED | 409 | نفس Idempotency-Key مع محتوى مختلف. |
INSUFFICIENT_BUDGET | 409 | الميزانية ما تكفي (الطلب ينحفظ كمتجاوز). |
RATE_LIMITED | 429 | طلبات كثيرة — انتظر عدد ثواني Retry-After. |
INTERNAL | 500 | صار خلل عندنا. أعد المحاولة بنفس Idempotency-Key. |
الويب هوك#
أضف نقطة في المطوّرون ← الويب هوك وتريز بيرسل لها أحداث JSON بطريقة POST — من الطلبات لين انزراع الشجرة. كل حدث يستخدم نفس الغلاف:
{
"id": "evt_01JB2Q8M4K7Y3ZP6",
"type": "tree.planted",
"createdAt": "2026-10-24T09:30:00.000Z",
"livemode": true,
"merchantId": "b2c1…",
"data": {
"tree": { "id": "…", "status": "planted", "species": { "nameEn": "Ghaf" }, "…": "…" },
"passportUrl": "https://treez.om/t/ghaf-7k4p",
"fundingShare": 0.35
}
}الإرسال وإعادة المحاولة
- رد بأي 2xx خلال 10 ثوانٍ، وخلّ الشغل الثقيل في مهمة خلفية.
- الإرسال الفاشل يُعاد بعد 1m · 5m · 30m · 2h · 6h · 12h · 24h (7 محاولات).
- النقطة اللي تفشل كل إرسالياتها لمدة 3 أيام تتعطّل تلقائياً. فعّلها من البوابة.
- الأحداث ممكن توصل أكثر من مرة أو بغير ترتيب — تجاهل المكرر حسب
id.
أنواع الأحداث
| الحدث | متى |
|---|---|
order.points_pending | طلب كسب نقاطاً صارت معلّقة. |
order.points_confirmed | انتهت فترة الانتظار وانضافت النقاط. |
order.points_reversed | طلب انلغى أو استُرجع قبل التأكيد. |
order.points_skipped | طلب ما كسب نقاط (بدون بريد للعميل، أو بدون قاعدة، أو أقل من الحد، أو الميزانية). |
order.refunded_after_confirmation | طلب استُرجع بعد تأكيد نقاطه. |
tree.ready | شجرة موّلها عملاؤك جاهزة للزراعة. |
tree.scheduled | الشجرة صار لها موعد زراعة. |
tree.planted | انزرعت، مع صورة وموقع GPS. |
tree.verified | مراجع وثّق أدلة الزراعة. |
claim_code.claimed | عميل استخدم واحد من رموزك. |
award.created | أعطيت عميلاً نقاطاً عند الكاشير. |
merchant.approved | تريز اعتمدت متجرك. |
budget.low | ميزانية نقاطك قاربت تخلص. |
ping | حدث تجريبي ترسله من البوابة. |
أحداث order.* تحمل { order }؛ وأحداث tree.* تحمل { tree, passportUrl, fundingShare } وتوصل بس للمتاجر اللي موّلت الشجرة.
التحقق من التوقيع#
كل طلب يحمل ترويسة توقيع:
Treez-Signature: t=1761298200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd- قسّم الترويسة على
,؛ واقرأt(ثواني يونكس) وكل قيمةv1. - احسب
HMAC-SHA256(secret, t + "." + rawBody)بصيغة hex. - اقبل إذا ساوى أي قيمة
v1(مقارنة بوقت ثابت) وكانtأحدث من 5 دقائق.
v1=new,v1=old)، فتقدر تنشر السر الجديد بدون ما تضيع أحداث.import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.TREEZ_WEBHOOK_SECRET; // "whsec_…"
// Keep the raw body: the signature covers the exact bytes we sent.
app.post("/treez/webhooks", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Treez-Signature") ?? "";
const parts = header.split(",").map((p) => p.trim().split("="));
const t = parts.find(([k]) => k === "t")?.[1];
const signatures = parts.filter(([k]) => k === "v1").map(([, v]) => v);
// Reject old timestamps (replay protection).
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(400).send("stale");
const expected = crypto.createHmac("sha256", SECRET).update(`${t}.`).update(req.body).digest("hex");
const valid = signatures.some(
(s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)),
);
if (!valid) return res.status(400).send("bad signature");
const event = JSON.parse(req.body.toString("utf8"));
switch (event.type) {
case "order.points_confirmed":
// event.data.order.points were credited to the customer
break;
case "tree.planted":
// event.data.passportUrl — share it with your customer!
break;
}
res.sendStatus(200); // answer fast; do slow work in a queue
});المنصات#
كل منصة عبارة عن محوّل يحوّل طريقة تثبيتها والويب هوك حقها لنفس شكل الطلب الموحّد — فالنقاط وفترة الانتظار والاسترجاع والويب هوك تشتغل بنفس الطريقة في كل مكان.
توكنات المنصات تُحفظ مشفّرة. حذف التطبيق من المنصة يفصله من تريز. تبي منصة ثانية؟ إضافتها محوّل واحد عندنا — راسلنا على hello@treez.om.
وضع التجربة#
- استخدم مفتاح
sk_test_: الطلبات تُحفظ بـlivemode: false، وتظهر في البوابة بشارة تجربة وتطلق الويب هوك — بدون تغيير الميزانية أو المحافظ. - اضغط أرسل اختبار على أي نقطة عشان يوصلك حدث
pingموقّع وتشوف الاستجابة في سجل الإرسال. - مفاتيح التجربة ما تقدر تنشئ رموز مطالبة أو تعطي نقاط (
TEST_MODE_UNSUPPORTED). - محلياً، استخدم نفق (مثل ngrok) لنقطتك — نقاط الإنتاج لازم تكون HTTPS.
سجّل، وخذ مفتاح تجربة، وأرسل أول طلب خلال خمس دقائق.