From 652765bf468eb47d128a6eebf6c7e94e725e370a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 5 Dec 2024 11:35:33 +0600 Subject: [PATCH] add support for subscriptions --- .../edit/payments/EditUserStarSubscription.kt | 65 +++++++++++++++++++ .../abstracts/types/SubscriptionInfo.kt | 7 ++ .../abstracts/types/SubscriptionPeriodInfo.kt | 7 ++ .../chat/invite_links/CreateChatInviteLink.kt | 8 ++- .../edit/payments/EditUserStarSubscription.kt | 32 +++++++++ .../send/payments/CreateInvoiceLink.kt | 15 ++++- .../dev/inmo/tgbotapi/types/ChatInviteLink.kt | 23 ++++++- .../kotlin/dev/inmo/tgbotapi/types/Common.kt | 4 ++ .../tgbotapi/types/payments/RecurringInfo.kt | 15 +++++ .../types/payments/SuccessfulPayment.kt | 19 +++++- .../payments/stars/TransactionPartner.kt | 8 ++- 11 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/edit/payments/EditUserStarSubscription.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionInfo.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionPeriodInfo.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/edit/payments/EditUserStarSubscription.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/RecurringInfo.kt diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/edit/payments/EditUserStarSubscription.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/edit/payments/EditUserStarSubscription.kt new file mode 100644 index 0000000000..78fa515333 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/edit/payments/EditUserStarSubscription.kt @@ -0,0 +1,65 @@ +package dev.inmo.tgbotapi.extensions.api.edit.payments + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.edit.payments.EditUserStarSubscription +import dev.inmo.tgbotapi.types.UserId +import dev.inmo.tgbotapi.types.chat.User +import dev.inmo.tgbotapi.types.payments.abstracts.TelegramPaymentChargeId + +public suspend fun TelegramBot.editUserStarSubscription( + userId: UserId, + telegramPaymentChargeId: TelegramPaymentChargeId, + isCanceled: Boolean +): Boolean = execute( + EditUserStarSubscription( + userId = userId, + telegramPaymentChargeId = telegramPaymentChargeId, + isCanceled = isCanceled + ) +) + +public suspend fun TelegramBot.editUserStarSubscription( + user: User, + telegramPaymentChargeId: TelegramPaymentChargeId, + isCanceled: Boolean +): Boolean = editUserStarSubscription( + userId = user.id, + telegramPaymentChargeId = telegramPaymentChargeId, + isCanceled = isCanceled +) + +public suspend fun TelegramBot.cancelUserStarSubscription( + userId: UserId, + telegramPaymentChargeId: TelegramPaymentChargeId, +): Boolean = editUserStarSubscription( + userId = userId, + telegramPaymentChargeId = telegramPaymentChargeId, + isCanceled = true +) + +public suspend fun TelegramBot.cancelUserStarSubscription( + user: User, + telegramPaymentChargeId: TelegramPaymentChargeId, +): Boolean = editUserStarSubscription( + user = user, + telegramPaymentChargeId = telegramPaymentChargeId, + isCanceled = true +) + +public suspend fun TelegramBot.enableUserStarSubscription( + userId: UserId, + telegramPaymentChargeId: TelegramPaymentChargeId, +): Boolean = editUserStarSubscription( + userId = userId, + telegramPaymentChargeId = telegramPaymentChargeId, + isCanceled = false +) + +public suspend fun TelegramBot.enableUserStarSubscription( + user: User, + telegramPaymentChargeId: TelegramPaymentChargeId, +): Boolean = editUserStarSubscription( + user = user, + telegramPaymentChargeId = telegramPaymentChargeId, + isCanceled = false +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionInfo.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionInfo.kt new file mode 100644 index 0000000000..826f4a0ed0 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionInfo.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.abstracts.types + +import korlibs.time.TimeSpan + +interface SubscriptionInfo : SubscriptionPeriodInfo { + val subscriptionPrice: UInt? +} \ No newline at end of file diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionPeriodInfo.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionPeriodInfo.kt new file mode 100644 index 0000000000..1eb6e77718 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/SubscriptionPeriodInfo.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.abstracts.types + +import korlibs.time.TimeSpan + +interface SubscriptionPeriodInfo { + val subscriptionPeriod: TimeSpan? +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/invite_links/CreateChatInviteLink.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/invite_links/CreateChatInviteLink.kt index d49a98df05..4b50b7b8df 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/invite_links/CreateChatInviteLink.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/invite_links/CreateChatInviteLink.kt @@ -1,5 +1,7 @@ package dev.inmo.tgbotapi.requests.chat.invite_links +import dev.inmo.tgbotapi.abstracts.types.SubscriptionInfo +import dev.inmo.tgbotapi.abstracts.types.SubscriptionPeriodInfo import korlibs.time.DateTime import dev.inmo.tgbotapi.requests.chat.abstracts.* import dev.inmo.tgbotapi.types.* @@ -15,9 +17,9 @@ sealed interface CreateChatInviteLink : EditChatInv override fun method(): String = "createChatInviteLink" - sealed interface Subscription : CreateChatInviteLink { - val subscriptionPeriod: TimeSpan - val subscriptionPrice: UInt + sealed interface Subscription : CreateChatInviteLink, SubscriptionInfo { + override val subscriptionPeriod: TimeSpan + override val subscriptionPrice: UInt override fun method(): String = "createChatSubscriptionInviteLink" } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/edit/payments/EditUserStarSubscription.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/edit/payments/EditUserStarSubscription.kt new file mode 100644 index 0000000000..960e4b2f5b --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/edit/payments/EditUserStarSubscription.kt @@ -0,0 +1,32 @@ +package dev.inmo.tgbotapi.requests.edit.payments + +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.UserId +import dev.inmo.tgbotapi.types.isCanceledField +import dev.inmo.tgbotapi.types.payments.abstracts.TelegramPaymentChargeId +import dev.inmo.tgbotapi.types.telegramPaymentChargeIdField +import dev.inmo.tgbotapi.types.userIdField +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.builtins.serializer + +@Serializable +data class EditUserStarSubscription( + @SerialName(userIdField) + val userId: UserId, + @SerialName(telegramPaymentChargeIdField) + val telegramPaymentChargeId: TelegramPaymentChargeId, + @SerialName(isCanceledField) + val isCanceled: Boolean +) : SimpleRequest { + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + override fun method(): String = "editUserStarSubscription" + + override val resultDeserializer: DeserializationStrategy + get() = Boolean.serializer() + +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/payments/CreateInvoiceLink.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/payments/CreateInvoiceLink.kt index f1114a47f4..eda5157e5e 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/payments/CreateInvoiceLink.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/payments/CreateInvoiceLink.kt @@ -3,8 +3,8 @@ package dev.inmo.tgbotapi.requests.send.payments import dev.inmo.tgbotapi.abstracts.CommonSendInvoiceData import dev.inmo.tgbotapi.abstracts.types.* import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest -import dev.inmo.tgbotapi.requests.send.abstracts.SendMessageRequest import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.content.InvoiceContent @@ -12,6 +12,8 @@ import dev.inmo.tgbotapi.types.payments.LabeledPrice import dev.inmo.tgbotapi.types.payments.LabeledPricesSerializer import dev.inmo.tgbotapi.types.payments.abstracts.Currency import dev.inmo.tgbotapi.types.payments.abstracts.XTR +import dev.inmo.tgbotapi.utils.TimeSpanAsSecondsSerializer +import korlibs.time.TimeSpan import kotlinx.serialization.* import kotlinx.serialization.builtins.serializer @@ -30,9 +32,14 @@ data class CreateInvoiceLink( override val providerToken: String?, @SerialName(currencyField) override val currency: Currency, + @SerialName(businessConnectionIdField) + override val businessConnectionId: BusinessConnectionId? = null, @Serializable(LabeledPricesSerializer::class) @SerialName(pricesField) override val prices: List, + @SerialName(subscriptionPeriodField) + @Serializable(TimeSpanAsSecondsSerializer::class) + override val subscriptionPeriod: TimeSpan? = null, @SerialName(maxTipAmountField) override val maxTipAmount: Int? = null, @SerialName(suggestedTipAmountsField) @@ -53,7 +60,7 @@ data class CreateInvoiceLink( override val shouldSendEmailToProvider: Boolean = false, @SerialName(priceDependOnShipAddressField) override val priceDependOnShipAddress: Boolean = false -) : CommonSendInvoiceData, SimpleRequest { +) : CommonSendInvoiceData, SimpleRequest, WithOptionalBusinessConnectionId, SubscriptionPeriodInfo { override fun method(): String = "createInvoiceLink" override val resultDeserializer: DeserializationStrategy get() = String.serializer() @@ -137,4 +144,8 @@ data class CreateInvoiceLink( photoWidth = null photoHeight = null } + + companion object { + const val DEFAULT: Seconds = 2592000 // 30 days + } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatInviteLink.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatInviteLink.kt index eab0f0349d..9a1f7f0ec6 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatInviteLink.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatInviteLink.kt @@ -2,8 +2,12 @@ package dev.inmo.tgbotapi.types import korlibs.time.DateTime import dev.inmo.tgbotapi.abstracts.WithUser +import dev.inmo.tgbotapi.abstracts.types.SubscriptionInfo +import dev.inmo.tgbotapi.abstracts.types.SubscriptionPeriodInfo import dev.inmo.tgbotapi.types.chat.User import dev.inmo.tgbotapi.utils.RiskFeature +import dev.inmo.tgbotapi.utils.TimeSpanAsSecondsSerializer +import korlibs.time.TimeSpan import kotlinx.serialization.* import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder @@ -64,7 +68,7 @@ sealed interface ChatInviteLink : WithUser { * Base interface for all [ChatInviteLink]s which are NOT [PrimaryInviteLink] */ @Serializable(ChatInviteLinkSerializer::class) -sealed interface SecondaryChatInviteLink : ChatInviteLink { +sealed interface SecondaryChatInviteLink : ChatInviteLink, SubscriptionInfo { override val isPrimary: Boolean get() = false } @@ -108,7 +112,12 @@ data class ChatInviteLinkWithJoinRequest( @SerialName(isRevokedField) override val isRevoked: Boolean = false, @SerialName(expireDateField) - private val expireDate: TelegramDate? = null + private val expireDate: TelegramDate? = null, + @SerialName(subscriptionPeriodField) + @Serializable(TimeSpanAsSecondsSerializer::class) + override val subscriptionPeriod: TimeSpan? = null, + @SerialName(subscriptionPriceField) + override val subscriptionPrice: UInt? = null ) : SecondaryChatInviteLink { override val expirationDateTime: DateTime? get() = expireDate ?.asDate @@ -131,6 +140,11 @@ data class ChatInviteLinkWithLimitedMembers( override val isRevoked: Boolean = false, @SerialName(expireDateField) private val expireDate: TelegramDate? = null, + @SerialName(subscriptionPeriodField) + @Serializable(TimeSpanAsSecondsSerializer::class) + override val subscriptionPeriod: TimeSpan? = null, + @SerialName(subscriptionPriceField) + override val subscriptionPrice: UInt? = null ) : SecondaryChatInviteLink { override val expirationDateTime: DateTime? get() = expireDate ?.asDate @@ -152,6 +166,11 @@ data class ChatInviteLinkUnlimited( override val isRevoked: Boolean = false, @SerialName(expireDateField) private val expireDate: TelegramDate? = null, + @SerialName(subscriptionPeriodField) + @Serializable(TimeSpanAsSecondsSerializer::class) + override val subscriptionPeriod: TimeSpan? = null, + @SerialName(subscriptionPriceField) + override val subscriptionPrice: UInt? = null ) : SecondaryChatInviteLink { override val expirationDateTime: DateTime? get() = expireDate ?.asDate diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt index 8f63bf469c..5a0be61389 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt @@ -535,7 +535,11 @@ const val secondStreetLineField = "street_line2" const val postCodeField = "post_code" const val shippingAddressField = "shipping_address" const val orderInfoField = "order_info" +const val subscriptionExpirationDateField = "subscription_expiration_date" +const val isRecurringField = "is_recurring" +const val isFirstRecurringField = "is_first_recurring" const val telegramPaymentChargeIdField = "telegram_payment_charge_id" +const val isCanceledField = "is_canceled" const val providerPaymentChargeIdField = "provider_payment_charge_id" const val providerTokenField = "provider_token" const val providerDataField = "provider_data" diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/RecurringInfo.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/RecurringInfo.kt new file mode 100644 index 0000000000..7043cc4274 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/RecurringInfo.kt @@ -0,0 +1,15 @@ +package dev.inmo.tgbotapi.types.payments + +import dev.inmo.tgbotapi.types.TelegramDate +import dev.inmo.tgbotapi.types.isFirstRecurringField +import dev.inmo.tgbotapi.types.subscriptionExpirationDateField +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RecurringInfo( + @SerialName(subscriptionExpirationDateField) + val subscriptionExpirationDate: TelegramDate, + @SerialName(isFirstRecurringField) + val firstSubscriptionPeriod: Boolean +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/SuccessfulPayment.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/SuccessfulPayment.kt index c075f706f8..04c3bb22ad 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/SuccessfulPayment.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/SuccessfulPayment.kt @@ -13,6 +13,12 @@ data class SuccessfulPayment( override val amount: Long, @SerialName(invoicePayloadField) val invoicePayload: String, + @SerialName(subscriptionExpirationDateField) + val subscriptionExpirationDate: TelegramDate? = null, + @SerialName(isRecurringField) + val subscriptionPayment: Boolean? = null, + @SerialName(isFirstRecurringField) + val isFirstPeriodPayment: Boolean? = null, @SerialName(telegramPaymentChargeIdField) val telegramPaymentChargeId: TelegramPaymentChargeId, @SerialName(providerPaymentChargeIdField) @@ -21,4 +27,15 @@ data class SuccessfulPayment( val shippingOptionId: String? = null, @SerialName(orderInfoField) val orderInfo: OrderInfo? = null -) : Amounted, Currencied +) : Amounted, Currencied { + val recurringInfo: RecurringInfo? by lazy { + if (subscriptionPayment == true && subscriptionExpirationDate != null) { + RecurringInfo( + subscriptionExpirationDate, + isFirstPeriodPayment == true, + ) + } else { + null + } + } +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/stars/TransactionPartner.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/stars/TransactionPartner.kt index 12fea54bad..6e526e9266 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/stars/TransactionPartner.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/payments/stars/TransactionPartner.kt @@ -2,12 +2,15 @@ package dev.inmo.tgbotapi.types.payments.stars +import dev.inmo.tgbotapi.abstracts.types.SubscriptionPeriodInfo import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.chat.PreviewUser import dev.inmo.tgbotapi.types.gifts.Gift import dev.inmo.tgbotapi.types.message.payments.PaidMedia +import dev.inmo.tgbotapi.utils.TimeSpanAsSecondsSerializer import dev.inmo.tgbotapi.utils.decodeDataAndJson import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded +import korlibs.time.TimeSpan import kotlinx.serialization.EncodeDefault import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName @@ -43,13 +46,16 @@ sealed interface TransactionPartner { val user: PreviewUser, @SerialName(invoicePayloadField) val invoicePayload: InvoicePayload? = null, + @SerialName(subscriptionPeriodField) + @Serializable(TimeSpanAsSecondsSerializer::class) + override val subscriptionPeriod: TimeSpan? = null, @SerialName(paidMediaField) val paidMedia: List? = null, @SerialName(paidMediaPayloadField) val paidMediaPayload: PaidMediaPayload? = null, @SerialName(giftField) val gift: Gift? = null - ) : TransactionPartner { + ) : TransactionPartner, SubscriptionPeriodInfo { @EncodeDefault override val type: String = Companion.type