Merge pull request #36 from woosignal/master

6.1.0
This commit is contained in:
Anthony Gordon 2022-07-09 15:15:09 +07:00 committed by GitHub
commit faa62bb9ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 380 additions and 117 deletions

View File

@ -38,7 +38,11 @@ PAYPAL_LIVE_MODE=null
PAYPAL_LOCALE=null PAYPAL_LOCALE=null
# Use BCP-47 code from this link https://developer.paypal.com/docs/api/reference/locale-codes/ # Use BCP-47 code from this link https://developer.paypal.com/docs/api/reference/locale-codes/
# *<! ------ RAZORPAY (OPTIONAL) ------!>*
RAZORPAY_API_KEY=""
# *<! ------ EXTRAS ------!>* # *<! ------ EXTRAS ------!>*
PRODUCT_PLACEHOLDER_IMAGE="https://woosignal.com/images/woocommerce-placeholder.png" PRODUCT_PLACEHOLDER_IMAGE="https://woosignal.com/images/woocommerce-placeholder.png"
# Sets the default placeholder image for products with no image # Sets the default placeholder image for products with no image

View File

@ -1,3 +1,10 @@
## [6.1.0] - 2022-07-09
* Add RazorPay
* Null safety tweaks to widgets
* Ability for users to delete their accounts using WP_JSON
* Pubspec.yaml dependency updates
## [6.0.0] - 2022-05-19 ## [6.0.0] - 2022-05-19
* Migrate to Nylo 3.x * Migrate to Nylo 3.x

View File

@ -4,7 +4,7 @@
# WooCommerce App: Label StoreMax # WooCommerce App: Label StoreMax
### Label StoreMax - v6.0.0 ### Label StoreMax - v6.1.0
[Official WooSignal WooCommerce App](https://woosignal.com) [Official WooSignal WooCommerce App](https://woosignal.com)
@ -44,7 +44,7 @@ Full documentation this available [here](https://woosignal.com/docs/app/label-st
- Browse products, make orders, customer login (via WordPress) - Browse products, make orders, customer login (via WordPress)
- Change app name, logo, customize default language, currency + more - Change app name, logo, customize default language, currency + more
- Light and dark mode - Light and dark mode
- Stripe, Cash On Delivery, PayPal - Stripe, Cash On Delivery, PayPal, RazorPay
- Localized for en, es, pt, it, hi, fr, zh, tr, nl, de - Localized for en, es, pt, it, hi, fr, zh, tr, nl, de
- Orders show as normal in WooCommerce - Orders show as normal in WooCommerce

View File

@ -219,5 +219,10 @@
"Leave a review": "Hinterlassen Sie eine Bewertung", "Leave a review": "Hinterlassen Sie eine Bewertung",
"How would you rate": "Wie beurteilen Sie", "How would you rate": "Wie beurteilen Sie",
"Submit": "einreichen", "Submit": "einreichen",
"Your review has been submitted": "Ihre Bewertung wurde übermittelt" "Your review has been submitted": "Ihre Bewertung wurde übermittelt",
"Delete Account": "Konto löschen",
"Delete your account": "Lösche deinen Account",
"Are you sure?": "Bist du dir sicher?",
"Yes, delete my account": "Ja, lösche mein Konto",
"Account deleted": "Konto gelöscht"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "Leave a review", "Leave a review": "Leave a review",
"How would you rate": "How would you rate", "How would you rate": "How would you rate",
"Submit": "Submit", "Submit": "Submit",
"Your review has been submitted": "Your review has been submitted" "Your review has been submitted": "Your review has been submitted",
"Delete Account": "Delete Account",
"Delete your account": "Delete your account",
"Are you sure?": "Are you sure?",
"Yes, delete my account": "Yes, delete my account",
"Account deleted": "Account deleted"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "Dejar un comentario", "Leave a review": "Dejar un comentario",
"How would you rate": "Cómo calificarías", "How would you rate": "Cómo calificarías",
"Submit": "Entregar", "Submit": "Entregar",
"Your review has been submitted": "Tu reseña ha sido enviada" "Your review has been submitted": "Tu reseña ha sido enviada",
"Delete Account": "Borrar cuenta",
"Delete your account": "eliminar su cuenta",
"Are you sure?": "¿Está seguro?",
"Yes, delete my account": "Sí, eliminar mi cuenta",
"Account deleted": "Cuenta borrada"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "Laisser un commentaire", "Leave a review": "Laisser un commentaire",
"How would you rate": "Comment évalueriez-vous", "How would you rate": "Comment évalueriez-vous",
"Submit": "Soumettre", "Submit": "Soumettre",
"Your review has been submitted": "Votre avis a été soumis" "Your review has been submitted": "Votre avis a été soumis",
"Delete Account": "Supprimer le compte",
"Delete your account": "Supprimer votre compte",
"Are you sure?": "Êtes-vous sûr?",
"Yes, delete my account": "Oui, supprimer mon compte",
"Account deleted": "Compte supprimé"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "sameeksha likhen", "Leave a review": "sameeksha likhen",
"How would you rate": "aap ise kya ret karate hain", "How would you rate": "aap ise kya ret karate hain",
"Submit": "prastut karana", "Submit": "prastut karana",
"Your review has been submitted": "aapakee sameeksha jama ho chukee hai" "Your review has been submitted": "aapakee sameeksha jama ho chukee hai",
"Delete Account": "khaata hata do",
"Delete your account": "apane khaate ko nasht karo",
"Are you sure?": "kya aapako yakeen hai?",
"Yes, delete my account": "haan, mera akaunt dileet kar do",
"Account deleted": "khaata hataaya gaya"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "Lascia una recensione", "Leave a review": "Lascia una recensione",
"How would you rate": "Come valuteresti", "How would you rate": "Come valuteresti",
"Submit": "Invia", "Submit": "Invia",
"Your review has been submitted": "La tua recensione è stata inviata" "Your review has been submitted": "La tua recensione è stata inviata",
"Delete Account": "Eliminare l'account",
"Delete your account": "cancella il tuo account",
"Are you sure?": "Sei sicuro?",
"Yes, delete my account": "Sì, elimina il mio account",
"Account deleted": "Account cancellato"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "Laat je mening achter", "Leave a review": "Laat je mening achter",
"How would you rate": "Hoe zou jij beoordelen", "How would you rate": "Hoe zou jij beoordelen",
"Submit": "Indienen", "Submit": "Indienen",
"Your review has been submitted": "Uw beoordeling is verzonden" "Your review has been submitted": "Uw beoordeling is verzonden",
"Delete Account": "Account verwijderen",
"Delete your account": "Verwijder je account",
"Are you sure?": "Weet je het zeker?",
"Yes, delete my account": "Ja, verwijder mijn account",
"Account deleted": "Account verwijderd"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "Deixe um comentário", "Leave a review": "Deixe um comentário",
"How would you rate": "Como você avaliaria", "How would you rate": "Como você avaliaria",
"Submit": "Enviar", "Submit": "Enviar",
"Your review has been submitted": "Sua avaliação foi enviada" "Your review has been submitted": "Sua avaliação foi enviada",
"Delete Account": "Deletar conta",
"Delete your account": "Deletar sua conta",
"Are you sure?": "Tem certeza?",
"Yes, delete my account": "Sim, excluir minha conta",
"Account deleted": "Conta excluída"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "İnceleme bırak", "Leave a review": "İnceleme bırak",
"How would you rate": "Nasıl değerlendirirsiniz", "How would you rate": "Nasıl değerlendirirsiniz",
"Submit": "Göndermek", "Submit": "Göndermek",
"Your review has been submitted": "İncelemeniz gönderildi" "Your review has been submitted": "İncelemeniz gönderildi",
"Delete Account": "Hesabı sil",
"Delete your account": "Hesabını sil",
"Are you sure?": "Emin misin?",
"Yes, delete my account": "Evet, hesabımı sil",
"Account deleted": "Hesap silindi"
} }

