Skip to content

feat(payment): add native USDT payment provider with live CNY rate display#2915

Open
s258852s wants to merge 1 commit into
Wei-Shaw:mainfrom
s258852s:pr/native-usdt
Open

feat(payment): add native USDT payment provider with live CNY rate display#2915
s258852s wants to merge 1 commit into
Wei-Shaw:mainfrom
s258852s:pr/native-usdt

Conversation

@s258852s
Copy link
Copy Markdown

@s258852s s258852s commented May 30, 2026

Summary

Adds a first-class usdt payment provider so EasyPay-protocol crypto gateways that settle in stablecoins (e.g. self-hosted BEpusdt) can be configured directly from the admin UI, instead of being shoehorned into the alipay/wxpay sub-types of the generic EasyPay provider.

Bundled with the provider is a small UX helper: a live CNY/USDT rate is shown beside the USDT method on the recharge page, so users know roughly how much USDT they will pay before being redirected to the gateway's checkout.

Motivation

Without a native provider, self-hosting a crypto gateway today requires either:

  • Overloading the EasyPay alipay or wxpay channel — confusing UI, brittle signing semantics, and the order's payment_type does not match what the user actually paid in.
  • Running an external re-signing bridge — extra moving part, extra failure mode.

A real usdt provider is ~170 lines that fits cleanly into the existing EasyPay helpers, with no new dependencies or protocol fork.

What's added

Backend

  • payment/provider/usdt.go — new provider, builds EasyPay-style submit requests with the on-chain trade type sent natively (usdt.trc20 by default, configurable per instance). Sign/verify reuse the EasyPay MD5 helpers; only the wire-level type field and the snapshot/notification metadata differ from EasyPay.
  • Provider key usdt registered in factory, types, webhook routes, webhook handler, validProviderKeys, sensitive-field list, and the pending-order-protected config field list.
  • PID is snapshotted on order create and verified on notification, matching how alipay / wxpay / easypay already enforce merchant identity.
  • GET /api/v1/payment/usdt/rate — cached (60s, singleflight, stale-on-error) CNY/USDT rate fetched from CoinGecko. Used by the recharge UI to preview the implied USDT amount.

Frontend

  • USDT is selectable in admin provider management, with its own icon (assets/icons/usdt.svg) and brand color (#26A17B), wired through providerConfig.ts, the payment flow helpers, admin order tables/filters, i18n (zh + en) and types/payment.ts.
  • PaymentMethodSelector accepts an optional usdtRate + usdtImpliedAmount. When USDT is among the visible methods, it renders an inline hint — Live rate 1 USDT ≈ X CNY · pay ~Y USDT — plus a one-line note that the checkout page amount is authoritative.
  • PaymentView polls /payment/usdt/rate on mount and every 60s, passing the rate + entered amount to the selector. The line softly hides if the rate fetch fails, so the UI never blocks.

Compatibility

  • No DB migration required — uses the existing payment_provider_instances JSON config.
  • No breaking changes to existing providers; only additive.

Testing

  • Verified end-to-end against a self-hosted BEpusdt instance: a real TRC20 payment (10.06 USDT for 68 CNY at a live CoinGecko rate of 6.76) was created, confirmed on-chain, notified back via webhook, and credited to the user balance.
  • go build ./..., golangci-lint, vue-tsc --noEmit and the existing vitest critical suite all pass.

CLA

I will sign the CLA in a separate comment below.

…splay

Adds a first-class `usdt` payment provider so EasyPay-protocol crypto
gateways that settle in stablecoins (e.g. self-hosted BEpusdt) can be
configured directly from the admin UI, instead of being shoehorned into
the alipay/wxpay sub-types of the generic EasyPay provider.

## Backend

- New provider `payment/provider/usdt.go` builds EasyPay-style submit
  requests with the on-chain trade type sent natively (default
  `usdt.trc20`, configurable per instance). Sign/verify reuse the
  EasyPay MD5 helpers, so the provider only differs in the wire-level
  `type` field and the snapshot/notification metadata.
- Provider key `usdt` registered in factory, types, webhook routes,
  webhook handler, `validProviderKeys`, sensitive-field list and the
  pending-order-protected config field list. PID is snapshotted on
  order create and verified on notification, matching how alipay /
  wxpay / easypay enforce merchant identity.
- New endpoint `GET /api/v1/payment/usdt/rate` returns a cached
  (60s, singleflight, stale-on-error) CNY/USDT rate fetched from
  CoinGecko, used by the recharge UI to show the user the implied
  USDT amount before they leave Sub2API for the gateway's checkout.

## Frontend

- USDT is selectable in admin provider management, with its own icon
  (`assets/icons/usdt.svg`) and brand color (#26A17B), wired through
  `providerConfig.ts`, the payment flow helpers, order tables/filters,
  i18n (zh + en) and `types/payment.ts`.
- `PaymentMethodSelector` accepts an optional `usdtRate` +
  `usdtImpliedAmount` and, when USDT is among the visible methods,
  renders a "Live rate 1 USDT ≈ X CNY · pay ~Y USDT" hint plus a
  one-line note that the checkout page amount is authoritative.
- `PaymentView` polls `/payment/usdt/rate` on mount and every 60s,
  passing the rate + entered amount to the selector; the line softly
  hides when the rate fetch fails so the UI never blocks.

## Why

Without a native provider, hosting a crypto gateway required either
overloading the EasyPay alipay/wxpay channel (confusing UI, brittle
sign), or running an external re-signing bridge. The new provider is
~170 lines of code and fits cleanly into the existing EasyPay
helpers — no protocol fork, no new dependencies.

## Not included

- The fork-local GHA workflow that publishes the image to GHCR is
  intentionally omitted; consumers can keep using the upstream
  publishing pipeline.

## Testing

- Verified end-to-end against a self-hosted BEpusdt instance: a real
  TRC20 payment (10.06 USDT for 68 CNY at live CoinGecko rate of
  6.76) was created, confirmed on-chain, notified back via webhook,
  and credited to the user balance.
- `go build ./...`, `golangci-lint`, `vue-tsc --noEmit` and the
  existing vitest critical suite all pass.
@github-actions
Copy link
Copy Markdown
Contributor

Thank you for your contribution! Before we can merge this PR, we need you to sign our Contributor License Agreement (CLA).

To sign, please reply with the following comment:

I have read the CLA Document and I hereby sign the CLA

You only need to sign once — it will be valid for all your future contributions to this project.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant