Browse Source

Recurring payments

master
Eduard Kuzmenko 2 years ago
parent
commit
24bdb104b0
  1. 40
      src/components/popups/payment.ts
  2. 6
      src/components/wrappers/messageActionTextNewUnsafe.ts
  3. 11
      src/lang.ts
  4. 9
      src/lib/appManagers/appDialogsManager.ts
  5. 2
      src/lib/appManagers/appMessagesManager.ts
  6. 5
      src/lib/langPack.ts
  7. 2
      src/lib/mtproto/dcConfigurator.ts
  8. 4
      src/scss/partials/_checkbox.scss
  9. 2
      src/scss/partials/_row.scss
  10. 4
      src/scss/partials/popups/_payment.scss

40
src/components/popups/payment.ts

@ -27,9 +27,11 @@ import { AccountTmpPassword, InputInvoice, InputPaymentCredentials, LabeledPrice
import I18n, { i18n, LangPackKey, _i18n } from "../../lib/langPack"; import I18n, { i18n, LangPackKey, _i18n } from "../../lib/langPack";
import { ApiError } from "../../lib/mtproto/apiManager"; import { ApiError } from "../../lib/mtproto/apiManager";
import wrapEmojiText from "../../lib/richTextProcessor/wrapEmojiText"; import wrapEmojiText from "../../lib/richTextProcessor/wrapEmojiText";
import wrapRichText from "../../lib/richTextProcessor/wrapRichText";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import AvatarElement from "../avatar"; import AvatarElement from "../avatar";
import Button from "../button"; import Button from "../button";
import CheckboxField from "../checkboxField";
import PeerTitle from "../peerTitle"; import PeerTitle from "../peerTitle";
import { putPreloader } from "../putPreloader"; import { putPreloader } from "../putPreloader";
import Row from "../row"; import Row from "../row";
@ -240,10 +242,6 @@ export default class PopupPayment extends PopupElement {
// console.log(paymentForm, lastRequestedInfo); // console.log(paymentForm, lastRequestedInfo);
await peerTitle.update({peerId: paymentForm.bot_id.toPeerId()});
preloaderContainer.remove();
this.element.classList.remove('is-loading');
const wrapAmount = (amount: string | number, skipSymbol?: boolean) => { const wrapAmount = (amount: string | number, skipSymbol?: boolean) => {
return paymentsWrapCurrencyAmount(amount, currency, skipSymbol); return paymentsWrapCurrencyAmount(amount, currency, skipSymbol);
}; };
@ -251,6 +249,13 @@ export default class PopupPayment extends PopupElement {
const {invoice} = paymentForm; const {invoice} = paymentForm;
const currency = invoice.currency; const currency = invoice.currency;
const isRecurring = invoice.pFlags.recurring && !isReceipt;
await peerTitle.update({peerId: paymentForm.bot_id.toPeerId()});
const peerTitle2 = isRecurring ? await wrapPeerTitle({peerId: paymentForm.bot_id.toPeerId()}) : undefined;
preloaderContainer.remove();
this.element.classList.remove('is-loading');
const makeLabel = () => { const makeLabel = () => {
const labelEl = document.createElement('div'); const labelEl = document.createElement('div');
labelEl.classList.add(pricesClassName + '-price'); labelEl.classList.add(pricesClassName + '-price');
@ -669,9 +674,23 @@ export default class PopupPayment extends PopupElement {
shippingEmailRow, shippingEmailRow,
shippingPhoneRow, shippingPhoneRow,
].filter(Boolean); ].filter(Boolean);
const acceptTermsCheckboxField = isRecurring && new CheckboxField({
text: 'Payments.Recurrent.Accept',
textArgs: [wrapRichText(invoice.recurring_terms_url), peerTitle2]
});
const acceptTermsRow = isRecurring && createRow({
checkboxField: acceptTermsCheckboxField,
noCheckboxSubtitle: true
});
const recurringElements = isRecurring ? [document.createElement('hr'), acceptTermsRow.container] : [];
this.scrollable.append(...[ this.scrollable.append(...[
document.createElement('hr'), document.createElement('hr'),
...rows.map((row) => row.container) ...rows.map((row) => row.container),
...recurringElements
].filter(Boolean)); ].filter(Boolean));
/// ///
@ -779,7 +798,11 @@ export default class PopupPayment extends PopupElement {
}); });
}; };
let payButton: HTMLElement; const onChange = () => {
payButton.disabled = !!(acceptTermsCheckboxField && !acceptTermsCheckboxField.checked);
};
let payButton: HTMLButtonElement;
if(isReceipt) { if(isReceipt) {
payButton = PaymentButton({ payButton = PaymentButton({
onClick: () => this.hide(), onClick: () => this.hide(),
@ -792,6 +815,11 @@ export default class PopupPayment extends PopupElement {
}); });
} }
onChange();
if(acceptTermsCheckboxField) {
acceptTermsCheckboxField.input.addEventListener('change', onChange);
}
this.body.append(this.btnConfirmOnEnter = payButton); this.body.append(this.btnConfirmOnEnter = payButton);
this.onContentUpdate(); this.onContentUpdate();

6
src/components/wrappers/messageActionTextNewUnsafe.ts

@ -250,7 +250,9 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage,
} }
case 'messageActionPaymentSent': { case 'messageActionPaymentSent': {
langPackKey = 'PaymentSuccessfullyPaidNoItem'; const isRecurringInit = action.pFlags.recurring_init;
const isRecurringUsed = action.pFlags.recurring_used;
langPackKey = isRecurringUsed ? 'Chat.Service.PaymentSentRecurringUsedNoTitle' : (isRecurringInit ? 'Chat.Service.PaymentSentRecurringInitNoTitle' : 'Chat.Service.PaymentSent1NoTitle');
const price = paymentsWrapCurrencyAmount(action.total_amount, action.currency); const price = paymentsWrapCurrencyAmount(action.total_amount, action.currency);
args = [price, getNameDivHTML(message.peerId, plain)]; args = [price, getNameDivHTML(message.peerId, plain)];
@ -263,7 +265,7 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage,
if(!invoiceMessage) { if(!invoiceMessage) {
managers.appMessagesManager.fetchMessageReplyTo(message); managers.appMessagesManager.fetchMessageReplyTo(message);
} else { } else {
langPackKey = 'PaymentSuccessfullyPaid'; langPackKey = isRecurringUsed ? 'Chat.Service.PaymentSentRecurringUsed' : (isRecurringInit ? 'Chat.Service.PaymentSentRecurringInit' : 'Chat.Service.PaymentSent1');
args.push(wrapLinkToMessage(invoiceMessage, plain)); args.push(wrapLinkToMessage(invoiceMessage, plain));
} }
} }

