From 61cfa7f3ff9e846f0e07b7379bb84590a9dae852 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 4 Jan 2026 17:05:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=B5=8B=E8=AF=95=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- winboll/build.gradle | 4 + winboll/build.properties | 4 +- winboll/src/main/AndroidManifest.xml | 2 + .../winboll/studio/winboll/MainActivity.java | 6 +- .../winboll/studio/winboll/WxPayConfig.java | 28 +++ .../winboll/activities/WXPayActivity.java | 211 ++++++++++++++++++ .../winboll/unittest/TestWeWorkSpecSDK.java | 2 +- .../studio/winboll/utils/OkHttpUtil.java | 83 +++++++ .../studio/winboll/utils/SpecUtil.java | 26 +++ .../studio/winboll/utils/WxPayApi.java | 100 +++++++++ .../studio/winboll/utils/ZXingUtils.java | 75 +++++++ .../src/main/res/layout/activity_wxpay.xml | 43 ++++ winboll/src/main/res/menu/toolbar_main.xml | 4 +- 13 files changed, 580 insertions(+), 8 deletions(-) create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/WxPayConfig.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/activities/WXPayActivity.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/utils/OkHttpUtil.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/utils/SpecUtil.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/utils/WxPayApi.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/utils/ZXingUtils.java create mode 100644 winboll/src/main/res/layout/activity_wxpay.xml diff --git a/winboll/build.gradle b/winboll/build.gradle index 7d780a2..774d29e 100644 --- a/winboll/build.gradle +++ b/winboll/build.gradle @@ -69,6 +69,10 @@ dependencies { api 'io.github.medyo:android-about-page:2.0.0' // 网络连接类库 api 'com.squareup.okhttp3:okhttp:4.4.1' + // OkHttp网络请求 + implementation 'com.squareup.okhttp3:okhttp:3.14.9' + // FastJSON解析 + implementation 'com.alibaba:fastjson:1.2.76' // AndroidX 类库 api 'androidx.appcompat:appcompat:1.1.0' diff --git a/winboll/build.properties b/winboll/build.properties index 45a4a46..dc0bb79 100644 --- a/winboll/build.properties +++ b/winboll/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Jan 03 03:15:16 GMT 2026 +#Sun Jan 04 09:03:20 GMT 2026 stageCount=9 libraryProject= baseVersion=15.11 publishVersion=15.11.8 -buildCount=2 +buildCount=5 baseBetaVersion=15.11.9 diff --git a/winboll/src/main/AndroidManifest.xml b/winboll/src/main/AndroidManifest.xml index 2dd0be7..8236f00 100644 --- a/winboll/src/main/AndroidManifest.xml +++ b/winboll/src/main/AndroidManifest.xml @@ -280,6 +280,8 @@ + + \ No newline at end of file diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java index 82c65e1..935b86f 100644 --- a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java +++ b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java @@ -16,8 +16,8 @@ import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.winboll.R; import cc.winboll.studio.winboll.activities.SettingsActivity; +import cc.winboll.studio.winboll.activities.WXPayActivity; import cc.winboll.studio.winboll.fragments.BrowserFragment; -import cc.winboll.studio.winboll.unittest.TestWeWorkSpecSDK; import java.util.ArrayList; public class MainActivity extends DrawerFragmentActivity { @@ -149,8 +149,8 @@ public class MainActivity extends DrawerFragmentActivity { } } else if (nItemId == R.id.item_settings) { WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), SettingsActivity.class); - } else if (nItemId == R.id.item_testweworkspecsdk) { - WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), TestWeWorkSpecSDK.class); + } else if (nItemId == R.id.item_wxpayactivity) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), WXPayActivity.class); } else if (nItemId == cc.winboll.studio.libaes.R.id.item_about) { Intent intent = new Intent(getApplicationContext(), AboutActivity.class); APPInfo appInfo = genDefaultAPPInfo(); diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/WxPayConfig.java b/winboll/src/main/java/cc/winboll/studio/winboll/WxPayConfig.java new file mode 100644 index 0000000..69c7493 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/WxPayConfig.java @@ -0,0 +1,28 @@ +package cc.winboll.studio.winboll; + +/** + * 微信支付配置类 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class WxPayConfig { + // ========== 核心修改点:替换为你的服务端地址 ========== + // 服务端IP/域名 + 端口(Docker部署的服务端,需确保安卓端可访问) + public static final String BASE_URL = "https://wxpay.winboll.cc"; + + // 统一下单接口路径(对应服务端的测试接口) + public static final String CREATE_ORDER_URL = BASE_URL + "/pay/createOrder"; + + // 订单查询接口路径 + public static final String QUERY_ORDER_URL = BASE_URL + "/pay/queryOrder"; + + // ========== 固定支付配置 ========== + public static final String ORDER_BODY = "定额测试支付"; // 商品描述 + public static final int TOTAL_FEE = 1; // 固定金额:1分(沙箱环境推荐) + public static final String TRADE_TYPE = "NATIVE"; // 支付类型:二维码 + + // ========== 轮询配置 ========== + public static final long POLL_INTERVAL = 10000; // 轮询间隔:10秒 + public static final long POLL_TIMEOUT = 45000; // 轮询超时:45秒 +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/WXPayActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WXPayActivity.java new file mode 100644 index 0000000..e881993 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WXPayActivity.java @@ -0,0 +1,211 @@ +package cc.winboll.studio.winboll.activities; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.WxPayConfig; +import cc.winboll.studio.winboll.utils.SpecUtil; +import cc.winboll.studio.winboll.utils.WxPayApi; +import cc.winboll.studio.winboll.utils.ZXingUtils; +import java.util.Timer; +import java.util.TimerTask; + + + +/** + * 主界面:生成二维码 + 轮询查询支付结果 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class WXPayActivity extends WinBoLLActivity implements IWinBoLLActivity { + + private static final String TAG = "WXPayActivity"; + + // Handler消息标识 + private static final int MSG_POLL_TIMEOUT = 1001; + private static final int MSG_POLL_SUCCESS = 1002; + private static final int MSG_POLL_FAILED = 1003; + + private ImageView mIvQrCode; + private TextView mTvOrderNo; + private TextView mTvPayStatus; + private Button mBtnCreateOrder; + + private String mOutTradeNo; // 商户订单号 + private Timer mPollTimer; // 轮询定时器 + private long mPollStartTime; // 轮询开始时间 + + + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + private Handler mPollHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case MSG_POLL_TIMEOUT: + stopPoll(); + mTvPayStatus.setText("支付状态:轮询超时"); + mTvPayStatus.setTextColor(getResources().getColor(android.R.color.darker_gray)); + Toast.makeText(WXPayActivity.this, "轮询超时,请手动查询", Toast.LENGTH_SHORT).show(); + break; + case MSG_POLL_SUCCESS: + boolean isPaySuccess = (boolean) msg.obj; + String tradeState = (String) msg.getData().getString("tradeState"); + stopPoll(); + if (isPaySuccess) { + mTvPayStatus.setText("支付状态:支付成功 ✅"); + mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark)); + Toast.makeText(WXPayActivity.this, "支付成功!", Toast.LENGTH_SHORT).show(); + } else { + mTvPayStatus.setText("支付状态:" + tradeState); + mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark)); + } + break; + case MSG_POLL_FAILED: + String errorMsg = (String) msg.obj; + mTvPayStatus.setText("查询失败:" + errorMsg); + break; + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_wxpay); + + initView(); + initListener(); + } + + private void initView() { + mIvQrCode = findViewById(R.id.iv_qrcode); + mTvOrderNo = findViewById(R.id.tv_order_no); + mTvPayStatus = findViewById(R.id.tv_pay_status); + mBtnCreateOrder = findViewById(R.id.btn_create_order); + } + + private void initListener() { + mBtnCreateOrder.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createOrder(); + } + }); + } + + /** + * 统一下单,生成二维码 + */ + private void createOrder() { + mBtnCreateOrder.setEnabled(false); + mTvPayStatus.setText("支付状态:生成订单中..."); + mIvQrCode.setImageBitmap(null); + + WxPayApi.createOrder(new WxPayApi.OnCreateOrderCallback() { + @Override + public void onSuccess(String outTradeNo, String codeUrl) { + mOutTradeNo = outTradeNo; + mTvOrderNo.setText("商户订单号:" + outTradeNo); + mTvPayStatus.setText("支付状态:未支付,请扫码"); + + // 生成二维码 + Bitmap qrCodeBitmap = ZXingUtils.createQRCodeBitmap(codeUrl, 250, 250); + if (qrCodeBitmap != null) { + mIvQrCode.setImageBitmap(qrCodeBitmap); + // 开始轮询查询支付结果 + startPoll(); + } else { + mTvPayStatus.setText("支付状态:生成二维码失败"); + mBtnCreateOrder.setEnabled(true); + } + } + + @Override + public void onFailure(String errorMsg) { + SpecUtil.WWSpecLogError(TAG, "统一下单失败:" + errorMsg); + mTvPayStatus.setText("生成订单失败:" + errorMsg); + mBtnCreateOrder.setEnabled(true); + Toast.makeText(WXPayActivity.this, errorMsg, Toast.LENGTH_SHORT).show(); + } + }); + } + + /** + * 开始轮询查询支付结果 + */ + private void startPoll() { + stopPoll(); // 先停止之前的轮询 + mPollStartTime = System.currentTimeMillis(); + mPollTimer = new Timer(); + mPollTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + // 检查是否超时 + long elapsedTime = System.currentTimeMillis() - mPollStartTime; + if (elapsedTime >= WxPayConfig.POLL_TIMEOUT) { + mPollHandler.sendEmptyMessage(MSG_POLL_TIMEOUT); + return; + } + + // 查询订单状态 + WxPayApi.queryOrder(mOutTradeNo, new WxPayApi.OnQueryOrderCallback() { + @Override + public void onSuccess(boolean isPaySuccess, String tradeState) { + Message msg = Message.obtain(); + msg.what = MSG_POLL_SUCCESS; + msg.obj = isPaySuccess; + Bundle bundle = new Bundle(); + bundle.putString("tradeState", tradeState); + msg.setData(bundle); + mPollHandler.sendMessage(msg); + } + + @Override + public void onFailure(String errorMsg) { + Message msg = Message.obtain(); + msg.what = MSG_POLL_FAILED; + msg.obj = errorMsg; + mPollHandler.sendMessage(msg); + } + }); + } + }, 0, WxPayConfig.POLL_INTERVAL); + } + + /** + * 停止轮询 + */ + private void stopPoll() { + if (mPollTimer != null) { + mPollTimer.cancel(); + mPollTimer = null; + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + stopPoll(); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TestWeWorkSpecSDK.java b/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TestWeWorkSpecSDK.java index 2278371..5fdac27 100644 --- a/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TestWeWorkSpecSDK.java +++ b/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TestWeWorkSpecSDK.java @@ -24,7 +24,7 @@ public class TestWeWorkSpecSDK extends WinBoLLActivity implements IWinBoLLActivi // ------------------- 企业微信SDK配置常量(需替换为实际项目参数) ------------------- // 企业微信 CorpID(从企业微信管理后台获取) - private static final String CORP_ID = "your_corp_id_here"; + private static final String CORP_ID = "wwb37c73f34c722852"; // 应用 AgentID(从企业微信应用管理后台获取) private static final String AGENT_ID = "your_agent_id_here"; // 应用 Secret(从企业微信应用管理后台获取,注意保密) diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/OkHttpUtil.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/OkHttpUtil.java new file mode 100644 index 0000000..4e23dcb --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/OkHttpUtil.java @@ -0,0 +1,83 @@ +package cc.winboll.studio.winboll.utils; + +import android.os.Handler; +import android.os.Looper; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * OkHttp网络请求工具类 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class OkHttpUtil { + + private static OkHttpClient sOkHttpClient; + private static Handler sMainHandler = new Handler(Looper.getMainLooper()); + + static { + sOkHttpClient = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .build(); + } + + /** + * GET请求 + * @param url 请求地址 + * @param callback 回调 + */ + public static void get(String url, final OnResultCallback callback) { + Request request = new Request.Builder() + .url(url) + .get() + .build(); + + sOkHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, final IOException e) { + sMainHandler.post(new Runnable() { + @Override + public void run() { + if (callback != null) { + callback.onFailure(e.getMessage()); + } + } + }); + } + + @Override + public void onResponse(Call call, final Response response) throws IOException { + final String result = response.body().string(); + sMainHandler.post(new Runnable() { + @Override + public void run() { + if (callback != null) { + if (response.isSuccessful()) { + callback.onSuccess(result); + } else { + callback.onFailure("请求失败:" + response.code()); + } + } + } + }); + } + }); + } + + /** + * 回调接口 + */ + public interface OnResultCallback { + void onSuccess(String result); + void onFailure(String errorMsg); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/SpecUtil.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/SpecUtil.java new file mode 100644 index 0000000..279f286 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/SpecUtil.java @@ -0,0 +1,26 @@ +package cc.winboll.studio.winboll.utils; + +import cc.winboll.studio.libappbase.LogUtils; + +/** + * 日志工具类(适配项目规范) + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class SpecUtil { + + private static final boolean isDebug = true; + + public static void WWSpecLogInfo(String tag, String msg) { + if (isDebug) { + LogUtils.i(tag, msg); + } + } + + public static void WWSpecLogError(String tag, String msg) { + if (isDebug) { + LogUtils.e(tag, msg); + } + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/WxPayApi.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/WxPayApi.java new file mode 100644 index 0000000..dd0b508 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/WxPayApi.java @@ -0,0 +1,100 @@ +package cc.winboll.studio.winboll.utils; + +import cc.winboll.studio.winboll.WxPayConfig; +import com.alibaba.fastjson.JSONObject; + +/** + * 微信支付服务端接口封装 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class WxPayApi { + + /** + * 统一下单(生成二维码) + * @param callback 回调 + */ + public static void createOrder(final OnCreateOrderCallback callback) { + // 拼接请求参数(服务端测试接口需支持GET传参,若为POST需修改为表单/JSON) + String url = WxPayConfig.CREATE_ORDER_URL + + "?body=" + WxPayConfig.ORDER_BODY + + "&totalFee=" + WxPayConfig.TOTAL_FEE + + "&tradeType=" + WxPayConfig.TRADE_TYPE; + + OkHttpUtil.get(url, new OkHttpUtil.OnResultCallback() { + @Override + public void onSuccess(String result) { + try { + JSONObject jsonObject = JSONObject.parseObject(result); + String outTradeNo = jsonObject.getString("out_trade_no"); + String codeUrl = jsonObject.getString("code_url"); + if (callback != null) { + callback.onSuccess(outTradeNo, codeUrl); + } + } catch (Exception e) { + if (callback != null) { + callback.onFailure("解析统一下单结果失败:" + e.getMessage()); + } + } + } + + @Override + public void onFailure(String errorMsg) { + if (callback != null) { + callback.onFailure("统一下单请求失败:" + errorMsg); + } + } + }); + } + + /** + * 订单查询 + * @param outTradeNo 商户订单号 + * @param callback 回调 + */ + public static void queryOrder(String outTradeNo, final OnQueryOrderCallback callback) { + String url = WxPayConfig.QUERY_ORDER_URL + "?outTradeNo=" + outTradeNo; + + OkHttpUtil.get(url, new OkHttpUtil.OnResultCallback() { + @Override + public void onSuccess(String result) { + try { + JSONObject jsonObject = JSONObject.parseObject(result); + String tradeState = jsonObject.getString("trade_state"); + boolean isSuccess = "SUCCESS".equals(tradeState); + if (callback != null) { + callback.onSuccess(isSuccess, tradeState); + } + } catch (Exception e) { + if (callback != null) { + callback.onFailure("解析订单查询结果失败:" + e.getMessage()); + } + } + } + + @Override + public void onFailure(String errorMsg) { + if (callback != null) { + callback.onFailure("订单查询请求失败:" + errorMsg); + } + } + }); + } + + /** + * 统一下单回调接口 + */ + public interface OnCreateOrderCallback { + void onSuccess(String outTradeNo, String codeUrl); + void onFailure(String errorMsg); + } + + /** + * 订单查询回调接口 + */ + public interface OnQueryOrderCallback { + void onSuccess(boolean isPaySuccess, String tradeState); + void onFailure(String errorMsg); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/ZXingUtils.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/ZXingUtils.java new file mode 100644 index 0000000..389298d --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/ZXingUtils.java @@ -0,0 +1,75 @@ +package cc.winboll.studio.winboll.utils; + +import android.graphics.Bitmap; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.journeyapps.barcodescanner.BarcodeEncoder; +import java.util.HashMap; +import java.util.Map; + +/** + * ZXing二维码生成工具类 + * 依赖:com.google.zxing:core:3.4.1 + com.journeyapps:zxing-android-embedded:3.6.0 + * @Author ZhanGSKen + * @Date 2026/01/07 + */ +public class ZXingUtils { + + /** + * 生成二维码Bitmap(核心方法,使用journeyapps工具类) + * @param content 内容(如微信支付的code_url) + * @param width 二维码宽度(px) + * @param height 二维码高度(px) + * @return 二维码Bitmap,失败返回null + */ + public static Bitmap createQRCodeBitmap(String content, int width, int height) { + // 1. 入参合法性校验 + if (content == null || content.trim().isEmpty()) { + return null; + } + if (width <= 0 || height <= 0) { + return null; + } + + // 2. 配置二维码参数 + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 字符编码 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 高容错级别(H级可容忍30%遮挡) + hints.put(EncodeHintType.MARGIN, 1); // 边距(值越小,二维码越紧凑,建议1-4) + + try { + // 3. 生成BitMatrix(二维码矩阵) + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix bitMatrix = qrCodeWriter.encode( + content, + BarcodeFormat.QR_CODE, + width, + height, + hints + ); + + // 4. 转换BitMatrix为Bitmap(关键:使用journeyapps的BarcodeEncoder) + BarcodeEncoder barcodeEncoder = new BarcodeEncoder(); + return barcodeEncoder.createBitmap(bitMatrix); + + } catch (WriterException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 重载方法:生成正方形二维码(宽度=高度) + * @param content 内容 + * @param size 二维码边长(px) + * @return 二维码Bitmap + */ + public static Bitmap createQRCodeBitmap(String content, int size) { + return createQRCodeBitmap(content, size, size); + } +} + diff --git a/winboll/src/main/res/layout/activity_wxpay.xml b/winboll/src/main/res/layout/activity_wxpay.xml new file mode 100644 index 0000000..bfd2596 --- /dev/null +++ b/winboll/src/main/res/layout/activity_wxpay.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + +