diff --git a/CHANGELOG.md b/CHANGELOG.md index bd3d117..aef5d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * The invitation and membership fields were made optional for `OrganizationEvent` (resolves #37) * The maintaining sponsor [onrock.online](https://onrock.online) has rebranded to [Cuedo Business Solutions](https://cuedo.com.au) +* Support for marketplace purchase event (by Alistair Burrowes, resolves #46) # 0.15.0 diff --git a/fixtures/marketplace-purchase-event.json b/fixtures/marketplace-purchase-event.json new file mode 100644 index 0000000..e4adea7 --- /dev/null +++ b/fixtures/marketplace-purchase-event.json @@ -0,0 +1,80 @@ +{ + "action": "changed", + "effective_date": "2017-10-25T00:00:00+00:00", + "sender": { + "login": "username", + "id": 3877742, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "avatar_url": "https://avatars2.githubusercontent.com/u/3877742?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/username", + "html_url": "https://github.com/username", + "followers_url": "https://api.github.com/users/username/followers", + "following_url": "https://api.github.com/users/username/following{/other_user}", + "gists_url": "https://api.github.com/users/username/gists{/gist_id}", + "starred_url": "https://api.github.com/users/username/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/username/subscriptions", + "organizations_url": "https://api.github.com/users/username/orgs", + "repos_url": "https://api.github.com/users/username/repos", + "events_url": "https://api.github.com/users/username/events{/privacy}", + "received_events_url": "https://api.github.com/users/username/received_events", + "type": "User", + "site_admin": true, + "email": "username@email.com" + }, + "marketplace_purchase": { + "account": { + "type": "Organization", + "id": 18404719, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "login": "username", + "organization_billing_email": "username@email.com" + }, + "billing_cycle": "monthly", + "unit_count": 10, + "on_free_trial": false, + "free_trial_ends_on": null, + "next_billing_date": "2017-11-05T00:00:00+00:00", + "plan": { + "id": 435, + "name": "Basic Plan", + "description": "Basic Plan", + "monthly_price_in_cents": 1000, + "yearly_price_in_cents": 10000, + "price_model": "per-unit", + "has_free_trial": true, + "unit_name": "seat", + "bullets": [ + "Is Basic", + "Because Basic " + ] + } + }, + "previous_marketplace_purchase": { + "account": { + "type": "Organization", + "id": 18404719, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "login": "username", + "organization_billing_email": "username@email.com" + }, + "billing_cycle": "monthly", + "on_free_trial": false, + "free_trial_ends_on": null, + "unit_count": 1, + "plan": { + "id": 435, + "name": "Basic Plan", + "description": "Basic Plan", + "monthly_price_in_cents": 1000, + "yearly_price_in_cents": 10000, + "price_model": "per-unit", + "has_free_trial": true, + "unit_name": "seat", + "bullets": [ + "Is Basic", + "Because Basic " + ] + } + } +} diff --git a/spec/DecodeEventsSpec.hs b/spec/DecodeEventsSpec.hs index b5cb79f..c5e19b4 100644 --- a/spec/DecodeEventsSpec.hs +++ b/spec/DecodeEventsSpec.hs @@ -53,6 +53,7 @@ spec = do it "can decode IssueCommentEvent" $ fixtureShouldMatch "fixtures/issue-comment-event.json" issueCommentEventFixture it "can decode IssuesEvent" $ fixtureShouldMatch "fixtures/issues-event.json" issuesEventFixture it "can decode LabelEvent" $ fixtureShouldMatch "fixtures/label-event.json" labelEventFixture + it "can decode MarketplacePurchaseEvent" $ fixtureShouldMatch "fixtures/marketplace-purchase-event.json" marketplacePurchaseEventFixture it "can decode MemberEvent" $ fixtureShouldMatch "fixtures/member-event.json" memberEventFixture it "can decode MembershipEvent" $ fixtureShouldMatch "fixtures/membership-event.json" membershipEventFixture it "can decode MilestoneEvent" $ fixtureShouldMatch "fixtures/milestone-event.json" milestoneEventFixture @@ -2174,6 +2175,90 @@ labelEventFixture = LabelEvent } } +marketplacePurchaseEventFixture :: MarketplacePurchaseEvent +marketplacePurchaseEventFixture = MarketplacePurchaseEvent + { evMarketplacePurchaseAction = MarketplacePurchaseChangedAction + , evMarketplacePurchaseEffectiveDate = read "2017-10-25 00:00:00 UTC" + , evMarketplacePurchaseSender = + HookUser + { whUserLogin = "username" + , whUserId = 3877742 + , whUserNodeId = "MDEyOk9yZ2FuaXphdGlvbjE=" + , whUserAvatarUrl = URL "https://avatars2.githubusercontent.com/u/3877742?v=4" + , whUserGravatarId = URL "" + , whUserUrl = URL "https://api.github.com/users/username" + , whUserHtmlUrl = URL "https://github.com/username" + , whUserFollowersUrl = URL "https://api.github.com/users/username/followers" + , whUserFollowingUrl = URL "https://api.github.com/users/username/following{/other_user}" + , whUserGistsUrl = URL "https://api.github.com/users/username/gists{/gist_id}" + , whUserStarredUrl = URL "https://api.github.com/users/username/starred{/owner}{/repo}" + , whUserSubscriptionsUrl = URL "https://api.github.com/users/username/subscriptions" + , whUserOrganizationsUrl = URL "https://api.github.com/users/username/orgs" + , whUserReposUrl = URL "https://api.github.com/users/username/repos" + , whUserEventsUrl = URL "https://api.github.com/users/username/events{/privacy}" + , whUserReceivedEventsUrl = URL "https://api.github.com/users/username/received_events" + , whUserType = OwnerUser + , whUserIsAdminOfSite = True + } + , evMarketplacePurchaseNew = + HookMarketplacePurchase + { whMarketplacePurchaseAccount = + HookMarketplaceAccount + { whMarketplaceAccountType = OwnerOrganization + , whMarketplaceAccountId = 18404719 + , whMarketplaceAccountNodeId = "MDEyOk9yZ2FuaXphdGlvbjE=" + , whMarketplaceAccountLogin = "username" + , whMarketplaceAccountOrganizationBillingEmail = Just "username@email.com" + } + , whMarketplacePurchaseBillingCycle = Just HookMarketplaceBillingCycleMonthly + , whMarketplacePurchaseUnitCount = 10 + , whMarketplacePurchaseOnFreeTrial = False + , whMarketplacePurchaseFreeTrialEndsOn = Nothing + , whMarketplacePurchaseNextBillingDate = Just (read "2017-11-05 00:00:00 UTC") + , whMarketplacePurchasePlan = + HookMarketplacePlan + { whMarketplacePlanId = 435 + , whMarketplacePlanName = "Basic Plan" + , whMarketplacePlanDescription = "Basic Plan" + , whMarketplacePlanMonthlyPriceInCents = 1000 + , whMarketplacePlanYearlyPriceInCents = 10000 + , whMarketplacePlanPriceModel = HookMarketplacePlanPriceModelPerUnit + , whMarketplacePlanHasFreeTrial = True + , whMarketplacePlanUnitName = Just "seat" + , whMarketplacePlanBullet = V.fromList ["Is Basic","Because Basic "] + } + } + , evMarketplacePurchasePrevious = + Just ( + HookMarketplacePurchase + { whMarketplacePurchaseAccount = + HookMarketplaceAccount + { whMarketplaceAccountType = OwnerOrganization + , whMarketplaceAccountId = 18404719 + , whMarketplaceAccountNodeId = "MDEyOk9yZ2FuaXphdGlvbjE=" + , whMarketplaceAccountLogin = "username" + , whMarketplaceAccountOrganizationBillingEmail = Just "username@email.com" + } + , whMarketplacePurchaseBillingCycle = Just HookMarketplaceBillingCycleMonthly + , whMarketplacePurchaseUnitCount = 1 + , whMarketplacePurchaseOnFreeTrial = False + , whMarketplacePurchaseFreeTrialEndsOn = Nothing + , whMarketplacePurchaseNextBillingDate = Nothing + , whMarketplacePurchasePlan = + HookMarketplacePlan + { whMarketplacePlanId = 435 + , whMarketplacePlanName = "Basic Plan" + , whMarketplacePlanDescription = "Basic Plan" + , whMarketplacePlanMonthlyPriceInCents = 1000 + , whMarketplacePlanYearlyPriceInCents = 10000 + , whMarketplacePlanPriceModel = HookMarketplacePlanPriceModelPerUnit + , whMarketplacePlanHasFreeTrial = True + , whMarketplacePlanUnitName = Just "seat" + , whMarketplacePlanBullet = V.fromList ["Is Basic","Because Basic "] + } + }) + } + memberEventFixture :: MemberEvent memberEventFixture = MemberEvent { evMemberAction = MemberAddedAction diff --git a/src/GitHub/Data/Webhooks/Events.hs b/src/GitHub/Data/Webhooks/Events.hs index 82b4045..867b402 100644 --- a/src/GitHub/Data/Webhooks/Events.hs +++ b/src/GitHub/Data/Webhooks/Events.hs @@ -51,6 +51,9 @@ module GitHub.Data.Webhooks.Events , LabelEvent(..) , LabelEventAction(..) -- + , MarketplacePurchaseEvent(..) + , MarketplacePurchaseEventAction(..) + -- , MemberEvent(..) , MemberEventAction(..) -- @@ -572,6 +575,48 @@ instance EventHasRepo LabelEvent where repoForEvent = evLabelEventRepo instance NFData LabelEvent where rnf = genericRnf +data MarketplacePurchaseEventAction + -- | Decodes from "purchased" + = MarketplacePurchasePurchasedAction + -- | Decodes from "cancelled" + | MarketplacePurchaseCancelledAction + -- | Decodes from "pending_change" + | MarketplacePurchasePendingChangeAction + -- | Decodes from "pending_change_cancelled" + | MarketplacePurchasePendingChangeCancelledAction + -- | Decodes from "changed" + | MarketplacePurchaseChangedAction + -- | The result of decoding an unknown marketplace purchase event action type + | MarketplacePurchaseActionOther !Text + deriving (Eq, Ord, Show, Generic, Typeable, Data) + +instance NFData MarketplacePurchaseEventAction where rnf = genericRnf + +instance FromJSON MarketplacePurchaseEventAction where + parseJSON = withText "Marketplace purchase event action" $ \t -> + case t of + "purchased" -> pure MarketplacePurchasePurchasedAction + "cancelled" -> pure MarketplacePurchaseCancelledAction + "pending_change" -> pure MarketplacePurchasePendingChangeAction + "pending_change_cancelled" -> pure MarketplacePurchasePendingChangeCancelledAction + "changed" -> pure MarketplacePurchaseChangedAction + _ -> pure (MarketplacePurchaseActionOther t) + +-- | A GitHub Marketplace app receives information about changes to a user's plan from the Marketplace purchase event webhook. A Marketplace purchase event is triggered when a user purchases, cancels, or changes their payment plan. +-- See . +data MarketplacePurchaseEvent = MarketplacePurchaseEvent + { evMarketplacePurchaseAction :: !MarketplacePurchaseEventAction + , evMarketplacePurchaseEffectiveDate :: !UTCTime + , evMarketplacePurchaseSender :: !HookUser + , evMarketplacePurchaseNew :: !HookMarketplacePurchase + , evMarketplacePurchasePrevious :: !(Maybe HookMarketplacePurchase) + } + deriving (Eq, Show, Typeable, Data, Generic) + +instance EventHasSender MarketplacePurchaseEvent where senderOfEvent = evMarketplacePurchaseSender +instance NFData MarketplacePurchaseEvent where rnf = genericRnf + + data MemberEventAction -- | Decodes from "added" = MemberAddedAction @@ -1399,6 +1444,14 @@ instance FromJSON LabelEvent where <*> o .:? "organization" <*> o .: "sender" +instance FromJSON MarketplacePurchaseEvent where + parseJSON = withObject "MarketplacePurchaseEvent" $ \o -> MarketplacePurchaseEvent + <$> o .: "action" + <*> o .: "effective_date" + <*> o .: "sender" + <*> o .: "marketplace_purchase" + <*> o .:? "previous_marketplace_purchase" + instance FromJSON MemberEvent where parseJSON = withObject "MemberEvent" $ \o -> MemberEvent <$> o .: "action" diff --git a/src/GitHub/Data/Webhooks/Payload.hs b/src/GitHub/Data/Webhooks/Payload.hs index 2d99bc7..d62b96b 100644 --- a/src/GitHub/Data/Webhooks/Payload.hs +++ b/src/GitHub/Data/Webhooks/Payload.hs @@ -23,6 +23,11 @@ module GitHub.Data.Webhooks.Payload , HookOrganizationInvitation(..) , HookOrganizationMembership(..) , HookTeam(..) + , HookMarketplaceAccount(..) + , HookMarketplaceBillingCycle(..) + , HookMarketplacePlan(..) + , HookMarketplacePlanPriceModel(..) + , HookMarketplacePurchase(..) , HookMilestone(..) , HookMembership(..) , HookProject(..) @@ -328,6 +333,90 @@ data HookTeam = HookTeam instance NFData HookTeam where rnf = genericRnf +-- | Represents the "billing_cycle" field in the +-- 'HookMarketplacePurchase' payload. +data HookMarketplaceBillingCycle + -- | Decodes from "yearly" + = HookMarketplaceBillingCycleYearly + -- | Decodes from "monthly". + | HookMarketplaceBillingCycleMonthly + -- | The result of decoding an unknown marketplace purchase billing cycle type + | HookMarketplaceBillingCycleOther !Text + deriving (Eq, Ord, Show, Generic, Typeable, Data) + +instance NFData HookMarketplaceBillingCycle where rnf = genericRnf + +instance FromJSON HookMarketplaceBillingCycle where + parseJSON = withText "Hook marketplace billing cycle" $ \t -> + case t of + "yearly" -> pure HookMarketplaceBillingCycleYearly + "monthly" -> pure HookMarketplaceBillingCycleMonthly + _ -> pure (HookMarketplaceBillingCycleOther t) + +-- | Represents the "marketplace_purchase" field in the 'MarketplacePurchaseEvent' payload. +data HookMarketplacePurchase = HookMarketplacePurchase + { whMarketplacePurchaseAccount :: !HookMarketplaceAccount + , whMarketplacePurchaseBillingCycle :: !(Maybe HookMarketplaceBillingCycle) + , whMarketplacePurchaseUnitCount :: !Int + , whMarketplacePurchaseOnFreeTrial :: !Bool + , whMarketplacePurchaseFreeTrialEndsOn :: !(Maybe UTCTime) + , whMarketplacePurchaseNextBillingDate :: !(Maybe UTCTime) + , whMarketplacePurchasePlan :: !HookMarketplacePlan + } + deriving (Eq, Show, Typeable, Data, Generic) + +instance NFData HookMarketplacePurchase where rnf = genericRnf + +-- | Represents the "account" field in the 'HookMarketplacePurchase' payload. +data HookMarketplaceAccount = HookMarketplaceAccount + { whMarketplaceAccountType :: !OwnerType + , whMarketplaceAccountId :: !Int + , whMarketplaceAccountNodeId :: !Text + , whMarketplaceAccountLogin :: !Text + , whMarketplaceAccountOrganizationBillingEmail :: !(Maybe Text) + } + deriving (Eq, Show, Typeable, Data, Generic) + +instance NFData HookMarketplaceAccount where rnf = genericRnf + +-- | Represents the "plan" field in the 'HookMarketplacePurchase' payload. +data HookMarketplacePlan = HookMarketplacePlan + { whMarketplacePlanId :: !Int + , whMarketplacePlanName :: !Text + , whMarketplacePlanDescription :: !Text + , whMarketplacePlanMonthlyPriceInCents :: !Int + , whMarketplacePlanYearlyPriceInCents :: !Int + , whMarketplacePlanPriceModel :: !HookMarketplacePlanPriceModel + , whMarketplacePlanHasFreeTrial :: !Bool + , whMarketplacePlanUnitName :: !(Maybe Text) + , whMarketplacePlanBullet :: !(Vector Text) + } + deriving (Eq, Show, Typeable, Data, Generic) + +instance NFData HookMarketplacePlan where rnf = genericRnf + +-- | Represents the "price_model" field in the +-- 'HookMarketplacePlan' payload. +data HookMarketplacePlanPriceModel + -- | Decodes from "flat-rate" + = HookMarketplacePlanPriceModelFlatRate + -- | Decodes from "per-unit". + | HookMarketplacePlanPriceModelPerUnit + -- | Decodes from "free". + | HookMarketplacePlanPriceModelFree + -- | The result of decoding an unknown marketplace plan price model + | HookMarketplacePlanPriceModelOther !Text + deriving (Eq, Ord, Show, Generic, Typeable, Data) + +instance NFData HookMarketplacePlanPriceModel where rnf = genericRnf + +instance FromJSON HookMarketplacePlanPriceModel where + parseJSON = withText "Hook marketplace plan price model" $ \t -> + case t of + "flat-rate" -> pure HookMarketplacePlanPriceModelFlatRate + "per-unit" -> pure HookMarketplacePlanPriceModelPerUnit + "free" -> pure HookMarketplacePlanPriceModelFree + _ -> pure (HookMarketplacePlanPriceModelOther t) type MilestoneState = Text @@ -1105,6 +1194,36 @@ instance FromJSON HookTeam where <*> o .: "members_url" <*> o .: "repositories_url" +instance FromJSON HookMarketplacePurchase where + parseJSON = withObject "HookMarketplacePurchase" $ \o -> HookMarketplacePurchase + <$> o .: "account" + <*> o .: "billing_cycle" + <*> o .: "unit_count" + <*> o .: "on_free_trial" + <*> o .: "free_trial_ends_on" + <*> o .:? "next_billing_date" + <*> o .: "plan" + +instance FromJSON HookMarketplaceAccount where + parseJSON = withObject "HookMarketplaceAccount" $ \o -> HookMarketplaceAccount + <$> o .: "type" + <*> o .: "id" + <*> o .: "node_id" + <*> o .: "login" + <*> o .:? "organization_billing_email" + +instance FromJSON HookMarketplacePlan where + parseJSON = withObject "HookMarketplacePlan" $ \o -> HookMarketplacePlan + <$> o .: "id" + <*> o .: "name" + <*> o .: "description" + <*> o .: "monthly_price_in_cents" + <*> o .: "yearly_price_in_cents" + <*> o .: "price_model" + <*> o .: "has_free_trial" + <*> o .: "unit_name" + <*> o .: "bullets" + instance FromJSON HookMilestone where parseJSON = withObject "HookMilestone" $ \o -> HookMilestone <$> o .: "url"