Payments fixes

This commit is contained in:
Eduard Kuzmenko 2022-07-18 20:32:00 +02:00
parent 25abb13b20
commit 6c68f1c606
11 changed files with 192 additions and 106 deletions

16
public/assets/img/mir.svg Normal file
View File

@ -0,0 +1,16 @@
<svg width="100" height="80" viewBox="0 0 100 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="80" fill="white"/>
<g clip-path="url(#clip0_510_6715)">
<path d="M31.558 29.0049V29.0148C31.5481 29.0148 28.4321 29.0049 27.6012 31.9824C26.8395 34.7126 24.6929 42.247 24.6336 42.4547H24.0401C24.0401 42.4547 21.844 34.7521 21.0725 31.9725C20.2415 28.995 17.1157 29.0049 17.1157 29.0049H10V51.651H17.119V38.2011H17.7125L21.8671 51.6509H26.8098L30.9645 38.2109H31.558V51.6509H38.6769V29.0049H31.558V29.0049ZM57.5641 29.0049C57.5641 29.0049 55.4769 29.1928 54.4976 31.3791L49.4559 42.4547H48.8624V29.0049H41.7435V51.651H48.4667C48.4667 51.651 50.6529 51.4532 51.6323 49.2769L56.575 38.2012H57.1685V51.651H64.2875V29.0049H57.5641ZM67.4528 39.2893V51.651H74.5718V44.4331H82.2843C85.6476 44.4331 88.4932 42.2866 89.5517 39.2926H67.4528V39.2893V39.2893Z" fill="#4DB45E"/>
<path d="M82.2875 29.0049H66.4537C67.2451 33.3178 70.4797 36.7767 74.6607 37.9044C75.6313 38.1673 76.6326 38.3004 77.6382 38.3002H89.8416C89.9504 37.7858 89.9999 37.2615 89.9999 36.7175C90 32.4572 86.5477 29.0049 82.2875 29.0049V29.0049Z" fill="url(#paint0_linear_510_6715)"/>
</g>
<defs>
<linearGradient id="paint0_linear_510_6715" x1="66.4537" y1="33.6525" x2="89.9999" y2="33.6525" gradientUnits="userSpaceOnUse">
<stop offset="0.3" stop-color="#00B4E6"/>
<stop offset="1" stop-color="#088CCB"/>
</linearGradient>
<clipPath id="clip0_510_6715">
<rect width="80" height="22.656" fill="white" transform="translate(10 29)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1588,11 +1588,17 @@ export default class ChatBubbles {
if(typeof(peerIdStr) === 'string' || savedFrom) {
if(savedFrom) {
const [peerId, mid] = savedFrom.split('_');
this.chat.appImManager.setInnerPeer({
peerId: peerId.toPeerId(),
lastMsgId: +mid
});
if(target.classList.contains('is-receipt-link')) {
const message = await this.managers.appMessagesManager.getMessageByPeer(peerId.toPeerId(), +mid);
if(message) {
new PopupPayment(message as Message.message, this.peerId, +bubble.dataset.mid);
}
} else {
this.chat.appImManager.setInnerPeer({
peerId: peerId.toPeerId(),
lastMsgId: +mid
});
}
} else {
const peerId = peerIdStr.toPeerId();
if(peerId !== NULL_PEER_ID) {

View File

@ -52,6 +52,7 @@ const icons = [
'mastercard',
'visa',
'unionpay',
'mir',
'logo',
];
@ -102,7 +103,11 @@ export default class PopupPayment extends PopupElement {
private currency: string;
private tipButtonsMap: Map<number, HTMLElement>;
constructor(private message: Message.message) {
constructor(
private message: Message.message,
private receiptPeerId?: PeerId,
private receiptMsgId?: number
) {
super('popup-payment', {
closable: true,
overlayClosable: true,
@ -153,7 +158,9 @@ export default class PopupPayment extends PopupElement {
const {message} = this;
const mediaInvoice = message.media as MessageMedia.messageMediaInvoice;
_i18n(this.title, mediaInvoice.receipt_msg_id ? 'PaymentReceipt' : 'PaymentCheckout');
const isReceipt = !!(this.receiptMsgId || mediaInvoice.receipt_msg_id);
_i18n(this.title, isReceipt ? 'PaymentReceipt' : 'PaymentCheckout');
if(mediaInvoice.pFlags.test) {
this.title.append(' (Test)');
}
@ -170,7 +177,7 @@ export default class PopupPayment extends PopupElement {
let photoEl: HTMLElement;
if(mediaInvoice.photo) {
photoEl = document.createElement('div');
photoEl.classList.add(detailsClassName + '-photo', 'media-container-cover');
photoEl.classList.add(detailsClassName + '-photo', 'media-container-contain');
wrapPhoto({
photo: mediaInvoice.photo,
container: photoEl,
@ -212,20 +219,22 @@ export default class PopupPayment extends PopupElement {
this.scrollable.container.append(preloaderContainer);
let paymentForm: PaymentsPaymentForm | PaymentsPaymentReceipt;
const isReceipt = !!mediaInvoice.receipt_msg_id;
this.receiptMsgId ??= mediaInvoice.receipt_msg_id;
this.receiptPeerId ??= this.receiptMsgId && message.peerId;
if(isReceipt) paymentForm = await this.managers.appPaymentsManager.getPaymentReceipt(message.peerId, mediaInvoice.receipt_msg_id);
if(isReceipt) paymentForm = await this.managers.appPaymentsManager.getPaymentReceipt(this.receiptPeerId, this.receiptMsgId);
else paymentForm = await this.managers.appPaymentsManager.getPaymentForm(message.peerId, message.mid);
let savedInfo = (paymentForm as PaymentsPaymentForm).saved_info || (paymentForm as PaymentsPaymentReceipt).info;
const savedCredentials = (paymentForm as PaymentsPaymentForm).saved_credentials;
let [lastRequestedInfo, passwordState, providerPeerTitle] = await Promise.all([
!isReceipt && savedInfo && this.managers.appPaymentsManager.validateRequestedInfo(message.peerId, message.mid, savedInfo),
!isReceipt && savedInfo && this.managers.appPaymentsManager.validateRequestedInfo(message.peerId, message.mid, savedInfo).catch(() => undefined),
savedCredentials && this.managers.passwordManager.getState(),
wrapPeerTitle({peerId: paymentForm.provider_id.toPeerId()})
]);
// console.log(paymentForm, lastRequestedInfo);
console.log(paymentForm, lastRequestedInfo);
await peerTitle.update({peerId: paymentForm.bot_id.toPeerId()});
preloaderContainer.remove();
@ -258,8 +267,8 @@ export default class PopupPayment extends PopupElement {
const _label = makeLabel();
_label.left.textContent = label;
const wrappedAmount = wrapAmount(Math.abs(+amount));
_label.right.textContent = (amount < 0 ? '-' : '') + wrappedAmount;
const wrappedAmount = wrapAmount(amount);
_label.right.textContent = wrappedAmount;
return _label.label;
});
@ -287,7 +296,7 @@ export default class PopupPayment extends PopupElement {
_i18n(totalLabel.left, 'PaymentTransactionTotal');
const totalAmount = accumulate(invoice.prices.map(({amount}) => +amount), 0);
const canTip = invoice.max_tip_amount !== undefined;
const canTip = (invoice.max_tip_amount !== undefined && !isReceipt) || !!(paymentForm as PaymentsPaymentReceipt).tip_amount;
if(canTip) {
const tipsClassName = className + '-tips';
@ -315,7 +324,7 @@ export default class PopupPayment extends PopupElement {
placeCaretAtEnd(input);
}
unsetActiveTip();
unsetActiveTip && unsetActiveTip();
const tipEl = this.tipButtonsMap.get(amount);
if(tipEl) {
tipEl.classList.add('active');
@ -326,7 +335,7 @@ export default class PopupPayment extends PopupElement {
};
const tipsLabel = makeLabel();
_i18n(tipsLabel.left, mediaInvoice.receipt_msg_id ? 'PaymentTip' : 'PaymentTipOptional');
_i18n(tipsLabel.left, isReceipt ? 'PaymentTip' : 'PaymentTipOptional');
const input = document.createElement('input');
input.type = 'tel';
// const input: HTMLElement = document.createElement('div');
@ -334,7 +343,12 @@ export default class PopupPayment extends PopupElement {
input.classList.add('input-clear', tipsClassName + '-input');
tipsLabel.right.append(input);
tipsLabel.label.style.cursor = 'text';
if(!isReceipt) {
tipsLabel.label.style.cursor = 'text';
} else {
tipsLabel.label.classList.add('disable-hover');
}
tipsLabel.label.addEventListener('mousedown', (e) => {
if(!findUpAsChild(e.target, input)) {
placeCaretAtEnd(input);
@ -384,53 +398,58 @@ export default class PopupPayment extends PopupElement {
pricesElements.push(tipsLabel.label);
///
const tipsEl = document.createElement('div');
tipsEl.classList.add(tipsClassName);
const tipClassName = tipsClassName + '-tip';
const tipButtons = invoice.suggested_tip_amounts.map((tipAmount) => {
const button = Button(tipClassName, {noRipple: true});
button.textContent = wrapAmount(tipAmount);
this.tipButtonsMap.set(+tipAmount, button);
return button;
});
const unsetActiveTip = () => {
const prevTipEl = tipsEl.querySelector('.active');
if(prevTipEl) {
prevTipEl.classList.remove('active');
}
};
attachClickEvent(tipsEl, (e) => {
const tipEl = findUpClassName(e.target, tipClassName);
if(!tipEl) {
return;
}
let tipAmount = 0;
if(tipEl.classList.contains('active')) {
tipEl.classList.remove('active');
} else {
unsetActiveTip();
tipEl.classList.add('active');
for(const [amount, el] of this.tipButtonsMap) {
if(el === tipEl) {
tipAmount = amount;
break;
let unsetActiveTip: () => void;
if(!isReceipt) {
const tipsEl = document.createElement('div');
tipsEl.classList.add(tipsClassName);
const tipClassName = tipsClassName + '-tip';
const tipButtons = invoice.suggested_tip_amounts.map((tipAmount) => {
const button = Button(tipClassName, {noRipple: true});
button.textContent = wrapAmount(tipAmount);
this.tipButtonsMap.set(+tipAmount, button);
return button;
});
unsetActiveTip = () => {
const prevTipEl = tipsEl.querySelector('.active');
if(prevTipEl) {
prevTipEl.classList.remove('active');
}
};
attachClickEvent(tipsEl, (e) => {
const tipEl = findUpClassName(e.target, tipClassName);
if(!tipEl) {
return;
}
let tipAmount = 0;
if(tipEl.classList.contains('active')) {
tipEl.classList.remove('active');
} else {
unsetActiveTip();
tipEl.classList.add('active');
for(const [amount, el] of this.tipButtonsMap) {
if(el === tipEl) {
tipAmount = amount;
break;
}
}
}
}
setInputValue(tipAmount);
});
setInputValue(0);
tipsEl.append(...tipButtons);
pricesElements.push(tipsEl);
setInputValue(tipAmount);
});
setInputValue(0);
tipsEl.append(...tipButtons);
pricesElements.push(tipsEl);
} else {
setInputValue((paymentForm as PaymentsPaymentReceipt).tip_amount);
}
} else {
setTotal();
}
@ -474,7 +493,7 @@ export default class PopupPayment extends PopupElement {
const setRowTitle = (row: Row, textContent: string) => {
row.title.textContent = textContent;
if(!textContent) {
const e = I18n.weakMap.get(row.subtitle) as I18n.IntlElement;
const e = I18n.weakMap.get(row.subtitle.firstElementChild as HTMLElement) as I18n.IntlElement;
row.title.append(i18n(e.key));
}
@ -543,7 +562,8 @@ export default class PopupPayment extends PopupElement {
const postAddress = shippingAddress.shipping_address;
setRowTitle(shippingAddressRow, [postAddress.city, postAddress.street_line1, postAddress.street_line2].filter(Boolean).join(', '));
shippingMethodRow.container.classList.remove('hide');
shippingMethodRow.container.classList.toggle('hide', !lastRequestedInfo && !isReceipt);
} : undefined;
const setShippingInfo = (info: PaymentRequestedInfo) => {
@ -586,7 +606,13 @@ export default class PopupPayment extends PopupElement {
shippingAmount = accumulate(shippingOption.prices.map(({amount}) => +amount), 0);
lastShippingPricesElements = makePricesElements(shippingOption.prices);
let l = totalLabel.label;
if(canTip) l = l.previousElementSibling.previousElementSibling as any;
if(canTip) {
l = l.previousElementSibling as any;
if(!isReceipt) {
l = l.previousElementSibling as any;
}
}
lastShippingPricesElements.forEach((element) => l.parentElement.insertBefore(element, l));
setTotal();

View File

@ -23,6 +23,7 @@ import Row from "../row";
import { SettingSection } from "../sidebarLeft";
import { getPaymentBrandIconPath, PaymentButton, PaymentsCredentialsToken } from "./payment";
import { createVerificationIframe } from "./paymentVerification";
// import { putPreloader } from "../putPreloader";
export type PaymentCardDetails = {
cardNumber: string;
@ -254,6 +255,7 @@ export default class PopupPaymentCard extends PopupElement<{
}
});
// putPreloader(this.body, true);
this.body.append(iframe);
this.show();
}

View File

@ -264,7 +264,10 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage,
managers.appMessagesManager.fetchMessageReplyTo(message);
} else {
langPackKey = 'PaymentSuccessfullyPaid';
args.push(wrapLinkToMessage(invoiceMessage, plain));
args.push(wrapLinkToMessage(invoiceMessage, plain).then((el) => {
el.classList.add('is-receipt-link');
return el;
}));
}
}

View File

@ -3,14 +3,15 @@ import replaceNonNumber from "../string/replaceNonNumber";
const CARD_BRAND_REGEXP: {[brand: string]: RegExp} = {
visa: /^4/,
mastercard: /^(51|52|53|54|55|22|23|24|25|26|27)/,
mastercard: /^(51|52|53|54|55|222|23|24|25|26|27)/,
amex: /^(34|37)/,
discover: /^(60|64|65)/,
diners: /^(30|38|39)/,
diners14: /^(36)/,
jcb: /^(35)/,
unionpay: /^(62[0-6,8-9]|627[0-6,8-9]|6277[0-7,9]|62778[1-9]|81)/,
elo: /^(5067|509|636368|627780)/
elo: /^(5067|509|636368|627780)/,
mir: /^(220[0-4])/
};
// * taken from Stripe
@ -74,6 +75,12 @@ export const CARD_BRANDS: {[b: string]: {
cvcMaxLength: 3,
cvcMinLength: null
},
mir: {
minLength: 16,
maxLength: 16,
cvcMaxLength: 3,
cvcMinLength: null
},
unknown: {
minLength: 16,
maxLength: 16,

View File

@ -16,6 +16,7 @@ function makeValidationError(code?: string) {
} : null;
}
// Luhn algorithm
function validateCompleteCardNumber(card: string) {
const t = '0'.charCodeAt(0);
const n = card.length % 2;
@ -61,7 +62,11 @@ function getCardInfoByNumber(card: string) {
}
function makeCardNumberError(str: string, length: number, ignoreIncomplete: boolean) {
return str.length >= length ? (validateCompleteCardNumber(str) ? null : makeValidationError('invalid')) : (ignoreIncomplete ? null : makeValidationError('incomplete'));
if(str.length >= length) {
return validateCompleteCardNumber(str) || detectCardBrand(str) === 'mir' ? null : makeValidationError('invalid');
}
return ignoreIncomplete ? null : makeValidationError('incomplete');
}
export function validateCardNumber(str: string, options: PatternValidationOptions = {}) {

View File

@ -25,49 +25,58 @@ function number_format(number: any, decimals: any, dec_point: any, thousands_sep
return s.join(dec);
}
export default function paymentsWrapCurrencyAmount($amount: number | string, $currency: string, $skipSymbol?: boolean) {
$amount = +$amount;
export default function paymentsWrapCurrencyAmount(amount: number | string, currency: string, skipSymbol?: boolean) {
amount = +amount;
const $currency_data = Currencies[$currency]; // вытащить из json
if(!$currency_data) {
const isNegative = amount < 0;
const currencyData = Currencies[currency];
if(!currencyData) {
throw new Error('CURRENCY_WRAP_INVALID');
}
const $amount_exp = $amount / Math.pow(10, $currency_data['exp']);
const amountExp = amount / Math.pow(10, currencyData.exp);
let $decimals = $currency_data['exp'];
if($currency == 'IRR' &&
Math.floor($amount_exp) == $amount_exp) {
$decimals = 0; // у иранцев копейки почти всегда = 0 и не показываются в UI
let decimals = currencyData.exp;
if(currency == 'IRR' && Math.floor(amountExp) == amountExp) {
decimals = 0; // у иранцев копейки почти всегда = 0 и не показываются в UI
}
const $formatted = number_format($amount_exp, $decimals, $currency_data['decimal_sep'], $currency_data['thousands_sep']);
if($skipSymbol) {
return $formatted;
let formatted = number_format(amountExp, decimals, currencyData.decimal_sep, currencyData.thousands_sep);
if(skipSymbol) {
return formatted;
}
const $splitter = $currency_data['space_between'] ? " " : '';
let $formatted_intern: string;
if($currency_data['symbol_left']) {
$formatted_intern = $currency_data['symbol'] + $splitter + $formatted;
let symbol = currencyData.symbol;
if(isNegative && !currencyData.space_between && currencyData.symbol_left) {
symbol = '-' + symbol;
formatted = formatted.replace('-', '');
}
let out: string;
const splitter = currencyData.space_between ? " " : '';
if(currencyData.symbol_left) {
out = symbol + splitter + formatted;
} else {
$formatted_intern = $formatted + $splitter + $currency_data['symbol'];
out = formatted + splitter + symbol;
}
return $formatted_intern;
return out;
}
function paymentsGetCurrencyExp($currency: string) {
if($currency == 'CLF') {
return 4;
}
if(['BHD','IQD','JOD','KWD','LYD','OMR','TND'].includes($currency)) {
return 3;
}
if(['BIF','BYR','CLP','CVE','DJF','GNF','ISK','JPY','KMF','KRW','MGA', 'PYG','RWF','UGX','UYI','VND','VUV','XAF','XOF','XPF'].includes($currency)) {
return 0;
}
if($currency == 'MRO') {
return 1;
}
return 2;
}
(window as any).p = paymentsWrapCurrencyAmount;
// function paymentsGetCurrencyExp($currency: string) {
// if($currency == 'CLF') {
// return 4;
// }
// if(['BHD','IQD','JOD','KWD','LYD','OMR','TND'].includes($currency)) {
// return 3;
// }
// if(['BIF','BYR','CLP','CVE','DJF','GNF','ISK','JPY','KMF','KRW','MGA', 'PYG','RWF','UGX','UYI','VND','VUV','XAF','XOF','XPF'].includes($currency)) {
// return 0;
// }
// if($currency == 'MRO') {
// return 1;
// }
// return 2;
// }

View File

@ -2788,6 +2788,7 @@ $bubble-beside-button-width: 38px;
overflow: hidden;
min-height: 2.5rem;
display: flex;
border-radius: .375rem;
&:last-child {
border-bottom-left-radius: $border-radius-big;
@ -2797,7 +2798,7 @@ $bubble-beside-button-width: 38px;
&-button {
padding: .5625rem 0;
border-radius: 6px;
border-radius: inherit;
z-index: 2;
font-size: .875rem;
user-select: none;

View File

@ -82,7 +82,8 @@
.payment-verification {
width: 100%;
min-height: 30rem;
height: 40rem;
max-height: 100%;
border: none;
flex: 1 1 auto;
}

View File

@ -1268,6 +1268,16 @@ middle-ellipsis-element {
// }
// }
.media-container-contain {
position: relative;
.media-photo {
object-fit: contain;
width: 100%;
height: 100%;
}
}
.media-container-cover {
position: relative;