View File

@ -219,5 +219,10 @@
"Leave a review": "发表评论", "Leave a review": "发表评论",
"How would you rate": "你如何评价", "How would you rate": "你如何评价",
"Submit": "提交", "Submit": "提交",
"Your review has been submitted": "您的评论已提交" "Your review has been submitted": "您的评论已提交",
"Delete Account": "删除帐户",
"Delete your account": "删除您的帐户",
"Are you sure?": "你确定吗?",
"Yes, delete my account": "是的,删除我的帐户",
"Account deleted": "帐号已删除"
} }

View File

@ -0,0 +1,81 @@
//
// LabelCore
// Label StoreMAX
//
// Created by Anthony Gordon.
// 2022, WooSignal Ltd. All rights reserved.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
import 'package:flutter/widgets.dart';
import 'package:flutter_app/app/models/cart.dart';
import 'package:flutter_app/bootstrap/data/order_wc.dart';
import 'package:flutter_app/bootstrap/helpers.dart';
import 'package:flutter_app/resources/pages/checkout_confirmation.dart';
import 'package:nylo_framework/nylo_framework.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import 'package:woosignal/models/response/tax_rate.dart';
import 'package:woosignal/models/payload/order_wc.dart';
import 'package:woosignal/models/response/order.dart';
razorPay(context,
{required CheckoutConfirmationPageState state, TaxRate? taxRate}) async {
Razorpay razorpay = Razorpay();
razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS,
(PaymentSuccessResponse response) async {
OrderWC orderWC = await buildOrderWC(taxRate: taxRate);
Order? order = await appWooSignal((api) => api.createOrder(orderWC));
if (order != null) {
Cart.getInstance.clear();
Navigator.pushNamed(context, "/checkout-status", arguments: order);
} else {
showToastNotification(
context,
title: "Error".tr(),
description: trans("Something went wrong, please contact our store"),
);
state.reloadState(showLoader: false);
}
});
razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, (PaymentFailureResponse response) {
showToastNotification(context,
title: trans("Error"),
description: response.message ?? "",
style: ToastNotificationStyleType.WARNING);
state.reloadState(showLoader: false);
});
razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
// CHECKOUT HELPER
await checkout(taxRate, (total, billingDetails, cart) async {
var options = {
'key': getEnv('RAZORPAY_API_KEY'),
'amount': (double.parse(total) * 100).toInt(),
'name': getEnv('APP_NAME'),
'description': await cart.cartShortDesc(),
'prefill': {
"name": [
billingDetails!.billingAddress?.firstName,
billingDetails.billingAddress?.lastName
].where((t) => t != null || t != "").toList().join(" "),
"method": "card",
'email': billingDetails.billingAddress?.emailAddress ?? ""
}
};
state.reloadState(showLoader: true);
razorpay.open(options);
});
}
void _handleExternalWallet(ExternalWalletResponse response) {}

