mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-24 06:48:38 +02:00
Cw 973 in app gift card redemption flow plus UI (#2285)
* refactor: cleanup cake_pay folder * refactor: add My Cards tab on the main screen * ui: add user card item * refactor: cakePay purchase flow * feat: add user card info * refactor: auth flow * refactor: theme data * revert * Update qr_widget.dart * fix: update with new themes * minor fixes * feat: put redemption flow under a feature flag * feat: implement simulated purchasing flow in debug mode * feat: support LTC and MWEB payments * Update savings_page.dart * fix: UI fixes * add Cake Pay transaction sent bottom sheet * localization * Update cake_pay_buy_card_page.dart * addressing review comments * fix marge conflicts
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
|
||||
class CakePayCard {
|
||||
final int id;
|
||||
final String name;
|
||||
final String? description;
|
||||
final String? termsAndConditions;
|
||||
final String? howToUse;
|
||||
final String? expiryAndValidity;
|
||||
final String? cardImageUrl;
|
||||
final String? country;
|
||||
final FiatCurrency fiatCurrency;
|
||||
final List<String> denominationsUsd;
|
||||
final List<String> denominations;
|
||||
final String? minValueUsd;
|
||||
final String? maxValueUsd;
|
||||
final String? minValue;
|
||||
final String? maxValue;
|
||||
|
||||
CakePayCard({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.description,
|
||||
this.termsAndConditions,
|
||||
this.howToUse,
|
||||
this.expiryAndValidity,
|
||||
this.cardImageUrl,
|
||||
this.country,
|
||||
required this.fiatCurrency,
|
||||
required this.denominationsUsd,
|
||||
required this.denominations,
|
||||
this.minValueUsd,
|
||||
this.maxValueUsd,
|
||||
this.minValue,
|
||||
this.maxValue,
|
||||
});
|
||||
|
||||
factory CakePayCard.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
final name = stripHtmlIfNeeded(json['name'] as String? ?? '');
|
||||
final description = stripHtmlIfNeeded(json['description'] as String? ?? '');
|
||||
final termsAndConditions = stripHtmlIfNeeded(json['terms_and_conditions'] as String? ?? '');
|
||||
final howToUse = stripHtmlIfNeeded(json['how_to_use'] as String? ?? '');
|
||||
|
||||
final fiatCurrency = FiatCurrency.deserialize(raw: json['currency_code'] as String? ?? '');
|
||||
|
||||
final List<String> denominationsUsd =
|
||||
(json['denominations_usd'] as List?)?.map((e) => e.toString()).toList() ?? [];
|
||||
final List<String> denominations =
|
||||
(json['denominations'] as List?)?.map((e) => e.toString()).toList() ?? [];
|
||||
|
||||
return CakePayCard(
|
||||
id: json['id'] as int? ?? 0,
|
||||
name: name,
|
||||
description: description,
|
||||
termsAndConditions: termsAndConditions,
|
||||
howToUse: howToUse,
|
||||
expiryAndValidity: json['expiry_and_validity'] as String?,
|
||||
cardImageUrl: json['card_image_url'] as String?,
|
||||
country: json['country'] as String?,
|
||||
fiatCurrency: fiatCurrency,
|
||||
denominationsUsd: denominationsUsd,
|
||||
denominations: denominations,
|
||||
minValueUsd: json['min_value_usd'] as String?,
|
||||
maxValueUsd: json['max_value_usd'] as String?,
|
||||
minValue: json['min_value'] as String?,
|
||||
maxValue: json['max_value'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
static String stripHtmlIfNeeded(String text) {
|
||||
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
enum CakePayPaymentMethod { BTC, BTC_LN, XMR, LTC, LTC_MWEB }
|
||||
|
||||
extension CakePayPaymentMethodLabel on CakePayPaymentMethod {
|
||||
String get label => switch (this) {
|
||||
CakePayPaymentMethod.BTC => 'Bitcoin',
|
||||
CakePayPaymentMethod.BTC_LN => 'Bitcoin Lightning',
|
||||
CakePayPaymentMethod.XMR => 'Monero',
|
||||
CakePayPaymentMethod.LTC => 'Litecoin',
|
||||
CakePayPaymentMethod.LTC_MWEB => 'Litecoin MWEB',
|
||||
};
|
||||
}
|
||||
|
||||
class CakePayOrder {
|
||||
final String orderId;
|
||||
final List<OrderCard> cards;
|
||||
final String? externalId;
|
||||
final double amountUsd;
|
||||
final String status;
|
||||
final String? vouchers;
|
||||
final PaymentData paymentData;
|
||||
|
||||
CakePayOrder({
|
||||
required this.orderId,
|
||||
required this.cards,
|
||||
required this.externalId,
|
||||
required this.amountUsd,
|
||||
required this.status,
|
||||
required this.vouchers,
|
||||
required this.paymentData,
|
||||
});
|
||||
|
||||
factory CakePayOrder.fromMap(Map<String, dynamic> map) {
|
||||
return CakePayOrder(
|
||||
orderId: map['order_id'] as String,
|
||||
cards: (map['cards'] as List<dynamic>)
|
||||
.map((x) => OrderCard.fromMap(x as Map<String, dynamic>))
|
||||
.toList(),
|
||||
externalId: map['external_id'] as String?,
|
||||
amountUsd: map['amount_usd'] as double,
|
||||
status: map['status'] as String,
|
||||
vouchers: map['vouchers'] as String?,
|
||||
paymentData: PaymentData.fromMap(map['payment_data'] as Map<String, dynamic>));
|
||||
}
|
||||
}
|
||||
|
||||
class OrderCard {
|
||||
final int cardId;
|
||||
final int? externalId;
|
||||
final String price;
|
||||
final int quantity;
|
||||
final String currencyCode;
|
||||
final String? cardName;
|
||||
final String? cardImagePath;
|
||||
|
||||
OrderCard({
|
||||
required this.cardId,
|
||||
required this.externalId,
|
||||
required this.price,
|
||||
required this.quantity,
|
||||
required this.currencyCode,
|
||||
required this.cardName,
|
||||
required this.cardImagePath,
|
||||
});
|
||||
|
||||
factory OrderCard.fromMap(Map<String, dynamic> map) {
|
||||
return OrderCard(
|
||||
cardId: map['card_id'] as int,
|
||||
externalId: map['external_id'] as int?,
|
||||
price: map['price'] as String,
|
||||
quantity: map['quantity'] as int,
|
||||
currencyCode: map['currency_code'] as String,
|
||||
cardName: map['name'] as String?,
|
||||
cardImagePath: map['card_image_url'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentData {
|
||||
final CryptoPaymentData btc;
|
||||
final CryptoPaymentData btc_ln;
|
||||
final CryptoPaymentData xmr;
|
||||
final CryptoPaymentData ltc;
|
||||
final CryptoPaymentData ltc_mweb;
|
||||
final DateTime invoiceTime;
|
||||
final DateTime expirationTime;
|
||||
final int? commission;
|
||||
|
||||
PaymentData({
|
||||
required this.btc,
|
||||
required this.btc_ln,
|
||||
required this.xmr,
|
||||
required this.ltc,
|
||||
required this.ltc_mweb,
|
||||
required this.invoiceTime,
|
||||
required this.expirationTime,
|
||||
required this.commission,
|
||||
});
|
||||
|
||||
factory PaymentData.fromMap(Map<String, dynamic> map) {
|
||||
return PaymentData(
|
||||
btc: CryptoPaymentData.fromMap(map['BTC'] as Map<String, dynamic>),
|
||||
btc_ln: CryptoPaymentData.fromMap(map['BTC_LN'] as Map<String, dynamic>),
|
||||
xmr: CryptoPaymentData.fromMap(map['XMR'] as Map<String, dynamic>),
|
||||
ltc: CryptoPaymentData.fromMap(map['LTC'] as Map<String, dynamic>),
|
||||
ltc_mweb: CryptoPaymentData.fromMap(map['LTC_MWEB'] as Map<String, dynamic>),
|
||||
invoiceTime: DateTime.fromMillisecondsSinceEpoch(map['invoice_time'] as int),
|
||||
expirationTime: DateTime.fromMillisecondsSinceEpoch(map['expiration_time'] as int),
|
||||
commission: map['commission'] as int?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CryptoPaymentData {
|
||||
final String price;
|
||||
final PaymentUrl? paymentUrls;
|
||||
final String address;
|
||||
|
||||
CryptoPaymentData({
|
||||
required this.price,
|
||||
this.paymentUrls,
|
||||
required this.address,
|
||||
});
|
||||
|
||||
factory CryptoPaymentData.fromMap(Map<String, dynamic> map) {
|
||||
return CryptoPaymentData(
|
||||
price: map['price'] as String,
|
||||
paymentUrls: PaymentUrl.fromMap(map['paymentUrls'] as Map<String, dynamic>?),
|
||||
address: map['address'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentUrl {
|
||||
final String? bip21;
|
||||
final String? bip72;
|
||||
final String? bip72b;
|
||||
final String? bip73;
|
||||
final String? bolt11;
|
||||
|
||||
PaymentUrl({
|
||||
this.bip21,
|
||||
this.bip72,
|
||||
this.bip72b,
|
||||
this.bip73,
|
||||
this.bolt11,
|
||||
});
|
||||
|
||||
factory PaymentUrl.fromMap(Map<String, dynamic>? map) {
|
||||
return PaymentUrl(
|
||||
bip21: map?['BIP21'] as String?,
|
||||
bip72: map?['BIP72'] as String?,
|
||||
bip72b: map?['BIP72b'] as String?,
|
||||
bip73: map?['BIP73'] as String?,
|
||||
bolt11: map?['BOLT11'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class CakePayUserCredentials {
|
||||
const CakePayUserCredentials(this.username, this.token);
|
||||
|
||||
final String username;
|
||||
final String token;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'cake_pay_card.dart';
|
||||
|
||||
class CakePayVendor {
|
||||
final int id;
|
||||
final String name;
|
||||
final bool unavailable;
|
||||
final String? cakeWarnings;
|
||||
final String country;
|
||||
final CakePayCard? card;
|
||||
|
||||
CakePayVendor({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.unavailable,
|
||||
this.cakeWarnings,
|
||||
required this.country,
|
||||
this.card,
|
||||
});
|
||||
|
||||
factory CakePayVendor.fromJson(Map<String, dynamic> json, String country) {
|
||||
final name = stripHtmlIfNeeded(json['name'] as String);
|
||||
|
||||
var cardsJson = json['cards'] as List?;
|
||||
CakePayCard? cardForVendor;
|
||||
|
||||
if (cardsJson != null && cardsJson.isNotEmpty) {
|
||||
try {
|
||||
cardForVendor = CakePayCard.fromJson(cardsJson
|
||||
.where((element) => element['country'] == country)
|
||||
.first as Map<String, dynamic>);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return CakePayVendor(
|
||||
id: json['id'] as int,
|
||||
name: name,
|
||||
unavailable: json['unavailable'] as bool? ?? false,
|
||||
cakeWarnings: json['cake_warnings'] as String?,
|
||||
country: country,
|
||||
card: cardForVendor,
|
||||
);
|
||||
}
|
||||
|
||||
static String stripHtmlIfNeeded(String text) {
|
||||
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user