diff --git a/api/auth.js b/api/auth.js
index e5171d6..1998bc7 100644
--- a/api/auth.js
+++ b/api/auth.js
@@ -175,3 +175,16 @@ export function appleLogin(params) {
cha:1
});
}
+
+/**
+ * 获取直推邀请用户列表
+ * @param {Object} params { page, pageSize }
+ */
+export function getDirectReferrals(params) {
+ return request({
+ url: "/user/getDirectReferrals",
+ method: "get",
+ data: params,
+ });
+}
+
diff --git a/api/coupon.js b/api/coupon.js
index 3158c8e..ec7abe0 100644
--- a/api/coupon.js
+++ b/api/coupon.js
@@ -60,3 +60,16 @@ export function getApplicableGoods(data) {
data,
});
}
+
+/**
+ * 查询首页弹窗优惠券通知列表
+ * @param {Object} data { page, pageSize }
+ */
+export function getPopNotifyList(data) {
+ return request({
+ url: "/coupon/getPopNotifyList",
+ method: "get",
+ data,
+ });
+}
+
diff --git a/components/common/guide-popup.vue b/components/common/guide-popup.vue
new file mode 100644
index 0000000..afdac86
--- /dev/null
+++ b/components/common/guide-popup.vue
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
+
+
diff --git a/manifest.json b/manifest.json
index 5cd97e0..7049cdc 100644
--- a/manifest.json
+++ b/manifest.json
@@ -72,6 +72,16 @@
"com.apple.developer.associated-domains" : [ "applinks:app.tbmall.xin" ]
}
},
+ "urlschemewhitelist" : [
+ "snssdk1128",
+ "douyin",
+ "douyinopensdk",
+ "douyinsharesdk",
+ "alipay",
+ "alipays",
+ "weixin",
+ "wechat"
+ ],
"privacyDescription" : {
"NSPhotoLibraryUsageDescription" : "App需要您的同意才能访问相册,以便您选择本地图片上传头像或发布商品评价。例如:当您修改个人头像或发布带图评价时,需要从相册选择图片。",
"NSPhotoLibraryAddUsageDescription" : "App需要您的同意才能添加图片到相册,以便您保存商品海报或图片到本地。例如:当您点击保存商品分享海报时,需要写入相册权限。",
@@ -141,7 +151,17 @@
"useOriginalMsgbox" : true,
"iosStyle" : "common",
"androidStyle" : "common"
- }
+ },
+ "urlschemewhitelist" : [
+ "snssdk1128",
+ "douyinopensdk",
+ "douyinsharesdk",
+ "snssdk141",
+ "weixin",
+ "weixinULAPI",
+ "alipay",
+ "alipays"
+ ]
},
/*忽略版本提示*/
"compatible" : {
diff --git a/pages.json b/pages.json
index 6a2eabe..31615ae 100644
--- a/pages.json
+++ b/pages.json
@@ -659,11 +659,11 @@
{
"path": "invite_friends/invite_friends",
"style": {
- "navigationBarTitleText": "%invite.friends.title%",
- "navigationBarBackgroundColor": "#FFFFFF",
+ "navigationBarTitleText": "邀请好友",
+ "navigationBarBackgroundColor": "#F3ECFF",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": false,
- "navigationStyle": "default" // 默认样式,会显示返回图标
+ "navigationStyle": "custom"
}
},
{
@@ -675,6 +675,12 @@
"enablePullDownRefresh": false,
"navigationStyle": "default" // 默认样式,会显示返回图标
}
+ },
+ {
+ "path": "douyin_pay_test/douyin_pay_test",
+ "style": {
+ "navigationStyle": "custom"
+ }
}
]
},
diff --git a/pages/home/home.vue b/pages/home/home.vue
index be4726b..b8cd847 100644
--- a/pages/home/home.vue
+++ b/pages/home/home.vue
@@ -113,6 +113,8 @@
+ 亲星星
+
@@ -215,7 +217,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥
+ {{ item.discountAmount }}
+
+ {{ formatThresholdText(item) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.title || '限时秒杀优惠券' }}
+
+ {{ formatScopeBadge(item) }}
+ {{ item.scopeText || (item.scopeType === 1 ? '所有商品可用' : '部分商品可用') }}
+
+ {{ formatValidityText(item) }}
+
+
+
+
+
+
+ 立即收下
+
+
+
+
+ 优惠券详情可在【我的-优惠券】中查看
+
+
+
+
+
+
@@ -238,6 +299,7 @@ import useTabber from "tabber-component";
import { getNewsList } from "@/api/news.js";
import { comparisonVersionHandler } from "@/utils/index.js";
import HomePopup from "@/components/common/home-popup.vue";
+import GuidePopup from "@/components/common/guide-popup.vue";
import productSeckillCard from "@/components/common/product-seckill-card.vue";
import { SHARE_URL } from "@/utils/config.js";
import { getUnreadCount as getKefuUnreadCount } from "@/uni_modules/hashmall-customer-service/js_sdk/api.js";
@@ -246,12 +308,14 @@ import {
startKefuRealtime,
} from "@/uni_modules/hashmall-customer-service/js_sdk/kefu-realtime.js";
import { getStorageFun, TOKEN_NAME, USER_DATA } from "@/utils/auth.js";
+import { getPopNotifyList } from "@/api/coupon.js";
export default {
components: {
choiceness,
CustomTabBar,
HomePopup,
+ GuidePopup,
productSeckillCard,
},
data() {
@@ -304,6 +368,8 @@ export default {
kefuUnreadRefreshTimer: null,
isScrolled: false,
scrollTop: 0,
+ showCouponPop: false,
+ popCouponList: [],
};
},
onPageScroll(e) {
@@ -337,6 +403,7 @@ export default {
this.querySearchList(SEARCH_TYPE.EXPLOSIVE); // 爆单专区
this.onNews(); // 获取公告
this.getCarouselImage();
+ this.fetchPopNotifyList(); // 获取专属优惠券弹窗通知
// 秒杀
@@ -394,6 +461,11 @@ export default {
// #endif
},
methods: {
+ aaa() {
+ uni.navigateTo({
+ url: '/pages/other_package/douyin_pay_test/douyin_pay_test'
+ });
+ },
getScrollTop(e) {
let top = 0;
if (typeof e === "number") {
@@ -885,6 +957,47 @@ export default {
url: "/pages/other_package/productInfo/productInfo?id=" + item.id,
});
},
+ /**
+ * 查询专属优惠券弹窗通知
+ */
+ fetchPopNotifyList() {
+ if (!getStorageFun(TOKEN_NAME)) {
+ return;
+ }
+ getPopNotifyList({ page: 1, pageSize: 100 })
+ .then((res) => {
+ if (res && res.bizcode === 100) {
+ const list = res.data?.entitys || res.data || [];
+ if (Array.isArray(list) && list.length > 0) {
+ this.popCouponList = list;
+ this.showCouponPop = true;
+ }
+ }
+ })
+ .catch((err) => {
+ console.error("获取首页优惠券弹窗失败", err);
+ });
+ },
+ formatThresholdText(item) {
+ if (!item || !item.thresholdAmount || Number(item.thresholdAmount) === 0) {
+ return "无门槛";
+ }
+ return "满减券";
+ },
+ formatScopeBadge(item) {
+ if (item && item.scopeType === 1) {
+ return "通用券";
+ }
+ return "满减券";
+ },
+ formatValidityText(item) {
+ if (item && item.startTime && item.endTime) {
+ const start = dayjs(item.startTime).format("YYYY.MM.DD");
+ const end = dayjs(item.endTime).format("YYYY.MM.DD");
+ return `${start}-${end}`;
+ }
+ return "领取之日起30天有效";
+ },
},
computed: {
isBlockedDate() {
@@ -1657,4 +1770,211 @@ export default {
bottom: 60rpx;
}
}
+
+/* 专属优惠券弹窗 */
+.coupon_pop_wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+}
+
+.coupon_pop_card {
+ width: 620rpx;
+ background: #ffffff;
+ border-radius: 32rpx;
+ padding: 36rpx 32rpx 36rpx;
+ box-sizing: border-box;
+ position: relative;
+
+ .coupon_pop_header {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ margin-bottom: 28rpx;
+
+ .coupon_pop_title {
+ font-size: 34rpx;
+ font-weight: bold;
+ color: #333333;
+ text-align: center;
+ }
+
+ .coupon_pop_close {
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 44rpx;
+ height: 44rpx;
+ // border: 2rpx solid #dcdcdc;
+ border-radius: 8rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .close_x {
+ color: #999999;
+ font-size: 24rpx;
+ line-height: 1;
+ }
+ }
+ }
+
+ .coupon_pop_list {
+ max-height: 520rpx;
+ margin-bottom: 24rpx;
+ }
+
+ .coupon_item {
+ display: flex;
+ align-items: center;
+ background: #fff5f5;
+ border-radius: 20rpx;
+ height: 160rpx;
+ margin-bottom: 20rpx;
+ position: relative;
+ overflow: hidden;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .coupon_left {
+ width: 200rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ .price_wrap {
+ display: flex;
+ align-items: baseline;
+ color: #ff1e27;
+
+ .currency {
+ font-size: 28rpx;
+ font-weight: bold;
+ margin-right: 2rpx;
+ }
+
+ .amount {
+ font-size: 56rpx;
+ font-weight: bold;
+ line-height: 1;
+ }
+ }
+
+ .threshold_tag {
+ font-size: 24rpx;
+ color: #ff1e27;
+ font-weight: bold;
+ margin-top: 8rpx;
+ }
+ }
+
+ .coupon_divider {
+ width: 1rpx;
+ height: 100%;
+ position: relative;
+
+ .dashed_line {
+ position: absolute;
+ top: 16rpx;
+ bottom: 16rpx;
+ width: 0;
+ border-left: 2rpx dashed #ffcdcd;
+ }
+
+ .notch {
+ width: 20rpx;
+ height: 20rpx;
+ background-color: #ffffff;
+ border-radius: 50%;
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+
+ &.notch_top {
+ top: -10rpx;
+ }
+
+ &.notch_bottom {
+ bottom: -10rpx;
+ }
+ }
+ }
+
+ .coupon_right {
+ flex: 1;
+ padding-left: 24rpx;
+ padding-right: 16rpx;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+
+ .coupon_name {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #222222;
+ margin-bottom: 8rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .coupon_scope_box {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8rpx;
+
+ .scope_badge {
+ font-size: 20rpx;
+ color: #ff2437;
+ border: 1rpx solid #ff8d96;
+ background: #fff0f0;
+ border-radius: 6rpx;
+ padding: 2rpx 10rpx;
+ margin-right: 12rpx;
+ flex-shrink: 0;
+ }
+
+ .scope_text {
+ font-size: 22rpx;
+ color: #666666;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+
+ .coupon_validity {
+ font-size: 22rpx;
+ color: #999999;
+ }
+ }
+
+ .coupon_pop_btn {
+ width: 100%;
+ height: 88rpx;
+ line-height: 88rpx;
+ background: #ff1e27;
+ border-radius: 44rpx;
+ color: #ffffff;
+ font-size: 32rpx;
+ font-weight: bold;
+ text-align: center;
+ margin-top: 10rpx;
+ }
+
+ .coupon_pop_tip {
+ font-size: 24rpx;
+ color: #999999;
+ text-align: center;
+ margin-top: 24rpx;
+ }
+}
diff --git a/pages/login_package/news/news.vue b/pages/login_package/news/news.vue
index 884125f..d3190b2 100644
--- a/pages/login_package/news/news.vue
+++ b/pages/login_package/news/news.vue
@@ -1,11 +1,12 @@
-
+ :class="form.type === item.key ? 'heder_item_sel heder_item' : 'heder_item'"
+ @click="headerClick(item)">
{{ item.value }}
@@ -15,8 +16,8 @@
{{ currentNum + 1 }}/{{ swiperList.length }}
-
-
+
+
@@ -32,8 +33,8 @@
-
+
{{ item.title }}
@@ -41,14 +42,16 @@
{{ formatDate(item.ctdate) }}
-
+
{{ item.browses }}
-
+
diff --git a/pages/mine/mine.vue b/pages/mine/mine.vue
index bbc1444..2942d86 100644
--- a/pages/mine/mine.vue
+++ b/pages/mine/mine.vue
@@ -381,7 +381,7 @@ export default {
// },
{
id: 8,
- title: '商学院资讯',
+ title: '资讯',
url: 'https://static.tbmall.xin/static/mine/tool_sxy.png',
path: '/pages/login_package/news/news'
},
diff --git a/pages/mine_package/mine_address_add/mine_address_add.vue b/pages/mine_package/mine_address_add/mine_address_add.vue
index 19f9b57..c517880 100644
--- a/pages/mine_package/mine_address_add/mine_address_add.vue
+++ b/pages/mine_package/mine_address_add/mine_address_add.vue
@@ -1,32 +1,18 @@
-
+
收货人
-
+
手机号码
-
+
所在地区
@@ -34,25 +20,15 @@
{{
(form.regionStr && form.regionStr.join("/")) || "所在地区"
}}
-
+
详细地址
-
+
-
-
-
+
+
+
+
+
diff --git a/pages/other_package/invite_friends/invite_friends.vue b/pages/other_package/invite_friends/invite_friends.vue
index b45fec7..6a00cab 100644
--- a/pages/other_package/invite_friends/invite_friends.vue
+++ b/pages/other_package/invite_friends/invite_friends.vue
@@ -1,354 +1,633 @@
-
-
-
- 加载分享图片中...
+
+
+
+
+
-
-
-
-
-
-
+
+
+ 我的邀请
-
-
+
+
+
+ 暂无邀请好友
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.userId || '-' }}
+ {{ item.mobile || '-' }}
+ {{ item.registTime || '-' }}
+
+
+
+
+ 已加载全部
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
\ No newline at end of file
+
+ /* 仅此处表格列表可垂直滑动,支持 App / 微信小程序 / H5 三端兼容 */
+ .table_body_scroll {
+ flex: 1;
+ height: 0;
+ min-height: 0;
+ box-sizing: border-box;
+
+ /* #ifdef H5 || APP-PLUS */
+ ::v-deep uni-scroll-view,
+ ::v-deep .uni-scroll-view {
+ height: 100% !important;
+ }
+
+ /* #endif */
+
+ .table_body {
+ .table_row {
+ display: flex;
+ border-bottom: 1rpx solid #F0EBF8;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ .td {
+ flex: 1;
+ text-align: center;
+ padding: 24rpx 8rpx;
+ font-size: 26rpx;
+ color: #333333;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ word-break: break-all;
+
+ &.td_border {
+ border-right: 1rpx solid #F0EBF8;
+ }
+ }
+ }
+ }
+ }
+}
+
+.load_more_tip {
+ text-align: center;
+ padding: 24rpx 0;
+ font-size: 22rpx;
+ color: #B0B0B0;
+}
+
+/* 底部固定分享按钮 */
+.bottom_action {
+ flex-shrink: 0;
+ background: #FFFFFF;
+ padding: 20rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
+ box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04);
+ box-sizing: border-box;
+
+ .share_btn {
+ width: 100%;
+ height: 88rpx;
+ line-height: 88rpx;
+ background: linear-gradient(135deg, #9553FF 0%, #7934F6 100%);
+ border-radius: 44rpx;
+ color: #FFFFFF;
+ font-weight: bold;
+ font-size: 32rpx;
+ border: none;
+ outline: none;
+ text-align: center;
+
+ &:active {
+ opacity: 0.9;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/uni_modules/hashmall-customer-service/js_sdk/api.js b/uni_modules/hashmall-customer-service/js_sdk/api.js
index d100267..e2827f7 100644
--- a/uni_modules/hashmall-customer-service/js_sdk/api.js
+++ b/uni_modules/hashmall-customer-service/js_sdk/api.js
@@ -45,7 +45,7 @@ export function getCurrentMember() {
return request({ url: '/user/getUserInfo', method: 'get', isShowLoading: false }).then(result => result.data)
}
-export function getMessages({ userId, conversationId, page = 1, pageSize = 80 }) {
+export function getMessages({ userId, conversationId, page = 1, pageSize = 20 }) {
const data = conversationId ? { userId, conversationId, page, pageSize } : { userId, page, pageSize }
return kefuRequest({
url: '/kefu/user/messages',
diff --git a/utils/1.md b/utils/1.md
new file mode 100644
index 0000000..7b5e439
--- /dev/null
+++ b/utils/1.md
@@ -0,0 +1,233 @@
+# 抖音支付:前端调用商城后端接口文档
+
+日期:2026-09-05。适用:商城 App 前端。本文只描述前端调用我们后端的接口,不涉及抖音官方服务端接口、证书配置或插件开发。
+
+## 一、公共约定
+
+测试环境 Base URL:`https://api.o.tbmall.xin`。生产环境沿用项目正式用户端 API 配置,不要使用后台管理 API 地址。
+
+请求头沿用现有登录请求封装:
+
+```http
+Authorization: <项目当前环境的应用授权值>
+HSM-AUTH: <当前用户登录令牌>
+Content-Type: application/x-www-form-urlencoded
+```
+
+POST 参数以表单提交,不是 JSON body;GET 使用 URL 查询参数。不要把后台管理 Token 填到 HSM-AUTH。
+
+业务响应统一格式:
+
+```json
+{ "bizcode": 100, "errcode": 0, "msg": "成功", "data": {} }
+```
+
+`bizcode=100` 表示本次接口调用成功,不等于用户已付款。其他业务码按 msg 提示;HTTP 错误、超时或网络失败不能视作付款成功。以下所有 ID、金额、签名均为示例。
+
+## 二、调用顺序
+
+1. 调用 `/cart/getSupportPay`,判断返回列表是否包含 `type=douyin`。
+2. 根据业务场景选择一个下单/支付接口,提交 `payway=douyin`。
+3. 保存返回的 `data.orderNum`,把 `data.douyin` 中的支付参数交给 App 支付能力拉起抖音。
+4. 用户返回 App 后调用 `/payment/queryResult` 查询结果,只有 `data.state=2` 才提示支付成功。
+
+**重点:支付方式筛选传 `types=6`;实际支付传 `payway=douyin`,不是 `payway=6`。**
+
+## 三、查询支付方式
+
+**POST `/cart/getSupportPay`**
+
+| 参数 | 类型 | 必填 | 说明 |
+| ----------- | ------- | ---- | -------------------------------------------- |
+| types | string | 否 | 只查抖音传 `6`;多种传 `1,2,6`;全部传 `ALL` |
+| isDeduction | boolean | 否 | 默认 false,沿用现有抵扣券结算逻辑 |
+
+请求表单:
+
+```text
+types=6&isDeduction=false
+```
+
+已启用抖音支付时的响应示例:
+
+```json
+{
+ "bizcode": 100,
+ "errcode": 0,
+ "msg": "成功",
+ "data": [{ "type": "douyin", "name": "抖音支付", "balance": 0 }]
+}
+```
+
+没有启用对应配置时,列表可能为空。显示名称使用 name,提交支付方式使用 type;balance 不是抖音钱包余额。
+
+## 四、发起支付(按场景选一个)
+
+### 4.1 已创建订单,继续支付
+
+**POST `/order/payment`**
+
+| 参数 | 类型 | 必填 | 说明 |
+| ------ | ------ | ---- | ----------------------- |
+| id | long | 是 | 待支付的商城业务订单 ID |
+| payway | string | 是 | 固定 `douyin` |
+
+```text
+id=123456&payway=douyin
+```
+
+后端接口还接受可选 password/code,但抖音支付不需要提交。成功响应见第五节。
+
+### 4.2 商品直接购买并支付
+
+**POST `/cart/settlementOrder`**
+
+| 参数 | 类型 | 必填 | 说明 |
+| ------------ | ------- | -------- | ------------------------------------ |
+| addrId | long | 是 | 收货地址 ID |
+| goodsId | long | 是 | 商品 ID |
+| specsId | long | 是 | 规格 ID |
+| goodsNum | decimal | 是 | 购买数量 |
+| payway | string | 本流程是 | `douyin` |
+| payway2 | string | 否 | `vcoin` 表示抵扣券,沿用现有结算选择 |
+| couponUserId | long | 否 | 已选用户优惠券 ID |
+| remark | string | 否 | 现有分组备注 JSON 字符串 |
+
+```text
+addrId=10001&goodsId=20001&specsId=30001&goodsNum=1&payway=douyin
+```
+
+remark 格式为 `[{"gkey":"结算返回的分组标识","rmk":"备注内容"}]`,通过现有请求封装表单编码。金额由后端计算,不传前端自算金额。优惠券、抵扣券沿用结算规则,不因选择抖音改变适用范围。
+
+### 4.3 购物车结算并支付
+
+**POST `/cart/settle`**
+
+| 参数 | 类型 | 必填 | 说明 |
+| ------------ | ------ | ---- | ---------------------------------- |
+| addrId | long | 是 | 收货地址 ID |
+| cartIds | string | 是 | 购物车 ID 逗号分隔,`ALL` 表示全选 |
+| payway | string | 是 | `douyin` |
+| couponUserId | long | 否 | 用户优惠券 ID |
+| remark | string | 否 | 分组备注 JSON 字符串,同上一接口 |
+
+```text
+addrId=10001&cartIds=40001,40002&payway=douyin
+```
+
+购物车可能拆分业务订单并合并支付,查询支付结果仍使用本次返回的 orderNum。
+
+### 4.4 余额充值
+
+**POST `/finance/recharge`**
+
+| 参数 | 类型 | 必填 | 说明 |
+| -------- | ------- | ---- | -------------------- |
+| category | string | 是 | 余额充值传 `balance` |
+| amount | decimal | 是 | 充值金额,单位元 |
+| payway | string | 是 | `douyin` |
+
+```text
+category=balance&amount=1.00&payway=douyin
+```
+
+注意:上述四个 POST 都可能创建支付单,请勿反复点击或自动重试创建。新下单接口已经返回支付参数时,不要紧接着再调用 `/order/payment`。
+
+## 五、下单/支付接口统一返回
+
+上述四个接口均返回 PayInfoDto。抖音待支付响应示例(省略其他业务字段):
+
+```json
+{
+ "bizcode": 100,
+ "errcode": 0,
+ "msg": "成功",
+ "data": {
+ "orderId": 123456,
+ "orderNum": "PAY_EXAMPLE_001",
+ "amount": 1.0,
+ "status": 0,
+ "douyin": {
+ "appId": "EXAMPLE_APP_ID",
+ "mchId": "EXAMPLE_MERCHANT_ID",
+ "prepayId": "EXAMPLE_PREPAY_ID",
+ "callbackScheme": "trustbridgeapp",
+ "packageValue": "Sign=DYPay",
+ "nonceStr": "0123456789abcdef0123456789abcdef",
+ "timeStamp": "1788585600",
+ "sign": "EXAMPLE_BASE64_SIGNATURE"
+ }
+ }
+}
+```
+
+| 字段 | 类型 | 前端用途 |
+| --------------------- | ------- | ---------------------------------------------- |
+| orderId | long | 后端返回的订单 ID |
+| orderNum | string | 保存下来,支付结果查询必须用它 |
+| amount | decimal | 实际支付金额(元) |
+| status | integer | 此 DTO 中 0=待支付、1=已支付;不同于查单 state |
+| douyin | object | App 拉起抖音所需参数 |
+| douyin.appId | string | 抖音应用 ID |
+| douyin.mchId | string | 抖音商户 ID |
+| douyin.prepayId | string | 预支付 ID |
+| douyin.callbackScheme | string | App 返回 Scheme |
+| douyin.packageValue | string | `Sign=DYPay` |
+| douyin.nonceStr | string | 随机字符串 |
+| douyin.timeStamp | string | 秒级时间戳,保留字符串类型 |
+| douyin.sign | string | 后端生成的签名,原样使用 |
+
+后端已处理签名,前端无需请求商户私钥或另调签名接口。参数缺失时停止拉起,提示支付参数异常并反馈后端,不要自行伪造字段。
+
+若 App 支付桥接要求 SDK 小写字段,映射如下;这不是另一个 HTTP 请求:
+
+```js
+const d = response.data.douyin;
+const payInfo = {
+ appid: d.appId,
+ partnerid: d.mchId,
+ prepayid: d.prepayId,
+ package: d.packageValue,
+ noncestr: d.nonceStr,
+ timestamp: d.timeStamp,
+ sign: d.sign,
+};
+```
+
+## 六、查询支付结果
+
+**GET `/payment/queryResult`**
+
+| 参数 | 类型 | 必填 | 说明 |
+| -------- | ------ | ---- | ------------------------------------------------------- |
+| orderNum | string | 是 | 发起支付接口返回的 data.orderNum,不是 orderId/prepayId |
+
+```http
+GET https://api.o.tbmall.xin/payment/queryResult?orderNum=PAY_EXAMPLE_001
+```
+
+```json
+{
+ "bizcode": 100,
+ "errcode": 0,
+ "msg": "成功",
+ "data": { "state": 2 }
+}
+```
+
+| state | 含义 | 前端处理 |
+| ----- | ---------- | -------------------------------- |
+| 0 | 订单不存在 | 核对支付编号及环境,不能提示成功 |
+| 1 | 支付处理中 | 稍后再次查询 |
+| 2 | 支付成功 | 展示成功,刷新订单或充值余额 |
+| 3 | 支付失败 | 提示失败,刷新订单状态 |
+
+当前 App 采用最多 5 次、间隔 1.5 秒的查询;这属于前端策略,不是后端强制限制。暂未确认、断网或超时应显示“支付结果确认中”,保存编号以便回前台再次查询,避免直接重复付款。
+
+用户取消或支付 SDK 提示成功后,都需要以服务端查询结果为准。不要调用 `/payment/feedback` 上报成功来替代查单;前端也不需要调用后端的抖音通知/退款回调接口。
+
+## 七、对接范围与发布注意
+
+- 本文按当前工作区后端代码整理;SDK 签名新增字段(packageValue、nonceStr、timeStamp、sign)尚未确认已部署到测试/生产服务器。
+- 后台配置已完成不代表预下单、签名、支付回调均验收通过,实际支付链路仍需联调。
+- 本次抖音支付是 App 原生支付;H5 仅可检查适用的后端业务请求,不能凭网页调用拉起此原生能力。
diff --git a/utils/enumUtils.js b/utils/enumUtils.js
index 2c473c0..95735e1 100644
--- a/utils/enumUtils.js
+++ b/utils/enumUtils.js
@@ -454,7 +454,7 @@ export const FINANCE_WITHDRAWAL_STATUS = [
];
/**
- * 页面:1-APP首页,2-APP分类页,3-商学院资讯,4-爆单区,5-好物区,6-积分专区,7-数字积分页 8-新人专享、9-超级补贴、10-限时秒杀、11-领券中心
+ * 页面:1-APP首页,2-APP分类页,3-资讯,4-爆单区,5-好物区,6-积分专区,7-数字积分页 8-新人专享、9-超级补贴、10-限时秒杀、11-领券中心
* @type {[{value: string, key: string}]}
*/
export const APP_PAGE_TYPE = {
@@ -486,8 +486,8 @@ export const NEWS_TYPE = [
key: 3,
value: "新闻"
}, {
- key: 5,
- value: "商学院"
+ // key: 5,
+ // value: "商学院"
},
// {
// key: 4,
diff --git a/utils/payUtils.js b/utils/payUtils.js
index 4b728ac..8788657 100644
--- a/utils/payUtils.js
+++ b/utils/payUtils.js
@@ -109,6 +109,255 @@ export function zfbPayFun(alipay, orderId, orderNum) {
// });
}
+/**
+ * 抖音支付(兼容 iOS、Android App 端)
+ * @param {Object|String} dyPay 抖音支付参数 (支持 orderStr, schema, url, payUrl, orderInfo 等)
+ * @param {Object|String|Number} orderId 支付的订单ID
+ * @param {Object|String} orderNum 支付的订单编号
+ */
+export function douyinPayFun(dyPay, orderId, orderNum) {
+ console.log("抖音支付---入参:", dyPay, orderId, orderNum);
+
+ return new Promise((resolve, reject) => {
+ if (!dyPay || (typeof dyPay === "object" && Object.keys(dyPay).length === 0)) {
+ uni.showToast({
+ title: "支付参数错误",
+ icon: "none",
+ });
+ jumpWayError();
+ return reject(new Error("支付参数为空"));
+ }
+
+ const d = typeof dyPay === "object" ? dyPay : {};
+ const sub = (typeof d.orderInfo === "object" && d.orderInfo) ? d.orderInfo : {};
+ const type = typeof dyPay === "object" ? (dyPay.type || "app") : "app";
+
+ // 1. 提取或生成 Scheme / URL (注意:排除 sign,避免将签名误认为 URL)
+ let orderStr = "";
+ if (typeof dyPay === "string") {
+ orderStr = dyPay;
+ } else if (typeof dyPay === "object") {
+ orderStr =
+ dyPay.orderStr ||
+ dyPay.schema ||
+ dyPay.scheme ||
+ dyPay.url ||
+ dyPay.payUrl ||
+ dyPay.link ||
+ sub.orderStr ||
+ sub.schema ||
+ sub.scheme ||
+ sub.url ||
+ "";
+
+ // 如果未配置直接链接,则尝试通过 prepayId / pay_token 自动生成抖音收银台 Scheme 备用
+ const token = d.prepayId || d.prepayid || sub.prepayid || sub.prepayId;
+ if (!orderStr && token) {
+ orderStr = `snssdk1128://pay?pay_token=${encodeURIComponent(token)}`;
+ }
+ }
+
+ console.log("抖音支付---提取到的 Scheme / orderStr:", orderStr);
+
+ // 2. 深度合并/映射原生支付所需的 orderInfo(融合根节点与 orderInfo 子节点,同时提供驼峰与小写命名)
+ const appId = d.appId || d.appid || sub.appId || sub.appid || "";
+ const mchId = d.mchId || d.partnerid || d.mchid || sub.mchId || sub.partnerid || sub.mchid || "";
+ const prepayId = d.prepayId || d.prepayid || sub.prepayId || sub.prepayid || "";
+ const callbackScheme = d.callbackScheme || sub.callbackScheme || "trustbridgeapp";
+ const packageValue = d.packageValue || d.package || sub.packageValue || sub.package || "Sign=DYPay";
+ const nonceStr = d.nonceStr || d.noncestr || sub.nonceStr || sub.noncestr || "";
+ const timeStamp = String(d.timeStamp || d.timestamp || sub.timeStamp || sub.timestamp || "");
+ const sign = d.sign || sub.sign || "";
+
+ const orderInfo = {
+ // 驼峰命名(标准 uni.requestPayment / 抖音 SDK)
+ appId,
+ mchId,
+ prepayId,
+ callbackScheme,
+ packageValue,
+ nonceStr,
+ timeStamp,
+ sign,
+ // 小写命名(部分原生桥接 SDK 兼容)
+ appid: appId,
+ partnerid: mchId,
+ mchid: mchId,
+ prepayid: prepayId,
+ package: packageValue,
+ noncestr: nonceStr,
+ timestamp: timeStamp,
+ service: 5,
+ };
+
+ // #ifdef APP-PLUS
+ // 情形 A:如果显式指定为 H5 方式
+ if (
+ type === "h5" &&
+ orderStr &&
+ (orderStr.startsWith("http://") || orderStr.startsWith("https://"))
+ ) {
+ uni.navigateTo({
+ url:
+ `/pages/other_package/payment_processing/payment_processing?link=${encodeURIComponent(
+ orderStr
+ )}&orderNum=` + orderNum,
+ });
+ resolve({ status: "processing" });
+ return;
+ }
+
+ // 情形 B:如果后端直接传入了协议 Scheme 串(如以 snssdk1128://, douyin:// 开头)
+ if (
+ typeof dyPay === "string" ||
+ (dyPay.orderStr && (dyPay.orderStr.startsWith("snssdk") || dyPay.orderStr.startsWith("douyin")))
+ ) {
+ let openSuccess = false;
+
+ // iOS Native.js 尝试
+ if (plus.os.name === "iOS") {
+ try {
+ const UIApplication = plus.ios.importClass("UIApplication");
+ const NSURL = plus.ios.importClass("NSURL");
+ const app = UIApplication.sharedApplication();
+ const nsUrl = NSURL.URLWithString(orderStr);
+ if (app && nsUrl && app.openURL(nsUrl)) {
+ openSuccess = true;
+ }
+ } catch (nativeErr) {
+ console.warn("iOS Native.js openURL 尝试:", nativeErr);
+ }
+ }
+
+ // Android Native.js 尝试
+ if (plus.os.name === "Android") {
+ try {
+ const Intent = plus.android.importClass("android.content.Intent");
+ const Uri = plus.android.importClass("android.net.Uri");
+ const main = plus.android.runtimeMainActivity();
+ const intent = new Intent(Intent.ACTION_VIEW, Uri.parse(orderStr));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ main.startActivity(intent);
+ openSuccess = true;
+ } catch (androidErr) {
+ console.warn("Android Native.js Intent 尝试:", androidErr);
+ }
+ }
+
+ if (!openSuccess) {
+ plus.runtime.openURL(
+ orderStr,
+ () => {
+ if (orderNum) {
+ uni.navigateTo({
+ url:
+ `/pages/other_package/payment_processing/payment_processing?orderNum=` +
+ orderNum,
+ });
+ }
+ resolve({ status: "invoked" });
+ },
+ (err) => {
+ console.error("拉起抖音客户端失败:", err);
+ const isSimulator =
+ plus.navigator && plus.navigator.isSimulator
+ ? plus.navigator.isSimulator()
+ : false;
+ let tip = "未检测到抖音客户端或拉起失败,请确认是否已安装抖音客户端";
+ if (isSimulator) {
+ tip = "iOS模拟器无法打开第三方应用Scheme,请在安装了抖音的真机上测试";
+ } else if (err && err.code === -3) {
+ tip = "未检测到抖音应用或当前基座未配置Scheme白名单,请确认真机已安装抖音";
+ }
+ uni.showToast({
+ title: tip,
+ icon: "none",
+ duration: 3000,
+ });
+ jumpWayError();
+ reject(err);
+ }
+ );
+ } else {
+ if (orderNum) {
+ uni.navigateTo({
+ url:
+ `/pages/other_package/payment_processing/payment_processing?orderNum=` +
+ orderNum,
+ });
+ }
+ resolve({ status: "invoked" });
+ }
+ return;
+ }
+
+ // 情形 C:使用 uni.requestPayment 调起原生支付,失败时通过生成 Scheme 降级拉起
+ uni.requestPayment({
+ provider: "toutiao",
+ orderInfo: orderInfo,
+ service: 5,
+ success: function (res) {
+ console.log("抖音支付成功:", res);
+ payFeedbackFun(orderId, orderNum, 1);
+ jumpWayOk();
+ resolve(res);
+ },
+ fail: function (err) {
+ console.error("uni.requestPayment 抖音支付失败/不兼容,尝试 Scheme 降级拉起:", err);
+ if (orderStr) {
+ plus.runtime.openURL(
+ orderStr,
+ () => {
+ if (orderNum) {
+ uni.navigateTo({
+ url:
+ `/pages/other_package/payment_processing/payment_processing?orderNum=` +
+ orderNum,
+ });
+ }
+ resolve({ status: "invoked_fallback" });
+ },
+ (openErr) => {
+ console.error("openURL 降级失败:", openErr);
+ jumpWayError();
+ reject(err);
+ }
+ );
+ } else {
+ jumpWayError();
+ reject(err);
+ }
+ },
+ });
+ return;
+ // #endif
+
+ // 兜底处理 (H5 及非 App 环境)
+ if (orderStr) {
+ uni.navigateTo({
+ url:
+ `/pages/other_package/payment_processing/payment_processing?link=${encodeURIComponent(
+ orderStr
+ )}&orderNum=` + orderNum,
+ });
+ resolve({ status: "processing" });
+ } else {
+ uni.showToast({
+ title: "抖音支付暂仅支持在 App 端调用",
+ icon: "none",
+ });
+ jumpWayError();
+ reject(new Error("抖音支付暂仅支持在 App 端调用"));
+ }
+ });
+}
+
+
+// 导出别名方便不同调用习惯
+export const dyPayFun = douyinPayFun;
+export const appDyPayFun = douyinPayFun;
+export const appDouyinPayFun = douyinPayFun;
+
// 跳转到支付成功页面
export function jumpWayOk() {
uni.navigateTo({