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.