11
src/lang.ts

@ -703,10 +703,6 @@ const lang = {
"PaymentInvoice": "INVOICE", "PaymentInvoice": "INVOICE",
"PaymentTestInvoice": "TEST INVOICE", "PaymentTestInvoice": "TEST INVOICE",
"PaymentReceipt": "Receipt", "PaymentReceipt": "Receipt",
"PaymentSuccessfullyPaid": "You successfully transferred %1$s to %2$s for %3$s",
"PaymentSuccessfullyPaidNoItem": "You successfully transferred %1$s to %2$s",
// "PaymentSuccessfullyPaidRecurrent": "You successfully transferred %1$s to %2$s for %3$s and allowed future recurring payments",
// "PaymentSuccessfullyPaidNoItemRecurrent": "You successfully transferred %1$s to %2$s and allowed future recurring payments",
"PaymentCheckout": "Checkout", "PaymentCheckout": "Checkout",
"PaymentTransactionTotal": "Total", "PaymentTransactionTotal": "Total",
"PaymentTip": "Tip", "PaymentTip": "Tip",
@ -835,6 +831,12 @@ const lang = {
"Chat.Service.Channel.UpdatedVideo": "Channel video updated", "Chat.Service.Channel.UpdatedVideo": "Channel video updated",
"Chat.Service.BotPermissionAllowed": "You allowed this bot to message you when you logged in on %@", "Chat.Service.BotPermissionAllowed": "You allowed this bot to message you when you logged in on %@",
"Chat.Service.Group.UpdatedPinnedMessage": "%@ pinned \"%@\"", "Chat.Service.Group.UpdatedPinnedMessage": "%@ pinned \"%@\"",
"Chat.Service.PaymentSent1": "You have successfully transferred **%1$@** to **%2$@** for **%3$@**",
"Chat.Service.PaymentSent1NoTitle": "You have successfully transferred **%1$@** to **%2$@**",
"Chat.Service.PaymentSentRecurringInit": "You successfully transferred **%1$@** to **%2$@** for **%3$@** and allowed future recurring payments",
"Chat.Service.PaymentSentRecurringInitNoTitle": "You successfully transferred **%1$@** to **%2$@** and allowed future recurring payments",
"Chat.Service.PaymentSentRecurringUsed": "You have just successfully transferred **%1$@** to **%2$@** for **%3$@** via recurrent payments",
"Chat.Service.PaymentSentRecurringUsedNoTitle": "You have just successfully transferred **%1$@** to **%2$@** via recurrent payments",
"Chat.Service.VoiceChatStarted": "%1$@ started a [video chat](open)", "Chat.Service.VoiceChatStarted": "%1$@ started a [video chat](open)",
"Chat.Service.VoiceChatStartedYou": "You started a [video chat](open)", "Chat.Service.VoiceChatStartedYou": "You started a [video chat](open)",
"Chat.Service.VoiceChatStarted.Channel": "[Live Stream](open) started", "Chat.Service.VoiceChatStarted.Channel": "[Live Stream](open) started",
@ -985,6 +987,7 @@ const lang = {
"NewPoll.Quiz": "Quiz Mode", "NewPoll.Quiz": "Quiz Mode",
"Notification.Contact.Reacted": "%1$@ to your \"%2$@\"", "Notification.Contact.Reacted": "%1$@ to your \"%2$@\"",
// "Notification.Group.Reacted": "%1$@: %2$@ to your \"%3$@\"", // "Notification.Group.Reacted": "%1$@: %2$@ to your \"%3$@\"",
"Payments.Recurrent.Accept": "I accept [Terms of Service]() of **%@**.",
"Peer.Activity.User.PlayingGame": "playing a game", "Peer.Activity.User.PlayingGame": "playing a game",
"Peer.Activity.User.TypingText": "typing", "Peer.Activity.User.TypingText": "typing",
"Peer.Activity.User.SendingPhoto": "sending a photo", "Peer.Activity.User.SendingPhoto": "sending a photo",

9
src/lib/appManagers/appDialogsManager.ts

@ -80,6 +80,7 @@ import noop from "../../helpers/noop";
import DialogsPlaceholder from "../../helpers/dialogsPlaceholder"; import DialogsPlaceholder from "../../helpers/dialogsPlaceholder";
import pause from "../../helpers/schedulers/pause"; import pause from "../../helpers/schedulers/pause";
import apiManagerProxy from "../mtproto/mtprotoworker"; import apiManagerProxy from "../mtproto/mtprotoworker";
import filterAsync from "../../helpers/array/filterAsync";
export const DIALOG_LIST_ELEMENT_TAG = 'A'; export const DIALOG_LIST_ELEMENT_TAG = 'A';
@ -1287,11 +1288,13 @@ export class AppDialogsManager {
this.loadContacts = () => { this.loadContacts = () => {
const pageCount = windowSize.height / 60 | 0; const pageCount = windowSize.height / 60 | 0;
const arr = contacts.splice(0, pageCount).filter(this.verifyPeerIdForContacts); const promise = filterAsync(contacts.splice(0, pageCount), this.verifyPeerIdForContacts);
promise.then((arr) => {
arr.forEach((peerId) => { arr.forEach((peerId) => {
sortedUserList.add(peerId); sortedUserList.add(peerId);
}); });
});
if(!contacts.length) { if(!contacts.length) {
this.loadContacts = undefined; this.loadContacts = undefined;
@ -1300,12 +1303,12 @@ export class AppDialogsManager {
this.loadContacts(); this.loadContacts();
this.processContact = (peerId) => { this.processContact = async(peerId) => {
if(peerId.isAnyChat()) { if(peerId.isAnyChat()) {
return; return;
} }
const good = this.verifyPeerIdForContacts(peerId); const good = await this.verifyPeerIdForContacts(peerId);
const added = sortedUserList.has(peerId); const added = sortedUserList.has(peerId);
if(!added && good) sortedUserList.add(peerId); if(!added && good) sortedUserList.add(peerId);
else if(added && !good) sortedUserList.delete(peerId); else if(added && !good) sortedUserList.delete(peerId);

2
src/lib/appManagers/appMessagesManager.ts

@ -4027,7 +4027,7 @@ export class AppMessagesManager extends AppManager {
this.onUpdateNewMessage(update); this.onUpdateNewMessage(update);
} }
if(message._ === 'messageService' && message.action._ === 'messageActionPaymentSent') { if(message._ === 'messageService' && message.action._ === 'messageActionPaymentSent' && message.reply_to) {
this.rootScope.dispatchEvent('payment_sent', { this.rootScope.dispatchEvent('payment_sent', {
peerId: message.reply_to.reply_to_peer_id ? getPeerId(message.reply_to.reply_to_peer_id) : message.peerId, peerId: message.reply_to.reply_to_peer_id ? getPeerId(message.reply_to.reply_to_peer_id) : message.peerId,
mid: message.reply_to_mid mid: message.reply_to_mid

5
src/lib/langPack.ts

@ -368,6 +368,11 @@ namespace I18n {
a.target = '_blank'; a.target = '_blank';
} else { } else {
a = args[indexHolder.i++] as HTMLAnchorElement; a = args[indexHolder.i++] as HTMLAnchorElement;
if(a instanceof DocumentFragment) { // right after wrapRichText
a = a.firstChild as any;
}
a.textContent = ''; // reset content a.textContent = ''; // reset content
} }

2
src/lib/mtproto/dcConfigurator.ts

@ -65,7 +65,7 @@ export class DcConfigurator {
/// #if MTPROTO_HAS_WS /// #if MTPROTO_HAS_WS
private transportSocket = (dcId: DcId, connectionType: ConnectionType, suffix: string, premium?: boolean) => { private transportSocket = (dcId: DcId, connectionType: ConnectionType, suffix: string, premium?: boolean) => {
const path = connectionType !== 'client' ? 'apiws' + (premium ? PREMIUM_SUFFIX : '') : ('apiws' + TEST_SUFFIX); const path = connectionType !== 'client' ? 'apiws' + (premium ? PREMIUM_SUFFIX : TEST_SUFFIX) : ('apiws' + TEST_SUFFIX);
const chosenServer = `wss://${App.suffix.toLowerCase()}ws${dcId}${suffix}.web.telegram.org/${path}`; const chosenServer = `wss://${App.suffix.toLowerCase()}ws${dcId}${suffix}.web.telegram.org/${path}`;
const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : ''; const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : '';

4
src/scss/partials/_checkbox.scss

@ -124,6 +124,10 @@
left: auto; left: auto;
} }
} }
.anchor-url {
pointer-events: all;
}
} }
.checkbox-ripple { .checkbox-ripple {

2
src/scss/partials/_row.scss

@ -119,7 +119,9 @@ $row-border-radius: $border-radius-medium;
} }
.checkbox-field { .checkbox-field {
margin-top: 0;
margin-right: 0; margin-right: 0;
margin-bottom: 0;
height: auto; height: auto;
.checkbox-caption { .checkbox-caption {

4
src/scss/partials/popups/_payment.scss

@ -36,6 +36,8 @@
hr { hr {
display: block !important; display: block !important;
margin: .5rem 0 !important;
padding: 0 !important;
} }
.input-field { .input-field {
@ -158,7 +160,7 @@
&-prices { &-prices {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 1rem .25rem; margin: 1rem .25rem .5rem;
&-price { &-price {
color: var(--secondary-text-color); color: var(--secondary-text-color);

Loading…
Cancel
Save