View File

@ -51,7 +51,7 @@ stripePay(context,
// // CHECKOUT HELPER // // CHECKOUT HELPER
await checkout(taxRate, (total, billingDetails, cart) async { await checkout(taxRate, (total, billingDetails, cart) async {
Map<String, dynamic> address = { Map<String, dynamic> address = {
"name": billingDetails!.billingAddress!.nameFull(), "name": billingDetails!.billingAddress?.nameFull(),
"line1": billingDetails.shippingAddress!.addressLine, "line1": billingDetails.shippingAddress!.addressLine,
"city": billingDetails.shippingAddress!.city, "city": billingDetails.shippingAddress!.city,
"postal_code": billingDetails.shippingAddress!.postalCode, "postal_code": billingDetails.shippingAddress!.postalCode,
@ -62,7 +62,7 @@ stripePay(context,
rsp = await appWooSignal((api) => api.stripePaymentIntent( rsp = await appWooSignal((api) => api.stripePaymentIntent(
amount: total, amount: total,
email: billingDetails.billingAddress!.emailAddress, email: billingDetails.billingAddress?.emailAddress,
desc: cartShortDesc, desc: cartShortDesc,
shipping: address, shipping: address,
)); ));

View File

@ -60,19 +60,19 @@ Future<OrderWC> buildOrderWC({TaxRate? taxRate, bool markPaid = true}) async {
BillingDetails billingDetails = checkoutSession.billingDetails!; BillingDetails billingDetails = checkoutSession.billingDetails!;
Billing billing = Billing(); Billing billing = Billing();
billing.firstName = billingDetails.billingAddress!.firstName; billing.firstName = billingDetails.billingAddress?.firstName;
billing.lastName = billingDetails.billingAddress!.lastName; billing.lastName = billingDetails.billingAddress?.lastName;
billing.address1 = billingDetails.billingAddress!.addressLine; billing.address1 = billingDetails.billingAddress?.addressLine;
billing.city = billingDetails.billingAddress!.city; billing.city = billingDetails.billingAddress?.city;
billing.postcode = billingDetails.billingAddress!.postalCode; billing.postcode = billingDetails.billingAddress?.postalCode;
billing.email = billingDetails.billingAddress!.emailAddress; billing.email = billingDetails.billingAddress?.emailAddress;
if (billingDetails.billingAddress!.phoneNumber != "") { if (billingDetails.billingAddress?.phoneNumber != "") {
billing.phone = billingDetails.billingAddress!.phoneNumber; billing.phone = billingDetails.billingAddress?.phoneNumber;
} }
if (billingDetails.billingAddress!.customerCountry!.hasState()) { if (billingDetails.billingAddress?.customerCountry?.hasState() ?? false) {
billing.state = billingDetails.billingAddress!.customerCountry!.state!.name; billing.state = billingDetails.billingAddress?.customerCountry!.state!.name;
} }
billing.country = billingDetails.billingAddress!.customerCountry!.name; billing.country = billingDetails.billingAddress?.customerCountry!.name;
orderWC.billing = billing; orderWC.billing = billing;

View File

@ -37,7 +37,7 @@ import 'package:flutter_web_browser/flutter_web_browser.dart';
import 'package:math_expressions/math_expressions.dart'; import 'package:math_expressions/math_expressions.dart';
import 'package:money_formatter/money_formatter.dart'; import 'package:money_formatter/money_formatter.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:status_alert/status_alert.dart'; import 'package:status_alert/status_alert.dart';
import 'package:woosignal/models/response/products.dart'; import 'package:woosignal/models/response/products.dart';
import 'package:woosignal/models/response/tax_rate.dart'; import 'package:woosignal/models/response/tax_rate.dart';

View File

@ -1,6 +1,7 @@
import 'package:flutter_app/app/models/payment_type.dart'; import 'package:flutter_app/app/models/payment_type.dart';
import 'package:flutter_app/app/providers/cash_on_delivery.dart'; import 'package:flutter_app/app/providers/cash_on_delivery.dart';
import 'package:flutter_app/app/providers/paypal_pay.dart'; import 'package:flutter_app/app/providers/paypal_pay.dart';
import 'package:flutter_app/app/providers/razorpay_pay.dart';
import 'package:flutter_app/app/providers/stripe_pay.dart'; import 'package:flutter_app/app/providers/stripe_pay.dart';
import 'package:flutter_app/bootstrap/helpers.dart'; import 'package:flutter_app/bootstrap/helpers.dart';
@ -14,7 +15,7 @@ import 'package:flutter_app/bootstrap/helpers.dart';
*/ */
const appPaymentGateways = []; const appPaymentGateways = [];
// Available: "Stripe", "CashOnDelivery", "PayPal" // Available: "Stripe", "CashOnDelivery", "PayPal", "RazorPay"
// e.g. app_payment_gateways = ["Stripe", "CashOnDelivery"]; will only use Stripe and Cash on Delivery. // e.g. app_payment_gateways = ["Stripe", "CashOnDelivery"]; will only use Stripe and Cash on Delivery.
List<PaymentType> paymentTypeList = [ List<PaymentType> paymentTypeList = [
@ -42,10 +43,18 @@ List<PaymentType> paymentTypeList = [
pay: payPalPay, pay: payPalPay,
), ),
addPayment(
id: 5,
name: "RazorPay",
desc: "Debit or Credit Card",
assetImage: "razorpay.png",
pay: razorPay,
),
// e.g. add more here // e.g. add more here
// addPayment( // addPayment(
// id: 5, // id: 6,
// name: "MyNewPaymentMethod", // name: "MyNewPaymentMethod",
// desc: "Debit or Credit Card", // desc: "Debit or Credit Card",
// assetImage: "add icon image to public/assets/images/myimage.png", // assetImage: "add icon image to public/assets/images/myimage.png",

View File

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:flutter_app/bootstrap/shared_pref/sp_auth.dart';
import 'package:flutter_app/resources/widgets/buttons.dart';
import 'package:flutter_app/resources/widgets/safearea_widget.dart';
import 'package:nylo_framework/nylo_framework.dart';
import 'package:wp_json_api/models/responses/wp_user_delete_response.dart';
import 'package:wp_json_api/wp_json_api.dart';
class AccountDeletePage extends StatefulWidget {
AccountDeletePage({Key? key}) : super(key: key);
@override
_AccountDeletePageState createState() => _AccountDeletePageState();
}
class _AccountDeletePageState extends NyState<AccountDeletePage> {
@override
init() async {
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(trans("Delete Account")),
),
body: SafeAreaWidget(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.no_accounts_rounded, size: 50),
Text(trans("Delete your account"), style: textTheme.headline3,),
Padding(
padding: const EdgeInsets.only(top: 18),
child: Text(trans("Are you sure?")),
),
],),
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
PrimaryButton(title: trans("Yes, delete my account"), isLoading: isLocked('delete_account'), action: _deleteAccount),
LinkButton(title: trans("Back"), action: pop)
],
)
],
)
),
);
}
_deleteAccount() async {
await lockRelease('delete_account', perform: () async {
String? userToken = await readAuthToken();
WPUserDeleteResponse? wpUserDeleteResponse;
try {
wpUserDeleteResponse = await WPJsonAPI.instance
.api((request) => request.wpUserDelete(
userToken
));
} on Exception catch (e) {
NyLogger.error(e.toString());
showToastNotification(
context,
title: trans("Oops!"),
description: trans("Something went wrong"),
style: ToastNotificationStyleType.DANGER,
);
}
if (wpUserDeleteResponse != null) {
showToast(title: trans("Success"), description: trans("Account deleted"));
await authLogout(context);
}
});
}
}

View File

@ -31,8 +31,8 @@ class AccountLandingPage extends StatefulWidget {
_AccountLandingPageState createState() => _AccountLandingPageState(); _AccountLandingPageState createState() => _AccountLandingPageState();
} }
class _AccountLandingPageState extends State<AccountLandingPage> { class _AccountLandingPageState extends NyState<AccountLandingPage> {
bool _hasTappedLogin = false;
final TextEditingController _tfEmailController = TextEditingController(), final TextEditingController _tfEmailController = TextEditingController(),
_tfPasswordController = TextEditingController(); _tfPasswordController = TextEditingController();
@ -98,7 +98,7 @@ class _AccountLandingPageState extends State<AccountLandingPage> {
obscureText: true), obscureText: true),
PrimaryButton( PrimaryButton(
title: trans("Login"), title: trans("Login"),
isLoading: _hasTappedLogin, isLoading: isLocked('login_button'),
action: _loginUser, action: _loginUser,
), ),
], ],
@ -185,11 +185,7 @@ class _AccountLandingPageState extends State<AccountLandingPage> {
return; return;
} }
if (_hasTappedLogin == false) { await lockRelease('login_button', perform: () async {
setState(() {
_hasTappedLogin = true;
});
WPUserLoginResponse? wpUserLoginResponse; WPUserLoginResponse? wpUserLoginResponse;
try { try {
wpUserLoginResponse = await WPJsonAPI.instance.api( wpUserLoginResponse = await WPJsonAPI.instance.api(
@ -221,17 +217,19 @@ class _AccountLandingPageState extends State<AccountLandingPage> {
description: trans("Invalid login credentials"), description: trans("Invalid login credentials"),
style: ToastNotificationStyleType.DANGER, style: ToastNotificationStyleType.DANGER,
icon: Icons.account_circle); icon: Icons.account_circle);
} finally {
setState(() {
_hasTappedLogin = false;
});
} }
if (wpUserLoginResponse != null && wpUserLoginResponse.status == 200) { if (wpUserLoginResponse == null) {
return;
}
if (wpUserLoginResponse.status != 200) {
return;
}
String? token = wpUserLoginResponse.data!.userToken; String? token = wpUserLoginResponse.data!.userToken;
String userId = wpUserLoginResponse.data!.userId.toString(); String userId = wpUserLoginResponse.data!.userId.toString();
User user = User.fromUserAuthResponse(token: token, userId: userId); User user = User.fromUserAuthResponse(token: token, userId: userId);
user.save(SharedKey.authUser); await user.save(SharedKey.authUser);
showToastNotification(context, showToastNotification(context,
title: trans("Hello"), title: trans("Hello"),
@ -240,7 +238,7 @@ class _AccountLandingPageState extends State<AccountLandingPage> {
icon: Icons.account_circle); icon: Icons.account_circle);
navigatorPush(context, navigatorPush(context,
routeName: UserAuth.instance.redirect, forgetLast: 1); routeName: UserAuth.instance.redirect, forgetLast: 1);
}
} });
} }
} }

View File

@ -37,10 +37,9 @@ class AccountRegistrationPage extends StatefulWidget {
_AccountRegistrationPageState(); _AccountRegistrationPageState();
} }
class _AccountRegistrationPageState extends State<AccountRegistrationPage> { class _AccountRegistrationPageState extends NyState<AccountRegistrationPage> {
_AccountRegistrationPageState(); _AccountRegistrationPageState();
bool _hasTappedRegister = false;
final TextEditingController _tfEmailAddressController = final TextEditingController _tfEmailAddressController =
TextEditingController(), TextEditingController(),
_tfPasswordController = TextEditingController(), _tfPasswordController = TextEditingController(),
@ -106,7 +105,7 @@ class _AccountRegistrationPageState extends State<AccountRegistrationPage> {
Padding( Padding(
child: PrimaryButton( child: PrimaryButton(
title: trans("Sign up"), title: trans("Sign up"),
isLoading: _hasTappedRegister, isLoading: isLocked('register_user'),
action: _signUpTapped, action: _signUpTapped,
), ),
padding: EdgeInsets.only(top: 10), padding: EdgeInsets.only(top: 10),
@ -122,7 +121,7 @@ class _AccountRegistrationPageState extends State<AccountRegistrationPage> {
TextSpan( TextSpan(
text: trans("terms and conditions"), text: trans("terms and conditions"),
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: ' ' + trans("and") + ' '), TextSpan(text: ' ${trans("and")} '),
TextSpan( TextSpan(
text: trans("privacy policy"), text: trans("privacy policy"),
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold)),
@ -171,18 +170,14 @@ class _AccountRegistrationPageState extends State<AccountRegistrationPage> {
return; return;
} }
if (_hasTappedRegister == false) { await lockRelease('register_user', perform: () async {
setState(() {
_hasTappedRegister = true;
});
String username = String username =
(email.replaceAll(RegExp(r'([@.])'), "")) + _randomStr(4); (email.replaceAll(RegExp(r'([@.])'), "")) + _randomStr(4);
WPUserRegisterResponse? wpUserRegisterResponse; WPUserRegisterResponse? wpUserRegisterResponse;
try { try {
wpUserRegisterResponse = await WPJsonAPI.instance.api( wpUserRegisterResponse = await WPJsonAPI.instance.api(
(request) => request.wpRegister( (request) => request.wpRegister(
email: email.toLowerCase(), email: email.toLowerCase(),
password: password, password: password,
username: username, username: username,
@ -197,7 +192,7 @@ class _AccountRegistrationPageState extends State<AccountRegistrationPage> {
showToastNotification(context, showToastNotification(context,
title: trans("Invalid details"), title: trans("Invalid details"),
description: description:
trans("Something went wrong, please contact our store"), trans("Something went wrong, please contact our store"),
style: ToastNotificationStyleType.DANGER); style: ToastNotificationStyleType.DANGER);
} on ExistingUserLoginException catch (_) { } on ExistingUserLoginException catch (_) {
showToastNotification(context, showToastNotification(context,
@ -224,18 +219,21 @@ class _AccountRegistrationPageState extends State<AccountRegistrationPage> {
title: trans("Oops!"), title: trans("Oops!"),
description: trans("Something went wrong"), description: trans("Something went wrong"),
style: ToastNotificationStyleType.DANGER); style: ToastNotificationStyleType.DANGER);
} finally {
setState(() {
_hasTappedRegister = false;
});
} }
if (wpUserRegisterResponse != null && if (wpUserRegisterResponse == null) {
wpUserRegisterResponse.status == 200) { return;
}
if (wpUserRegisterResponse.status != 200) {
return;
}
// Save user to shared preferences
String? token = wpUserRegisterResponse.data!.userToken; String? token = wpUserRegisterResponse.data!.userToken;
String userId = wpUserRegisterResponse.data!.userId.toString(); String userId = wpUserRegisterResponse.data!.userId.toString();
User user = User.fromUserAuthResponse(token: token, userId: userId); User user = User.fromUserAuthResponse(token: token, userId: userId);
user.save(SharedKey.authUser); await user.save(SharedKey.authUser);
await WPJsonAPI.instance.api((request) => request await WPJsonAPI.instance.api((request) => request
.wpUpdateUserInfo(token, firstName: firstName, lastName: lastName)); .wpUpdateUserInfo(token, firstName: firstName, lastName: lastName));
@ -247,8 +245,7 @@ class _AccountRegistrationPageState extends State<AccountRegistrationPage> {
icon: Icons.account_circle); icon: Icons.account_circle);
navigatorPush(context, navigatorPush(context,
routeName: UserAuth.instance.redirect, forgetLast: 2); routeName: UserAuth.instance.redirect, forgetLast: 2);
} });
}
} }
_viewTOSModal() async { _viewTOSModal() async {

View File

@ -18,7 +18,7 @@ import 'package:flutter_app/resources/widgets/buttons.dart';
import 'package:flutter_app/resources/widgets/safearea_widget.dart'; import 'package:flutter_app/resources/widgets/safearea_widget.dart';
import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; import 'package:flutter_app/resources/widgets/woosignal_ui.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:woosignal/models/response/product_category.dart'; import 'package:woosignal/models/response/product_category.dart';
import 'package:woosignal/models/response/products.dart' as ws_product; import 'package:woosignal/models/response/products.dart' as ws_product;

View File

@ -15,7 +15,7 @@ import 'package:flutter_app/bootstrap/helpers.dart';
import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; import 'package:flutter_app/resources/widgets/app_loader_widget.dart';
import 'package:flutter_app/resources/widgets/safearea_widget.dart'; import 'package:flutter_app/resources/widgets/safearea_widget.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:woosignal/models/response/products.dart' as ws_product; import 'package:woosignal/models/response/products.dart' as ws_product;
class BrowseSearchPage extends NyStatefulWidget { class BrowseSearchPage extends NyStatefulWidget {

View File

@ -294,7 +294,7 @@ class CheckoutConfirmationPageState extends State<CheckoutConfirmationPage> {
return; return;
} }
if (checkoutSession.billingDetails!.billingAddress!.hasMissingFields()) { if (checkoutSession.billingDetails?.billingAddress?.hasMissingFields() ?? true) {
showToastNotification( showToastNotification(
context, context,
title: trans("Oops"), title: trans("Oops"),

View File

@ -90,7 +90,7 @@ class _CheckoutDetailsPageState extends State<CheckoutDetailsPage> {
CheckoutSession.getInstance.billingDetails!.initSession(); CheckoutSession.getInstance.billingDetails!.initSession();
CheckoutSession.getInstance.billingDetails!.shippingAddress! CheckoutSession.getInstance.billingDetails!.shippingAddress!
.initAddress(); .initAddress();
CheckoutSession.getInstance.billingDetails!.billingAddress!.initAddress(); CheckoutSession.getInstance.billingDetails!.billingAddress?.initAddress();
} }
BillingDetails billingDetails = CheckoutSession.getInstance.billingDetails!; BillingDetails billingDetails = CheckoutSession.getInstance.billingDetails!;
_setFieldsFromCustomerAddress(billingDetails.billingAddress, _setFieldsFromCustomerAddress(billingDetails.billingAddress,

View File

@ -154,7 +154,7 @@ class _CouponPageState extends State<CouponPage> {
// Check email restrictions // Check email restrictions
String? emailAddress = String? emailAddress =
checkoutSession.billingDetails!.billingAddress!.emailAddress; checkoutSession.billingDetails!.billingAddress?.emailAddress;
if (coupon.emailRestrictions!.contains(emailAddress)) { if (coupon.emailRestrictions!.contains(emailAddress)) {
_showAlert( _showAlert(
message: trans('You cannot redeem this coupon'), message: trans('You cannot redeem this coupon'),

View File

@ -18,7 +18,7 @@ import 'package:flutter_app/resources/widgets/product_review_item_container_widg
import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:woosignal/models/response/product_review.dart'; import 'package:woosignal/models/response/product_review.dart';
import 'package:woosignal/models/response/products.dart'; import 'package:woosignal/models/response/products.dart';
import '../../app/controllers/product_reviews_controller.dart'; import '../../app/controllers/product_reviews_controller.dart';

View File

@ -15,7 +15,7 @@ import 'package:flutter_app/bootstrap/helpers.dart';
import 'package:flutter_app/bootstrap/shared_pref/sp_auth.dart'; import 'package:flutter_app/bootstrap/shared_pref/sp_auth.dart';
import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; import 'package:flutter_app/resources/widgets/app_loader_widget.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:woosignal/models/response/order.dart'; import 'package:woosignal/models/response/order.dart';
class AccountDetailOrdersWidget extends StatefulWidget { class AccountDetailOrdersWidget extends StatefulWidget {

View File

@ -46,6 +46,14 @@ class AccountDetailSettingsWidget extends StatelessWidget {
Navigator.pushNamed(context, "/account-billing-details"), Navigator.pushNamed(context, "/account-billing-details"),
), ),
), ),
Card(
child: ListTile(
leading: Icon(Icons.no_accounts_rounded),
title: Text("Delete Account"),
onTap: () =>
Navigator.pushNamed(context, "/account-delete"),
),
),
Card( Card(
child: ListTile( child: ListTile(
leading: Icon(Icons.exit_to_app), leading: Icon(Icons.exit_to_app),

View File

@ -63,7 +63,7 @@ class CheckoutSelectCouponWidget extends StatelessWidget {
return; return;
} }
if (checkoutSession.billingDetails!.billingAddress!.hasMissingFields()) { if (checkoutSession.billingDetails?.billingAddress?.hasMissingFields() ?? true) {
showToastNotification( showToastNotification(
context, context,
title: trans("Oops"), title: trans("Oops"),

View File

@ -18,6 +18,7 @@ class CheckoutStoreHeadingWidget extends StatelessWidget {
child: ClipRRect( child: ClipRRect(
child: StoreLogo(height: 65), child: StoreLogo(height: 65),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
)); ),
);
} }
} }

View File

@ -23,10 +23,9 @@ class CheckoutUserDetailsWidget extends StatelessWidget {
leadImage: Icon(Icons.home), leadImage: Icon(Icons.home),
leadTitle: hasUserCheckoutInfo leadTitle: hasUserCheckoutInfo
? (checkoutSession.billingDetails == null || ? (checkoutSession.billingDetails == null ||
checkoutSession.billingDetails!.billingAddress! (checkoutSession.billingDetails?.billingAddress?.hasMissingFields() ?? true)
.hasMissingFields()
? trans("Billing address is incomplete") ? trans("Billing address is incomplete")
: checkoutSession.billingDetails!.billingAddress!.addressFull()) : checkoutSession.billingDetails!.billingAddress?.addressFull())
: trans("Add billing & shipping details"), : trans("Add billing & shipping details"),
action: _actionCheckoutDetails, action: _actionCheckoutDetails,
showBorderBottom: true, showBorderBottom: true,
@ -36,10 +35,6 @@ class CheckoutUserDetailsWidget extends StatelessWidget {
_actionCheckoutDetails() { _actionCheckoutDetails() {
Navigator.pushNamed(context, "/checkout-details").then((e) { Navigator.pushNamed(context, "/checkout-details").then((e) {
resetState!(); resetState!();
// setState(() {
// _showFullLoader = true;
// });
// _getTaxes();
}); });
} }
} }

View File

@ -7,7 +7,7 @@ import 'package:flutter_app/resources/widgets/home_drawer_widget.dart';
import 'package:flutter_app/resources/widgets/safearea_widget.dart'; import 'package:flutter_app/resources/widgets/safearea_widget.dart';
import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; import 'package:flutter_app/resources/widgets/woosignal_ui.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:woosignal/models/response/woosignal_app.dart'; import 'package:woosignal/models/response/woosignal_app.dart';
import 'package:woosignal/models/response/product_category.dart' as ws_category; import 'package:woosignal/models/response/product_category.dart' as ws_category;
import 'package:woosignal/models/response/products.dart' as ws_product; import 'package:woosignal/models/response/products.dart' as ws_product;

View File

@ -10,7 +10,7 @@ import 'package:flutter_app/resources/widgets/safearea_widget.dart';
import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; import 'package:flutter_app/resources/widgets/woosignal_ui.dart';
import 'package:flutter_swiper_tv/flutter_swiper.dart'; import 'package:flutter_swiper_tv/flutter_swiper.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:woosignal/models/response/woosignal_app.dart'; import 'package:woosignal/models/response/woosignal_app.dart';
import 'package:woosignal/models/response/product_category.dart' as ws_category; import 'package:woosignal/models/response/product_category.dart' as ws_category;
import 'package:woosignal/models/response/products.dart' as ws_product; import 'package:woosignal/models/response/products.dart' as ws_product;

View File

@ -23,7 +23,7 @@ import 'package:flutter_app/resources/widgets/top_nav_widget.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:flutter_swiper_tv/flutter_swiper.dart'; import 'package:flutter_swiper_tv/flutter_swiper.dart';
import 'package:nylo_framework/nylo_framework.dart'; import 'package:nylo_framework/nylo_framework.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'package:woosignal/models/response/products.dart'; import 'package:woosignal/models/response/products.dart';
import 'package:woosignal/models/response/tax_rate.dart'; import 'package:woosignal/models/response/tax_rate.dart';

View File

@ -1,4 +1,5 @@
import 'package:flutter_app/resources/pages/account_billing_details.dart'; import 'package:flutter_app/resources/pages/account_billing_details.dart';
import 'package:flutter_app/resources/pages/account_delete_page.dart';
import 'package:flutter_app/resources/pages/account_detail.dart'; import 'package:flutter_app/resources/pages/account_detail.dart';
import 'package:flutter_app/resources/pages/account_landing.dart'; import 'package:flutter_app/resources/pages/account_landing.dart';
import 'package:flutter_app/resources/pages/account_order_detail.dart'; import 'package:flutter_app/resources/pages/account_order_detail.dart';
@ -103,6 +104,8 @@ appRouter() => nyRoutes((router) {
router.route("/account-update", (context) => AccountProfileUpdatePage()); router.route("/account-update", (context) => AccountProfileUpdatePage());
router.route("/account-delete", (context) => AccountDeletePage());
router.route( router.route(
"/account-billing-details", (context) => AccountBillingDetailsPage()); "/account-billing-details", (context) => AccountBillingDetailsPage());

View File

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "40.0.0" version: "41.0.0"
analyzer: analyzer:
dependency: "direct main" dependency: "direct main"
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.1.0" version: "4.2.0"
animate_do: animate_do:
dependency: "direct main" dependency: "direct main"
description: description:
@ -154,7 +154,7 @@ packages:
name: cupertino_icons name: cupertino_icons
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.5"
device_info_plus: device_info_plus:
dependency: transitive dependency: transitive
description: description:
@ -204,6 +204,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.6" version: "4.0.6"
eventify:
dependency: transitive
description:
name: eventify
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -346,7 +353,7 @@ packages:
name: flutter_stripe name: flutter_stripe
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.2.0"
flutter_styled_toast: flutter_styled_toast:
dependency: "direct main" dependency: "direct main"
description: description:
@ -399,6 +406,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.8.5+1" version: "0.8.5+1"
fluttertoast:
dependency: transitive
description:
name: fluttertoast
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.9"
freezed_annotation: freezed_annotation:
dependency: transitive dependency: transitive
description: description:
@ -580,7 +594,7 @@ packages:
name: math_expressions name: math_expressions
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.0" version: "2.3.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -608,14 +622,14 @@ packages:
name: nylo_framework name: nylo_framework
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.3" version: "3.1.4"
nylo_support: nylo_support:
dependency: transitive dependency: transitive
description: description:
name: nylo_support name: nylo_support
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.2.0"
octo_image: octo_image:
dependency: transitive dependency: transitive
description: description:
@ -643,7 +657,7 @@ packages:
name: page_transition name: page_transition
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.0.9"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -763,13 +777,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
pull_to_refresh: pull_to_refresh_flutter3:
dependency: "direct main" dependency: "direct main"
description: description:
name: pull_to_refresh name: pull_to_refresh_flutter3
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.0.1"
razorpay_flutter:
dependency: "direct main"
description:
name: razorpay_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
recase: recase:
dependency: transitive dependency: transitive
description: description:
@ -900,21 +921,21 @@ packages:
name: stripe_android name: stripe_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.2.0"
stripe_ios: stripe_ios:
dependency: transitive dependency: transitive
description: description:
name: stripe_ios name: stripe_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.2.0"
stripe_platform_interface: stripe_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: stripe_platform_interface name: stripe_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.2.0"
synchronized: synchronized:
dependency: transitive dependency: transitive
description: description:
@ -963,7 +984,7 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.2" version: "6.1.4"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
@ -998,7 +1019,7 @@ packages:
name: url_launcher_platform_interface name: url_launcher_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.1.0"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
@ -1159,7 +1180,7 @@ packages:
name: wp_json_api name: wp_json_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.3" version: "3.2.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@ -1,7 +1,7 @@
# Official WooSignal App Template for WooCommerce # Official WooSignal App Template for WooCommerce
# Label StoreMax # Label StoreMax
# Version: 6.0.0 # Version: 6.1.0
# Author: Anthony Gordon # Author: Anthony Gordon
# Homepage: https://woosignal.com # Homepage: https://woosignal.com
# Documentation: https://woosignal.com/docs/app/label-storemax # Documentation: https://woosignal.com/docs/app/label-storemax
@ -26,25 +26,26 @@ environment:
dependencies: dependencies:
google_fonts: ^2.3.3 google_fonts: ^2.3.3
analyzer: ^4.1.0 analyzer: ^4.2.0
intl: ^0.17.0 intl: ^0.17.0
page_transition: ^2.0.5 page_transition: ^2.0.9
nylo_framework: ^3.1.3 nylo_framework: ^3.1.4
woosignal: ^3.1.0 woosignal: ^3.1.0
flutter_stripe: ^3.0.0 flutter_stripe: ^3.2.0
wp_json_api: ^3.1.3 wp_json_api: ^3.2.0
cached_network_image: ^3.2.1 cached_network_image: ^3.2.1
package_info: ^2.0.2 package_info: ^2.0.2
money_formatter: ^0.0.3 money_formatter: ^0.0.3
flutter_web_browser: ^0.17.1 flutter_web_browser: ^0.17.1
webview_flutter: ^3.0.4 webview_flutter: ^3.0.4
pull_to_refresh: 2.0.0 pull_to_refresh_flutter3: 2.0.1
url_launcher: ^6.1.2 url_launcher: ^6.1.4
flutter_styled_toast: ^2.1.3 flutter_styled_toast: ^2.1.3
animate_do: ^2.1.0 animate_do: ^2.1.0
bubble_tab_indicator: ^0.1.5 bubble_tab_indicator: ^0.1.5
razorpay_flutter: ^1.3.0
status_alert: ^1.0.0 status_alert: ^1.0.0
math_expressions: ^2.3.0 math_expressions: ^2.3.1
validated: ^2.0.0 validated: ^2.0.0
flutter_spinkit: ^5.1.0 flutter_spinkit: ^5.1.0
auto_size_text: ^3.0.0 auto_size_text: ^3.0.0
@ -62,7 +63,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.4 cupertino_icons: ^1.0.5
collection: ^1.15.0-nullsafety.4 collection: ^1.15.0-nullsafety.4
dev_dependencies: dev_dependencies:
@ -78,9 +79,6 @@ flutter_icons:
image_path: "public/assets/app_icon/appicon.png" image_path: "public/assets/app_icon/appicon.png"
remove_alpha_ios: true remove_alpha_ios: true
dependency_overrides:
intl: ^0.17.0
flutter: flutter:
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is

View File

@ -4,7 +4,7 @@
# WooCommerce App: Label StoreMax # WooCommerce App: Label StoreMax
### Label StoreMax - v6.0.0 ### Label StoreMax - v6.1.0
[Official WooSignal WooCommerce App](https://woosignal.com) [Official WooSignal WooCommerce App](https://woosignal.com)
@ -45,7 +45,7 @@ Full documentation this available [here](https://woosignal.com/docs/app/ios/labe
- Change app name, logo, customize default language, currency + more - Change app name, logo, customize default language, currency + more
- Light and dark mode - Light and dark mode
- Theme customization - Theme customization
- Stripe, Cash On Delivery and PayPal - Stripe, Cash On Delivery, PayPal and RazorPay
- Localized for en, es, pt, it, hi, fr, zh, tr, nl - Localized for en, es, pt, it, hi, fr, zh, tr, nl
- Orders show as normal in WooCommerce - Orders show as normal in WooCommerce