|
|
@@ -15,6 +15,9 @@ import ohos.eventhandler.EventHandler;
|
|
|
import ohos.eventhandler.EventRunner;
|
|
|
import ohos.media.image.PixelMap;
|
|
|
|
|
|
+import java.text.DecimalFormat;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
|
|
/**
|
|
|
@@ -29,34 +32,37 @@ public class QRCodeScanDialog {
|
|
|
private OnPayResultListener payResultListener;
|
|
|
private OnCloseListener closeListener;
|
|
|
private String paySceneCode = "ALI_PCRE"; // 默认支付宝扫码支付
|
|
|
-
|
|
|
+
|
|
|
// UI组件
|
|
|
private Image imgQRCode;
|
|
|
private Text txtTitle;
|
|
|
private Text txtLoading;
|
|
|
private Button btnCancelPay;
|
|
|
-
|
|
|
+
|
|
|
// 倒计时相关
|
|
|
private static final int COUNTDOWN_SECONDS = 180; // 3分钟 = 180秒
|
|
|
private int remainingSeconds = COUNTDOWN_SECONDS;
|
|
|
private EventHandler countdownHandler;
|
|
|
private boolean isCountdownRunning = false;
|
|
|
-
|
|
|
+
|
|
|
// 支付查询相关
|
|
|
private String payNo;
|
|
|
private boolean isPollingPayStatus = false;
|
|
|
private static final int POLL_INTERVAL = 3000; // 轮询间隔3秒
|
|
|
private boolean isClosedByPayResult = false; // 是否由支付结果关闭的
|
|
|
-
|
|
|
+ private Runnable startPollingTask; // 延迟启动轮询的任务
|
|
|
+
|
|
|
/**
|
|
|
* 支付结果回调接口
|
|
|
*/
|
|
|
public interface OnPayResultListener {
|
|
|
void onPaySuccess(String payNo);
|
|
|
+
|
|
|
void onPayFailed(String errorMsg);
|
|
|
+
|
|
|
void onPayTimeout();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 弹框关闭回调接口
|
|
|
* 用于在弹框关闭时触发ChargeComponent的刷新
|
|
|
@@ -64,7 +70,7 @@ public class QRCodeScanDialog {
|
|
|
public interface OnCloseListener {
|
|
|
void onClose();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 构造函数
|
|
|
*/
|
|
|
@@ -72,53 +78,54 @@ public class QRCodeScanDialog {
|
|
|
this.context = context;
|
|
|
initDialog();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 设置费用信息
|
|
|
*/
|
|
|
public void setFeeInfo(ParkingFeeInfo feeInfo) {
|
|
|
this.feeInfo = feeInfo;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 设置支付结果监听器
|
|
|
*/
|
|
|
public void setOnPayResultListener(OnPayResultListener listener) {
|
|
|
this.payResultListener = listener;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 设置弹框关闭监听器
|
|
|
*/
|
|
|
public void setOnCloseListener(OnCloseListener listener) {
|
|
|
this.closeListener = listener;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 设置支付场景代码
|
|
|
+ *
|
|
|
* @param paySceneCode 支付场景代码(如:WX_NA-微信Native支付,ALI_PCRE-支付宝扫码支付)
|
|
|
*/
|
|
|
public void setPaySceneCode(String paySceneCode) {
|
|
|
this.paySceneCode = paySceneCode;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 初始化对话框
|
|
|
*/
|
|
|
private void initDialog() {
|
|
|
// 创建 CommonDialog
|
|
|
dialog = new CommonDialog(context);
|
|
|
-
|
|
|
+
|
|
|
// 加载自定义布局
|
|
|
dialogRootView = LayoutScatter.getInstance(context)
|
|
|
.parse(ResourceTable.Layout_layout_qrcode_scan_dialog, null, false);
|
|
|
-
|
|
|
+
|
|
|
// 获取UI组件
|
|
|
imgQRCode = (Image) dialogRootView.findComponentById(ResourceTable.Id_img_qrcode);
|
|
|
txtTitle = (Text) dialogRootView.findComponentById(ResourceTable.Id_txt_title);
|
|
|
txtLoading = (Text) dialogRootView.findComponentById(ResourceTable.Id_txt_loading);
|
|
|
btnCancelPay = (Button) dialogRootView.findComponentById(ResourceTable.Id_btn_cancel_pay);
|
|
|
-
|
|
|
+
|
|
|
// 设置关闭按钮点击事件
|
|
|
Image btnClose = (Image) dialogRootView.findComponentById(ResourceTable.Id_btn_close_scan);
|
|
|
if (btnClose != null) {
|
|
|
@@ -127,7 +134,7 @@ public class QRCodeScanDialog {
|
|
|
hide();
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 设置取消支付按钮点击事件
|
|
|
if (btnCancelPay != null) {
|
|
|
btnCancelPay.setClickedListener(component -> {
|
|
|
@@ -135,20 +142,20 @@ public class QRCodeScanDialog {
|
|
|
hide();
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 设置对话框内容
|
|
|
dialog.setContentCustomComponent(dialogRootView);
|
|
|
-
|
|
|
+
|
|
|
// 设置对话框属性
|
|
|
dialog.setTransparent(true); // 设置透明背景
|
|
|
dialog.setSize(ComponentContainer.LayoutConfig.MATCH_PARENT,
|
|
|
ComponentContainer.LayoutConfig.MATCH_PARENT);
|
|
|
dialog.setAlignment(LayoutAlignment.CENTER);
|
|
|
-
|
|
|
+
|
|
|
// 初始化倒计时Handler
|
|
|
countdownHandler = new EventHandler(EventRunner.getMainEventRunner());
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 调用支付接口生成订单和二维码
|
|
|
*/
|
|
|
@@ -157,10 +164,34 @@ public class QRCodeScanDialog {
|
|
|
showError("支付信息缺失");
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 显示加载提示
|
|
|
showLoading(true);
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+ ArrayList<String> subOrderIdList = new ArrayList<>();
|
|
|
+ String trueOrderId = "";
|
|
|
+ //车道码有临停和欠费补缴逻辑
|
|
|
+ if (feeInfo.getActualAmount() > 0 && (feeInfo.getFollowList() == null || feeInfo.getFollowList().isEmpty())) {
|
|
|
+ trueOrderId = feeInfo.getOrderId();
|
|
|
+ } else if (feeInfo.getActualAmount() == 0 && feeInfo.getFollowList()!=null && feeInfo.getFollowList().size() == 1) {
|
|
|
+ //如果没有停车费只有一笔欠费,取followId
|
|
|
+ trueOrderId = feeInfo.getFollowList().get(0).getFollowId();
|
|
|
+ } else {
|
|
|
+ //多笔缴费取mainOrderId
|
|
|
+ trueOrderId = feeInfo.getMainOrderId();
|
|
|
+ if (feeInfo.getActualAmount() > 0) {
|
|
|
+ subOrderIdList.add(feeInfo.getOrderId());
|
|
|
+ for (com.fujica.abk.model.out.ArrearageOrder item : feeInfo.getFollowList()) {
|
|
|
+ subOrderIdList.add(item.getFollowId());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ for (com.fujica.abk.model.out.ArrearageOrder item : feeInfo.getFollowList()) {
|
|
|
+ subOrderIdList.add(item.getFollowId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 构建支付请求参数
|
|
|
PayRequest request = new PayRequest();
|
|
|
request.setActualAmount(feeInfo.getActualAmount() != null ? feeInfo.getActualAmount() : 1);
|
|
|
@@ -168,41 +199,45 @@ public class QRCodeScanDialog {
|
|
|
request.setDiscountAmount(feeInfo.getDiscountAmount() != null ? feeInfo.getDiscountAmount() : 0);
|
|
|
request.setDoubleConfirm(true);
|
|
|
request.setGoodsName(feeInfo.getPlateNo() != null ? feeInfo.getPlateNo() : "停车费");
|
|
|
- request.setOrderId(feeInfo.getOrderId());
|
|
|
+ request.setOrderId(trueOrderId);
|
|
|
+ request.setSubOrderIdList(subOrderIdList);
|
|
|
request.setParkId(feeInfo.getParkId());
|
|
|
request.setParkName(feeInfo.getParkName());
|
|
|
request.setPaySceneCode(paySceneCode); // 使用设置的支付场景代码
|
|
|
request.setPaySourceCode("HW_CAR"); // 华为车机
|
|
|
request.setProjectNo(feeInfo.getProjectNo()); // 项目编号
|
|
|
- request.setTotalAmount(feeInfo.getTotalAmount() != null ? feeInfo.getTotalAmount() : 1);
|
|
|
-
|
|
|
+ request.setTotalAmount(calculateActualAmount());
|
|
|
+
|
|
|
Log.info("发起支付请求: orderId=" + request.getOrderId());
|
|
|
-
|
|
|
+
|
|
|
// 调用支付接口
|
|
|
CompletableFuture<R<PayResponse>> future = pay.payByUnify(context, request);
|
|
|
future.thenAccept(result -> {
|
|
|
context.getUITaskDispatcher().asyncDispatch(() -> {
|
|
|
showLoading(false);
|
|
|
-
|
|
|
+
|
|
|
if (result != null && result.isSuccess() && result.getData() != null) {
|
|
|
PayResponse payData = result.getData();
|
|
|
String qrCode = payData.getQrCode();
|
|
|
payNo = payData.getOutTradeNo();
|
|
|
-
|
|
|
+
|
|
|
if (qrCode != null && !qrCode.isEmpty()) {
|
|
|
// 生成并显示二维码
|
|
|
displayQRCode(qrCode);
|
|
|
-
|
|
|
+
|
|
|
// 开始倒计时
|
|
|
startCountdown();
|
|
|
-
|
|
|
- // 开始轮询支付状态
|
|
|
- startPaymentPolling();
|
|
|
+
|
|
|
+ // 延迟10秒后开始轮询支付状态
|
|
|
+ startPollingTask = () -> {
|
|
|
+ startPaymentPolling();
|
|
|
+ };
|
|
|
+ countdownHandler.postTask(startPollingTask, 10000); // 10000毫秒 = 10秒
|
|
|
} else {
|
|
|
showError("未获取到支付二维码");
|
|
|
}
|
|
|
} else {
|
|
|
- String errorMsg = result != null && result.getMsg() != null ?
|
|
|
+ String errorMsg = result != null && result.getMsg() != null ?
|
|
|
result.getMsg() : "生成支付订单失败";
|
|
|
showError(errorMsg);
|
|
|
}
|
|
|
@@ -216,7 +251,7 @@ public class QRCodeScanDialog {
|
|
|
return null;
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 显示二维码
|
|
|
*/
|
|
|
@@ -225,7 +260,7 @@ public class QRCodeScanDialog {
|
|
|
// 生成二维码图片
|
|
|
int qrCodeSize = 800; // 二维码像素大小
|
|
|
PixelMap qrCodePixelMap = QRCodeUtil.generateQRCode(qrCodeContent, qrCodeSize);
|
|
|
-
|
|
|
+
|
|
|
if (qrCodePixelMap != null && imgQRCode != null) {
|
|
|
imgQRCode.setPixelMap(qrCodePixelMap);
|
|
|
Log.info("二维码生成成功");
|
|
|
@@ -239,7 +274,7 @@ public class QRCodeScanDialog {
|
|
|
showError("二维码显示失败");
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 开始倒计时
|
|
|
*/
|
|
|
@@ -247,15 +282,15 @@ public class QRCodeScanDialog {
|
|
|
if (isCountdownRunning) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
isCountdownRunning = true;
|
|
|
remainingSeconds = COUNTDOWN_SECONDS;
|
|
|
updateCountdownText();
|
|
|
-
|
|
|
+
|
|
|
// 使用Handler进行倒计时
|
|
|
countdownRunnable.run();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 停止倒计时
|
|
|
*/
|
|
|
@@ -263,9 +298,13 @@ public class QRCodeScanDialog {
|
|
|
isCountdownRunning = false;
|
|
|
if (countdownHandler != null) {
|
|
|
countdownHandler.removeTask(countdownRunnable);
|
|
|
+ // 移除延迟启动轮询的任务
|
|
|
+ if (startPollingTask != null) {
|
|
|
+ countdownHandler.removeTask(startPollingTask);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 倒计时Runnable
|
|
|
*/
|
|
|
@@ -275,10 +314,10 @@ public class QRCodeScanDialog {
|
|
|
if (!isCountdownRunning) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
remainingSeconds--;
|
|
|
updateCountdownText();
|
|
|
-
|
|
|
+
|
|
|
if (remainingSeconds <= 0) {
|
|
|
// 倒计时结束
|
|
|
onCountdownFinished();
|
|
|
@@ -288,7 +327,7 @@ public class QRCodeScanDialog {
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 更新倒计时文本
|
|
|
*/
|
|
|
@@ -304,23 +343,23 @@ public class QRCodeScanDialog {
|
|
|
txtTitle.setText(titleText);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 倒计时结束处理
|
|
|
*/
|
|
|
private void onCountdownFinished() {
|
|
|
stopCountdown();
|
|
|
stopPaymentPolling();
|
|
|
-
|
|
|
+
|
|
|
// 标记为由支付结果关闭
|
|
|
isClosedByPayResult = true;
|
|
|
hide();
|
|
|
-
|
|
|
+
|
|
|
// 回调支付超时
|
|
|
if (payResultListener != null) {
|
|
|
payResultListener.onPayTimeout();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 显示支付失败弹窗(超时)
|
|
|
PayFailDialog failDialog = new PayFailDialog(context);
|
|
|
failDialog.setErrorMsg("支付超时,请重新支付");
|
|
|
@@ -330,11 +369,11 @@ public class QRCodeScanDialog {
|
|
|
closeListener.onClose();
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 显示失败对话框
|
|
|
failDialog.show();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 开始轮询支付状态
|
|
|
*/
|
|
|
@@ -343,18 +382,18 @@ public class QRCodeScanDialog {
|
|
|
Log.error("payNo为空,无法轮询支付状态");
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
isPollingPayStatus = true;
|
|
|
-
|
|
|
+
|
|
|
// 创建轮询任务
|
|
|
new Thread(() -> {
|
|
|
while (isPollingPayStatus && remainingSeconds > 0) {
|
|
|
try {
|
|
|
Thread.sleep(POLL_INTERVAL);
|
|
|
-
|
|
|
+
|
|
|
// 查询支付状态
|
|
|
checkPaymentStatus();
|
|
|
-
|
|
|
+
|
|
|
} catch (InterruptedException e) {
|
|
|
Log.error("支付状态查询线程中断: " + e.getMessage());
|
|
|
break;
|
|
|
@@ -362,14 +401,36 @@ public class QRCodeScanDialog {
|
|
|
}
|
|
|
}).start();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 停止轮询支付状态
|
|
|
*/
|
|
|
private void stopPaymentPolling() {
|
|
|
isPollingPayStatus = false;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算实际支付金额
|
|
|
+ * 参考 pay.vue 中的 actualAmount computed 属性
|
|
|
+ */
|
|
|
+ private int calculateActualAmount() {
|
|
|
+// DecimalFormat df = new DecimalFormat("0.00");
|
|
|
+ int total = 0;
|
|
|
+
|
|
|
+ // 实付金额
|
|
|
+ if (feeInfo.getActualAmount() != null) {
|
|
|
+ total = feeInfo.getActualAmount();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加上历史欠费(如果有)
|
|
|
+ if (feeInfo.getFollowSum() != null && feeInfo.getFollowSum() > 0) {
|
|
|
+ total += feeInfo.getFollowSum();
|
|
|
+ }
|
|
|
+ return total;
|
|
|
+// return df.format(total);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 查询支付状态
|
|
|
*/
|
|
|
@@ -377,7 +438,7 @@ public class QRCodeScanDialog {
|
|
|
if (payNo == null || payNo.isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
CompletableFuture<R<PayStatusResponse>> future = pay.queryPayStatus(context, payNo);
|
|
|
future.thenAccept(result -> {
|
|
|
// 检查是否应该继续处理响应:
|
|
|
@@ -388,7 +449,7 @@ public class QRCodeScanDialog {
|
|
|
Log.info("支付状态查询响应被忽略:对话框已关闭或轮询已停止");
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (result != null && result.isSuccess() && result.getData() != null) {
|
|
|
Integer status = result.getData().getPayStatus();
|
|
|
|
|
|
@@ -413,7 +474,7 @@ public class QRCodeScanDialog {
|
|
|
return null;
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 支付成功处理
|
|
|
*/
|
|
|
@@ -421,11 +482,11 @@ public class QRCodeScanDialog {
|
|
|
context.getUITaskDispatcher().asyncDispatch(() -> {
|
|
|
stopCountdown();
|
|
|
stopPaymentPolling();
|
|
|
-
|
|
|
+
|
|
|
// 标记为由支付结果关闭
|
|
|
isClosedByPayResult = true;
|
|
|
hide();
|
|
|
-
|
|
|
+
|
|
|
// 显示支付成功弹窗
|
|
|
PaySuccessDialog successDialog = new PaySuccessDialog(context);
|
|
|
successDialog.setPayAmount(feeInfo != null ? feeInfo.getActualAmount() : 0);
|
|
|
@@ -435,28 +496,28 @@ public class QRCodeScanDialog {
|
|
|
closeListener.onClose();
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 显示成功对话框
|
|
|
successDialog.show();
|
|
|
-
|
|
|
+
|
|
|
// 回调支付成功
|
|
|
if (payResultListener != null) {
|
|
|
payResultListener.onPaySuccess(payNo);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 支付失败处理
|
|
|
*/
|
|
|
private void onPaymentFailed(String errorMsg) {
|
|
|
stopCountdown();
|
|
|
stopPaymentPolling();
|
|
|
-
|
|
|
+
|
|
|
// 标记为由支付结果关闭
|
|
|
isClosedByPayResult = true;
|
|
|
hide();
|
|
|
-
|
|
|
+
|
|
|
// 显示支付失败弹窗
|
|
|
PayFailDialog failDialog = new PayFailDialog(context);
|
|
|
failDialog.setErrorMsg(errorMsg);
|
|
|
@@ -466,16 +527,16 @@ public class QRCodeScanDialog {
|
|
|
closeListener.onClose();
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 显示失败对话框
|
|
|
failDialog.show();
|
|
|
-
|
|
|
+
|
|
|
// 回调支付失败
|
|
|
if (payResultListener != null) {
|
|
|
payResultListener.onPayFailed(errorMsg);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 显示/隐藏加载提示
|
|
|
*/
|
|
|
@@ -490,20 +551,20 @@ public class QRCodeScanDialog {
|
|
|
btnCancelPay.setVisibility(show ? Component.HIDE : Component.VISIBLE);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 显示错误信息
|
|
|
*/
|
|
|
private void showError(String errorMsg) {
|
|
|
Toast.error(context, errorMsg);
|
|
|
hide();
|
|
|
-
|
|
|
+
|
|
|
// 回调支付失败
|
|
|
if (payResultListener != null) {
|
|
|
payResultListener.onPayFailed(errorMsg);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 显示对话框
|
|
|
*/
|
|
|
@@ -514,38 +575,44 @@ public class QRCodeScanDialog {
|
|
|
// 弹框显示时自动调用支付接口
|
|
|
createPayOrder();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 隐藏对话框
|
|
|
*/
|
|
|
public void hide() {
|
|
|
stopCountdown();
|
|
|
stopPaymentPolling();
|
|
|
-
|
|
|
+
|
|
|
// 只有在手动关闭(非支付结果关闭)时,才触发刷新回调
|
|
|
// 支付结果关闭时,会在支付成功/失败弹窗关闭后才触发回调
|
|
|
if (!isClosedByPayResult && closeListener != null) {
|
|
|
closeListener.onClose();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 重置标志
|
|
|
isClosedByPayResult = false;
|
|
|
-
|
|
|
+
|
|
|
// 隐藏对话框
|
|
|
if (dialog != null) {
|
|
|
dialog.hide();
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 销毁对话框,释放资源
|
|
|
*/
|
|
|
public void destroy() {
|
|
|
stopCountdown();
|
|
|
stopPaymentPolling();
|
|
|
+
|
|
|
+ // 清理延迟任务
|
|
|
+ if (countdownHandler != null && startPollingTask != null) {
|
|
|
+ countdownHandler.removeTask(startPollingTask);
|
|
|
+ }
|
|
|
+
|
|
|
payResultListener = null;
|
|
|
closeListener = null;
|
|
|
-
|
|
|
+
|
|
|
// 释放二维码图片资源
|
|
|
if (imgQRCode != null && imgQRCode.getPixelMap() != null) {
|
|
|
imgQRCode.getPixelMap().release();
|