Skip to content

Commit

Permalink
Feature/tta/free products (#396)
Browse files Browse the repository at this point in the history
* ReceiptsRepository now use v2 endpoint,

* User can purchase free products in the app, when such products occur in the database
  • Loading branch information
TTA777 authored Apr 16, 2023
1 parent 2374f77 commit 8e17d4e
Show file tree
Hide file tree
Showing 21 changed files with 253 additions and 138 deletions.
3 changes: 2 additions & 1 deletion lib/base/strings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ abstract class Strings {
static const confirmSwipe = 'Confirm use of ticket';
static const confirmPurchase = 'Confirm purchase';
static const tapHereToCancel = 'Tap here to cancel';
static const String paymentConfirmationButtonRedeem = 'Get your free product';

static String paymentConfirmationTopTickets(int amount, String title) {
return "You're buying $amount $title tickets";
Expand All @@ -147,7 +148,7 @@ abstract class Strings {
return "You're buying and swiping $amount $title";
}

static String paymentConfirmationBottom(int price) {
static String paymentConfirmationBottomPurchase(int price) {
return 'Pay $price,- with...';
}

Expand Down
13 changes: 7 additions & 6 deletions lib/cubits/purchase/purchase_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,21 @@ class PurchaseCubit extends Cubit<PurchaseState> {
PurchaseCubit({required this.paymentHandler, required this.product})
: super(const PurchaseInitial());

Future<void> payWithMobilePay() async {
Future<void> pay() async {
sl<FirebaseAnalyticsEventLogging>().beginCheckoutEvent(product);

if (state is PurchaseInitial) {
sl<FirebaseAnalyticsEventLogging>().beginCheckoutEvent(product);
emit(const PurchaseStarted());

final either = await paymentHandler.initPurchase(product.id);

either.fold(
(error) => emit(PurchaseError(error.reason)),
(payment) async {
if (payment.status != PaymentStatus.error) {
(payment) {
if (payment.status == PaymentStatus.completed) {
emit(PurchaseCompleted(payment));
} else if (payment.status == PaymentStatus.awaitingPayment) {
emit(PurchaseProcessing(payment));
await paymentHandler
.invokePaymentMethod(Uri.parse(payment.deeplink));
} else {
emit(PurchasePaymentRejected(payment));
}
Expand Down
2 changes: 1 addition & 1 deletion lib/cubits/receipt/receipt_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:coffeecard/base/strings.dart';
import 'package:coffeecard/data/repositories/v1/receipt_repository.dart';
import 'package:coffeecard/data/repositories/v2/receipt_repository.dart';
import 'package:coffeecard/models/receipts/receipt.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Expand Down
40 changes: 0 additions & 40 deletions lib/data/repositories/v1/receipt_repository.dart

This file was deleted.

2 changes: 1 addition & 1 deletion lib/data/repositories/v1/ticket_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ class TicketRepository {
),
);

return result.map(Receipt.fromTicketDTO);
return result.map(Receipt.fromTicketDto);
}
}
50 changes: 50 additions & 0 deletions lib/data/repositories/v2/receipt_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:coffeecard/core/errors/failures.dart';
import 'package:coffeecard/core/network/network_request_executor.dart';
import 'package:coffeecard/data/repositories/v1/product_repository.dart';
import 'package:coffeecard/generated/api/coffeecard_api_v2.swagger.dart';
import 'package:coffeecard/models/receipts/receipt.dart';
import 'package:dartz/dartz.dart';

class ReceiptRepository {
ReceiptRepository({
required this.productRepository,
required this.apiV2,
required this.executor,
});

final CoffeecardApiV2 apiV2;
final ProductRepository productRepository;
final NetworkRequestExecutor executor;

/// Retrieves all of the users receipts
/// This includes both their used tickets and purchased tickets
Future<Either<Failure, List<Receipt>>> getUserReceipts() async {
final usedTicketsFutureEither = executor(
() => apiV2.apiV2TicketsGet(includeUsed: true),
);
final purchasedTicketsFutureEither = executor(
apiV2.apiV2PurchasesGet,
);

final usedTicketsEither = (await usedTicketsFutureEither)
.map((dto) => dto.map(Receipt.fromTicketResponse));
final purchasedTicketsEither = (await purchasedTicketsFutureEither).map(
(r) => r.map(
(purchase) => Receipt.fromSimplePurchaseResponse(
purchase,
),
),
);

return usedTicketsEither.fold(
(l) => Left(l),
(usedTickets) => purchasedTicketsEither.map(
(purchasedTickets) {
final allTickets = [...usedTickets, ...purchasedTickets];
allTickets.sort((a, b) => b.timeUsed.compareTo(a.timeUsed));
return allTickets;
},
),
);
}
}
3 changes: 0 additions & 3 deletions lib/models/purchase/payment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'package:coffeecard/models/purchase/payment_status.dart';

class Payment {
final int id;
final String paymentId;
final PaymentStatus status;
final String deeplink;
final int price;
Expand All @@ -14,7 +13,6 @@ class Payment {
required this.id,
required this.price,
required this.purchaseTime,
required this.paymentId,
required this.status,
required this.deeplink,
required this.productId,
Expand All @@ -33,7 +31,6 @@ class Payment {
}) {
return Payment(
id: id ?? this.id,
paymentId: paymentId ?? this.paymentId,
status: status ?? this.status,
deeplink: deeplink ?? this.deeplink,
price: price ?? this.price,
Expand Down
24 changes: 21 additions & 3 deletions lib/models/purchase/payment_status.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:coffeecard/generated/api/coffeecard_api_v2.enums.swagger.dart';

enum PaymentStatus {
/// Payment is completed
completed,
Expand All @@ -8,11 +10,27 @@ enum PaymentStatus {
/// Payment is not yet complete
reserved,

// User has not approved the purchase
/// User has not approved the purchase
awaitingPayment,

// User has rejected payment
/// User has rejected payment
rejectedPayment,

awaitingCompletionAfterRetry,
/// Payment has been refunded
refunded;

static PaymentStatus fromPurchaseStatus(PurchaseStatus status) {
switch (status) {
case PurchaseStatus.swaggerGeneratedUnknown:
return PaymentStatus.error;
case PurchaseStatus.completed:
return PaymentStatus.completed;
case PurchaseStatus.cancelled:
return PaymentStatus.rejectedPayment;
case PurchaseStatus.pendingpayment:
return PaymentStatus.awaitingPayment;
case PurchaseStatus.refunded:
return PaymentStatus.refunded;
}
}
}
9 changes: 6 additions & 3 deletions lib/models/purchase/single_purchase.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import 'package:coffeecard/generated/api/coffeecard_api_v2.swagger.dart';
import 'package:coffeecard/models/purchase/payment_status.dart';

class SinglePurchase {
final int id;
final int totalAmount;
final Map<String, dynamic> paymentDetails;
final String purchaseStatus;
final PaymentStatus status;
final DateTime dateCreated;

const SinglePurchase({
required this.id,
required this.totalAmount,
required this.paymentDetails,
required this.purchaseStatus,
required this.status,
required this.dateCreated,
});

SinglePurchase.fromDto(SinglePurchaseResponse dto)
: id = dto.id,
totalAmount = dto.totalAmount,
paymentDetails = dto.paymentDetails as Map<String, dynamic>,
purchaseStatus = dto.purchaseStatus as String,
status = PaymentStatus.fromPurchaseStatus(
purchaseStatusFromJson(dto.purchaseStatus),
),
dateCreated = dto.dateCreated;
}
20 changes: 16 additions & 4 deletions lib/models/receipts/receipt.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:coffeecard/generated/api/coffeecard_api.swagger.dart';
import 'package:coffeecard/generated/api/coffeecard_api_v2.models.swagger.dart';

enum TransactionType { purchase, ticketSwipe, placeholder }

Expand All @@ -21,7 +22,7 @@ class Receipt {
});

/// Creates a receipt from a used ticket DTO
Receipt.fromTicketDTO(TicketDto dto)
Receipt.fromTicketDto(TicketDto dto)
: productName = dto.productName,
transactionType = TransactionType.ticketSwipe,
timeUsed = dto
Expand All @@ -30,12 +31,23 @@ class Receipt {
amountPurchased = 1,
id = dto.id;

/// Creates a receipt from a purchase DTO
Receipt.fromPurchaseDTO(PurchaseDto dto)
/// Creates a receipt from a used ticket DTO
Receipt.fromTicketResponse(TicketResponse dto)
: productName = dto.productName,
transactionType = TransactionType.ticketSwipe,
timeUsed = dto
.dateUsed!, // will not be null as the dto is a ticket that has been used at some point
price = 1,
amountPurchased = 1,
id = dto.id;

/// Creates a receipt from a purchase DTO
Receipt.fromSimplePurchaseResponse(
SimplePurchaseResponse dto,
) : productName = dto.productName,
transactionType = TransactionType.purchase,
timeUsed = dto.dateCreated,
price = dto.price,
price = dto.totalAmount,
amountPurchased = dto.numberOfTickets,
id = dto.id;
}
36 changes: 36 additions & 0 deletions lib/payment/free_product_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:coffeecard/core/errors/failures.dart';
import 'package:coffeecard/generated/api/coffeecard_api_v2.enums.swagger.dart';
import 'package:coffeecard/models/purchase/payment.dart';
import 'package:coffeecard/models/purchase/payment_status.dart';
import 'package:coffeecard/payment/payment_handler.dart';
import 'package:dartz/dartz.dart';

class FreeProductService extends PaymentHandler {
const FreeProductService({
required super.purchaseRepository,
required super.context,
});

@override
Future<Either<Failure, Payment>> initPurchase(int productId) async {
final response = await purchaseRepository.initiatePurchase(
productId,
PaymentType.freepurchase,
);

return response.fold(
(error) => Left(error),
(purchase) => Right(
Payment(
id: purchase.id,
status: PaymentStatus.completed,
deeplink: '',
purchaseTime: purchase.dateCreated,
price: purchase.totalAmount,
productId: purchase.productId,
productName: purchase.productName,
),
),
);
}
}
Loading

0 comments on commit 8e17d4e

Please sign in to comment.