mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 19:58:41 +00:00
show refund
This commit is contained in:
parent
e7129614fa
commit
20be849641
9 changed files with 187 additions and 27 deletions
|
|
@ -187,6 +187,7 @@
|
||||||
"open": "Offene",
|
"open": "Offene",
|
||||||
"buy": "Kaufen",
|
"buy": "Kaufen",
|
||||||
"createOrRedeemVoucher": "Gutschein erstellen oder einlösen",
|
"createOrRedeemVoucher": "Gutschein erstellen oder einlösen",
|
||||||
|
"subscriptionRefund": "Wenn du ein Upgrade durchführst, erhältst du eine Rückerstattung von {refund} für dein aktuelles Abonnement.",
|
||||||
"createVoucher": "Gutschein kaufen",
|
"createVoucher": "Gutschein kaufen",
|
||||||
"createVoucherDesc": "Wähle den Wert des Gutscheins. Der Wert des Gutschein wird von deinem twonly-Guthaben abgezogen.",
|
"createVoucherDesc": "Wähle den Wert des Gutscheins. Der Wert des Gutschein wird von deinem twonly-Guthaben abgezogen.",
|
||||||
"redeemVoucher": "Gutschein einlösen",
|
"redeemVoucher": "Gutschein einlösen",
|
||||||
|
|
@ -199,6 +200,7 @@
|
||||||
"transactionCash": "Bargeldtransaktion",
|
"transactionCash": "Bargeldtransaktion",
|
||||||
"transactionPlanUpgrade": "Planupgrade",
|
"transactionPlanUpgrade": "Planupgrade",
|
||||||
"transactionRefund": "Rückerstattung",
|
"transactionRefund": "Rückerstattung",
|
||||||
|
"refund": "Rückerstattung",
|
||||||
"transactionThanksForTesting": "Danke fürs Testen",
|
"transactionThanksForTesting": "Danke fürs Testen",
|
||||||
"transactionUnknown": "Unbekannte Transaktion",
|
"transactionUnknown": "Unbekannte Transaktion",
|
||||||
"transactionVoucherCreated": "Gutschein erstellt",
|
"transactionVoucherCreated": "Gutschein erstellt",
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,7 @@
|
||||||
"requestedVouchers": "Requested vouchers",
|
"requestedVouchers": "Requested vouchers",
|
||||||
"redeemedVouchers": "Redeemed vouchers",
|
"redeemedVouchers": "Redeemed vouchers",
|
||||||
"buy": "Buy",
|
"buy": "Buy",
|
||||||
|
"subscriptionRefund": "When you upgrade, you will receive a refund of {refund} for your current subscription.",
|
||||||
"transactionCash": "Cash transaction",
|
"transactionCash": "Cash transaction",
|
||||||
"transactionPlanUpgrade": "Plan upgrade",
|
"transactionPlanUpgrade": "Plan upgrade",
|
||||||
"transactionRefund": "Refund transaction",
|
"transactionRefund": "Refund transaction",
|
||||||
|
|
@ -364,6 +365,7 @@
|
||||||
"transactionVoucherCreated": "Voucher created",
|
"transactionVoucherCreated": "Voucher created",
|
||||||
"transactionVoucherRedeemed": "Voucher redeemed",
|
"transactionVoucherRedeemed": "Voucher redeemed",
|
||||||
"checkoutOptions": "Options",
|
"checkoutOptions": "Options",
|
||||||
|
"refund": "Refund",
|
||||||
"checkoutPayYearly": "Pay yearly",
|
"checkoutPayYearly": "Pay yearly",
|
||||||
"checkoutTotal": "Total",
|
"checkoutTotal": "Total",
|
||||||
"selectPaymentMethode": "Select Payment Method",
|
"selectPaymentMethode": "Select Payment Method",
|
||||||
|
|
|
||||||
|
|
@ -1199,6 +1199,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Buy'**
|
/// **'Buy'**
|
||||||
String get buy;
|
String get buy;
|
||||||
|
|
||||||
|
/// No description provided for @subscriptionRefund.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'When you upgrade, you will receive a refund of {refund} for your current subscription.'**
|
||||||
|
String subscriptionRefund(Object refund);
|
||||||
|
|
||||||
/// No description provided for @transactionCash.
|
/// No description provided for @transactionCash.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -1247,6 +1253,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Options'**
|
/// **'Options'**
|
||||||
String get checkoutOptions;
|
String get checkoutOptions;
|
||||||
|
|
||||||
|
/// No description provided for @refund.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Refund'**
|
||||||
|
String get refund;
|
||||||
|
|
||||||
/// No description provided for @checkoutPayYearly.
|
/// No description provided for @checkoutPayYearly.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -572,6 +572,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get buy => 'Kaufen';
|
String get buy => 'Kaufen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String subscriptionRefund(Object refund) {
|
||||||
|
return 'Wenn du ein Upgrade durchführst, erhältst du eine Rückerstattung von $refund für dein aktuelles Abonnement.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get transactionCash => 'Bargeldtransaktion';
|
String get transactionCash => 'Bargeldtransaktion';
|
||||||
|
|
||||||
|
|
@ -596,6 +601,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get checkoutOptions => 'Optionen';
|
String get checkoutOptions => 'Optionen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get refund => 'Rückerstattung';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get checkoutPayYearly => 'Jährlich bezahlen';
|
String get checkoutPayYearly => 'Jährlich bezahlen';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -572,6 +572,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get buy => 'Buy';
|
String get buy => 'Buy';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String subscriptionRefund(Object refund) {
|
||||||
|
return 'When you upgrade, you will receive a refund of $refund for your current subscription.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get transactionCash => 'Cash transaction';
|
String get transactionCash => 'Cash transaction';
|
||||||
|
|
||||||
|
|
@ -596,6 +601,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get checkoutOptions => 'Options';
|
String get checkoutOptions => 'Options';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get refund => 'Refund';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get checkoutPayYearly => 'Pay yearly';
|
String get checkoutPayYearly => 'Pay yearly';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class AdditionalUsersView extends StatefulWidget {
|
||||||
|
const AdditionalUsersView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AdditionalUsersView> createState() => _AdditionalUsersViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AdditionalUsersViewState extends State<AdditionalUsersView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,15 @@ import 'package:twonly/src/views/settings/subscription/select_payment.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
|
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
|
||||||
|
|
||||||
class CheckoutView extends StatefulWidget {
|
class CheckoutView extends StatefulWidget {
|
||||||
const CheckoutView({
|
const CheckoutView(
|
||||||
super.key,
|
{super.key,
|
||||||
required this.planId,
|
required this.planId,
|
||||||
});
|
this.refund,
|
||||||
|
this.disableMonthlyOption});
|
||||||
|
|
||||||
final String planId;
|
final String planId;
|
||||||
|
final int? refund;
|
||||||
|
final bool? disableMonthlyOption;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CheckoutView> createState() => _CheckoutViewState();
|
State<CheckoutView> createState() => _CheckoutViewState();
|
||||||
|
|
@ -49,28 +52,53 @@ class _CheckoutViewState extends State<CheckoutView> {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
PlanCard(planId: widget.planId),
|
PlanCard(planId: widget.planId),
|
||||||
Padding(
|
if (widget.disableMonthlyOption == null ||
|
||||||
padding: const EdgeInsets.all(16.0),
|
!widget.disableMonthlyOption!)
|
||||||
child: ListTile(
|
Padding(
|
||||||
title: Text(context.lang.checkoutPayYearly),
|
padding: const EdgeInsets.all(16.0),
|
||||||
onTap: () {
|
child: ListTile(
|
||||||
paidMonthly = !paidMonthly;
|
title: Text(context.lang.checkoutPayYearly),
|
||||||
setCheckout(false);
|
onTap: () {
|
||||||
},
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: !paidMonthly,
|
|
||||||
onChanged: (a) {
|
|
||||||
paidMonthly = !paidMonthly;
|
paidMonthly = !paidMonthly;
|
||||||
setCheckout(false);
|
setCheckout(false);
|
||||||
},
|
},
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: !paidMonthly,
|
||||||
|
onChanged: (a) {
|
||||||
|
paidMonthly = !paidMonthly;
|
||||||
|
setCheckout(false);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.refund != null)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.lang.refund,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"+${localePrizing(context, widget.refund!)}",
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: TextStyle(color: context.color.primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Card(
|
child: Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.all(16),
|
||||||
|
|
@ -90,6 +118,7 @@ class _CheckoutViewState extends State<CheckoutView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
|
|
@ -97,7 +126,10 @@ class _CheckoutViewState extends State<CheckoutView> {
|
||||||
bool? success = await Navigator.push(context,
|
bool? success = await Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return SelectPaymentView(
|
return SelectPaymentView(
|
||||||
planId: widget.planId, payMonthly: paidMonthly);
|
planId: widget.planId,
|
||||||
|
payMonthly: paidMonthly,
|
||||||
|
refund: widget.refund,
|
||||||
|
);
|
||||||
}));
|
}));
|
||||||
if (success != null && success && context.mounted) {
|
if (success != null && success && context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,17 @@ import 'package:twonly/src/views/settings/subscription/voucher_view.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class SelectPaymentView extends StatefulWidget {
|
class SelectPaymentView extends StatefulWidget {
|
||||||
const SelectPaymentView({
|
const SelectPaymentView(
|
||||||
super.key,
|
{super.key,
|
||||||
this.planId,
|
this.planId,
|
||||||
this.payMonthly,
|
this.payMonthly,
|
||||||
this.valueInCents,
|
this.valueInCents,
|
||||||
});
|
this.refund});
|
||||||
|
|
||||||
final String? planId;
|
final String? planId;
|
||||||
final bool? payMonthly;
|
final bool? payMonthly;
|
||||||
final int? valueInCents;
|
final int? valueInCents;
|
||||||
|
final int? refund;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SelectPaymentView> createState() => _SelectPaymentViewState();
|
State<SelectPaymentView> createState() => _SelectPaymentViewState();
|
||||||
|
|
@ -162,8 +163,31 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.refund != null)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.lang.refund,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"+${localePrizing(context, widget.refund!)}",
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: TextStyle(color: context.color.primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Card(
|
child: Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.all(16),
|
||||||
|
|
@ -183,6 +207,7 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
|
|
@ -199,6 +224,7 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
|
||||||
user.subscriptionPlan = widget.planId!;
|
user.subscriptionPlan = widget.planId!;
|
||||||
await updateUser(user);
|
await updateUser(user);
|
||||||
}
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
context
|
context
|
||||||
.read<CustomChangeProvider>()
|
.read<CustomChangeProvider>()
|
||||||
.updatePlan(widget.planId!);
|
.updatePlan(widget.planId!);
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,33 @@ Future<Response_PlanBallance?> loadPlanBallance() async {
|
||||||
return ballance;
|
return ballance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: constant_identifier_names
|
||||||
|
const int MONTHLY_PAYMENT_DAYS = 30;
|
||||||
|
// ignore: constant_identifier_names
|
||||||
|
const int YEARLY_PAYMENT_DAYS = 365;
|
||||||
|
|
||||||
|
int calculateRefund(Response_PlanBallance current) {
|
||||||
|
int refund = getPlanPrice("Pro", true);
|
||||||
|
|
||||||
|
if (current.paymentPeriodDays == YEARLY_PAYMENT_DAYS) {
|
||||||
|
final elapsedDays = DateTime.now()
|
||||||
|
.difference(DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
current.lastPaymentDoneUnixTimestamp.toInt() * 1000))
|
||||||
|
.inDays;
|
||||||
|
if (elapsedDays < current.paymentPeriodDays.toInt()) {
|
||||||
|
// User has yearly plan with 10€
|
||||||
|
// used it half a year and wants now to upgrade => gets 5€ discount...
|
||||||
|
// math.ceil(((365-(365/2))/365)*10)
|
||||||
|
// => 5€
|
||||||
|
|
||||||
|
refund = (((YEARLY_PAYMENT_DAYS - elapsedDays) / YEARLY_PAYMENT_DAYS) *
|
||||||
|
getPlanPrice("Pro", false))
|
||||||
|
.ceil();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return refund;
|
||||||
|
}
|
||||||
|
|
||||||
class SubscriptionView extends StatefulWidget {
|
class SubscriptionView extends StatefulWidget {
|
||||||
const SubscriptionView({super.key});
|
const SubscriptionView({super.key});
|
||||||
|
|
||||||
|
|
@ -94,6 +121,10 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
String currentPlan = context.read<CustomChangeProvider>().plan;
|
String currentPlan = context.read<CustomChangeProvider>().plan;
|
||||||
|
int refund = 0;
|
||||||
|
if (currentPlan == "Pro" && ballance != null) {
|
||||||
|
refund = calculateRefund(ballance!);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -138,7 +169,9 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.push(context,
|
await Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return CheckoutView(planId: "Pro");
|
return CheckoutView(
|
||||||
|
planId: "Pro",
|
||||||
|
);
|
||||||
}));
|
}));
|
||||||
initAsync();
|
initAsync();
|
||||||
},
|
},
|
||||||
|
|
@ -146,10 +179,17 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
if (currentPlan != "Family")
|
if (currentPlan != "Family")
|
||||||
PlanCard(
|
PlanCard(
|
||||||
planId: "Family",
|
planId: "Family",
|
||||||
|
refund: refund,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.push(context,
|
await Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return CheckoutView(planId: "Family");
|
return CheckoutView(
|
||||||
|
planId: "Family",
|
||||||
|
refund: (refund > 0) ? refund : null,
|
||||||
|
disableMonthlyOption: (currentPlan == "Pro" &&
|
||||||
|
ballance!.paymentPeriodDays.toInt() ==
|
||||||
|
YEARLY_PAYMENT_DAYS),
|
||||||
|
);
|
||||||
}));
|
}));
|
||||||
initAsync();
|
initAsync();
|
||||||
},
|
},
|
||||||
|
|
@ -243,10 +283,12 @@ int getPlanPrice(String planId, bool paidMonthly) {
|
||||||
class PlanCard extends StatelessWidget {
|
class PlanCard extends StatelessWidget {
|
||||||
final String planId;
|
final String planId;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
final int? refund;
|
||||||
|
|
||||||
const PlanCard({
|
const PlanCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.planId,
|
required this.planId,
|
||||||
|
this.refund,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -340,6 +382,19 @@ class PlanCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (refund != null && refund! > 0)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 7),
|
||||||
|
child: Text(
|
||||||
|
context.lang
|
||||||
|
.subscriptionRefund(localePrizing(context, refund!)),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.color.primary,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue