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
min_purchase_quantitynumberMinimum purchase quantity per order (0 means unlimited)
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://telegram.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"
    },
    "google_auth": {
      "enabled": true,
      "client_id": "1234567890-xxxx.apps.googleusercontent.com"
    }
  }
}

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)
google_authobjectPublic Google sign-in config (enabled, client_id)
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": "[email protected]",
  "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": "[email protected]",
  "password": "StrongPass123",
  "code": "123456",
  "agreement_accepted": true
}

Successful Response Example ​

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "user": {
      "id": 101,
      "email": "[email protected]",
      "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": "[email protected]",
  "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": "[email protected]",
      "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": "[email protected]",
  "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": "[email protected]",
      "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.


4.6 Google Sign-In ​

Endpoint: POST /auth/google/login

Authentication: No

Body Parameters ​

FieldTypeRequiredDescription
credentialstringYesOpaque ID token returned by the Google Identity Services callback; the frontend must not parse it and trust its claims

Request Example ​

json
{
  "credential": "eyJhbGciOiJSUzI1NiIs..."
}

Success Response Examples ​

Without two-factor authentication:

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "requires_totp": false,
    "user": {
      "id": 101,
      "email": "[email protected]",
      "nickname": "Google User",
      "email_verified_at": "2026-07-30T10:00:00Z"
    },
    "token": "eyJhbGciOi...",
    "expires_at": "2026-07-30T12:00:00Z"
  }
}

When TOTP is enabled:

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "requires_totp": true,
    "challenge_token": "eyJhbGciOi...",
    "challenge_expires_at": "2026-07-30T10:05:00Z"
  }
}

After receiving a two-factor challenge, continue with POST /auth/login/verify-2fa.

The backend verifies the Google signature, aud, iss, expiry, sub, and email verification status. Gmail addresses and Google Workspace addresses with an hd claim can be safely matched to an existing account by email. Other custom-domain addresses must first sign in to the site account and then use the explicit binding endpoint. New accounts remain subject to registration settings and the email-domain allowlist.

iOS/iPadOS Redirect Sign-In ​

Desktop browsers and Android use the popup/FedCM credential endpoint above. iOS/iPadOS use the following redirect form_post flow:

  1. The frontend calls POST /auth/google/redirect/intent to create a one-time sign-in intent.
  2. Google submits the credential, state, and CSRF fields to the same-origin POST /auth/google/redirect/callback.
  3. After validating and consuming the one-time state, the backend sends a 303 redirect to the fixed frontend path /auth/google/callback?flow=login (failures add only an allowlisted error code).
  4. The frontend calls POST /auth/google/redirect/exchange to consume the HttpOnly handoff and receive the same login/2FA response as popup sign-in.
Create a Sign-In Intent ​

Endpoint: POST /auth/google/redirect/intent

Authentication: No

Body: Empty JSON object {}.

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "state": "c3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3M",
    "expires_in": 600,
    "issued_at": "2026-07-30T10:00:00Z"
  }
}

The frontend must pass the returned state unchanged to the Google Identity Services button configuration. The backend also sets a same-origin, Secure, HttpOnly state cookie. An intent can be used only once.

Google form_post Callback ​

Endpoint: POST /auth/google/redirect/callback

Authentication: No. Google Identity Services submits directly to this endpoint; frontend code must not call it manually.

Content-Type: application/x-www-form-urlencoded

FieldTypeRequiredDescription
credentialstringYesID token returned by Google
statestringYesOne-time state returned when the intent was created
g_csrf_tokenstringYesMust match the cookie of the same name set by Google

The callback does not return JSON. After validating the Google credential, double-submit CSRF token, state, tenant, and flow, the backend responds with 303 See Other to a fixed frontend callback page. The credential, state, handoff, and site token are never placed in the redirect URL.

Exchange the Sign-In Result ​

Endpoint: POST /auth/google/redirect/exchange

Authentication: No

Body: Empty JSON object {}; the browser must include same-origin cookies.

