From 7ffcc848d040da65a05613993c5f8594225404ce Mon Sep 17 00:00:00 2001 From: peima Date: Fri, 24 Jul 2026 02:47:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=201=20=E2=80=94=20remove=20Stripe?= =?UTF-8?q?,=20switch=20to=20e-Transfer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop stripe_payment + stripe_sdk deps; delete stripe pay pages/widgets - New ETransferPay widget: shows payment@wisetronic.com + R# remark - goPayment stripe/native branches now route to e-Transfer - pay_now widgets: strip saved-cards section & dead nativePay logic - my_cards pages rewritten as e-Transfer info (route kept) - models/utils/constants cleaned of stripe; env constraint -> Dart 3 See UPGRADE_NOTES.md. (Null-safety migration is Phase 3; deps upgrade Phase 2.) --- UPGRADE_NOTES.md | 40 +++ lib/constants.dart | 10 +- lib/models/stripe_payment_method.dart | 46 ---- lib/models/user.dart | 7 +- lib/pages/stripe_pay_web.dart | 34 --- lib/utils/util_io.dart | 149 +---------- lib/utils/util_web.dart | 6 +- lib/utils/utils.dart | 74 ------ lib/widgets/desktop/desktop_my_cards.dart | 220 ++++----------- lib/widgets/desktop/desktop_pay_now.dart | 144 +--------- .../desktop/desktop_stripe_pay_web.dart | 250 ------------------ lib/widgets/general/e_transfer_pay.dart | 206 +++++++++++++++ lib/widgets/general/stripe_pay.dart | 158 ----------- lib/widgets/mobile/mobile_my_cards.dart | 200 ++++---------- lib/widgets/mobile/mobile_pay_now.dart | 143 +--------- lib/widgets/mobile/mobile_stripe_pay_web.dart | 221 ---------------- pubspec.yaml | 11 +- 17 files changed, 371 insertions(+), 1548 deletions(-) create mode 100644 UPGRADE_NOTES.md delete mode 100644 lib/models/stripe_payment_method.dart delete mode 100644 lib/pages/stripe_pay_web.dart delete mode 100644 lib/widgets/desktop/desktop_stripe_pay_web.dart create mode 100644 lib/widgets/general/e_transfer_pay.dart delete mode 100644 lib/widgets/general/stripe_pay.dart delete mode 100644 lib/widgets/mobile/mobile_stripe_pay_web.dart diff --git a/UPGRADE_NOTES.md b/UPGRADE_NOTES.md new file mode 100644 index 0000000..ccfc980 --- /dev/null +++ b/UPGRADE_NOTES.md @@ -0,0 +1,40 @@ +# flutter_wisetronic 升级日志 + +## 目标 +- 升级到 Dart 3 强制空安全(目标 SDK:本机 Flutter 3.29.3 / Dart 3.7.2,不升级全局) +- 移除 Stripe 收款,改为页面展示 e-transfer email 让客户自行 e-transfer 付款 +- 第一阶段:能编译、Web 能跑起来(暂不美化) + +## 关键决策 +- 目标版本:Flutter 3.29.3 / Dart 3.7.2(钉定本机,不动全局) +- e-transfer 邮箱:`payment@wisetronic.com` +- 提醒客户在 e-transfer remark 里写 `R#` 以便自动化处理订单 +- 搜索框:自写轻量 Web 搜索组件替换 flappy_search_bar +- Dart 3 已移除 `dart migrate`,空安全迁移用手工 + IDE 修复 + +## 阶段 +- [x] 阶段 0:基线 & 锁版本 +- [x] 阶段 1:去掉 Stripe +- [ ] 阶段 2:依赖替换与升级 +- [ ] 阶段 3:空安全迁移 +- [ ] 阶段 4:Web 跑起来 + +--- + +## 改动记录 + +### 阶段 0 +- 创建 UPGRADE_NOTES.md +- pubspec.yaml: environment sdk `>=2.7.0 <3.0.0` → `>=3.0.0 <4.0.0`,flutter `>=3.29.0` + +### 阶段 1 — 去掉 Stripe(已完成) +- 删除: stripe_pay_web.dart, stripe_pay.dart, desktop/mobile_stripe_pay_web.dart, stripe_payment_method.dart +- pubspec: 移除 stripe_payment + stripe_sdk(git) +- user.dart: 移除 stripePaymentMethods 字段与解析 +- utils.dart: 移除 createOrUpdateStripePaymentMethod / stripePaymentIntent / stripeChargedSuccess +- util_web.dart / util_io.dart: goPayment 的 stripe/native 分支 → 跳转 ETransferPay +- desktop/mobile_pay_now.dart: 移除“已有卡片”区块与 nativePay 死逻辑,简化列表索引 +- desktop/mobile_my_cards.dart: 重写为 e-Transfer 说明页(保留路由入口) +- constants.dart: 新增 ETRANSFER_EMAIL(payment@wisetronic.com) 与 REMARK_PREFIX(R#) +- 新增 lib/widgets/general/e_transfer_pay.dart: 展示邮箱 + 提醒备注写 R# +- 保留: PAYMENT_METHOD_CODE_STRIPE='stripe'(API 仍返回该 code,路由到 e-Transfer);payment_platform.dart 的 'stripe' 显示名(后续美化时调整) diff --git a/lib/constants.dart b/lib/constants.dart index 37b5eb7..6eb2c4f 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -49,13 +49,15 @@ class Constants { static const String PAYMENT_METHOD_CODE_OTT_ALIPAY = 'ALIPAY'; static const String PAYMENT_METHOD_CODE_OTT_WECHATPAY = 'WECHATPAY'; static const String PAYMENT_METHOD_CODE_PAYPAL = 'paypal'; - static const String PAYMENT_METHOD_CODE_STRIPE = 'stripe'; + static const String PAYMENT_METHOD_CODE_STRIPE = 'stripe'; // API still returns 'stripe' code; now routed to e-Transfer static const String PAYMENT_METHOD_CODE_POD = 'payondeliverypickup'; static const String PAYMENT_METHOD_NATIVE_PAY = 'native_pay'; - static const String STRIPE_STATUS_REQUIRES_CONFIRMATION = 'requires_confirmation'; - static const String STRIPE_CLIENT_SECRET = 'client_secret'; - static const String STRIPE_STATUS_SUCCEDED = 'succeeded'; + // e-Transfer (replaces Stripe) + static const String ETRANSFER_EMAIL = 'payment@wisetronic.com'; + static const String ETRANSFER_QA = 'wisetronic'; // security question answer placeholder, if needed + // Remark template for auto-processing: R# (= order id) + static const String ETRANSFER_REMARK_PREFIX = 'R#'; static const int STATUS_PENDING = 0; static const int STATUS_ACCEPT = 1; diff --git a/lib/models/stripe_payment_method.dart b/lib/models/stripe_payment_method.dart deleted file mode 100644 index 1286035..0000000 --- a/lib/models/stripe_payment_method.dart +++ /dev/null @@ -1,46 +0,0 @@ - -import 'dart:convert'; - -class StripePaymentMethod { - int id; - String customerId; - String paymentMethodId; - String paymentMethodType; - String cardBrand; - String cardCountry; - int cardExpMonth; - int cardExpYear; - String cardFunding; - String cardLast4; - - StripePaymentMethod.fromJson(Map json) : - id = json['id'], - customerId = json['customer_id'], - paymentMethodId = json['payment_method_id'], - paymentMethodType = json['payment_method_type'], - cardBrand = json['card_brand'], - cardCountry = json['card_country'], - cardExpMonth = json['card_exp_month'], - cardExpYear = json['card_exp_year'], - cardFunding = json['card_funding'], - cardLast4 = json['card_last4']; - - Map toJson() => { - 'id': id, - 'customer_id': customerId, - 'payment_method_id': paymentMethodId, - 'payment_method_type': paymentMethodType, - 'card_brand': cardBrand, - 'card_country': cardCountry, - 'card_exp_month': cardExpMonth, - 'card_exp_year': cardExpYear, - 'card_funding': cardFunding, - 'card_last4': cardLast4, - }; - - @override - String toString() { - return json.encode(this); - } - -} \ No newline at end of file diff --git a/lib/models/user.dart b/lib/models/user.dart index e532ade..c467a19 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -1,8 +1,6 @@ import 'dart:convert'; -import 'stripe_payment_method.dart'; - class User { int id; String username; @@ -20,7 +18,6 @@ class User { double pointsToCreditsConversionRate; String contactNumber; String flutterMiniStoreToken; - List stripePaymentMethods; User.fromJson(Map json) : id = json['id'], @@ -38,8 +35,7 @@ class User { orderNum = json['order_num'], pointsToCreditsConversionRate = double.parse(json['points_to_credits_conversion_rate'].toString()), contactNumber = json['contact_number'], - flutterMiniStoreToken = json['flutter_ministore_token'], - stripePaymentMethods = (json['stripe_payment_methods'] as List).map((e) => StripePaymentMethod.fromJson(e)).toList(); + flutterMiniStoreToken = json['flutter_ministore_token']; Map toJson() => { 'id': id, @@ -58,7 +54,6 @@ class User { 'points_to_credits_conversion_rate': pointsToCreditsConversionRate, 'contact_number': contactNumber, 'flutter_ministore_token': flutterMiniStoreToken, - 'stripe_payment_methods': stripePaymentMethods, }; @override diff --git a/lib/pages/stripe_pay_web.dart b/lib/pages/stripe_pay_web.dart deleted file mode 100644 index 9d4c2d8..0000000 --- a/lib/pages/stripe_pay_web.dart +++ /dev/null @@ -1,34 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:responsive_builder/responsive_builder.dart'; - -import '../models/order.dart'; -import '../models/payment_platform.dart'; -import '../models/stripe_payment_method.dart'; -import '../store/actions.dart'; -import '../store/store.dart'; -import '../widgets/desktop/desktop_stripe_pay_web.dart'; -import '../widgets/mobile/mobile_stripe_pay_web.dart'; - -class StripePayWeb extends StatelessWidget { - final Order order; - final PaymentPlatform paymentPlatform; - final StripePaymentMethod stripePaymentMethod; - - const StripePayWeb(this.order, this.paymentPlatform, {this.stripePaymentMethod}); - - @override - Widget build(BuildContext context) { - store.dispatch(UpdateContext(context)); - - return ResponsiveBuilder( - builder: (context, sizingInformation) => - ScreenTypeLayout( - mobile: MobileStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,), - tablet: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,), - desktop: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,), - ), - ); - } - -} \ No newline at end of file diff --git a/lib/utils/util_io.dart b/lib/utils/util_io.dart index 18847c3..dd74031 100644 --- a/lib/utils/util_io.dart +++ b/lib/utils/util_io.dart @@ -15,7 +15,6 @@ import 'package:hive/hive.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:stripe_payment/stripe_payment.dart'; import '../constants.dart'; import '../events/eventbus.dart'; @@ -24,12 +23,11 @@ import '../generated/l10n.dart'; import '../models/comment.dart'; import '../models/order.dart'; import '../models/payment_platform.dart'; -import '../models/stripe_payment_method.dart'; import '../models/user.dart'; import '../routes.dart'; import '../store/actions.dart'; import '../store/store.dart'; -import '../widgets/general/stripe_pay.dart'; +import '../widgets/general/e_transfer_pay.dart'; import 'http_util.dart'; import 'utils.dart'; @@ -507,62 +505,14 @@ class Util { } Widget getNativePay(BuildContext context, Order order, PaymentPlatform paymentPlatform) { - return GestureDetector( - child: Container( - padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - margin: EdgeInsets.only(right: 6.0), - child: Text( - Platform.isAndroid ? S.of(context).pay_with : S.of(context).pay_with, - style: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - ), - ), - ), - Container( - padding: EdgeInsets.only(right: 10.0), - child: Platform.isAndroid ? Image.asset( - 'assets/images/google_pay.png', - height: 22.0, - fit: BoxFit.fitHeight, - ) : Image.asset( - 'assets/images/apple_pay.png', - height: 22.0, - fit: BoxFit.fitHeight, - ), - ), - ], - ), - ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 10, - color: Colors.black26, - ), - ), - ), - ), - onTap: () { - print(paymentPlatform); - PaymentPlatform newPaymentPlatform = PaymentPlatform.fromJson(json.decode(json.encode(paymentPlatform))); - newPaymentPlatform.code = Constants.PAYMENT_METHOD_NATIVE_PAY; - goPayment(context, order, newPaymentPlatform); - }, - ); + // Native (Apple/Google) pay removed with Stripe. Web uses e-Transfer. + return SizedBox.shrink(); } static void goPayment(BuildContext context, Order order, PaymentPlatform paymentPlatform, { bool googlePay=false, bool applePay=false, - StripePaymentMethod stripePaymentMethod, }) { switch(paymentPlatform.code) { case Constants.PAYMENT_METHOD_CODE_SQUARE: @@ -584,102 +534,23 @@ class Util { break; case Constants.PAYMENT_METHOD_NATIVE_PAY: - StripePayment.setOptions( - StripeOptions(publishableKey: paymentPlatform.publishableKey, - merchantId: paymentPlatform.merchantId, - androidPayMode: paymentPlatform.publishableKey.contains('_test_') - ? 'test' - : 'production' - ) - ); - StripePayment.paymentRequestWithNativePay( - androidPayOptions: AndroidPayPaymentRequest( - totalPrice: order.totalPrice.toStringAsFixed(2), - currencyCode: order.businessInfo.currency, - ), - applePayOptions: ApplePayPaymentOptions( - currencyCode: order.businessInfo.currency, - countryCode: order.businessInfo.address.country, - items: [ - ApplePayItem( - label: order.businessInfo.name, - amount: order.totalPrice.toStringAsFixed(2), - ), - ], - ), - ).then((token) { - print('return native token: ${token.tokenId}'); - processNativePay(context, token, order); - }).catchError((error) { - print('native pay error: $error'); - }); + // Native (Apple/Google) pay removed with Stripe; route to e-Transfer. + Navigator.pushReplacement(context, MaterialPageRoute( + builder: (BuildContext context) { + return ETransferPay(order: order); + } + )); break; case Constants.PAYMENT_METHOD_CODE_STRIPE: Navigator.pushReplacement(context, MaterialPageRoute( builder: (BuildContext context) { - return StripePay(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,); + return ETransferPay(order: order); } )); break; } } - static void processNativePay(BuildContext context, Token token, Order order) async { - PaymentMethod paymentMethod = await StripePayment.createPaymentMethod( - PaymentMethodRequest( - card: CreditCard( - token: token.tokenId, - ), - ), - ); - if (paymentMethod != null) { - Utils.stripePaymentIntent(order, null, paymentMethod.id, paymentMethod.type, (response) { - if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { - StripePayment.confirmPaymentIntent( - PaymentIntent( - clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET], - paymentMethodId: response.data['payment_method'], - ), - ).then((paymentIntentResult) { - if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) { - Utils.stripeChargedSuccess(order, - paymentMethod.id, - paymentIntentResult.paymentIntentId, - (response) { - StripePayment.completeNativePayRequest().then((_) { - eventBus.fire(OnOrderUpdated()); - Routes.router.navigateTo(context, '/orderdetail/${order.id}', replace: true); - }).catchError((error) { - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - }); - }); - }, - (error) { - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - }); - } - ); - } else { - Utils.showMessageDialog(context, Exception('Unknown error')); - } - }).catchError((error) { - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - }); - }); - } - }, (error) { - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - }); - }); - } else { - - } - } - static Future getBytesFromAsset(String path, int width) async { ByteData data = await rootBundle.load(path); ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width); diff --git a/lib/utils/util_web.dart b/lib/utils/util_web.dart index 4f62cf1..9632234 100644 --- a/lib/utils/util_web.dart +++ b/lib/utils/util_web.dart @@ -16,11 +16,10 @@ import '../generated/l10n.dart'; import '../models/comment.dart' as mComment; import '../models/order.dart'; import '../models/payment_platform.dart'; -import '../models/stripe_payment_method.dart'; import '../models/user.dart'; import '../store/actions.dart'; import '../store/store.dart'; -import '../pages/stripe_pay_web.dart'; +import '../widgets/general/e_transfer_pay.dart'; import 'http_util.dart'; import 'utils.dart'; @@ -252,7 +251,6 @@ class Util { PaymentPlatform paymentPlatform, { bool googlePay=false, bool applePay=false, - StripePaymentMethod stripePaymentMethod, }) { switch(paymentPlatform.code) { case Constants.PAYMENT_METHOD_CODE_SQUARE: @@ -276,7 +274,7 @@ class Util { case Constants.PAYMENT_METHOD_CODE_STRIPE: Navigator.pushReplacement(context, MaterialPageRoute( builder: (BuildContext context) { - return StripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,); + return ETransferPay(order: order); } )); break; diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index ee277c7..32941ba 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -291,39 +291,6 @@ class Utils { return amount.toStringAsFixed(decimalPlace); } - static void createOrUpdateStripePaymentMethod( - String paymentMethodId, - String cardBrand, - String paymentMethodType, - String cardCountry, - int cardExpMonth, - int cardExpYear, - String cardFunding, - String cardLast4, - ) { - HttpUtil.httpPost( - 'v1/create-update-stripe-payment-method', - (response) { - if (response.statusCode == 200) { - print( - 'create or update customer stripe payment method success. ${response.data}'); - } - }, - body: { - 'payment_method_id': paymentMethodId, - 'card_brand': cardBrand, - 'payment_method_type': paymentMethodType, - 'card_country': cardCountry, - 'card_exp_month': cardExpMonth, - 'card_exp_year': cardExpYear, - 'card_funding': cardFunding, - 'card_last4': cardLast4, - }, - isFormData: true, - ).catchError((error) { - print('Error: ${error}'); - }); - } static showSubmitDialog(BuildContext context) { showDialog( @@ -900,48 +867,7 @@ class Utils { } } - static void stripePaymentIntent(Order order, String customerId, String paymentMethodId, String cardType, - OnSuccess onSuccess, OnError onError, - { - String cardBrand, - String cardCountry, - int cardExpMonth, - int cardExpYear, - String cardFunding, - String cardLast4, - }) { - HttpUtil.httpPost('v1/stripe-payment-intent', - onSuccess, - body: { - 'order_id': order.id, - 'pay_amount': order.totalPrice, - 'customer_id': customerId == null ? '' : customerId, - 'payment_method_id': paymentMethodId, - 'payment_method_type': cardType, - 'card_brand': cardBrand, - 'card_country': cardCountry, - 'card_exp_month': cardExpMonth, - 'card_exp_year': cardExpYear, - 'card_funding': cardFunding, - 'card_last4': cardLast4, - }, - isFormData: true, - ).catchError(onError); - } - - static void stripeChargedSuccess(Order order, String paymentMethodId, - String paymentIntentId, OnSuccess onSuccess, OnError onError) { - HttpUtil.httpPost('v1/stripe-charged-success', - onSuccess, - isFormData: true, - body: { - 'order_id': order.id, - 'payment_method_id': paymentMethodId, - 'payment_intent_id': paymentIntentId, - } - ).catchError(onError); - } static int getProductLineInOrder(CartInfo cartInfo) { int qty = 0; diff --git a/lib/widgets/desktop/desktop_my_cards.dart b/lib/widgets/desktop/desktop_my_cards.dart index d043c3c..51b3ad9 100644 --- a/lib/widgets/desktop/desktop_my_cards.dart +++ b/lib/widgets/desktop/desktop_my_cards.dart @@ -1,49 +1,24 @@ - import 'package:flutter/material.dart'; import '../../constants.dart'; -import '../../events/eventbus.dart'; -import '../../events/events.dart'; import '../../generated/l10n.dart'; -import '../../models/stripe_payment_method.dart'; -import '../../models/user.dart'; import '../../store/actions.dart'; import '../../store/store.dart'; -import '../../utils/http_util.dart'; -import '../../utils/utils.dart'; import '../../widgets/general/bottom_nav.dart'; import '../../widgets/general/breadcrumbs.dart'; import '../../widgets/general/navigationbar.dart'; -class DesktopMyCards extends StatefulWidget { - final Key key; - const DesktopMyCards({this.key}); - - @override - State createState() { - return MyCardsState(); - } - -} - -class MyCardsState extends State { - User _user; - - bool isSubmitting; - - double sideSpace = 0; - double mainSpace = 1200; +/// “My Cards” 页:已停止保存银行卡(Stripe 移除)。改为展示 e-Transfer 说明。 +class DesktopMyCards extends StatelessWidget { + const DesktopMyCards({Key? key}); @override Widget build(BuildContext context) { store.dispatch(UpdateContext(context)); - if (MediaQuery.of(context).size.width <= 1200) { - mainSpace = MediaQuery.of(context).size.width; - sideSpace = 0; - } else { - mainSpace = 1200; + double sideSpace = 0; + if (MediaQuery.of(context).size.width > 1200) { sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; } @@ -58,153 +33,62 @@ class MyCardsState extends State { ), body: Row( children: [ - Container( - width: sideSpace, - ), + Container(width: sideSpace), Expanded( - child: ListView.builder( - itemCount: _user.stripePaymentMethods.length, - itemBuilder: (BuildContext context, int position) { - StripePaymentMethod paymentMethod = _user.stripePaymentMethods[position]; - return cardWidget(paymentMethod); - } - ), - ), - Container( - width: sideSpace, - ), - ], - ), - bottomNavigationBar: BottomNav(), - ); - } - - Widget cardWidget(StripePaymentMethod paymentMethod) { - return Container( - padding: EdgeInsets.only( - top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - paymentMethod.cardBrand == 'visa' ? - Image.asset( - 'assets/images/visa.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : (paymentMethod.cardBrand == 'mastercard' ? - Image.asset( - 'assets/images/master.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : Icon( - Icons.credit_card, size: 50.0, color: Colors.black38,)), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + child: ListView( + children: [ Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - '**** ${paymentMethod.cardLast4}', - style: TextStyle( - fontSize: 20.0, - ), + margin: const EdgeInsets.all(24), + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.lightBlueAccent.withOpacity(0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.black12), ), - ), - Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - S.of(context).expire_token(paymentMethod.cardExpMonth, paymentMethod.cardExpYear), - style: TextStyle( - fontSize: 14.0, - color: Colors.black26, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Icon(Icons.info_outline, color: Colors.lightBlueAccent), + SizedBox(width: 8), + Text( + 'Saved cards are no longer used', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 12), + const Text( + 'We have switched to Interac e-Transfer for payments. ' + 'You no longer need to save a credit card.', + ), + const SizedBox(height: 16), + const Text('Send your e-Transfer to:'), + const SizedBox(height: 4), + SelectableText( + Constants.ETRANSFER_EMAIL, + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + const Text( + 'Remember to put your order code ' + '(e.g. R#) in the e-Transfer message so we ' + 'can process your order automatically.', + style: TextStyle(color: Colors.black54), + ), + ], ), ), ], ), ), - IconButton( - icon: Icon(Icons.clear, color: Colors.black26,), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text(S.of(context).warning), - content: Text( - S.of(context).are_you_sure_to_remove_the_card, - ), - actions: [ - TextButton( - child: Text(S.of(context).cancel), - onPressed: () { - Navigator.of(context).pop(); - }, - style: TextButton.styleFrom( - primary: Theme.of(context).primaryColor, - ), - ), - TextButton( - child: Text(S.of(context).yes_i_am_sure), - onPressed: () { - Navigator.of(context).pop(); - _removeCard(paymentMethod); - }, - ) - ], - ); - } - ); - }, - ), + Container(width: sideSpace), ], ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 0.5, - color: Colors.black12, - ), - ), - ), + bottomNavigationBar: BottomNav(), ); } - - _removeCard(StripePaymentMethod paymentMethod) { - HttpUtil.httpDelete('v1/stripe-card/${paymentMethod.id}', (response) { - _user = User.fromJson(response.data); - store.dispatch(UpdateCurrentUser(_user)); - eventBus.fire(OnCurrentUserUpdated()); - if (mounted) { - Navigator.of(context).pop(); - setState(() { - isSubmitting = false; - }); - } - }).catchError((error) { - if (isSubmitting) { - Navigator.of(context).pop(); - isSubmitting = false; - } - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }); - }); - isSubmitting = true; - Utils.showSubmitDialog(context); - } - - @override - void initState() { - super.initState(); - setState(() { - isSubmitting = false; - _user = store.state.user; - }); - } -} \ No newline at end of file +} diff --git a/lib/widgets/desktop/desktop_pay_now.dart b/lib/widgets/desktop/desktop_pay_now.dart index b81bc1d..c8aa6fe 100644 --- a/lib/widgets/desktop/desktop_pay_now.dart +++ b/lib/widgets/desktop/desktop_pay_now.dart @@ -11,7 +11,6 @@ import '../../events/events.dart'; import '../../generated/l10n.dart'; import '../../models/order.dart'; import '../../models/payment_platform.dart'; -import '../../models/stripe_payment_method.dart'; import '../../models/user.dart'; import '../../routes.dart'; import '../../store/actions.dart'; @@ -19,7 +18,6 @@ import '../../store/store.dart'; import '../../utils/http_util.dart'; import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; import '../../utils/utils.dart'; -import '../../widgets/general/payment_verification_code_dialog.dart'; class DesktopPayNow extends StatefulWidget { final Key key; @@ -38,8 +36,6 @@ class DesktopPayNowState extends State { List paymentPlatforms; User _user; - bool nativePay; - double sideSpace = 0; double mainSpace = 1200; @@ -69,7 +65,7 @@ class DesktopPayNowState extends State { BuildContext mainContext = context; ListView listView = ListView.builder( - itemCount: nativePay ? paymentPlatforms.length + 3 : paymentPlatforms.length + 2, + itemCount: paymentPlatforms.length + 1, itemBuilder: (BuildContext context, int position) { if (position == 0) { return Column( @@ -169,135 +165,7 @@ class DesktopPayNowState extends State { ); } PaymentPlatform paymentPlatform; - if (position == 1) { - if (_user.stripePaymentMethods.length > 0) { - paymentPlatform = paymentPlatforms[position - 1]; - Column column = Column( - children: [], - ); - column.children.add( - Container( - padding: EdgeInsets.all(16.0), - width: double.infinity, - child: Text( - S.of(context).pay_with_existing_cards, - textAlign: TextAlign.left, - ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 1, - color: Colors.black26, - ), - ), - ), - ), - ); - for (StripePaymentMethod stripePaymentMethod in _user.stripePaymentMethods) { - column.children.add( - GestureDetector( - child: Container( - padding: EdgeInsets.only( - top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - stripePaymentMethod.cardBrand == 'visa' ? - Image.asset( - 'assets/images/visa.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : (stripePaymentMethod.cardBrand == 'mastercard' ? - Image.asset( - 'assets/images/master.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : Icon( - Icons.credit_card, size: 50.0, color: Colors.black38,)), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - '**** ${stripePaymentMethod.cardLast4}', - style: TextStyle( - fontSize: 20.0, - ), - ), - ), - Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - S.of(context).expire_token(stripePaymentMethod.cardExpMonth, stripePaymentMethod.cardExpYear), - style: TextStyle( - fontSize: 14.0, - color: Colors.black26, - ), - ), - ), - ], - ), - ), - Icon( - Icons.arrow_forward_ios, - color: Colors.black26, - ), - ], - ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 0.5, - color: Colors.black12, - ), - ), - ), - ), - onTap: () { - showDialog( - context: mainContext, - builder: (BuildContext context) { - return PaymentVerificationCodeDialog(_user, () { - Util.goPayment(context, order, paymentPlatform, - stripePaymentMethod: stripePaymentMethod); - }, () { - - }); - }, - ); - }, - ), - ); - } - column.children.add( - Container( - width: double.infinity, - child: SizedBox.shrink(), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 10, - color: Colors.black26, - ), - ), - ), - ) - ); - return column; - } else { - return SizedBox.shrink(); - } - } - if (position == 2 && nativePay) { - paymentPlatform = paymentPlatforms[position - 2]; - return Util().getNativePay(mainContext, order, paymentPlatform); - } - paymentPlatform = nativePay ? paymentPlatforms[position - 3] : paymentPlatforms[position - 2]; + paymentPlatform = paymentPlatforms[position - 1]; return GestureDetector( child: Container( padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), @@ -371,14 +239,6 @@ class DesktopPayNowState extends State { ).then((data) { paymentPlatforms = (data['payment_platforms'] as List).map((e) => PaymentPlatform.fromJson(e)).toList(); - PaymentPlatform pp = paymentPlatforms[0]; - if (Constants.ENABLE_NATIVE_PAY && pp.publishableKey != null - && pp.publishableKey.isNotEmpty && pp.merchantId != null - && pp.merchantId.isNotEmpty) { - nativePay = true; - } else { - nativePay = false; - } _user = User.fromJson(data['contact']); store.dispatch(UpdateCurrentUser(_user)); eventBus.fire(OnCurrentUserUpdated()); diff --git a/lib/widgets/desktop/desktop_stripe_pay_web.dart b/lib/widgets/desktop/desktop_stripe_pay_web.dart deleted file mode 100644 index 6940334..0000000 --- a/lib/widgets/desktop/desktop_stripe_pay_web.dart +++ /dev/null @@ -1,250 +0,0 @@ - - -import 'package:flutter/material.dart'; -import 'package:stripe_sdk/stripe_sdk.dart'; -import 'package:stripe_sdk/stripe_sdk_ui.dart'; - -import '../../constants.dart'; -import '../../events/eventbus.dart'; -import '../../events/events.dart'; -import '../../generated/l10n.dart'; -import '../../models/order.dart'; -import '../../models/payment_platform.dart'; -import '../../models/stripe_payment_method.dart'; -import '../../routes.dart'; -import '../../store/actions.dart'; -import '../../store/store.dart'; -import '../../utils/utils.dart'; -import '../../widgets/general/breadcrumbs.dart'; -import '../../widgets/general/navigationbar.dart'; - - -class DesktopStripePayWeb extends StatefulWidget { - final Key key; - final Order order; - final PaymentPlatform paymentPlatform; - final StripePaymentMethod stripePaymentMethod; - const DesktopStripePayWeb(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod}); - - @override - State createState() { - return DesktopStripePayWebState(); - } - -} - -class DesktopStripePayWebState extends State { - - GlobalKey _scaffoldKey = GlobalKey(); - final formKey = GlobalKey(); - final card = StripeCard(); - - CardForm form; - - bool isSubmitting; - - double sideSpace = 0; - double mainSpace = 1200; - - @override - Widget build(BuildContext context) { - store.dispatch(UpdateContext(context)); - - if (MediaQuery.of(context).size.width <= 1200) { - mainSpace = MediaQuery.of(context).size.width; - sideSpace = 0; - } else { - mainSpace = 1200; - sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; - } - - Widget body = Center( - child: Icon( - Icons.credit_card, - size: 40.0, - color: Colors.black26, - ), - ); - if (widget.stripePaymentMethod == null) { - form = CardForm(card: card, formKey: formKey, displayPostalCode: false,); - body = ListView( - children: [ - form, - Container( - padding: EdgeInsets.only(top: 20.0, bottom: 20.0, right: 16.0), - alignment: Alignment.centerRight, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - primary: Theme.of(context).primaryColor, - ), - child: Text( - S.of(context).submit, - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - if (formKey.currentState.validate()) { - formKey.currentState.save(); - _paymentRequestWithCard(context); - } else { - ScaffoldMessenger.of(context).showSnackBar( - messageSnackBar( - context, S.of(context).this_credit_card_is_invalid - ) - ); - } - }, - ), - ), - ], - ); - } - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - if (widget.stripePaymentMethod != null) { - _paymentWithPaymentMethod(context); - } - }); - - return Scaffold( - key: _scaffoldKey, - appBar: MiniNavigationBar( - title: S.of(context).blog, - back: true, - breadCrumbs: [ - BreadCrumb(S.of(context).add_credit_card, null), - ], - breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, - ), - body: Row( - children: [ - Container(width: sideSpace,), - Expanded(child: body,), - Container(width: sideSpace,), - ], - ), - ); - } - - @override - void initState() { - super.initState(); - isSubmitting = false; - StripeApi.init(widget.paymentPlatform.publishableKey); - } - - SnackBar messageSnackBar(BuildContext context, String message) { - Column column = Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [Text( - message, - style: TextStyle( - color: Colors.white, - ), - )], - ); - return SnackBar( - content: Container( - height: 45.0, - child: column, - ), - action: SnackBarAction( - label: S.of(context).ok, - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - ), - ); - } - - _paymentWithPaymentMethod(BuildContext context) async { - Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId, - widget.stripePaymentMethod.paymentMethodId, - widget.stripePaymentMethod.paymentMethodType, (response) async { - - if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { - await StripeApi.instance.confirmPaymentIntent( - response.data[Constants.STRIPE_CLIENT_SECRET], - data: { - 'payment_method': response.data['payment_method'], - }, - ).then((result2) { - if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { - Utils.stripeChargedSuccess(widget.order, - widget.stripePaymentMethod.paymentMethodId, - result2['id'], (response) { - if (isSubmitting) { - Navigator.of(context).pop(); - } - eventBus.fire(OnOrderUpdated()); - Routes.router.navigateTo(context, '/orderdetail/${widget - .order.id}', replace: true); - }, - (showErrorDialog) - ); - } else { - showErrorDialog(Exception('Unknown error')); - } - }).catchError(showErrorDialog); - } - }, (showErrorDialog)); - - isSubmitting = true; - Utils.showSubmitDialog(context); - } - - _paymentRequestWithCard(BuildContext context) async { - isSubmitting = true; - Utils.showSubmitDialog(context); - await StripeApi.instance.createPaymentMethodFromCard(card) - .then((result) { - Utils.stripePaymentIntent(widget.order, null, result['id'], result['type'], (response) async { - if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { - await StripeApi.instance.confirmPaymentIntent( - response.data[Constants.STRIPE_CLIENT_SECRET], - data: { - 'payment_method': response.data['payment_method'], - } - ).then((result2) { - if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { - Utils.stripeChargedSuccess(widget.order, - result['id'], // payment method id - result2['id'], (response) { // payment intent id - if (isSubmitting) { - Navigator.of(context).pop(); - } - eventBus.fire(OnOrderUpdated()); - Routes.router.navigateTo(context, '/orderdetail/${widget - .order.id}', replace: true); - }, - (showErrorDialog) - ); - } else { - showErrorDialog(Exception('Unknown error')); - } - }).catchError(showErrorDialog); - } - }, (showErrorDialog), - cardBrand: result['card']['brand'], - cardCountry: result['card']['country'], - cardExpMonth: result['card']['exp_month'], - cardExpYear: result['card']['exp_year'], - cardFunding: result['card']['funding'], - cardLast4: result['card']['last4'], - ); - }).catchError(showErrorDialog); - } - - void showErrorDialog(dynamic error) { - if (isSubmitting) { - Navigator.of(context).pop(); - isSubmitting = false; - } - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }); - } -} \ No newline at end of file diff --git a/lib/widgets/general/e_transfer_pay.dart b/lib/widgets/general/e_transfer_pay.dart new file mode 100644 index 0000000..18e7130 --- /dev/null +++ b/lib/widgets/general/e_transfer_pay.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/order.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; + +/// e-Transfer 支付页(替代原 Stripe 收款)。 +/// 向顾客展示 e-Transfer 邮箱,并提醒在备注里写 R# 以便自动处理订单。 +class ETransferPay extends StatelessWidget { + final Order order; + + const ETransferPay({required this.order}); + + String get _remark => + '${Constants.ETRANSFER_REMARK_PREFIX}${order.id}'; + + Future _copy(BuildContext context, String text, String label) async { + await Clipboard.setData(ClipboardData(text: text)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('$label copied')), + ); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + final isWide = MediaQuery.of(context).size.width > 700; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () => Navigator.of(context).pop(), + ), + title: const Text('e-Transfer Payment'), + ), + body: Center( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: isWide ? 640 : double.infinity, + ), + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 金额 + Container( + padding: const EdgeInsets.symmetric( + vertical: 24, horizontal: 16), + decoration: BoxDecoration( + color: Colors.lightBlueAccent.withOpacity(0.12), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).payment_amount, + style: const TextStyle( + fontSize: 15, color: Colors.black54), + ), + Text( + '\$${order.totalPrice.toStringAsFixed(2)}', + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + const SizedBox(height: 24), + const Text( + 'Please complete your payment via Interac e-Transfer:', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 16), + // 邮箱 + _CopyRow( + icon: Icons.alternate_email, + title: 'Send e-Transfer to', + value: Constants.ETRANSFER_EMAIL, + onCopy: () => _copy( + context, Constants.ETRANSFER_EMAIL, 'Email'), + ), + const SizedBox(height: 16), + // 备注 + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFF8E1), + border: Border.all(color: Colors.orangeAccent), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Icon(Icons.priority_high, color: Colors.orange), + SizedBox(width: 8), + Text( + 'Important — message / remark', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 8), + const Text( + 'In the e-Transfer message (remark) field, please include ' + 'the following code so we can match and process your order ' + 'automatically:', + ), + const SizedBox(height: 12), + _CopyRow( + icon: Icons.receipt_long, + title: 'Order code', + value: _remark, + emphasize: true, + onCopy: () => + _copy(context, _remark, 'Order code'), + ), + ], + ), + ), + const SizedBox(height: 24), + FilledButton.icon( + icon: const Icon(Icons.check_circle_outline), + label: const Text("I've sent the payment"), + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + ), + onPressed: () { + Routes.router.navigateTo(context, '/orderdetail/${order.id}', + replace: true); + }, + ), + ], + ), + ), + ), + ), + ); + } +} + +class _CopyRow extends StatelessWidget { + final IconData icon; + final String title; + final String value; + final VoidCallback onCopy; + final bool emphasize; + + const _CopyRow({ + required this.icon, + required this.title, + required this.value, + required this.onCopy, + this.emphasize = false, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.black12), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon(icon, color: Colors.lightBlueAccent), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: const TextStyle( + fontSize: 12, color: Colors.black54)), + Text( + value, + style: TextStyle( + fontSize: emphasize ? 20 : 16, + fontWeight: + emphasize ? FontWeight.bold : FontWeight.w600, + letterSpacing: emphasize ? 1.0 : 0, + ), + ), + ], + ), + ), + IconButton( + icon: const Icon(Icons.copy, color: Colors.black54), + onPressed: onCopy, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/general/stripe_pay.dart b/lib/widgets/general/stripe_pay.dart deleted file mode 100644 index bbc442c..0000000 --- a/lib/widgets/general/stripe_pay.dart +++ /dev/null @@ -1,158 +0,0 @@ - - -import 'package:flutter/material.dart'; -import 'package:stripe_payment/stripe_payment.dart'; - -import '../../constants.dart'; -import '../../events/eventbus.dart'; -import '../../events/events.dart'; -import '../../models/order.dart'; -import '../../models/payment_platform.dart'; -import '../../models/stripe_payment_method.dart'; -import '../../routes.dart'; -import '../../store/actions.dart'; -import '../../store/store.dart'; -import '../../utils/utils.dart'; - - -class StripePay extends StatefulWidget { - final Key key; - final Order order; - final PaymentPlatform paymentPlatform; - final StripePaymentMethod stripePaymentMethod; - const StripePay(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod}); - - @override - State createState() { - return StripePayState(); - } - -} - -class StripePayState extends State { - - GlobalKey _scaffoldKey = GlobalKey(); - - bool isSubmitting; - - @override - Widget build(BuildContext context) { - store.dispatch(UpdateContext(context)); - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - if (widget.stripePaymentMethod != null) { - _paymentWithPaymentMethod(context); - } else { - _paymentRequestWithCardForm(context); - } - }); - - return Scaffold( - key: _scaffoldKey, - body: Center( - child: Icon( - Icons.credit_card, - size: 40.0, - color: Colors.black26, - ), - ), - ); - } - - @override - void initState() { - super.initState(); - isSubmitting = false; - StripePayment.setOptions( - StripeOptions(publishableKey: widget.paymentPlatform.publishableKey, - merchantId: widget.paymentPlatform.merchantId, - androidPayMode: 'test') - ); - } - - _paymentWithPaymentMethod(BuildContext context) async { - Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId, - widget.stripePaymentMethod.paymentMethodId, - widget.stripePaymentMethod.paymentMethodType, (response){ - - if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { - StripePayment.confirmPaymentIntent( - PaymentIntent( - clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET], - paymentMethodId: response.data['payment_method'], - ), - ).then((paymentIntentResult) { - if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) { - Utils.stripeChargedSuccess(widget.order, - widget.stripePaymentMethod.paymentMethodId, - paymentIntentResult.paymentIntentId, - (response) { - eventBus.fire(OnOrderUpdated()); - Routes.router.navigateTo(context, '/orderdetail/${widget - .order.id}', replace: true); - }, - (showErrorDialog) - ); - } else { - showErrorDialog(Exception('Unknown error')); - } - }).catchError(showErrorDialog); - } - }, (showErrorDialog)); - isSubmitting = true; - Utils.showSubmitDialog(context); - } - - _paymentRequestWithCardForm(BuildContext context) async { - StripePayment.paymentRequestWithCardForm( - CardFormPaymentRequest() - ).then((paymentMethod) { - Utils.stripePaymentIntent(widget.order, null, paymentMethod.id, paymentMethod.type, (response){ - - if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { - StripePayment.confirmPaymentIntent( - PaymentIntent( - clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET], - paymentMethodId: response.data['payment_method'], - ), - ).then((paymentIntentResult) { - if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) { - Utils.stripeChargedSuccess(widget.order, - paymentMethod.id, - paymentIntentResult.paymentIntentId, - (response) { - eventBus.fire(OnOrderUpdated()); - Routes.router.navigateTo(context, '/orderdetail/${widget - .order.id}', replace: true); - }, - (showErrorDialog) - ); - } else { - showErrorDialog(Exception('Unknown error')); - } - }).catchError(showErrorDialog); - } - }, (showErrorDialog), - cardBrand: paymentMethod.card.brand, - cardCountry: paymentMethod.card.country, - cardExpMonth: paymentMethod.card.expMonth, - cardExpYear: paymentMethod.card.expYear, - cardFunding: paymentMethod.card.funding, - cardLast4: paymentMethod.card.last4, - ); - }).catchError(showErrorDialog); - isSubmitting = true; - Utils.showSubmitDialog(context); - } - - void showErrorDialog(dynamic error) { - if (isSubmitting) { - Navigator.of(context).pop(); - isSubmitting = false; - } - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }); - } -} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_my_cards.dart b/lib/widgets/mobile/mobile_my_cards.dart index 38481bb..62b21be 100644 --- a/lib/widgets/mobile/mobile_my_cards.dart +++ b/lib/widgets/mobile/mobile_my_cards.dart @@ -1,32 +1,14 @@ - import 'package:flutter/material.dart'; -import '../../events/eventbus.dart'; -import '../../events/events.dart'; +import '../../constants.dart'; import '../../generated/l10n.dart'; -import '../../models/stripe_payment_method.dart'; -import '../../models/user.dart'; import '../../store/actions.dart'; import '../../store/store.dart'; -import '../../utils/http_util.dart'; -import '../../utils/utils.dart'; -class MobileMyCards extends StatefulWidget { - final Key key; - const MobileMyCards({this.key}); - - @override - State createState() { - return MyCardsState(); - } - -} - -class MyCardsState extends State { - User _user; - - bool isSubmitting; +/// “My Cards” 页(移动端):已停止保存银行卡(Stripe 移除)。改为展示 e-Transfer 说明。 +class MobileMyCards extends StatelessWidget { + const MobileMyCards({Key? key}); @override Widget build(BuildContext context) { @@ -35,150 +17,62 @@ class MyCardsState extends State { return Scaffold( appBar: AppBar( leading: IconButton( - icon: Icon(Icons.arrow_back_ios), - onPressed: (){ - Navigator.of(context).pop(); - }, + icon: const Icon(Icons.arrow_back_ios), + onPressed: () => Navigator.of(context).pop(), ), title: Text(S.of(context).my_cards), backgroundColor: Theme.of(context).primaryColor, ), - body: ListView.builder( - itemCount: _user.stripePaymentMethods.length, - itemBuilder: (BuildContext context, int position) { - StripePaymentMethod paymentMethod = _user.stripePaymentMethods[position]; - return cardWidget(paymentMethod); - } - ), - ); - } - - Widget cardWidget(StripePaymentMethod paymentMethod) { - return Container( - padding: EdgeInsets.only( - top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - paymentMethod.cardBrand == 'visa' ? - Image.asset( - 'assets/images/visa.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : (paymentMethod.cardBrand == 'mastercard' ? - Image.asset( - 'assets/images/master.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : Icon( - Icons.credit_card, size: 50.0, color: Colors.black38,)), - Expanded( + body: ListView( + children: [ + Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.lightBlueAccent.withOpacity(0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.black12), + ), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - '**** ${paymentMethod.cardLast4}', - style: TextStyle( - fontSize: 20.0, + children: [ + const Row( + children: [ + Icon(Icons.info_outline, color: Colors.lightBlueAccent), + SizedBox(width: 8), + Expanded( + child: Text( + 'Saved cards are no longer used', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.bold), + ), ), - ), + ], ), - Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - S.of(context).expire_token(paymentMethod.cardExpMonth, paymentMethod.cardExpYear), - style: TextStyle( - fontSize: 14.0, - color: Colors.black26, - ), - ), + const SizedBox(height: 12), + const Text( + 'We have switched to Interac e-Transfer for payments. ' + 'You no longer need to save a credit card.', + ), + const SizedBox(height: 16), + const Text('Send your e-Transfer to:'), + const SizedBox(height: 4), + SelectableText( + Constants.ETRANSFER_EMAIL, + style: const TextStyle( + fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + const Text( + 'Remember to put your order code (e.g. R#) in the ' + 'e-Transfer message so we can process your order automatically.', + style: TextStyle(color: Colors.black54), ), ], ), ), - IconButton( - icon: Icon(Icons.clear, color: Colors.black26,), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text(S.of(context).warning), - content: Text( - S.of(context).are_you_sure_to_remove_the_card, - ), - actions: [ - TextButton( - child: Text(S.of(context).cancel), - style: TextButton.styleFrom( - primary: Theme.of(context).primaryColor, - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text(S.of(context).yes_i_am_sure), - onPressed: () { - Navigator.of(context).pop(); - _removeCard(paymentMethod); - }, - ) - ], - ); - } - ); - }, - ), ], ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 0.5, - color: Colors.black12, - ), - ), - ), ); } - - _removeCard(StripePaymentMethod paymentMethod) { - HttpUtil.httpDelete('v1/stripe-card/${paymentMethod.id}', (response) { - _user = User.fromJson(response.data); - store.dispatch(UpdateCurrentUser(_user)); - eventBus.fire(OnCurrentUserUpdated()); - if (mounted) { - Navigator.of(context).pop(); - setState(() { - isSubmitting = false; - }); - } - }).catchError((error) { - if (isSubmitting) { - Navigator.of(context).pop(); - isSubmitting = false; - } - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }); - }); - isSubmitting = true; - Utils.showSubmitDialog(context); - } - - @override - void initState() { - super.initState(); - setState(() { - isSubmitting = false; - _user = store.state.user; - }); - } -} \ No newline at end of file +} diff --git a/lib/widgets/mobile/mobile_pay_now.dart b/lib/widgets/mobile/mobile_pay_now.dart index 5428d14..938f31b 100644 --- a/lib/widgets/mobile/mobile_pay_now.dart +++ b/lib/widgets/mobile/mobile_pay_now.dart @@ -9,7 +9,6 @@ import '../../events/events.dart'; import '../../generated/l10n.dart'; import '../../models/order.dart'; import '../../models/payment_platform.dart'; -import '../../models/stripe_payment_method.dart'; import '../../models/user.dart'; import '../../routes.dart'; import '../../store/actions.dart'; @@ -17,7 +16,6 @@ import '../../store/store.dart'; import '../../utils/http_util.dart'; import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; import '../../utils/utils.dart'; -import '../../widgets/general/payment_verification_code_dialog.dart'; class MobilePayNow extends StatefulWidget { final Key key; @@ -36,7 +34,6 @@ class MobilePayNowState extends State { List paymentPlatforms; User _user; - bool nativePay; @override Widget build(BuildContext context) { @@ -56,7 +53,7 @@ class MobilePayNowState extends State { BuildContext mainContext = context; ListView listView = ListView.builder( - itemCount: nativePay ? paymentPlatforms.length + 3 : paymentPlatforms.length + 2, + itemCount: paymentPlatforms.length + 1, itemBuilder: (BuildContext context, int position) { if (position == 0) { return Column( @@ -156,135 +153,7 @@ class MobilePayNowState extends State { ); } PaymentPlatform paymentPlatform; - if (position == 1) { - if (_user.stripePaymentMethods.length > 0) { - paymentPlatform = paymentPlatforms[position - 1]; - Column column = Column( - children: [], - ); - column.children.add( - Container( - padding: EdgeInsets.all(16.0), - width: double.infinity, - child: Text( - S.of(context).pay_with_existing_cards, - textAlign: TextAlign.left, - ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 1, - color: Colors.black26, - ), - ), - ), - ), - ); - for (StripePaymentMethod stripePaymentMethod in _user.stripePaymentMethods) { - column.children.add( - GestureDetector( - child: Container( - padding: EdgeInsets.only( - top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - stripePaymentMethod.cardBrand == 'visa' ? - Image.asset( - 'assets/images/visa.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : (stripePaymentMethod.cardBrand == 'mastercard' ? - Image.asset( - 'assets/images/master.png', - width: 50.0, - height: 50.0, - fit: BoxFit.fill, - ) : Icon( - Icons.credit_card, size: 50.0, color: Colors.black38,)), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - '**** ${stripePaymentMethod.cardLast4}', - style: TextStyle( - fontSize: 20.0, - ), - ), - ), - Container( - margin: EdgeInsets.only(left: 20.0, right: 10.0), - child: Text( - S.of(context).expire_token(stripePaymentMethod.cardExpMonth, stripePaymentMethod.cardExpYear), - style: TextStyle( - fontSize: 14.0, - color: Colors.black26, - ), - ), - ), - ], - ), - ), - Icon( - Icons.arrow_forward_ios, - color: Colors.black26, - ), - ], - ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 0.5, - color: Colors.black12, - ), - ), - ), - ), - onTap: () { - showDialog( - context: mainContext, - builder: (BuildContext context) { - return PaymentVerificationCodeDialog(_user, () { - Util.goPayment(context, order, paymentPlatform, - stripePaymentMethod: stripePaymentMethod); - }, () { - - }); - }, - ); - }, - ), - ); - } - column.children.add( - Container( - width: double.infinity, - child: SizedBox.shrink(), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 10, - color: Colors.black26, - ), - ), - ), - ) - ); - return column; - } else { - return SizedBox.shrink(); - } - } - if (position == 2 && nativePay) { - paymentPlatform = paymentPlatforms[position - 2]; - return Util().getNativePay(mainContext, order, paymentPlatform); - } - paymentPlatform = nativePay ? paymentPlatforms[position - 3] : paymentPlatforms[position - 2]; + paymentPlatform = paymentPlatforms[position - 1]; return GestureDetector( child: Container( padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), @@ -354,14 +223,6 @@ class MobilePayNowState extends State { ).then((data) { paymentPlatforms = (data['payment_platforms'] as List).map((e) => PaymentPlatform.fromJson(e)).toList(); - PaymentPlatform pp = paymentPlatforms[0]; - if (Constants.ENABLE_NATIVE_PAY && pp.publishableKey != null - && pp.publishableKey.isNotEmpty && pp.merchantId != null - && pp.merchantId.isNotEmpty) { - nativePay = true; - } else { - nativePay = false; - } _user = User.fromJson(data['contact']); store.dispatch(UpdateCurrentUser(_user)); eventBus.fire(OnCurrentUserUpdated()); diff --git a/lib/widgets/mobile/mobile_stripe_pay_web.dart b/lib/widgets/mobile/mobile_stripe_pay_web.dart deleted file mode 100644 index a6c80a6..0000000 --- a/lib/widgets/mobile/mobile_stripe_pay_web.dart +++ /dev/null @@ -1,221 +0,0 @@ - - -import 'package:flutter/material.dart'; -import 'package:stripe_sdk/stripe_sdk.dart'; -import 'package:stripe_sdk/stripe_sdk_ui.dart'; - -import '../../constants.dart'; -import '../../events/eventbus.dart'; -import '../../events/events.dart'; -import '../../generated/l10n.dart'; -import '../../models/order.dart'; -import '../../models/payment_platform.dart'; -import '../../models/stripe_payment_method.dart'; -import '../../routes.dart'; -import '../../store/actions.dart'; -import '../../store/store.dart'; -import '../../utils/utils.dart'; - - -class MobileStripePayWeb extends StatefulWidget { - final Key key; - final Order order; - final PaymentPlatform paymentPlatform; - final StripePaymentMethod stripePaymentMethod; - const MobileStripePayWeb(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod}); - - @override - State createState() { - return MobileStripePayWebState(); - } - -} - -class MobileStripePayWebState extends State { - - GlobalKey _scaffoldKey = GlobalKey(); - final formKey = GlobalKey(); - final card = StripeCard(); - - CardForm form; - - bool isSubmitting; - - @override - Widget build(BuildContext context) { - store.dispatch(UpdateContext(context)); - - Widget body = Center( - child: Icon( - Icons.credit_card, - size: 40.0, - color: Colors.black26, - ), - ); - if (widget.stripePaymentMethod == null) { - form = CardForm(card: card, formKey: formKey, displayPostalCode: false,); - body = ListView( - children: [form,], - ); - } - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - if (widget.stripePaymentMethod != null) { - _paymentWithPaymentMethod(context); - } - }); - - return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - leading: IconButton( - icon: Icon(Icons.arrow_back_ios), - onPressed: (){ - Navigator.of(context).pop(); - }, - ), - title: Text(S.of(context).add_credit_card), - backgroundColor: Theme.of(context).primaryColor, - actions: [ - IconButton( - icon: Icon(Icons.check), - onPressed: widget.stripePaymentMethod == null ? () { - if (formKey.currentState.validate()) { - formKey.currentState.save(); - _paymentRequestWithCard(context); - } else { - ScaffoldMessenger.of(context).showSnackBar( - messageSnackBar( - context, S.of(context).this_credit_card_is_invalid - ) - ); - } - } : null, - ), - ], - ), - body: body, - ); - } - - @override - void initState() { - super.initState(); - isSubmitting = false; - StripeApi.init(widget.paymentPlatform.publishableKey); - } - - SnackBar messageSnackBar(BuildContext context, String message) { - Column column = Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [Text( - message, - style: TextStyle( - color: Colors.white, - ), - )], - ); - return SnackBar( - content: Container( - height: 45.0, - child: column, - ), - action: SnackBarAction( - label: S.of(context).ok, - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - ), - ); - } - - _paymentWithPaymentMethod(BuildContext context) async { - Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId, - widget.stripePaymentMethod.paymentMethodId, - widget.stripePaymentMethod.paymentMethodType, (response) async { - - if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { - await StripeApi.instance.confirmPaymentIntent( - response.data[Constants.STRIPE_CLIENT_SECRET], - data: { - 'payment_method': response.data['payment_method'], - }, - ).then((result2) { - if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { - Utils.stripeChargedSuccess(widget.order, - widget.stripePaymentMethod.paymentMethodId, - result2['id'], (response) { - if (isSubmitting) { - Navigator.of(context).pop(); - } - eventBus.fire(OnOrderUpdated()); - Routes.router.navigateTo(context, '/orderdetail/${widget - .order.id}', replace: true); - }, - (showErrorDialog) - ); - } else { - showErrorDialog(Exception('Unknown error')); - } - }).catchError(showErrorDialog); - } - }, (showErrorDialog)); - - isSubmitting = true; - Utils.showSubmitDialog(context); - } - - _paymentRequestWithCard(BuildContext context) async { - isSubmitting = true; - Utils.showSubmitDialog(context); - await StripeApi.instance.createPaymentMethodFromCard(card) - .then((result) { - Utils.stripePaymentIntent(widget.order, null, result['id'], result['type'], (response) async { - if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { - await StripeApi.instance.confirmPaymentIntent( - response.data[Constants.STRIPE_CLIENT_SECRET], - data: { - 'payment_method': response.data['payment_method'], - } - ).then((result2) { - if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { - Utils.stripeChargedSuccess(widget.order, - result['id'], // payment method id - result2['id'], (response) { // payment intent id - if (isSubmitting) { - Navigator.of(context).pop(); - } - eventBus.fire(OnOrderUpdated()); - Routes.router.navigateTo(context, '/orderdetail/${widget - .order.id}', replace: true); - }, - (showErrorDialog) - ); - } else { - showErrorDialog(Exception('Unknown error')); - } - }).catchError(showErrorDialog); - } - }, (showErrorDialog), - cardBrand: result['card']['brand'], - cardCountry: result['card']['country'], - cardExpMonth: result['card']['exp_month'], - cardExpYear: result['card']['exp_year'], - cardFunding: result['card']['funding'], - cardLast4: result['card']['last4'], - ); - }).catchError(showErrorDialog); - } - - void showErrorDialog(dynamic error) { - if (isSubmitting) { - Navigator.of(context).pop(); - isSubmitting = false; - } - Utils.showMessageDialog(context, error, onOk: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }); - } -} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index ccf6a26..250a433 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.1+2 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.29.0" dependencies: flutter: @@ -77,13 +78,7 @@ dependencies: toggle_switch: ^1.2.0 pinput: ^1.2.0 google_maps_flutter: ^2.0.6 #^1.2.0 - stripe_payment: ^1.0.10 -# stripe_sdk: -# git: -# url: git://github.com/romme86/stripe-sdk.git - stripe_sdk: - git: - url: https://github.com/ezet/stripe-sdk.git +# Stripe removed — switched to e-Transfer. See UPGRADE_NOTES.md. uuid: ^3.0.4 #^2.2.2 ffi: ^1.0.0 # firebase_messaging: ^10.0.4 #^7.0.3