AnQiCMS v3.8.1 released: Mall merged into main branch, Feishu DingTalk takes over backend, four payment callback rewritten

📅 November 11, 2026 👁️ 4

This version spans three weeks from 3.6.7 to 3.8.1, with 922 commits, 116 mainline items, and 51 items in the update log. There are many features, but to put it simply, half of the effort has been spent on the same thing: finding those 'interface back to success, actually nothing was done' places.

The mall has been integrated into the main branch.

The mall version was previously an independent branch, branching out from v3.5.8. This time, it is移植整体移植以 v3.6.7 为底座,处理了 151 个冲突文件。

24 new data models have been added: SKU, shopping cart, wish list, coupons, marketing activities, wholesale rules, logistics freight, inquiry order, product synchronization, points, exchange rate, and more. The order settlement includes coupons, freight, member price, promotional price, distribution, invoice, and logistics tracking, and an additional 15 mall email templates and one mall template are provided.

The main branch hasn't lost anything accumulated in this period: AI intent kernel, Partial semantics, SQL injection reinforcement, payment account system are all there.

The content station is not affected. shop_mode is default 0, 38 routes on the mall front end return 404 in normal mode, background data can still be managed, but the menu does not display. To open the mall, go to system settings and switch.

The background can now receive Feishu and DingTalk.

Not the kind of access where a notification is sent for new messages. Administrators chat privately in Feishu or in authorized groups, directly interacting with AI to do tasks: modify content, check orders, adjust settings. New messages and orders on the website can also be pushed to designated groups or administrators' private chats according to the rules.

Both platforms use long connections, Feishu uses oapi-sdk, and DingTalk uses Stream mode. There is no need for a public callback address, nor do you need to open an inbound port. Whether the site deployed in the internal network can be used basically depends on this one.

Some trade-offs in implementation:

· Receive a message and reply with an emoji first. Post OnIt in Feishu, reply with emojis in DingTalk. After the model and tools finish running that silent period, just give a

· Replies should be rendered in Markdown, the title in DingTalk will skip over code fences and strip the markers.

· Change the organization identifier to be automatically retrieved by calling the platform interface. That value has no display entry in the platform backend, requiring the site administrator to manually fill it in is equivalent to setting the access threshold in an unreachable place.

The authority is limited to one set. What channels can do is entirely determined by the site's intention exposure and the scope of interface openness. There is no second set of authorization within the channel, nor is there an approval status machine. The first version included approval, which was later removed - the same thing only needs to be configured once in the background, and two sets of rules will only fight each other.

One source code, multiple white-label packages

The brand is converged to one place: config/oem/{id}/brand., embedded into the binary with go:embed, injected during the build phase with -ldflags. The same repository can produce multiple packages that do not affect each other, so there is no need to maintain a fork for each customer.

The brand is not pulled from the remote, which is deliberate. When deploying on the internal network or when the official website is not accessible, 'fallback to default when it cannot be pulled' is equivalent to turning the customer's paid backend into a competitor's advertising space.

What if the logo is spelled incorrectly? It will fall back to a neutral brand, and the display name will be that incorrectly spelled logo, never falling back to the official brand. The only place where it would look very ugly in the entire transformation is right here.

Acceptance cannot rely on grep source code. Brand replacement occurs at the runtime output, and retaining the official copy in the source files is part of the design itself. So, we added a make brand-check: capture the real response of the running site, check the homepage, backend shell, robots.txt, sitemap.xml, and make a two-way judgment——things that should not appear should not appear, and things that should appear should not be missing.

· · ·

Four payment callbacks, they were all broken

This sentence is not rhetoric. Before the change, WeChat, PayPal, Stripe's asynchronous callbacks were all silently disabled, and Alipay would mark unpaid orders as paid.

The first parameter of WeChat's verification function was passed as appId, but the actual one needed is the merchant apiKey; the verification algorithm is hardcoded MD5, and the notification message does not carry sign_type, while the order uses HMAC-SHA256. Every notification fails.

Stripe takes an event ID starting with evt_ to check the payment_id, it will definitely not be found, then return directly. Iris defaults to returning a 200 empty response, Stripe considers it successfully delivered, and no longer retries.

PayPal's processing function requires a ?token= parameter. That's only available on the synchronous return_url, while the registered webhook address does not have parameters - all callbacks are blocked at the door.

Alipay signature check is fine, but it does not check trade_status at all. The WAIT_BUYER_PAY status will also mark the order as paid.

All four issues were fixed according to the root cause. Conveniently, I added a gate to the accounting: Notification retries and template polling will come in concurrently, using the condition paid_time = 0 to update the gate, only one can truly settle. Without this, the financial flow will be recorded repeatedly.

Additionally, two new channels were added. Hot pepper: Sign according to the official V3 document, the callback needs to verify the signature, appid, status=OD, and amount, and only full order refunds are supported. NOWPayments: Cryptocurrency, invoice mode, IPN uses HMAC-SHA512 to verify the signature, only recognizing finished, confirmed, and sending three states - it does not have a refund API, so the refund branch directly reports an error instead of marking the order as refunded and actually not returning any money.

Several security patches

The attachment upload has been modified to the whitelist, default is to reject. The trigger is the appearance of shell.php5 and s2.phtml in public/uploads. The original logic only blocked .php and .jsp, and content that the filetype could not recognize would fall back to the user's filename. The watermark and title image suffixes are no longer taken from the filename, but are determined by the result of image.Decode. The template package import conveniently added Zip Slip.

The password recovery credential algorithm. Originally derived from Md5(email + password), accounts with empty passwords (Google, public account, verification code registration) will degrade to Md5(email)——if you know the email, you can change the password and log in.

Email detection with rate limiting. The three endpoints of registration, GET/POST verify/email can detect whether an email is registered, the original ones did not have rate limiting, and they could bypass it by alternating use. Now they share a budget: 20 times / 10 minutes / IP.

/aigenerate The entire group lacks authorization. 7 endpoints can read AI configuration and trigger batch generation without a token. After adding the middleware, it all returns to 1001.

Those bugs that did not react when clicked.

Anchor text batch update, 3557 articles, failed 3557 articles, completed in 287 milliseconds. The reason is hidden deep: CurrentSite returns a copy with the request ctx, go currentSite.ReplaceAnchor(nil) brings it into the goroutine, handler returns ctx is canceled immediately, and then each article is context canceled, and the error is swallowed by the replacement function.Added Website.Async() removed request ctx, 0 failed, completed in 3.065 seconds.

The coupons batch code issuance, completely ignoring the error of insertion in the loop. Several tens of codes all failed but still returned success, and the background refresh list is empty. Changed to check one by one, the batch code issuance goes through a transaction, either all fall into the database or none remain.

Write endpoints are more common: 51 return 'Saved' when the request body is empty, 24 return 'Success' when the id does not exist. A unified parameter guard has been adopted, rejecting and explaining the reason for any empty space, missing target, or non-existent target.

The one that is more sinister in the upgrade process. After the main program is renamed to xxx.old, the process is still running on the old inode, and Linux will also rename /proc/self/exe to xxx.old. After the upgrade is completed without a reboot and the upgrade is clicked again, xxx.old is backed up to xxx.old.old, and the actual xxx has never been replaced, and the interface prompts that it is successful.

There is a judgment in one place: there are 5 places in the code that use ModuleId == 2 to identify 'this is a product'. The module id is auto-incremented, and if the model is deleted and rebuilt, or the site is migrated, the id may not be 2. The product page can still be opened, but the price block, SKU filtering, and Product structured data will all disappear without any error. In the statistics, module_id = 2 is more direct, making the 'new' chart always 0. Now, these places all read modules.kind.

· · ·

Before upgrading, check these four things first

  1. The database will delete columns, so back up first

The archives and archive_drafts will delete 29 historical columns and cannot be rolled back. The product attributes have been moved to the archive_products side table, and if you have written plugins to directly query these columns, you must change them together - conditions like WHERE product_type = ? are now LEFT JOIN + IFNULL, otherwise, the ordinary products without side table rows will be omitted.

  1. The default submission permissions are empty.

The 'Submittable Columns' of the user group have been upgraded to be empty, which means you cannot submit. There is no 'empty is all' fallback, and parent-child columns do not inherit. Go to the user group after the upgrade and check once, otherwise the front-end submission will report a permission error directly.

  1. The attachment suffix has been changed to a whitelist

