Skip to content

User Frontend API Documentation

Last Updated: 2026-03-31

This document covers all current frontend APIs in user/src/api/index.ts, with field definitions based on the following implementations:

  • api/internal/router/router.go
  • api/internal/http/handlers/public/*.go
  • api/internal/models/*.go

0. API Changelog

0.0.1 Public API Response DTO Simplification and Security Hardening (2026-03-31)

Breaking Changes

  • All order-related endpoints no longer return the auto-increment id field (Order.id, OrderItem.id, Fulfillment.id); order_no is now the sole order identifier.
  • Order detail route changed from GET /orders/:id to GET /orders/:order_no; cancel route changed from POST /orders/:id/cancel to POST /orders/:order_no/cancel; fulfillment download changed from GET /orders/:id/fulfillment/download to GET /orders/:order_no/fulfillment/download.
  • Guest order detail route changed from GET /guest/orders/:id to GET /guest/orders/:order_no; fulfillment download likewise.
  • Legacy routes GET /orders/by-order-no/:order_no and GET /guest/orders/by-order-no/:order_no have been removed; use GET /orders/:order_no directly.
  • Payment endpoints POST /payments and GET /payments/latest request parameter order_id changed to order_no (string type). Guest payment endpoints likewise.
  • GET /payments/latest response field order_id changed to order_no.

Removed Fields

The following fields have been permanently removed from Public API responses; the frontend should no longer depend on them:

  • Order: id, parent_id, user_id, coupon_id, promotion_id, client_ip, updated_at
  • OrderItem: id, order_id, delivered_by, created_at, updated_at
  • Fulfillment: id, order_id, delivered_by, created_at, updated_at
  • PublicProduct: cost_price_amount, manual_stock_locked, manual_stock_sold, is_active, sort_order, created_at, updated_at, is_affiliate_enabled, is_mapped, seo_meta
  • PublicSKU: cost_price_amount, product_id, manual_stock_locked, auto_stock_total, auto_stock_locked, auto_stock_sold, sort_order, created_at, updated_at
  • Banner: name, is_active, start_at, end_at, sort_order, created_at, updated_at
  • Post: is_published, created_at
  • Category: created_at
  • WalletTransaction: order_id
  • AffiliateCommission: order_id

New Fields

  • Order: member_discount_amount, wallet_paid_amount, online_paid_amount, refunded_amount
  • OrderItem: sku_snapshot, member_discount_amount
  • Fulfillment: payload_line_count
  • UserProfile: member_level_id, total_recharged, total_spent
  • Category: parent_id, icon

0.0 Promotion System Enhancement: Tiered Rules + Frontend Display (2026-03-09)

New Fields

  • PublicProduct now includes a promotion_rules field (type PromotionRule[]), returning all active promotion rules for the product.
  • This field is populated even when the current SKU unit price does not meet the rule threshold, allowing the frontend to display "buy more to get a discount" hints.

Tiered Promotion Rules

  • A single product can have multiple promotion rules with different min_amount thresholds, creating tiered discounts.
  • The backend matches from highest to lowest threshold against the purchase subtotal (unit price × quantity), applying the highest tier that qualifies.
  • Example:
    • Rule A: min_amount=50, 1% off
    • Rule B: min_amount=150, 2% off
    • Subtotal 49 → no discount; 100 → Rule A (1% off); 200 → Rule B (2% off)
  • Single-rule scenarios behave identically to before — no breaking changes.

Promotion Types

typeMeaningCalculation (per item)
percentPercentage discountunit price = original × (100 - value) / 100
fixedFixed amount reductionunit price = original - value
special_priceDirect price overrideunit price = value

Note: All discounts apply to the per-item unit price, not the order total. min_amount is the subtotal threshold (unit price × quantity); once met, each item receives the corresponding discount.


1. General Conventions

1.1 Base URL

  • API Prefix: /api/v1
  • All paths in this document omit /api/v1; please append it when making requests.

1.2 Authentication

User authenticated endpoints require the following:

http
Authorization: Bearer <user_token>

1.3 Unified Response Structure

Successful Response

json
{
  "status_code": 0,
  "msg": "success",
  "data": {},
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 100,
    "total_page": 5
  }
}

Failed Response

json
{
  "status_code": 400,
  "msg": "invalid request parameters",
  "data": {
    "request_id": "01HR..."
  }
}

Top-Level Field Description

FieldTypeDescription
status_codenumberBusiness status code, 0 indicates success, non-0 indicates failure
msgstringBusiness message
dataobject/array/nullBusiness data
paginationobjectPagination information, only returned by paginated APIs

1.4 Pagination Parameter Convention

ParameterTypeRequiredDefaultDescription
pagenumberNo1Page number, minimum 1
page_sizenumberNo20Number of items per page, maximum 100

1.5 Common Request Structure

CaptchaPayload (Captcha Payload)

FieldTypeRequiredDescription
captcha_idstringNoImage captcha ID (used when provider=image)
captcha_codestringNoImage captcha text (used when provider=image)
turnstile_tokenstringNoTurnstile Token (used when provider=turnstile)

OrderItemInput (Order Item)

FieldTypeRequiredDescription
product_idnumberYesProduct ID
quantitynumberYesPurchase quantity (>0)
fulfillment_typestringNoFulfillment type, recommended values: manual / auto

ManualFormData (Manual Fulfillment Form Values)

manual_form_data is an object where the Key is product_id and the Value is the form submission data for that product.

json
{
  "1001": {
    "receiver_name": "John Doe",
    "phone": "13277745648",
    "address": "Shenzhen, Guangdong..."
  }
}

2. Data Object Field Dictionary

The following objects are referenced in the "response structure" of subsequent interfaces.

2.1 PublicProduct

FieldTypeDescription
category_idnumberCategory ID
slugstringUnique product identifier
titleobjectMultilingual title
descriptionobjectMultilingual summary
contentobjectMultilingual detailed content
price_amountstringProduct price amount
imagesstring[]List of product images
tagsstring[]List of tags
purchase_typestringPurchase access restriction: guest / member
max_purchase_quantitynumberMaximum purchase quantity per order (0 means unlimited)
fulfillment_typestringDelivery type: manual / auto
manual_form_schemaobjectManual delivery form schema
manual_stock_availablenumberManually available stock
auto_stock_availablenumberAutomatically available stock
stock_statusstringStock status: unlimited / in_stock / low_stock / out_of_stock
is_sold_outbooleanWhether sold out
categoryCategoryCategory information
skusPublicSKU[]SKU list
promotion_idnumberApplied promotion ID (optional)
promotion_namestringPromotion name (optional)
promotion_typestringPromotion type (optional)
promotion_price_amountstringPromotion price amount (optional)
promotion_rulesPromotionRule[]Promotion rules list (optional)
member_pricesMemberLevelPrice[]Member level price list (optional)

2.1.1 PublicSKU

Each element in the skus[] array has the following structure:

FieldTypeDescription
idnumberSKU ID (use this ID when placing orders)
sku_codestringSKU code (unique within the same product)
spec_valuesobjectSpecification values (multilingual)
price_amountstringSKU original price
manual_stock_totalnumberManual stock total (-1 means unlimited)
manual_stock_soldnumberManual stock sold quantity
auto_stock_availablenumberAuto-delivery stock available
upstream_stocknumberUpstream stock (-1 = unlimited, 0 = sold out)
is_activebooleanWhether enabled
promotion_price_amountstringSKU promotion price amount (optional)
member_price_amountstringMember price amount (optional)

2.1.2 PromotionRule

Each element in the promotion_rules[] array has the following structure:

FieldTypeDescription
idnumberPromotion rule ID
namestringPromotion name
typestringPromotion type: percent / fixed / special_price
valuestringPromotion value (string amount/percentage, e.g., "2.00" or "5.00")
min_amountstringThreshold amount (purchase subtotal = unit price × quantity, e.g., "200.00"; "0.00" means no threshold)

Promotion types and discount calculation:

typeMeaningDiscount calculation (per item)
percentPercentage discountunit price = original × (100 - value) / 100
fixedFixed amount reductionunit price = original - value
special_priceDirect price overrideunit price = value
  • All discounts apply to the per-item unit price, not the order total.
  • min_amount is the purchase subtotal threshold (unit price × quantity); once met, each item receives the corresponding discount.
  • A product can have multiple rules with different min_amount thresholds to create tiered discounts. The backend matches from highest to lowest, applying the best qualifying tier.

Promotion price calculation: Promotions are configured at the product level, with prices calculated independently for each SKU. For example, if a product has a "2% off" promotion, a 99.00 SKU will have a promotion price of 97.02, while a 77.00 SKU will have a promotion price of 75.46. The product-level promotion_price_amount is the lowest promotion price among all SKUs, suitable for list page display. promotion_rules returns all active rules (sorted by min_amount ascending), even when the current price does not meet the threshold, for frontend promotion hints.

2.2 Post

FieldTypeDescription
idnumberArticle ID
slugstringUnique article identifier
typestringType: blog / notice
titleobjectMultilingual title
summaryobjectMultilingual summary
contentobjectMultilingual content
thumbnailstringThumbnail URL
published_atstring/nullPublication time

2.3 Banner

FieldTypeDescription
idnumberBanner ID
positionstringPlacement position (e.g., home_hero)
titleobjectMultilingual title
subtitleobjectMultilingual subtitle
imagestringMain image
mobile_imagestringMobile image
link_typestringLink type: none / internal / external
link_valuestringLink value
open_in_new_tabbooleanOpen in a new tab

2.4 Category

FieldTypeDescription
idnumberCategory ID
parent_idnumberParent category ID (0 means top-level)
slugstringUnique category identifier
nameobjectMultilingual name
iconstringCategory icon
sort_ordernumberSort order

2.5 UserProfile

FieldTypeDescription
idnumberUser ID
emailstringEmail
nicknamestringNickname
email_verified_atstring/nullEmail verification time
localestringLanguage (e.g., zh-CN)
member_level_idnumberMember level ID
total_rechargedstringTotal recharged amount
total_spentstringTotal spent amount
email_change_modestringEmail change mode: bind_only / change_with_old_and_new
password_change_modestringPassword change mode: set_without_old / change_with_old

2.6 UserLoginLog

FieldTypeDescription
idnumberLog ID
user_idnumberUser ID (may be 0 if failed)
emailstringLogin email
statusstringLogin result: success / failed
fail_reasonstringFailure reason enum
client_ipstringClient IP
user_agentstringClient UA
login_sourcestringLogin source: web / telegram
request_idstringRequest trace ID
created_atstringRecord creation time

2.7 OrderPreview

FieldTypeDescription
currencystringCurrency (site-wide unified, sourced from site_config.currency)
original_amountstringOriginal total amount
discount_amountstringTotal discount amount
promotion_discount_amountstringPromotion discount amount
total_amountstringTotal payable amount
itemsOrderPreviewItem[]Preview order items

2.8 OrderPreviewItem

FieldTypeDescription
product_idnumberProduct ID
titleobjectProduct title snapshot (multilingual)
tagsstring[]Product tags snapshot
unit_pricestringUnit price
quantitynumberQuantity
total_pricestringSubtotal
coupon_discount_amountstringCoupon discount allocation amount
promotion_discount_amountstringPromotion discount allocation amount
fulfillment_typestringFulfillment type

2.9 Order

FieldTypeDescription
order_nostringOrder number
guest_emailstringGuest email (for guest orders)
guest_localestringGuest language
statusstringOrder status: pending_payment / paid / fulfilling / partially_delivered / delivered / completed / canceled
currencystringOrder currency
original_amountstringOriginal price
discount_amountstringDiscount amount
member_discount_amountstringMember discount amount
promotion_discount_amountstringPromotional discount amount
total_amountstringAmount paid
wallet_paid_amountstringWallet payment amount
online_paid_amountstringOnline payment amount
refunded_amountstringRefunded amount
expires_atstring/nullPayment expiry time
paid_atstring/nullPayment success time
canceled_atstring/nullCancellation time
created_atstringCreation time
itemsOrderItem[]Order items
fulfillmentFulfillmentDelivery record (optional)
childrenOrder[]List of sub-orders (optional)

2.10 OrderItem

FieldTypeDescription
titleobjectProduct title snapshot
sku_snapshotobjectSKU snapshot (code/spec)
tagsstring[]Product tags snapshot
unit_pricestringUnit price
quantitynumberQuantity
total_pricestringSubtotal
coupon_discount_amountstringCoupon allocation amount
member_discount_amountstringMember discount allocation amount
promotion_discount_amountstringPromotion discount amount
fulfillment_typestringFulfillment type
manual_form_schema_snapshotobjectManual fulfillment form schema snapshot
manual_form_submissionobjectUser-submitted manual form values

2.11 Fulfillment

FieldTypeDescription
typestringDelivery type: auto / manual
statusstringDelivery status: pending / delivered
payloadstringText delivery content
payload_line_countnumberTotal line count of delivery content
delivery_dataobjectStructured delivery information
delivered_atstring/nullDelivery time

2.12 PaymentLaunch

FieldTypeDescription
payment_idnumberPayment record ID
order_nostringOrder number (returned by latest API)
channel_idnumberPayment channel ID (returned by latest API)
provider_typestringProvider: official / epay
channel_typestringChannel: alipay / wechat / paypal / stripe, etc.
interaction_modestringInteraction mode: qr / redirect / wap / page
pay_urlstringRedirect payment link
qr_codestringQR code content
expires_atstring/nullPayment order expiration time

3. Public APIs (No login required)

3.1 Get site configuration

Endpoint: GET /public/config

Authentication: No

Request Parameters

None

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "languages": ["zh-CN", "zh-TW", "en-US"],
    "currency": "CNY",
    "contact": {
      "telegram": "https://t.me/dujiaostudio",
      "whatsapp": "https://wa.me/1234567890"
    },
    "site_name": "Dujiao-Next",
    "scripts": [
      {
        "name": "Plausible",
        "enabled": true,
        "position": "head",
        "code": "<script defer data-domain=\"localhost\" src=\"https://xxx.com/js/script.js\"></script>"
      }
    ],
    "payment_channels": [
      {
        "id": 1,
        "name": "Alipay Desktop",
        "provider_type": "official",
        "channel_type": "alipay",
        "interaction_mode": "page",
        "fee_rate": "0.00"
      }
    ],
    "captcha": {
      "provider": "turnstile",
      "scenes": {
        "login": true,
        "register_send_code": true,
        "reset_send_code": false,
        "guest_create_order": false
      },
      "turnstile": {
        "site_key": "0x4AAA..."
      }
    },
    "telegram_auth": {
      "enabled": true,
      "bot_username": "dujiao_auth_bot"
    }
  }
}

Response Structure (data)

FieldTypeDescription
languagesstring[]List of enabled site languages
currencystringSite-wide currency (3-letter uppercase code, e.g. CNY)
contactobjectContact configuration
scriptsobject[]Custom frontend JS script configuration
payment_channelsobject[]List of available payment channels on the frontend
captchaobjectPublic captcha configuration
telegram_authobjectPublic Telegram login config (enabled, bot_username)
other fieldsanyPublic fields from the backend site settings (dynamically extended)

3.2 Product List

Endpoint: GET /public/products

Authentication: No

Query Parameters

ParameterTypeRequiredDescription
pagenumberNoPage number
page_sizenumberNoNumber of items per page (max 100)
category_idstringNoCategory ID
searchstringNoSearch keyword (title, etc.)

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": [
    {
      "category_id": 10,
      "slug": "netflix-plus",
      "title": { "zh-CN": "Netflix Membership" },
      "description": { "zh-CN": "Available in all regions" },
      "content": { "zh-CN": "Detailed description" },
      "price_amount": "99.00",
      "images": ["/uploads/product/1.png"],
      "tags": ["Popular"],
      "purchase_type": "member",
      "fulfillment_type": "manual",
      "manual_form_schema": { "fields": [] },
      "manual_stock_available": 88,
      "auto_stock_available": 0,
      "stock_status": "in_stock",
      "is_sold_out": false
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_page": 1
  }
}

Response Structure (data)

  • data: PublicProduct[]
  • pagination: Pagination object (see general conventions)

3.3 Product Details

Endpoint: GET /public/products/:slug

Authentication: No

Path Parameters

ParameterTypeRequiredDescription
slugstringYesProduct slug

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "slug": "netflix-plus",
    "title": { "zh-CN": "Netflix Membership" },
    "price_amount": "99.00",
    "fulfillment_type": "manual",
    "manual_form_schema": {
      "fields": [
        {
          "key": "receiver_name",
          "type": "text",
          "required": true,
          "label": { "zh-CN": "Recipient" }
        }
      ]
    },
    "manual_stock_available": 88,
    "auto_stock_available": 0,
    "stock_status": "in_stock",
    "is_sold_out": false
  }
}

Response Structure (data)

  • data: PublicProduct

3.4 Article List

Endpoint: GET /public/posts

Authentication: No

Query Parameters

ParameterTypeRequiredDescription
pagenumberNoPage number
page_sizenumberNoNumber of items per page
typestringNoArticle type: blog / notice

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "slug": "release-2026-02",
      "type": "notice",
      "title": { "zh-CN": "Release Update" },
      "summary": { "zh-CN": "Added payment channels" },
      "content": { "zh-CN": "Detailed content" },
      "thumbnail": "/uploads/post/1.png",
      "published_at": "2026-02-11T10:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_page": 1
  }
}

Response Structure (data)

  • data: Post[]
  • pagination: Pagination object

3.5 Article Details

Endpoint: GET /public/posts/:slug

Authentication: No

Path Parameters

ParameterTypeRequiredDescription
slugstringYesArticle slug

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "id": 1,
    "slug": "release-2026-02",
    "type": "notice",
    "title": { "zh-CN": "Release Update" },
    "summary": { "zh-CN": "Added payment channels" },
    "content": { "zh-CN": "Detailed content" },
    "thumbnail": "/uploads/post/1.png",
    "published_at": "2026-02-11T10:00:00Z"
  }
}

Response Structure (data)

  • data: Post

3.6 Banner List

Endpoint: GET /public/banners

Authentication: No

Query Parameters

ParameterTypeRequiredDefaultDescription
positionstringNohome_heroBanner position
limitnumberNo10Maximum 50

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "position": "home_hero",
      "title": { "zh-CN": "Welcome to D&N" },
      "subtitle": { "zh-CN": "Reliable fulfillment" },
      "image": "/uploads/banner/hero.png",
      "mobile_image": "/uploads/banner/hero-mobile.png",
      "link_type": "internal",
      "link_value": "/products",
      "open_in_new_tab": false
    }
  ]
}

Response Structure (data)

  • data: Banner[]

3.7 Category List

Endpoint: GET /public/categories

Authentication: No

Request Parameters

None

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": [
    {
      "id": 10,
      "parent_id": 0,
      "slug": "memberships",
      "name": { "zh-CN": "Membership Services" },
      "icon": "",
      "sort_order": 100
    }
  ]
}

Response Structure (data)

  • data: Category[]

3.8 Get Image CAPTCHA Challenge

Endpoint: GET /public/captcha/image

Authentication: No

Request Parameters

None

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "captcha_id": "9f2b2be147df4f6eb6f8",
    "image_base64": "data:image/png;base64,iVBORw0KGgoAAA..."
  }
}

Response Structure (data)

FieldTypeDescription
captcha_idstringID of this CAPTCHA
image_base64stringBase64 image (data URL)

4. Authentication API (No Login Required)

4.1 Send Email Verification Code

Endpoint: POST /auth/send-verify-code

Authentication: No

Body Parameters

FieldTypeRequiredDescription
emailstringYesEmail address
purposestringYesPurpose of the verification code: register / reset
captcha_payloadobjectNoCAPTCHA parameters (see common structure)

Request Example

json
{
  "email": "user@example.com",
  "purpose": "register",
  "captcha_payload": {
    "captcha_id": "",
    "captcha_code": "",
    "turnstile_token": ""
  }
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "sent": true
  }
}

Response Structure (data)

FieldTypeDescription
sentbooleanWhether the send was successful

4.2 User Registration

Endpoint: POST /auth/register

Authentication: No

Body Parameters

FieldTypeRequiredDescription
emailstringYesEmail
passwordstringYesPassword
codestringYesEmail verification code
agreement_acceptedbooleanYesWhether the agreement is accepted, must be true

Request Example

json
{
  "email": "user@example.com",
  "password": "StrongPass123",
  "code": "123456",
  "agreement_accepted": true
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "user": {
      "id": 101,
      "email": "user@example.com",
      "nickname": "user",
      "email_verified_at": "2026-02-11T10:00:00Z"
    },
    "token": "eyJhbGciOi...",
    "expires_at": "2026-02-18T10:00:00Z"
  }
}

Response Structure (data)

FieldTypeDescription
userobjectRegistered user information (id/email/nickname/email_verified_at)
tokenstringUser JWT
expires_atstringToken expiration time (RFC3339)

4.3 User Login

Endpoint: POST /auth/login

Authentication: No

Body Parameters

FieldTypeRequiredDescription
emailstringYesEmail
passwordstringYesPassword
remember_mebooleanNoWhether to extend login session
captcha_payloadobjectNoCaptcha parameters (see common structure)

Request Example

json
{
  "email": "user@example.com",
  "password": "StrongPass123",
  "remember_me": true,
  "captcha_payload": {
    "captcha_id": "",
    "captcha_code": "",
    "turnstile_token": ""
  }
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "user": {
      "id": 101,
      "email": "user@example.com",
      "nickname": "user",
      "email_verified_at": "2026-02-11T10:00:00Z"
    },
    "token": "eyJhbGciOi...",
    "expires_at": "2026-02-25T10:00:00Z"
  }
}

Response Structure (data)

Consistent with the registration interface: user + token + expires_at


4.4 Forgot Password

Endpoint: POST /auth/forgot-password

Authentication: No

Body Parameters

FieldTypeRequiredDescription
emailstringYesEmail
codestringYesEmail verification code
new_passwordstringYesNew password

Request Example

json
{
  "email": "user@example.com",
  "code": "123456",
  "new_password": "NewStrongPass123"
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "reset": true
  }
}

Response Structure (data)

FieldTypeDescription
resetbooleanWhether the reset was successful

4.5 Telegram Login

Endpoint: POST /auth/telegram/login

Authentication: No

Body Parameters

FieldTypeRequiredDescription
idnumberYesTelegram user ID
first_namestringNoFirst name
last_namestringNoLast name
usernamestringNoTelegram username
photo_urlstringNoTelegram avatar URL
auth_datenumberYesTelegram auth timestamp (seconds)
hashstringYesTelegram login signature

Request Example

json
{
  "id": 123456789,
  "first_name": "Dujiao",
  "last_name": "User",
  "username": "dujiao_user",
  "photo_url": "https://t.me/i/userpic/320/xxx.jpg",
  "auth_date": 1739250000,
  "hash": "f1b2c3..."
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "user": {
      "id": 101,
      "email": "telegram_123456789@login.local",
      "nickname": "telegram_123456789",
      "email_verified_at": null
    },
    "token": "eyJhbGciOi...",
    "expires_at": "2026-02-25T10:00:00Z"
  }
}

Response Structure (data)

Same as registration: user + token + expires_at

On first Telegram login without an existing binding, the system auto-creates an account and signs in directly.


5. Login User Profile API (Bearer Token Required)

5.1 Get Current User

Endpoint: GET /me

Authentication: Yes

Request Parameters

None

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "id": 101,
    "email": "user@example.com",
    "nickname": "user",
    "email_verified_at": "2026-02-11T10:00:00Z",
    "locale": "zh-CN",
    "email_change_mode": "change_with_old_and_new",
    "password_change_mode": "change_with_old"
  }
}

Response Structure (data)

  • data: UserProfile

5.2 Login Log List

Endpoint: GET /me/login-logs

Authentication: Yes

Query Parameters

ParameterTypeRequiredDescription
pagenumberNoPage number
page_sizenumberNoNumber of items per page

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "user_id": 101,
      "email": "user@example.com",
      "status": "success",
      "fail_reason": "",
      "client_ip": "127.0.0.1",
      "user_agent": "Mozilla/5.0",
      "login_source": "web",
      "request_id": "01HR...",
      "created_at": "2026-02-11T12:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_page": 1
  }
}

Response Structure (data)

  • data: UserLoginLog[]
  • pagination: Pagination object

5.3 Update User Profile

Endpoint: PUT /me/profile

Authentication: Required

Body Parameters

FieldTypeRequiredDescription
nicknamestringNoNickname
localestringNoLanguage, e.g., zh-CN

At least one of nickname or locale must be provided.

Request Example

json
{
  "nickname": "new-nickname",
  "locale": "zh-CN"
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "id": 101,
    "email": "user@example.com",
    "nickname": "new-nickname",
    "email_verified_at": "2026-02-11T10:00:00Z",
    "locale": "zh-CN",
    "email_change_mode": "change_with_old_and_new",
    "password_change_mode": "change_with_old"
  }
}

Response Structure (data)

  • data: UserProfile

5.4 Get Telegram Binding Status

Endpoint: GET /me/telegram

Authentication: Yes

Request Parameters

None

Successful Response Example (Bound)

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "bound": true,
    "provider": "telegram",
    "provider_user_id": "123456789",
    "username": "dujiao_user",
    "avatar_url": "https://t.me/i/userpic/320/xxx.jpg",
    "auth_at": "2026-02-20T12:00:00Z",
    "updated_at": "2026-02-20T12:00:00Z"
  }
}

Successful Response Example (Unbound)

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "bound": false
  }
}

Response Structure (data)

FieldTypeDescription
boundbooleanWhether Telegram is bound
providerstringOAuth provider (telegram when bound)
provider_user_idstringTelegram user ID (string)
usernamestringTelegram username
avatar_urlstringTelegram avatar URL
auth_atstringTelegram authorization time
updated_atstringBinding updated time

When bound=false, only the bound field is returned.


5.5 Bind Telegram

Endpoint: POST /me/telegram/bind

Authentication: Yes

Body Parameters

FieldTypeRequiredDescription
idnumberYesTelegram user ID
first_namestringNoFirst name
last_namestringNoLast name
usernamestringNoTelegram username
photo_urlstringNoTelegram avatar URL
auth_datenumberYesTelegram auth timestamp (seconds)
hashstringYesTelegram login signature

Request Example

json
{
  "id": 123456789,
  "first_name": "Dujiao",
  "last_name": "User",
  "username": "dujiao_user",
  "photo_url": "https://t.me/i/userpic/320/xxx.jpg",
  "auth_date": 1739250000,
  "hash": "f1b2c3..."
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "bound": true,
    "provider": "telegram",
    "provider_user_id": "123456789",
    "username": "dujiao_user",
    "avatar_url": "https://t.me/i/userpic/320/xxx.jpg",
    "auth_at": "2026-02-20T12:00:00Z",
    "updated_at": "2026-02-20T12:00:00Z"
  }
}

Response Structure (data)

Same as GET /me/telegram (bound case).


5.6 Unbind Telegram

Endpoint: DELETE /me/telegram/unbind

Authentication: Yes

Request Parameters

None

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "unbound": true
  }
}

Response Structure (data)

FieldTypeDescription
unboundbooleanWhether unbinding succeeded

If the user has not bound a real email yet (email_change_mode=bind_only), unbinding Telegram is not allowed.


5.7 Send Verification Code to Change Email

Endpoint: POST /me/email/send-verify-code

Authentication: Yes

Body Parameters

FieldTypeRequiredDescription
kindstringYesold (send to old email) / new (send to new email)
new_emailstringConditionally requiredRequired when kind=new

When email_change_mode=bind_only, kind=old is not available. Use kind=new to bind a real email.

Request Example

json
{
  "kind": "new",
  "new_email": "new@example.com"
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "sent": true
  }
}

Response Structure (data)

FieldTypeDescription
sentbooleanWhether the sending was successful

5.8 Change Email

Endpoint: POST /me/email/change

Authentication: Required

Body Parameters

FieldTypeRequiredDescription
new_emailstringYesNew email
old_codestringConditionally requiredRequired when email_change_mode=change_with_old_and_new; optional and ignored when bind_only
new_codestringYesVerification code of the new email

Request Example

json
{
  "new_email": "new@example.com",
  "old_code": "123456",
  "new_code": "654321"
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "id": 101,
    "email": "new@example.com",
    "nickname": "user",
    "email_verified_at": "2026-02-11T10:00:00Z",
    "locale": "zh-CN",
    "email_change_mode": "change_with_old_and_new",
    "password_change_mode": "change_with_old"
  }
}

Response Structure (data)

  • data: UserProfile

5.9 Change Password

Endpoint: PUT /me/password

Authentication: Yes

Body Parameters

FieldTypeRequiredDescription
old_passwordstringConditionally requiredRequired when password_change_mode=change_with_old; optional when set_without_old
new_passwordstringYesNew password

For accounts auto-created via Telegram that have never set a password, password_change_mode=set_without_old; only new_password is needed.

Request Example

json
{
  "old_password": "OldPass123",
  "new_password": "NewPass123"
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "updated": true
  }
}

Response Structure (data)

FieldTypeDescription
updatedbooleanWhether the update was successful

6. User Order and Payment API (Requires Bearer Token)

6.1 Order Amount Preview

Endpoint: POST /orders/preview

Authentication: Yes

Body Parameters

FieldTypeRequiredDescription
itemsOrderItemInput[]YesOrder items
coupon_codestringNoCoupon code
manual_form_dataobjectNoValues submitted from manual delivery form (see general structure)

Request Example

json
{
  "items": [
    {
      "product_id": 1001,
      "quantity": 1,
      "fulfillment_type": "manual"
    }
  ],
  "coupon_code": "SPRING2026",
  "manual_form_data": {
    "1001": {
      "receiver_name": "John Doe",
      "phone": "13277745648",
      "address": "Nanshan District, Shenzhen, Guangdong"
    }
  }
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "currency": "CNY",
    "original_amount": "99.00",
    "discount_amount": "10.00",
    "promotion_discount_amount": "5.00",
    "total_amount": "84.00",
    "items": [
      {
        "product_id": 1001,
        "title": { "zh-CN": "Netflix Membership" },
        "tags": ["Popular"],
        "unit_price": "99.00",
        "quantity": 1,
        "total_price": "99.00",
        "coupon_discount_amount": "10.00",
        "promotion_discount_amount": "5.00",
        "fulfillment_type": "manual"
      }
    ]
  }
}

Response Structure (data)

  • data: OrderPreview

6.2 Create Order

Endpoint: POST /orders

Authentication: Yes

Body Parameters

Same as POST /orders/preview.

Request Example

json
{
  "items": [
    {
      "product_id": 1001,
      "quantity": 1,
      "fulfillment_type": "manual"
    }
  ],
  "manual_form_data": {
    "1001": {
      "receiver_name": "John Doe",
      "phone": "13277745648",
      "address": "Nanshan District, Shenzhen, Guangdong"
    }
  }
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "order_no": "DN202602110001",
    "status": "pending_payment",
    "currency": "CNY",
    "original_amount": "99.00",
    "discount_amount": "0.00",
    "promotion_discount_amount": "0.00",
    "total_amount": "99.00",
    "expires_at": "2026-02-11T12:30:00Z",
    "items": [
      {
        "title": { "zh-CN": "Netflix Membership" },
        "quantity": 1,
        "unit_price": "99.00",
        "total_price": "99.00",
        "coupon_discount_amount": "0.00",
        "promotion_discount_amount": "0.00",
        "fulfillment_type": "manual",
        "manual_form_schema_snapshot": {
          "fields": [
            { "key": "receiver_name", "type": "text", "required": true }
          ]
        },
        "manual_form_submission": {
          "receiver_name": "John Doe"
        }
      }
    ]
  }
}

Response Structure (data)

  • data: Order

6.3 Order List

Endpoint: GET /orders

Authentication: Yes

Query Parameters

ParameterTypeRequiredDescription
pagenumberNoPage number
page_sizenumberNoNumber of items per page
statusstringNoStatus filter (see Order.status enum)
order_nostringNoFuzzy search by order number

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": [
    {
      "order_no": "DN202602110001",
      "status": "pending_payment",
      "currency": "CNY",
      "total_amount": "99.00",
      "created_at": "2026-02-11T12:00:00Z",
      "items": [
        {
          "title": { "zh-CN": "Netflix Membership" },
          "quantity": 1,
          "unit_price": "99.00",
          "total_price": "99.00",
          "coupon_discount_amount": "0.00",
          "promotion_discount_amount": "0.00",
          "fulfillment_type": "manual",
          "manual_form_schema_snapshot": {},
          "manual_form_submission": {}
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_page": 1
  }
}

Response Structure (data)

  • data: Order[]
  • pagination: Pagination object

6.4 Order Details

Endpoint: GET /orders/:order_no

Authentication: Yes

Path Parameters

ParameterTypeRequiredDescription
order_nostringYesOrder number

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "order_no": "DN202602110001",
    "status": "pending_payment",
    "currency": "CNY",
    "total_amount": "99.00",
    "items": [
      {
        "title": { "zh-CN": "Netflix Membership" },
        "quantity": 1,
        "unit_price": "99.00",
        "total_price": "99.00",
        "coupon_discount_amount": "0.00",
        "promotion_discount_amount": "0.00",
        "fulfillment_type": "manual",
        "manual_form_schema_snapshot": {},
        "manual_form_submission": {}
      }
    ],
    "fulfillment": null,
    "children": []
  }
}

Response Structure (data)

  • data: Order

6.5 Cancel Order

Endpoint: POST /orders/:order_no/cancel

Authentication: Yes

Path Parameters

ParameterTypeRequiredDescription
order_nostringYesOrder number

Body Parameters

None

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "order_no": "DN202602110001",
    "status": "canceled",
    "currency": "CNY",
    "total_amount": "99.00",
    "canceled_at": "2026-02-11T12:10:00Z"
  }
}

Response Structure (data)

  • data: Order

6.6 Create Payment Order

Endpoint: POST /payments

Authentication: Yes

Body Parameters

FieldTypeRequiredDescription
order_nostringYesOrder number
channel_idnumberYesPayment Channel ID

Request Example

json
{
  "order_no": "DN202602110001",
  "channel_id": 10
}

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "payment_id": 3001,
    "provider_type": "official",
    "channel_type": "alipay",
    "interaction_mode": "page",
    "pay_url": "https://openapi.alipay.com/gateway.do?...",
    "qr_code": "",
    "expires_at": "2026-02-11T12:30:00Z"
  }
}

Response Structure (data)

  • data: PaymentLaunch (usually does not include order_no/channel_id fields when creating a payment)

6.7 Capture Payment Result

Endpoint: POST /payments/:id/capture

Authentication: Yes

Path Parameters

ParameterTypeRequiredDescription
idnumberYesPayment record ID

Body Parameters

None

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "payment_id": 3001,
    "status": "success"
  }
}

Response Structure (data)

FieldTypeDescription
payment_idnumberPayment record ID
statusstringPayment status: initiated / pending / success / failed / expired

6.8 Get Latest Pending Payment Record

Endpoint: GET /payments/latest

Authentication: Yes

Query Parameters

ParameterTypeRequiredDescription
order_nostringYesOrder number

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "payment_id": 3001,
    "order_no": "DN202602110001",
    "channel_id": 10,
    "provider_type": "official",
    "channel_type": "alipay",
    "interaction_mode": "page",
    "pay_url": "https://openapi.alipay.com/gateway.do?...",
    "qr_code": "",
    "expires_at": "2026-02-11T12:30:00Z"
  }
}

Response Structure (data)

  • data: PaymentLaunch

7. Guest Orders and Payment Interface

Guest order access credentials: email + order_password.

7.1 Guest Order Preview

Endpoint: POST /guest/orders/preview

Authentication: No

Body Parameters

FieldTypeRequiredDescription
emailstringYesGuest email
order_passwordstringYesQuery password
itemsOrderItemInput[]YesOrder items
coupon_codestringNoCoupon code
manual_form_dataobjectNoSubmitted values for manual delivery form
captcha_payloadobjectNoCaptcha parameters (not validated in current preview, can be ignored)

Request Example

json
{
  "email": "guest@example.com",
  "order_password": "guest-pass",
  "items": [
    {
      "product_id": 1001,
      "quantity": 1,
      "fulfillment_type": "manual"
    }
  ],
  "manual_form_data": {
    "1001": {
      "receiver_name": "John Doe",
      "phone": "13277745648",
      "address": "Nanshan District, Shenzhen, Guangdong"
    }
  }
}

Successful Response Example

Same as POST /orders/preview.

Response Structure (data)

  • data: OrderPreview

7.2 Guest Creates Order

Endpoint: POST /guest/orders

Authentication: No

Body Parameters

Same as POST /guest/orders/preview.

Request Example

json
{
  "email": "guest@example.com",
  "order_password": "guest-pass",
  "items": [
    {
      "product_id": 1001,
      "quantity": 1,
      "fulfillment_type": "manual"
    }
  ],
  "manual_form_data": {
    "1001": {
      "receiver_name": "John Doe",
      "phone": "13277745648",
      "address": "Nanshan District, Shenzhen, Guangdong"
    }
  },
  "captcha_payload": {
    "captcha_id": "abc",
    "captcha_code": "x7g5",
    "turnstile_token": ""
  }
}

Successful Response Example

Same as POST /orders (for guest orders, user_id=0, guest_email has a value).

Response Structure (data)

  • data: Order

7.3 Guest Order List

Endpoint: GET /guest/orders

Authentication: No

Query Parameters

ParameterTypeRequiredDescription
emailstringYesGuest email
order_passwordstringYesQuery password
order_nostringNoOrder number, if provided, queries by order number and returns 0/1 record
pagenumberNoPage number
page_sizenumberNoNumber of items per page

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": [
    {
      "order_no": "DN202602110002",
      "guest_email": "guest@example.com",
      "status": "pending_payment",
      "currency": "CNY",
      "total_amount": "99.00",
      "items": [
        {
          "title": { "zh-CN": "Netflix Membership" },
          "quantity": 1,
          "unit_price": "99.00",
          "total_price": "99.00",
          "coupon_discount_amount": "0.00",
          "promotion_discount_amount": "0.00",
          "fulfillment_type": "manual",
          "manual_form_schema_snapshot": {},
          "manual_form_submission": {}
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_page": 1
  }
}

Response Structure (data)

  • data: Order[]
  • pagination: Pagination object

7.4 Guest Order Details

Endpoint: GET /guest/orders/:order_no

Authentication: No

Path Parameters

ParameterTypeRequiredDescription
order_nostringYesOrder number

Query Parameters

ParameterTypeRequiredDescription
emailstringYesGuest email
order_passwordstringYesQuery password

Successful Response Example

Same structure as user order details.

Response Structure (data)

  • data: Order

7.5 Guest Create Payment

Endpoint: POST /guest/payments

Authentication: No

Body Parameters

FieldTypeRequiredDescription
emailstringYesGuest email
order_passwordstringYesQuery password
order_nostringYesOrder number
channel_idnumberYesPayment channel ID

Request Example

json
{
  "email": "guest@example.com",
  "order_password": "guest-pass",
  "order_no": "DN202602110002",
  "channel_id": 10
}

Successful Response Example

Matches the structure returned by POST /payments.

Response Structure (data)

  • data: PaymentLaunch

7.6 Guest Capture Payment Result

Endpoint: POST /guest/payments/:id/capture

Authentication: No

Path Parameters

ParameterTypeRequiredDescription
idnumberYesPayment record ID

Body Parameters

FieldTypeRequiredDescription
emailstringYesGuest email
order_passwordstringYesQuery password

Request Example

json
{
  "email": "guest@example.com",
  "order_password": "guest-pass"
}

Successful Response Example

Same as POST /payments/:id/capture.

Response Structure (data)

FieldTypeDescription
payment_idnumberPayment record ID
statusstringPayment status

7.7 Guest Get Latest Pending Payment Record

Endpoint: GET /guest/payments/latest

Authentication: No

Query Parameters

ParameterTypeRequiredDescription
emailstringYesGuest email
order_passwordstringYesQuery password
order_nostringYesOrder number

Successful Response Example

Same as GET /payments/latest.

Response Structure (data)

  • data: PaymentLaunch

8. Frontend Integration Recommendations

8.1 All Order Endpoints Use order_no

All user-facing order endpoints now use order_no as the identifier, no longer exposing auto-increment IDs:

  • GET /orders/:order_no — Order details
  • POST /orders/:order_no/cancel — Cancel order
  • GET /orders/:order_no/fulfillment/download — Download fulfillment content
  • GET /guest/orders/:order_no — Guest order details
  • GET /guest/orders/:order_no/fulfillment/download — Guest download fulfillment content
  • POST /payments, GET /payments/latest — Use order_no parameter

8.2 Unified Error Handling

The frontend must check both:

  • HTTP status (network layer)
  • status_code (business layer)

When status_code != 0, please read the msg for prompts and record data.request_id for troubleshooting.

8.3 Payment Success Page and Polling

It is recommended to combine the payment process as follows:

  1. After initiating payment, redirect to pay_url or display qr_code
  2. After payment completion and redirect, call capture
  3. Then call latest as a fallback polling check

This can significantly reduce the perceived issue of "payment made but the page not updating in time."


9. Interfaces Not Actively Called by the Frontend (Explanation)

9.1 Payment Platform Callback Interfaces

The following callback interfaces are generally called by the payment platform's server, so the frontend template does not need to actively request them:

  • POST /payments/callback
  • GET /payments/callback
  • POST /payments/webhook/paypal
  • POST /payments/webhook/stripe

9.2 Admin Telegram Login Settings APIs

The following endpoints are for the admin panel and are not frontend user APIs:

9.2.1 Get Telegram Login Settings

Endpoint: GET /admin/settings/telegram-auth

Authentication: Admin token

Successful Response Example

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "enabled": true,
    "bot_username": "dujiao_auth_bot",
    "bot_token": "",
    "has_bot_token": true,
    "login_expire_seconds": 300,
    "replay_ttl_seconds": 300
  }
}

bot_token is always masked as an empty string. Use has_bot_token to check whether a token is configured.

9.2.2 Update Telegram Login Settings

Endpoint: PUT /admin/settings/telegram-auth

Authentication: Admin token

Body Parameters (Patch)

FieldTypeRequiredDescription
enabledbooleanNoWhether Telegram login is enabled
bot_usernamestringNoTelegram bot username (without @)
bot_tokenstringNoTelegram bot token (empty string will not overwrite existing value)
login_expire_secondsnumberNoLogin validity window (30-86400 seconds)
replay_ttl_secondsnumberNoReplay-protection TTL (60-86400 seconds)

Request Example

json
{
  "enabled": true,
  "bot_username": "dujiao_auth_bot",
  "bot_token": "123456:ABCDEF",
  "login_expire_seconds": 300,
  "replay_ttl_seconds": 300
}

Successful Response

Same structure as GET /admin/settings/telegram-auth (masked).

9.3 Admin Product Multi-SKU APIs

The following endpoints are used by the admin panel and are not frontend user APIs.

9.3.1 Create Product (Multi-SKU Supported)

Endpoint: POST /admin/productsAuthentication: Admin token

Key Body Fields

FieldTypeRequiredDescription
category_idnumberYesCategory ID
slugstringYesUnique product slug
titleobjectYesLocalized title
price_amountnumberYesProduct price in single-SKU mode; when using multi-SKU, you can pass 0 or any placeholder value, final value is derived from SKUs
fulfillment_typestringNomanual / auto
manual_stock_totalnumberNoTotal manual stock in single-SKU mode
skusarrayNoMulti-SKU array; empty or omitted means single-SKU mode

skus[] Fields

FieldTypeRequiredDescription
idnumberNoPass when updating an existing SKU; omit for new SKU
sku_codestringYesSKU code (unique within the same product)
spec_valuesobjectNoSKU display labels (e.g. localized {"en-US":"Standard","zh-CN":"标准版"})
price_amountnumberYesSKU price (must be greater than 0)
manual_stock_totalnumberNoSKU manual stock (effective in manual fulfillment mode)
is_activebooleanNoWhether SKU is active, default true
sort_ordernumberNoSort weight, default 0; higher value appears earlier

Request Example (Single-SKU Compatible)

json
{
  "category_id": 1,
  "slug": "vpn-monthly",
  "title": {
    "zh-CN": "VPN 月付",
    "zh-TW": "VPN 月付",
    "en-US": "VPN Monthly"
  },
  "price_amount": 29.9,
  "fulfillment_type": "manual",
  "manual_stock_total": 100
}

Request Example (Multi-SKU)

json
{
  "category_id": 1,
  "slug": "vpn-subscription",
  "title": {
    "zh-CN": "VPN 订阅",
    "zh-TW": "VPN 訂閱",
    "en-US": "VPN Subscription"
  },
  "price_amount": 0,
  "fulfillment_type": "manual",
  "skus": [
    {
      "sku_code": "STANDARD",
      "spec_values": {
        "zh-CN": "标准版",
        "zh-TW": "標準版",
        "en-US": "Standard"
      },
      "price_amount": 29.9,
      "manual_stock_total": 100,
      "is_active": true,
      "sort_order": 10
    },
    {
      "sku_code": "PRO",
      "spec_values": {
        "zh-CN": "专业版",
        "zh-TW": "專業版",
        "en-US": "Pro"
      },
      "price_amount": 49.9,
      "manual_stock_total": 80,
      "is_active": true,
      "sort_order": 20
    }
  ]
}

9.3.2 Update Product (Multi-SKU Supported)

Endpoint: PUT /admin/products/:idAuthentication: Admin token

Request body is the same as POST /admin/products; to update existing SKUs, pass their id in skus[].

9.3.3 Backend Derivation Rules

  • When skus is not empty:
    • product display price is auto-derived from the lowest active SKU price;
    • in manual fulfillment mode, product manual stock total is auto-summed from active SKU manual_stock_total.
  • When skus is empty:
    • legacy single-SKU mode applies, and product-level price/stock fields are used.

9.3.4 Admin UI Operation Guide

  1. Open Admin Product Management, then create or edit a product.
  2. In the SKU configuration section, add one or more SKUs and fill code, labels, price, stock, status, and sort order.
  3. Once SKU mode is configured, product-level price/manual stock fields are informational; effective values come from SKUs.
  4. Save and verify SKU selection/display on the frontend product detail page.

9.4 Affiliate APIs

These endpoints support frontend affiliate center features and admin affiliate review workflows.

9.4.1 Order APIs Add affiliate_code

The request body of the following endpoints supports optional affiliate_code (affiliate ID):

  • POST /orders/preview
  • POST /orders
  • POST /guest/orders/preview
  • POST /guest/orders

Field definition:

FieldTypeRequiredDescription
affiliate_codestringNoAffiliate ID (for example AB12CD34) used for commission attribution

9.4.2 Public Click Tracking

Endpoint: POST /public/affiliate/clickAuthentication: None

FieldTypeRequiredDescription
affiliate_codestringYesAffiliate ID
visitor_keystringNoVisitor identifier (can be persisted by frontend)
landing_pathstringNoLanding path (for example /?aff=AB12CD34)
referrerstringNoReferrer page URL

Successful response:

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "ok": true
  }
}

9.4.3 User Affiliate Center APIs (Bearer Token Required)

A) Open Affiliate
  • Endpoint: POST /affiliate/open
  • Description: Returns the affiliate profile after activation (including affiliate ID).
B) Get Affiliate Dashboard
  • Endpoint: GET /affiliate/dashboard

Key data fields:

FieldTypeDescription
openedbooleanWhether affiliate is activated
affiliate_codestringAffiliate ID
promotion_pathstringPromotion path (for example /?aff=AB12CD34)
click_countnumberClick count
valid_order_countnumberValid order count
conversion_ratenumberConversion rate (percentage value)
pending_commissionstringPending commission
available_commissionstringWithdrawable commission
withdrawn_commissionstringWithdrawn commission
C) Get My Commission Records
  • Endpoint: GET /affiliate/commissions
  • Query params: page, page_size, status
  • status values: pending_confirm / available / rejected / withdrawn
D) Get My Withdraw Records
  • Endpoint: GET /affiliate/withdraws
  • Query params: page, page_size, status
  • status values: pending_review / rejected / paid
E) Apply for Withdraw
  • Endpoint: POST /affiliate/withdraws
FieldTypeRequiredDescription
amountstringYesWithdraw amount (string amount with 2 decimal places)
channelstringYesWithdraw channel
accountstringYesWithdraw account

9.4.4 Admin Affiliate Settings APIs

A) Get Affiliate Settings
  • Endpoint: GET /admin/settings/affiliate
  • Authentication: Admin token
B) Update Affiliate Settings
  • Endpoint: PUT /admin/settings/affiliate
  • Authentication: Admin token
FieldTypeRequiredDescription
enabledbooleanYesWhether affiliate is enabled
commission_ratenumberYesCommission rate (0-100, up to 2 decimals)
confirm_daysnumberYesCommission confirmation days (0-3650)
min_withdraw_amountnumberYesMinimum withdraw amount (>=0)
withdraw_channelsstring[]YesWithdraw channel list

9.4.5 Admin Affiliate Management APIs

The following endpoints all require Admin token:

EndpointDescription
GET /admin/affiliates/usersAffiliate user list
GET /admin/affiliates/commissionsCommission record list
GET /admin/affiliates/withdrawsWithdraw request list
POST /admin/affiliates/withdraws/:id/rejectReject withdraw request
POST /admin/affiliates/withdraws/:id/payMark withdraw as paid

Notes:

  • POST /admin/affiliates/withdraws/:id/reject body supports { "reason": "rejection reason" }
  • POST /admin/affiliates/withdraws/:id/pay requires no extra body fields

Released under the MIT License.