The success response is identical to POST /auth/google/login, including the TOTP challenge branch. A handoff can be exchanged only once and expires after 120 seconds.

Redirect sign-in requires an enabled and available Redis 7 instance and atomically consumes state/handoff records with GETDEL. When Redis is unavailable, these redirect endpoints return a service-unavailable response. Popup/FedCM POST /auth/google/login does not depend on this state store.


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": "[email protected]",
    "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": "[email protected]",
      "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": "[email protected]",
    "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

Telegram can be unbound only when the account still has a valid local password or another third-party sign-in method that is currently enabled and fully configured. Otherwise, the backend rejects the operation to prevent removing every usable sign-in method.


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": "[email protected]"
}

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": "[email protected]",
  "old_code": "123456",
  "new_code": "654321"
}

Successful Response Example ​

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "id": 101,
    "email": "[email protected]",
    "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

5.10 Get Google Binding Status ​

Endpoint: GET /me/google

Authentication: Yes

Success Response Example ​

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "bound": true,
    "provider": "google",
    "provider_user_id": "109876543210987654321",
    "username": "[email protected]",
    "email": "[email protected]",
    "display_name": "Google User",
    "avatar_url": "https://lh3.googleusercontent.com/a/example",
    "auth_at": "2026-07-30T10:00:00Z",
    "can_unbind": true
  }
}

can_unbind=false means unbinding would remove every usable sign-in method; the frontend must keep the action disabled.


5.11 Bind Google ​

Endpoint: POST /me/google/bind

Authentication: Yes

Body Parameters ​

FieldTypeRequiredDescription
credentialstringYesOpaque ID token returned by the Google Identity Services callback
json
{
  "credential": "eyJhbGciOiJSUzI1NiIs..."
}

The success response is the same as GET /me/google. The endpoint rejects an identity already bound to another user and rejects replacing a different Google identity already bound to the current account.

iOS/iPadOS Redirect Binding ​

Redirect binding shares POST /auth/google/redirect/callback with sign-in, but both the intent and exchange endpoints require current-user authentication:

  1. Call POST /me/google/redirect/intent to create a binding intent (Body is {}; the response matches the sign-in intent response).
  2. Pass the returned state to Google Identity Services, which sends a same-origin form_post callback.
  3. On success, the callback sends a 303 redirect to /auth/google/callback?flow=bind.
  4. Call POST /me/google/redirect/exchange (Body is {}) to exchange the binding result. Its success response matches GET /me/google.

Creating and exchanging a binding intent both require a Bearer token. One-time state is bound to both the current tenant and the user who created it; changing the domain, tenant, or user prevents the exchange. This flow also requires Redis 7 to be available.


5.12 Unbind Google ​

Endpoint: DELETE /me/google/unbind

Authentication: Yes

Unbinding is allowed only when the account still has a valid local password or another currently usable third-party sign-in method.

Success Response Example ​

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

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": "[email protected]",
  "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": "[email protected]",
  "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": "[email protected]",
      "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": "[email protected]",
  "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": "[email protected]",
  "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

9.5 Admin Google Sign-In Settings APIs ​

The following endpoints are used by the admin frontend and require the system_admin permission.

9.5.1 Get Google Sign-In Settings ​

Endpoint: GET /admin/settings/google-auth

Authentication: Admin token

json
{
  "status_code": 0,
  "msg": "success",
  "data": {
    "enabled": true,
    "client_id": "1234567890-example.apps.googleusercontent.com"
  }
}

9.5.2 Update Google Sign-In Settings ​

Endpoint: PUT /admin/settings/google-auth

Authentication: Admin token

Body Parameters (Patch) ​

FieldTypeRequiredDescription
enabledbooleanNoWhether Google sign-in is enabled
client_idstringNoGoogle OAuth 2.0 Web Client ID; required when enabling sign-in
json
{
  "enabled": true,
  "client_id": "1234567890-example.apps.googleusercontent.com"
}

The change takes effect immediately. The Client ID is a public browser identifier, so no Client Secret is required. This feature does not request Gmail mailbox permissions.

Released under the GPLv3 License.