Files like .psd, .ai, .apk that were previously allowed to be uploaded will be rejected now. If needed, fill in the form, the judgment logic has only one entry point, no need to change it in multiple places.

  1. The mall is turned off by default

If you need to use it, you need to switch shop_mode to 1 in the system settings and save it.

The complete 51 changes are in the CHANGELOG.md repository, each one explains why it was changed and what it was like before. This version has many changes, so make a backup before upgrading.

If you encounter any problems during the upgrade process, send the version number, log snippet, and steps to reproduce to the repository Issues, and we will follow up.

Related articles

Why can the template replacement of the form module become an entry point for code execution?

When the form module hands over the submitted content to the template for rendering, if the replaceable markers are not limited to safe sources, the field values may be treated as instructions and form an entry point for code execution. The defense is divided into two stages: on the input side, use captcha to block automated submissions and suspicious content, and on the rendering side, only whitelist markers are allowed. External modules need to be on the version list, and once the announcement is made, it can be located to one's own site.

2026-11-01

The relationship filtering of the content API has not been purified, what data will the interface return more?

When the content interface directly concatenates the relationship filtering conditions into the query without purification, the caller can read associated fields that should not be publicly accessible using filtering syntax, such as author, order, and internal identifiers of cross-table data. Self-check should cover parameter whitelist, association depth, and readable field list, and clearly specify what each endpoint returns in the interface documentation. Authorization and minimum privilege determine the exposure surface, and run regression test cases before upgrading.

2026-11-01

Why would making a page into an HTML cache turn it into an entry point for executable code?

To speed up the front-end page by making it an HTML static cache, but if the content with parameters is reflected in the cache file exactly as it is and the cache product is then handed over to the script parsing engine, malicious code can be executed in the cache. There are two boundaries: strip executable tags when writing, and do not treat the cache directory as the script directory when parsing. After the repair, the historical cache should be cleared, and then check the pseudo-static rules and page structure.

2026-11-01

Why can't the registrar be changed within two months after changing the domain holder's email address?

Changing the name, organization, or email of the domain holder will trigger a transfer lock, during which the domain cannot be transferred to another registrar, regardless of whether the domain is due to expire. When scheduling website construction or replacing the host, this period needs to be reversed: first confirm that the holder's information is stable, then apply for the transfer code, and finally move the resolution. When managing multiple site domains, it is necessary to check if there are any domains that have just changed records.

2026-11-01

Static site hosting charges per head, how much does a five-person team need to pay in one year?

Static site hosting's free tier excludes deployment and bandwidth, but when it comes to team collaboration, it switches to per-head seat fees, plus additional minutes and traffic overages. For a team of five, the annual bill needs to be calculated separately for seats, overages, and construction. Self-hosted content management charges by resource rather than by headcount, and multiple sites sharing a single backend can also reduce unit costs, with memory usage reduced by about 80%.

2026-11-01

After turning off the switch for the Generative AI feature, can you still receive AI recommended traffic?

Turning off the Generative AI feature switch is different from turning off natural search: the former affects the display and traffic brought by that feature, while the latter affects regular inclusion and recommendation. Before making a judgment, first look at the two dates, the report data start date should be earlier than the switch effective date, and the observation window must be after the effective date. The recommended traffic should be checked by grouping the proxy sources, and changes should retain the rollback records.

2026-11-01

The traffic share of the Generative platform is rearranged in a year, which foreign trade websites should pay attention to which entry points

The traffic share among generative AI platforms has been significantly reordered within a year, making it unreliable to consider a single entry as the entire source. Foreign trade websites should keep separate accounts for each entry, compare the conversion differences with recommended sessions, and match corresponding landing pages for multi-language sites and multi-sites. Look back quarterly once to avoid using the share structure of the previous quarter to judge this quarter.

2026-11-01

First look at the crawling log to decide who to block, and find out which paths the proxy is accessing.

Before deciding which AI agents to intercept, first use the server to capture logs and group the access volume by agent identifier and access path. Most sites place llms.txt, which is almost never read, and the few that do read it are concentrated on a few paths. Understand the real access distribution before writing robots rules, which is more bandwidth-saving than copying others' intercept lists and easier to roll back and review.

2026-11-01