sunshibo hai 1 día
pai
achega
cd171c2164

+ 23 - 30
pages/checkout/index.vue

@@ -134,6 +134,7 @@ import { go } from '@/utils/utils.js';
 import { getFileUrl } from '@/utils/file.js';
 import { useUserStore } from '@/store/user.js';
 import * as diningApi from '@/api/dining.js';
+import { normalizeUserCouponListItem } from '@/utils/couponNormalize.js';
 import SelectCouponModal from '@/pages/orderFood/components/SelectCouponModal.vue';
 
 const orderId = ref('');
@@ -230,30 +231,6 @@ const couponDisplayText = computed(() => {
   return o.couponName || '已使用优惠券';
 });
 
-// 规范化优惠券项(供 SelectCouponModal 使用)
-function normalizeCouponItem(item) {
-  if (!item || typeof item !== 'object') return null;
-  const raw = item;
-  const couponType = Number(raw.couponType) || 0;
-  const nominalValue = Number(raw.nominalValue ?? raw.amount ?? 0) || 0;
-  const discountRate = ((Number(raw.discountRate) || 0) / 10) || 0;
-  const minAmount = Number(raw.minimumSpendingAmount ?? raw.minAmount ?? 0) || 0;
-  let amountDisplay = nominalValue + '元';
-  if (couponType === 1) amountDisplay = nominalValue + '元';
-  else if (couponType === 2 && discountRate > 0) amountDisplay = (discountRate % 1 === 0 ? discountRate : discountRate.toFixed(1)) + '折';
-  return {
-    id: raw.userCouponId ?? raw.id ?? raw.couponId ?? '',
-    couponId: raw.couponId ?? raw.id ?? raw.userCouponId ?? '',
-    amount: nominalValue,
-    minAmount,
-    name: raw.name ?? raw.title ?? raw.couponName ?? '',
-    expireDate: raw.expirationTime ?? raw.endGetDate ?? raw.expireDate ?? '',
-    couponType,
-    discountRate,
-    amountDisplay
-  };
-}
-
 // 点击优惠券行:打开选择弹窗
 const onCouponRowClick = () => {
   openCouponModal();
@@ -268,9 +245,16 @@ const openCouponModal = async () => {
     return;
   }
   try {
-    const res = await diningApi.GetUserOwnedCouponList({ storeId });
+    const res = await diningApi.GetUserCouponList({
+      storeId,
+      tabType: 0,
+      page: 1,
+      size: 20
+    });
     const list = Array.isArray(res) ? res : (res?.data ?? res?.records ?? res?.list ?? []);
-    const normalized = (Array.isArray(list) ? list : []).map(normalizeCouponItem).filter(Boolean);
+    const normalized = (Array.isArray(list) ? list : [])
+      .map((item) => normalizeUserCouponListItem(item, 0))
+      .filter(Boolean);
     couponList.value = normalized;
     couponModalOpen.value = true;
   } catch (err) {
@@ -291,19 +275,28 @@ const handleCouponSelect = ({ coupon, selectedId }) => {
     orderInfo.value.discountRate = null;
     orderInfo.value.nominalValue = null;
   } else if (coupon) {
+    const couponType = Number(coupon.couponType) || 0;
     orderInfo.value.couponName = coupon.name ?? '';
-    orderInfo.value.couponType = Number(coupon.couponType) ?? null;
+    orderInfo.value.couponType = couponType || null;
     orderInfo.value.discountRate = coupon.discountRate != null ? coupon.discountRate : null;
-    orderInfo.value.nominalValue = coupon.amount != null ? coupon.amount : null;
+    const nvRaw = coupon.nominalValue;
+    if (nvRaw != null && nvRaw !== '') {
+      const nv = Number(nvRaw);
+      orderInfo.value.nominalValue = Number.isFinite(nv) ? nv : null;
+    } else if (couponType === 1) {
+      orderInfo.value.nominalValue = Number(coupon.amount) || null;
+    } else {
+      orderInfo.value.nominalValue = null;
+    }
     const food = foodSubtotalForDisplay.value;
     const fee = Number(orderInfo.value.serviceFee) || 0;
     const baseForDiscount = food + fee;
-    const couponType = Number(coupon.couponType) || 0;
     if (couponType === 2 && coupon.discountRate != null && baseForDiscount > 0) {
       const rate = Number(coupon.discountRate) || 0;
       orderInfo.value.discountAmount = Math.round(baseForDiscount * (1 - rate / 10) * 100) / 100;
     } else {
-      orderInfo.value.discountAmount = Number(coupon.amount) || 0;
+      orderInfo.value.discountAmount =
+        Number(coupon.nominalValue) || Number(coupon.amount) || 0;
     }
   }
   updateCheckoutPayAmount();

+ 29 - 9
pages/coupon/components/RulesModal.vue

@@ -114,19 +114,39 @@ const conditionLine = computed(() => {
 });
 
 const expireLine = computed(() => {
-  const d = props.couponData?.expireDate;
-  if (!d) return '';
-  return `${d} 到期`;
+  const c = props.couponData;
+  if (Number(c?.longTermValid) === 1) return '长期有效';
+  const d = c?.expirationTime ?? c?.expireDate;
+  if (d == null || String(d).trim() === '') return '';
+  return `${String(d).trim()}到期`;
 });
 
-const validityText = computed(() => {
-  const s = props.couponData?.specifiedDay;
-  if (s == null || s === '') return '—';
+/** 从 specifiedDay / validDays 解析天数,用于「领取后*天有效」 */
+function parseReceiveValidDays(c) {
+  const s = c?.specifiedDay ?? c?.validDays;
+  if (s == null || String(s).trim() === '') return null;
   const str = String(s).trim();
-  if (str.includes('天')) return str;
+  const m1 = str.match(/领取后\s*(\d+)\s*天/);
+  if (m1) {
+    const n = Number(m1[1]);
+    return !Number.isNaN(n) && n > 0 ? n : null;
+  }
+  const m2 = str.match(/(\d+)\s*天/);
+  if (m2) {
+    const n = Number(m2[1]);
+    return !Number.isNaN(n) && n > 0 ? n : null;
+  }
   const n = Number(str);
-  if (!Number.isNaN(n) && str !== '') return `${n}天`;
-  return str;
+  if (!Number.isNaN(n) && n > 0) return n;
+  return null;
+}
+
+const validityText = computed(() => {
+  const c = props.couponData;
+  if (Number(c?.longTermValid) === 1) return '长期有效';
+  const days = parseReceiveValidDays(c);
+  if (days != null) return `领取后${days}天有效`;
+  return '—';
 });
 
 const supplementText = computed(() => {

+ 12 - 33
pages/coupon/index.vue

@@ -70,7 +70,7 @@
                   使用规则
                   <text class="arrow">›</text>
                 </view>
-                <text class="coupon-expire">{{ coupon.expireDate ? coupon.expireDate + '到期' : '' }}</text>
+                <text class="coupon-expire">{{ formatCouponExpireLine(coupon) }}</text>
               </view>
             </view>
           </view>
@@ -95,6 +95,7 @@
 import { onShow } from "@dcloudio/uni-app";
 import { ref, computed } from "vue";
 import { getFileUrl } from "@/utils/file.js";
+import { normalizeUserCouponListItem } from "@/utils/couponNormalize.js";
 import RulesModal from "./components/RulesModal.vue";
 import * as diningApi from "@/api/dining.js";
 
@@ -139,6 +140,8 @@ const selectedCoupon = ref({
   minAmount: 0,
   name: '',
   expireDate: '',
+  expirationTime: '',
+  longTermValid: 0,
   specifiedDay: '',
   supplementaryInstruction: '',
   conditionText: '',
@@ -150,39 +153,8 @@ const selectedCoupon = ref({
 const couponList = ref([]);
 const loading = ref(false);
 
-// 规范化接口返回的优惠券项
 function normalizeCouponItem(raw) {
-  if (!raw || typeof raw !== 'object') return null;
-  const couponType = Number(raw.couponType) ?? 1;
-  const nominalValue = Number(raw.nominalValue ?? raw.amount ?? 0) || 0;
-  const discountRate = ((Number(raw.discountRate ?? 0) || 0) / 10) || 0;
-  const minAmount = Number(raw.minimumSpendingAmount ?? raw.minAmount ?? 0) || 0;
-  let amount = nominalValue;
-  let amountUnit = '元';
-  let conditionText = minAmount > 0 ? `满${minAmount}可用` : '无门槛';
-  if (couponType === 2 && discountRate > 0) {
-    amount = discountRate;
-    amountUnit = '折';
-    conditionText = minAmount > 0 ? `满${minAmount}可用` : '无门槛';
-  }
-  return {
-    id: raw.id ?? raw.userCouponId ?? raw.couponId ?? '',
-    amount,
-    amountUnit,
-    minAmount,
-    name: raw.name ?? raw.title ?? raw.couponName ?? '',
-    expireDate: raw.expirationTime ?? raw.endGetDate ?? raw.expireDate ?? '',
-    status: currentTab.value,
-    couponType,
-    nominalValue,
-    discountRate,
-    conditionText,
-    specifiedDay: raw.specifiedDay ?? raw.validDays ?? '',
-    supplementaryInstruction: raw.supplementaryInstruction ?? raw.description ?? '',
-    qrCodeUrl: raw.qrCodeUrl ?? raw.qrcodeUrl ?? raw.qrUrl ?? '',
-    verificationCode:
-      raw.verificationCode ?? raw.verifyCode ?? raw.couponCode ?? raw.pickUpCode ?? ''
-  };
+  return normalizeUserCouponListItem(raw, currentTab.value);
 }
 
 // 列表:主 Tab 数据 + 类型筛选(全部 / 折扣券 / 满减券)
@@ -219,6 +191,13 @@ const fetchCouponList = async () => {
   }
 };
 
+/** longTermValid=1 显示长期有效;=0 显示 expirationTime + 到期 */
+function formatCouponExpireLine(coupon) {
+  if (Number(coupon?.longTermValid) === 1) return '长期有效';
+  const t = String(coupon?.expirationTime ?? coupon?.expireDate ?? '').trim();
+  return t ? `${t}到期` : '';
+}
+
 // 获取优惠券卡片样式类
 const getCouponCardClass = (coupon) => {
   if (coupon?.status === 2) return 'coupon-card--used';

+ 17 - 15
pages/orderFood/components/CouponModal.vue

@@ -11,20 +11,24 @@
 
       <!-- 优惠券列表 -->
       <scroll-view class="coupon-modal__list" scroll-y v-if="couponList.length > 0">
-        <view v-for="(coupon, index) in couponList" :key="coupon.id || index" class="coupon-card">
+        <view
+          v-for="(coupon, index) in couponList"
+          :key="'coupon-row-' + index + '-' + String(coupon.id ?? '')"
+          class="coupon-card"
+        >
           <!-- 左侧金额信息:数字大号,“元/折”小号 -->
           <view class="coupon-card__left">
             <view class="amount-row">
-              <text class="amount-num">{{ amountNum(coupon) }}</text>
-              <text class="amount-unit">{{ amountUnit(coupon) }}</text>
+              <text class="amount-num">{{ coupon.amount }}</text>
+              <text class="amount-unit">{{ coupon.amountUnit || '元' }}</text>
             </view>
-            <text class="condition-text">{{ (coupon.minAmount && Number(coupon.minAmount) > 0) ? ('满' + coupon.minAmount + '可用') : '无门槛' }}</text>
+            <text class="condition-text">{{ coupon.conditionText || ((coupon.minAmount && Number(coupon.minAmount) > 0) ? ('满' + coupon.minAmount + '可用') : '无门槛') }}</text>
           </view>
 
           <!-- 中间信息 -->
           <view class="coupon-card__center">
             <text class="name-text">{{ coupon.name }}</text>
-            <text class="expire-text">{{ formatExpireDate(coupon.expireDate) }}到期</text>
+            <text class="expire-text">{{ formatExpireLine(coupon) }}</text>
           </view>
         </view>
       </scroll-view>
@@ -63,6 +67,14 @@ const getOpen = computed({
   set: (val) => emit('update:open', val)
 });
 
+// 与券包页一致:longTermValid=1 长期有效;否则展示 expirationTime / expireDate
+const formatExpireLine = (coupon) => {
+  if (Number(coupon?.longTermValid) === 1) return '长期有效';
+  const d = coupon?.expirationTime ?? coupon?.expireDate;
+  const f = formatExpireDate(d);
+  return f ? `${f}到期` : '';
+};
+
 // 格式化到期日期
 const formatExpireDate = (date) => {
   if (!date) return '';
@@ -83,16 +95,6 @@ const formatExpireDate = (date) => {
   return date;
 };
 
-// 金额数字与单位分开展示,单位用小字号
-const amountNum = (coupon) => {
-  const str = coupon.amountDisplay || (coupon.amount + '元');
-  return str.length > 0 ? str.slice(0, -1) : '';
-};
-const amountUnit = (coupon) => {
-  const str = coupon.amountDisplay || (coupon.amount + '元');
-  return str.length > 0 ? str.slice(-1) : '元';
-};
-
 // 处理关闭
 const handleClose = () => {
   getOpen.value = false;

+ 14 - 17
pages/orderFood/components/SelectCouponModal.vue

@@ -11,9 +11,9 @@
 
       <!-- 优惠券列表:仅查看模式下不展示选中态、点击不触发选择 -->
       <scroll-view class="select-coupon-modal__list" scroll-y v-if="couponList.length > 0">
-        <view 
-          v-for="(coupon, index) in couponList" 
-          :key="coupon.id || index" 
+        <view
+          v-for="(coupon, index) in couponList"
+          :key="'coupon-row-' + index + '-' + String(coupon.id ?? '')"
           class="coupon-card"
           :class="{ 'coupon-card--view-only': viewOnly }"
           @click="handleCardClick(coupon, index)"
@@ -21,16 +21,16 @@
           <!-- 左侧金额信息:数字大号,“元/折”小号 -->
           <view class="coupon-card__left">
             <view class="amount-row">
-              <text class="amount-num">{{ amountNum(coupon) }}</text>
-              <text class="amount-unit">{{ amountUnit(coupon) }}</text>
+              <text class="amount-num">{{ coupon.amount }}</text>
+              <text class="amount-unit">{{ coupon.amountUnit || '元' }}</text>
             </view>
-            <text class="condition-text">{{ (coupon.minAmount && Number(coupon.minAmount) > 0) ? ('满' + coupon.minAmount + '可用') : '无门槛' }}</text>
+            <text class="condition-text">{{ coupon.conditionText || ((coupon.minAmount && Number(coupon.minAmount) > 0) ? ('满' + coupon.minAmount + '可用') : '无门槛') }}</text>
           </view>
 
           <!-- 中间信息 -->
           <view class="coupon-card__center">
             <text class="name-text">{{ coupon.name }}</text>
-            <text class="expire-text">{{ formatExpireDate(coupon.expireDate) }}到期</text>
+            <text class="expire-text">{{ formatExpireLine(coupon) }}</text>
           </view>
 
           <!-- 右侧单选(仅在选择模式下显示) -->
@@ -80,6 +80,13 @@ const getOpen = computed({
   set: (val) => emit('update:open', val)
 });
 
+const formatExpireLine = (coupon) => {
+  if (Number(coupon?.longTermValid) === 1) return '长期有效';
+  const d = coupon?.expirationTime ?? coupon?.expireDate;
+  const f = formatExpireDate(d);
+  return f ? `${f}到期` : '';
+};
+
 // 格式化到期日期
 const formatExpireDate = (date) => {
   if (!date) return '';
@@ -100,16 +107,6 @@ const formatExpireDate = (date) => {
   return date;
 };
 
-// 金额数字部分(用于与“元/折”分开展示,单位用小字号)
-const amountNum = (coupon) => {
-  const str = coupon.amountDisplay || (coupon.amount + '元');
-  return str.length > 0 ? str.slice(0, -1) : '';
-};
-const amountUnit = (coupon) => {
-  const str = coupon.amountDisplay || (coupon.amount + '元');
-  return str.length > 0 ? str.slice(-1) : '元';
-};
-
 // 判断优惠券是否被选中(统一转字符串比较,避免 number/string 不一致)
 const isSelected = (coupon) => {
   if (props.selectedCouponId == null || props.selectedCouponId === '') return false;

+ 20 - 39
pages/orderFood/index.vue

@@ -85,7 +85,8 @@ import CartModal from "./components/CartModal.vue";
 import SelectCouponModal from "./components/SelectCouponModal.vue";
 import { go } from "@/utils/utils.js";
 import { getFileUrl } from "@/utils/file.js";
-import { DiningOrderFood, GetCategoriesWithCuisines, GetStoreDetail, getOrderSseConfig, GetOrderCart, PostOrderCartAdd, PostOrderCartUpdate, PostOrderCartClear, GetUserOwnedCouponList } from "@/api/dining.js";
+import { DiningOrderFood, GetCategoriesWithCuisines, GetStoreDetail, getOrderSseConfig, GetOrderCart, PostOrderCartAdd, PostOrderCartUpdate, PostOrderCartClear, GetUserCouponList } from "@/api/dining.js";
+import { normalizeUserCouponListItem } from "@/utils/couponNormalize.js";
 import { createSSEConnection } from "@/utils/sse.js";
 
 // 商品图片:取第一张,若为逗号分隔字符串则截取逗号前的第一张
@@ -129,7 +130,7 @@ const couponModalOpen = ref(false);
 const cartModalOpen = ref(false);
 const selectCouponModalOpen = ref(false);
 const selectCouponViewOnly = ref(false); // true=左下角仅查看,false=购物车内可选
-const discountAmount = ref(12); // 优惠金额,示例数据
+const discountAmount = ref(0);
 const selectedCouponId = ref(null); // 选中的优惠券ID
 
 // 分类列表(由接口 /store/info/categories 返回后赋值)
@@ -263,40 +264,11 @@ const displayTotalAmount = computed(() => {
   return displayCartList.value.reduce((sum, item) => sum + getItemLinePrice(item), 0);
 });
 
-// 优惠券列表(由接口 /dining/coupon/storeUsableList 返回后赋值
+// 优惠券列表(CouponModal,与券包同源接口数据
 const couponList = ref([]);
-// 门店可用优惠券列表(选择优惠券弹窗用,与 couponList 同步自同一接口)
+// 门店可用优惠券(SelectCouponModal):GET /dining/coupon/getUserCouponList,tabType=0 未使用
 const storeUsableCouponList = ref([]);
 
-// 规范化接口优惠券项为弹窗所需格式(对接 getUserCouponList 返回的 data:id/couponId/userCouponId、name、couponType、discountRate、minimumSpendingAmount、expirationTime 等)
-// couponType 1=满减券显示 nominalValue 为金额,2=折扣券显示 discountRate 为折扣力度
-function normalizeCouponItem(item) {
-  if (!item || typeof item !== 'object') return null;
-  const raw = item;
-  const couponType = Number(raw.couponType) || 0;
-  const nominalValue = Number(raw.nominalValue ?? raw.amount ?? 0) || 0;
-  const discountRate = ((Number(raw.discountRate) || 0) / 10) || 0;
-  const minAmount = Number(raw.minimumSpendingAmount ?? raw.minAmount ?? raw.min_amount ?? 0) || 0;
-  const isReceived = raw.canReceived === false;
-  let amountDisplay = nominalValue + '元';
-  if (couponType === 1) {
-    amountDisplay = nominalValue + '元';
-  } else if (couponType === 2 && discountRate > 0) {
-    amountDisplay = (discountRate % 1 === 0 ? discountRate : discountRate.toFixed(1)) + '折';
-  }
-  return {
-    id: raw.userCouponId ?? raw.id ?? raw.couponId ?? raw.coupon_id ?? '',
-    amount: nominalValue,
-    minAmount,
-    name: raw.name ?? raw.title ?? raw.couponName ?? '',
-    expireDate: raw.expirationTime ?? raw.endGetDate ?? raw.validDate ?? raw.expireDate ?? raw.endTime ?? '',
-    isReceived,
-    couponType,
-    discountRate,
-    amountDisplay
-  };
-}
-
 // 可用的优惠券列表(选择优惠券弹窗用,来自 storeUsableCouponList)
 const availableCoupons = computed(() => storeUsableCouponList.value);
 
@@ -719,15 +691,22 @@ const handleDecrease = (item) => {
   if (food && (food.quantity || 0) > 0) updateFoodQuantity(food, -1);
 };
 
-// 拉取用户优惠券列表(GET /dining/coupon/userOwnedByStore,入参 storeId),列表在 SelectCouponModal 中展示
+// 拉取用户优惠券列表(GET /dining/coupon/getUserCouponList,与券包页一致:storeId、tabType、page、size)
 const fetchUserOwnedCoupons = async () => {
   const storeId = uni.getStorageSync('currentStoreId') || '';
   try {
-    const res = await GetUserOwnedCouponList({ storeId });
-    // 接口返回 { code, data: [...], msg, success },请求层可能只返回 data,故 res 可能为数组
+    const res = await GetUserCouponList({
+      storeId,
+      tabType: 0,
+      page: 1,
+      size: 20
+    });
     const list = Array.isArray(res) ? res : (res?.data ?? res?.records ?? res?.list ?? []);
-    const normalized = (Array.isArray(list) ? list : []).map((item) => normalizeCouponItem(item)).filter(Boolean);
+    const normalized = (Array.isArray(list) ? list : [])
+      .map((item) => normalizeUserCouponListItem(item, 0))
+      .filter(Boolean);
     storeUsableCouponList.value = normalized;
+    couponList.value = normalized;
     return true;
   } catch (err) {
     console.error('获取用户优惠券失败:', err);
@@ -749,7 +728,8 @@ const openSelectCouponModal = async (viewOnly = false) => {
         const first = list[0];
         const firstId = first.id != null && first.id !== '' ? String(first.id) : null;
         selectedCouponId.value = firstId;
-        discountAmount.value = first.amount ?? 0;
+        discountAmount.value =
+          Number(first.nominalValue) || Number(first.amount) || 0;
       } else {
         selectedCouponId.value = null;
         discountAmount.value = 0;
@@ -775,7 +755,8 @@ const handleCouponSelect = ({ coupon, index, selectedId }) => {
   selectedCouponId.value = selectedId != null && selectedId !== '' ? String(selectedId) : null;
   // 根据选中的优惠券更新优惠金额
   if (selectedCouponId.value) {
-    discountAmount.value = coupon.amount ?? 0;
+    discountAmount.value =
+      Number(coupon.nominalValue) || Number(coupon.amount) || 0;
   } else {
     discountAmount.value = 0;
   }

+ 57 - 0
utils/couponNormalize.js

@@ -0,0 +1,57 @@
+/**
+ * 将 getUserCouponList 等接口返回的单条优惠券规范为前端统一结构(与券包页一致)
+ * @param {object} raw 接口原始项
+ * @param {number} [tabStatus=0] 列表 Tab 状态(券包页传入 currentTab)
+ */
+export function normalizeUserCouponListItem(raw, tabStatus = 0) {
+  if (!raw || typeof raw !== 'object') return null;
+  const couponType = Number(raw.couponType) ?? 1;
+  const nominalValue = Number(raw.nominalValue ?? raw.amount ?? 0) || 0;
+  const discountRate = ((Number(raw.discountRate ?? 0) || 0) / 10) || 0;
+  const minAmount = Number(raw.minimumSpendingAmount ?? raw.minAmount ?? 0) || 0;
+  let amount = nominalValue;
+  let amountUnit = '元';
+  let conditionText = minAmount > 0 ? `满${minAmount}可用` : '无门槛';
+  if (couponType === 2 && discountRate > 0) {
+    amount = discountRate;
+    amountUnit = '折';
+    conditionText = minAmount > 0 ? `满${minAmount}可用` : '无门槛';
+  }
+  const expirationTime =
+    raw.expirationTime ?? raw.endGetDate ?? raw.expireDate ?? '';
+  const longTermValid = Number(raw.longTermValid) === 1 ? 1 : 0;
+
+  // id 用于列表选中态与 :key,必须为「用户持有实例」唯一键;勿把模板 couponId 放首位,否则多张相同活动券会 id 重复、出现多选假象
+  const listId =
+    raw.userCouponId ??
+    raw.user_coupon_id ??
+    raw.memberCouponId ??
+    raw.receiveRecordId ??
+    raw.receiveId ??
+    raw.id ??
+    raw.couponId ??
+    '';
+
+  return {
+    id: listId,
+    /** 券模板/业务侧 couponId,与 id(用户券实例)区分,支付等场景按需选用 */
+    couponId: raw.couponId ?? raw.coupon_id ?? '',
+    amount,
+    amountUnit,
+    minAmount,
+    name: raw.name ?? raw.title ?? raw.couponName ?? '',
+    expirationTime,
+    expireDate: expirationTime,
+    longTermValid,
+    status: tabStatus,
+    couponType,
+    nominalValue,
+    discountRate,
+    conditionText,
+    specifiedDay: raw.specifiedDay ?? raw.validDays ?? '',
+    supplementaryInstruction: raw.supplementaryInstruction ?? raw.description ?? '',
+    qrCodeUrl: raw.qrCodeUrl ?? raw.qrcodeUrl ?? raw.qrUrl ?? '',
+    verificationCode:
+      raw.verificationCode ?? raw.verifyCode ?? raw.couponCode ?? raw.pickUpCode ?? ''
+  };
+}