Add aweber roam files
This commit is contained in:
parent
23301f89d1
commit
9775046d26
90 changed files with 7001 additions and 0 deletions
14
aweber/20200713131302-migration_to_common_rabbitmq.org
Normal file
14
aweber/20200713131302-migration_to_common_rabbitmq.org
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: ac416861-ce45-49ac-8b60-f8ea39362135
|
||||||
|
:END:
|
||||||
|
#+title: Migration to common RabbitMQ
|
||||||
|
|
||||||
|
All services and consumers pointed at the legacy RabbitMQ cluster in
|
||||||
|
Conshohocken should be migrated to the new common-rabbitmq cluster.
|
||||||
|
|
||||||
|
The new servers are available at
|
||||||
|
=common-rabbitmq.service.${ENVIRONMENT}.consul=.
|
||||||
|
|
||||||
|
* Legacy hostnames
|
||||||
|
- =rabbitmq.service.${ENVIRONMENT}.consul=
|
||||||
|
- =rabbit{1-3}.int.{stg,prd}.csh=
|
4
aweber/20200713131512-coreapi.org
Normal file
4
aweber/20200713131512-coreapi.org
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: e4d00c11-da8a-4c91-8f38-ce939846e5cb
|
||||||
|
:END:
|
||||||
|
#+title: CoreAPI
|
10
aweber/20200713162108-puppet.org
Normal file
10
aweber/20200713162108-puppet.org
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: ddeea682-c8f0-4607-8e2b-0f8ee4fd6191
|
||||||
|
:END:
|
||||||
|
#+title: Puppet
|
||||||
|
|
||||||
|
- Repository :: [[https://gitlab.aweber.io/PSE/config-management/puppet/]]
|
||||||
|
|
||||||
|
* Applying changes on a node
|
||||||
|
- Ensure changes are merged and tagged in the upstream repository.
|
||||||
|
- As root, run =puppetd --test=.
|
4
aweber/20200714100836-pgbouncer_port_migration.org
Normal file
4
aweber/20200714100836-pgbouncer_port_migration.org
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 592aa825-154c-4659-8193-75b0ce1f2e5c
|
||||||
|
:END:
|
||||||
|
#+title: PGBouncer port migration
|
7
aweber/20200714125028-pagerduty.org
Normal file
7
aweber/20200714125028-pagerduty.org
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: ebea379a-8fa6-4e22-9275-a9fc98c02804
|
||||||
|
:END:
|
||||||
|
#+title: Pagerduty
|
||||||
|
|
||||||
|
https://aweber.pagerduty.com/
|
||||||
|
|
8
aweber/20200714130151-mail_relay.org
Normal file
8
aweber/20200714130151-mail_relay.org
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 24578fe5-6ca0-4000-a7cd-201e952e4c76
|
||||||
|
:END:
|
||||||
|
#+title: Mail Relay
|
||||||
|
|
||||||
|
A postfix instance running in Kubernetes for the express purpose of supporting
|
||||||
|
legacy emails in the [[id:57ee2f00-9bcd-4e0f-8a77-ae1f2d4cda89][Control Panel]] until they are all migrated to use [[id:32c66bc8-a397-4f50-96cd-2aec70dd14c5][Corporate
|
||||||
|
Notifications]].
|
4
aweber/20200714141221-momentum.org
Normal file
4
aweber/20200714141221-momentum.org
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: e1b95d0e-366e-4ecf-b867-409b6b6c6ee8
|
||||||
|
:END:
|
||||||
|
#+title: Momentum
|
4
aweber/20200714141314-control_panel.org
Normal file
4
aweber/20200714141314-control_panel.org
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 57ee2f00-9bcd-4e0f-8a77-ae1f2d4cda89
|
||||||
|
:END:
|
||||||
|
#+title: Control Panel
|
4
aweber/20200714141333-corporate_notifications.org
Normal file
4
aweber/20200714141333-corporate_notifications.org
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 32c66bc8-a397-4f50-96cd-2aec70dd14c5
|
||||||
|
:END:
|
||||||
|
#+title: Corporate Notifications
|
11
aweber/20200714143255-refunding_an_order.org
Normal file
11
aweber/20200714143255-refunding_an_order.org
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 7a362881-875f-4f74-8053-55f63826da63
|
||||||
|
:END:
|
||||||
|
#+title: Refunding an Order
|
||||||
|
|
||||||
|
Refund options (via Admin)
|
||||||
|
1. Use a balance item
|
||||||
|
1. Insert a manual balance item, set Term at 1, a negative amount, and optionally a description.
|
||||||
|
2. Add an invoice and pay it.
|
||||||
|
2. Close the current package
|
||||||
|
1. Open the current package, select cancellation in date closed, with a cancel reason
|
46
aweber/20200714212153-login_throttling.org
Normal file
46
aweber/20200714212153-login_throttling.org
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: d17e934b-b340-4246-88f0-9b36527100c0
|
||||||
|
:END:
|
||||||
|
#+title: Login Throttling
|
||||||
|
|
||||||
|
* CAPTCHA Throttling
|
||||||
|
|
||||||
|
We have login captcha throttling in place for the following:
|
||||||
|
| Tracked behavior | CAPTCHA threshold | Time Interval |
|
||||||
|
|-------------------------------------------------------+-------------------+---------------|
|
||||||
|
| Repeated unsuccessful attempts with the same username | 3 attempts | 10 minutes |
|
||||||
|
| Repeated attempts from the same IP address | 3 attempts | 12 hours |
|
||||||
|
| Repeated attempts using the same Sift ID | 3 attempts | 30 minutes |
|
||||||
|
| Invalid or missing CSRF token | Immediate | N/A |
|
||||||
|
| Missing customer cookie | Immediate | N/A |
|
||||||
|
|
||||||
|
When a user meets one of the thresholds above, they will be presented with a
|
||||||
|
CAPTCHA challenge. This does not necessarily mean a puzzle will have to be
|
||||||
|
solved, only that the CAPTCHA script will attempt to determine if the user is a
|
||||||
|
bot. Even if the user has correctly entered their credentials on the subsequent
|
||||||
|
attempt, the CAPTCHA challenge will still occur.
|
||||||
|
|
||||||
|
All of the above thresholds are checked concurrently for each login attempt.
|
||||||
|
|
||||||
|
When a throttled user logs in successfully, the following occurs, the *username*
|
||||||
|
threshold is reset. No other thresholds are cleared. This means that even after
|
||||||
|
a user is able to successfully log in to an account, it is still possible for
|
||||||
|
them to be throttled after failing to log in again because they are now being
|
||||||
|
throttled by IP address.
|
||||||
|
|
||||||
|
* Sift ID Blocking
|
||||||
|
During previous login attacks, we've documented a set of Sift IDs that have been
|
||||||
|
used repeatedly during those attempts. Those IDs are blocked with CAPTCHA
|
||||||
|
*immediately*, with a 20% chance that we will present them a faked successful
|
||||||
|
response. This is done to throw off attackers using these IDs.
|
||||||
|
|
||||||
|
* Code
|
||||||
|
All the captcha / throttling logic that’s currently in place lives in
|
||||||
|
[[https://gitlab.aweber.io/CP/applications/sites/-/blob/master/aweber_app/controllers/account_controller.php][aweber_app/controllers/account_controller.php]], mainly in the =loginAjax= and
|
||||||
|
=isThrottled= methods. The repeated actions are tracked using
|
||||||
|
[[https://gitlab.aweber.io/CP/applications/sites/-/blob/master/php5-vendors/vendors/throttler.php][php5-vendors/vendors/throttler.php]], which uses counters in Redis with a TTL
|
||||||
|
attached.
|
||||||
|
|
||||||
|
* Graphs
|
||||||
|
Login attempts and throttling are graphed in Grafana on the [[https://grafana.aweber.io/d/000000530/account-logins][Account Logins
|
||||||
|
dashboard]].
|
70
aweber/20201022141542-python_services.org
Normal file
70
aweber/20201022141542-python_services.org
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: a81b2ff0-5ede-44b3-8f82-960357f15428
|
||||||
|
:END:
|
||||||
|
#+title: Python Services
|
||||||
|
#+OPTIONS: ^:nil
|
||||||
|
#+PROPERTY: header-args :exports code
|
||||||
|
|
||||||
|
* Platform
|
||||||
|
- Python 3.9[fn:programming-languages]
|
||||||
|
- Tornado 6[fn:frameworks]
|
||||||
|
|
||||||
|
* Code Style
|
||||||
|
Code style must be enforced using flake8.[fn:python-lint-checking]
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
[flake8]
|
||||||
|
application-import-names = PACKAGE_NAME,tests
|
||||||
|
exclude = build,env
|
||||||
|
import-order-style = pycharm
|
||||||
|
#+end_example
|
||||||
|
* Requirements
|
||||||
|
** Uncaught errors are logged and alerted via Sentry
|
||||||
|
#+NAME: packages
|
||||||
|
- =sentry-sdk=
|
||||||
|
|
||||||
|
#+begin_src python
|
||||||
|
from sentry_sdk import init
|
||||||
|
init(SENTRY_DSN)
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** A status endpoint is exposed
|
||||||
|
The endpoint should be provide the following fields:
|
||||||
|
- application :: The name of the service
|
||||||
|
- environment :: The operating environment the instance of the service is
|
||||||
|
running in (i.e. "development", "testing", "staging" or "production")
|
||||||
|
- status :: Current service status (e.g.: "ok", "starting")
|
||||||
|
- version :: Packaged version of the service instance
|
||||||
|
- python_version :: The python version running the service instance
|
||||||
|
(=platform.python_version()=)
|
||||||
|
|
||||||
|
The endpoint should return =200= when the service is healthy, and =503= if the
|
||||||
|
service is not ready to serve requests.
|
||||||
|
** The service self-hosts its API documentation
|
||||||
|
An OpenAPI specification is hosted using ReDoc at the root service URL.[fn:backend-services]
|
||||||
|
** Test coverage reports are available in SonarQube
|
||||||
|
** Structured logging
|
||||||
|
json-scribe
|
||||||
|
** Provides consistent error responses
|
||||||
|
json-problem
|
||||||
|
** The service represents itself using its service name and version
|
||||||
|
- The service must include its name and version in its =Server= response header.[fn:response-headers]
|
||||||
|
- The service must include its name and version in the =User-Agent= header for all its HTTP requests.[fn:user-agent]
|
||||||
|
|
||||||
|
Both of these should be presented as =${service-name}/${version}=, e.g.:
|
||||||
|
=user-management/1.0.0=.
|
||||||
|
* References
|
||||||
|
- [[https://confluence.aweber.io/display/STD/Back+End+Services]]
|
||||||
|
|
||||||
|
* Footnotes
|
||||||
|
|
||||||
|
[fn:user-agent] https://confluence.aweber.io/display/STD/RESTful+APIs#heading-User-AgentRequestHeader
|
||||||
|
[fn:response-headers] https://confluence.aweber.io/display/STD/RESTful+APIs#heading-ResponseHeaders
|
||||||
|
|
||||||
|
[fn:backend-services] https://confluence.aweber.io/display/STD/Back+End+Services
|
||||||
|
|
||||||
|
[fn:frameworks] https://confluence.aweber.io/display/STD/Development+Frameworks
|
||||||
|
|
||||||
|
[fn:python-lint-checking] https://confluence.aweber.io/display/STD/Python+Lint+Checking
|
||||||
|
|
||||||
|
[fn:programming-languages] https://confluence.aweber.io/display/STD/Programming+Languages
|
58
aweber/20201028130030-easy_commerce_mvp_brainstorm_notes.org
Normal file
58
aweber/20201028130030-easy_commerce_mvp_brainstorm_notes.org
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: dcb2f0ad-72e8-41ff-84f5-07caf8c7fe8e
|
||||||
|
:END:
|
||||||
|
#+title: Easy Commerce MVP Brainstorm Notes
|
||||||
|
#+TODO: ASK(a) FOLLOW-UP(f) | ANSWERED(d)
|
||||||
|
#+DATE: <2020-10-29 Thu>
|
||||||
|
|
||||||
|
* ANSWERED How is a product identified and tracked?
|
||||||
|
- The customer configures a product name and price on the landing page.
|
||||||
|
- The product name is the "goal description" (page description or note in the
|
||||||
|
DB) in our current sales tracking.
|
||||||
|
- We'd like to include the product name, price, and URL in the subscriber's
|
||||||
|
activity.
|
||||||
|
- Full, separate product tracking could be something we build later if there is
|
||||||
|
sufficient interest.
|
||||||
|
|
||||||
|
* ANSWERED How will the Stripe integration be configured?
|
||||||
|
Will it be configured at the account level, and if so, could it leverage the
|
||||||
|
existing system for linking things like FB & Twitter?
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
- Stripe links via OAuth.
|
||||||
|
- We will have only one Stripe account per AWeber account.
|
||||||
|
- We will be logging charges to Stripe, not full orders (with product tracking
|
||||||
|
information).
|
||||||
|
- Investigation on whether the existing system will work is pending.
|
||||||
|
* ANSWERED How does this interact with service limits?
|
||||||
|
- Availability of the easy commerce feature
|
||||||
|
- Subscriber limits?
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
- Handle errors with free-tier subscriber limits.
|
||||||
|
- Consider transaction service fee changes based on service limits.
|
||||||
|
* ANSWERED When should the buyer be added as a subscriber?
|
||||||
|
Immediately upon payment submission, or upon asynchronous payment confirmation?
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
- The async web hook confirms the payment was made successfully.
|
||||||
|
+ We're avoiding putting logic here only to limit the scope of adding storage
|
||||||
|
for subscriber and purchase information to react to the event with.
|
||||||
|
- If the subscriber already exists, we'll update them with any new information.
|
||||||
|
* ANSWERED How will reports be handled?
|
||||||
|
Is there a dependency on the upcoming Analytics View service?
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
- Only add All Lists: Products Sold in MVP
|
||||||
|
+ This seems like it'd just be a filter on event type in our current sales
|
||||||
|
over time report.
|
||||||
|
- The Analytics View service will provide report data via the authenticated
|
||||||
|
public API interface.
|
||||||
|
+ May not need to be coupled to the project, as we may be able to filter the
|
||||||
|
existing report to distinguish between current sales tracking and landing
|
||||||
|
page sales.
|
||||||
|
+ Anything /new/ should be built using the new Analytics View service.
|
53
aweber/20210112104838-stripe_payments_service.org
Normal file
53
aweber/20210112104838-stripe_payments_service.org
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 9b3ed74d-41d4-4784-89e3-6a9183903b9e
|
||||||
|
:END:
|
||||||
|
#+title: Stripe Payments Service
|
||||||
|
* Overview
|
||||||
|
The Stripe Payments service mediates purchases made by buyers from AWeber
|
||||||
|
customers through the [[id:7d940785-68b9-4da7-bad1-4771d496168c][Stripe payment platform]] and delivers their goods via list
|
||||||
|
subscription.
|
||||||
|
* How It Works
|
||||||
|
** Ordering
|
||||||
|
The order endpoint prepares a [[https://stripe.com/docs/api/payment_intents][Payment Intent]] with Stripe that the buyer will pay
|
||||||
|
directly using Stripe's public API. The payment intent captures the amount to be
|
||||||
|
paid, any fees that AWeber will collect, and account and list metadata used to
|
||||||
|
fulfill the order.
|
||||||
|
*** Email validation
|
||||||
|
Emails are checked against the [[https://gitlab.aweber.io/CP/Services/validation][CP Email Validation Service]] to ensure they can be
|
||||||
|
added as subscribers successfully. The following steps are performed by the service:
|
||||||
|
1. Validates that the format of the submitted email address matches AWeber's
|
||||||
|
internal email formatting rules.
|
||||||
|
2. Applies address normalization to remove any ISP specific markup.
|
||||||
|
3. Extracts the domain-part from the normalized address and validates that there
|
||||||
|
are MX records for the domain-part via DNS lookup.
|
||||||
|
4. Checks if the both the submitted and normalized version of the email address
|
||||||
|
would be blocked by the blocklist.
|
||||||
|
|
||||||
|
Should the validation service fail unexpectedly or be unavailable at the time an
|
||||||
|
order is placed, a warning will be logged and only the email format check will
|
||||||
|
be performed.
|
||||||
|
*** CAPTCHA
|
||||||
|
**** Domain validation
|
||||||
|
***** Allowed domains
|
||||||
|
The following root domains provided by AWeber for [[https://confluence.aweber.io/pages/viewpage.action?pageId=118885193][Web Content (a.k.a Landing
|
||||||
|
Pages)]] always pass CAPTCHA domain validation:
|
||||||
|
|
||||||
|
- =aweberpages.com=
|
||||||
|
- =aweb.page=
|
||||||
|
***** Custom domains
|
||||||
|
All other domains are checked against the [[https://confluence.aweber.io/display/AR/Custom+Domain+Service][Custom Domain Service]] to ensure they
|
||||||
|
are owned and managed by the AWeber account from which the purchase is being
|
||||||
|
made.
|
||||||
|
*** Fees
|
||||||
|
Fees are collected for each sale as a percentage of the sale value configured in
|
||||||
|
the [[https://confluence.aweber.io/display/CT/Service+Limits+API][Service Limits API]] for the AWeber account, rounded to the nearest cent with
|
||||||
|
a minimum fee of $0.01.
|
||||||
|
** Fulfillment
|
||||||
|
*** Adding the subscriber
|
||||||
|
Subscribers are added to the list configured for the payment if they are not
|
||||||
|
already subscribed. If the subscriber is currently on the list in an unconfirmed
|
||||||
|
state, they will be marked as subscribed. Any tags configured for the sale will
|
||||||
|
be added to the subscriber.
|
||||||
|
*** Purchase Tracking
|
||||||
|
A [[https://confluence.aweber.io/display/AR/Pageview][Pageview Event]] is emitted with details of the order so that the purchase is
|
||||||
|
tracked in the subscriber's activity.
|
448
aweber/20210127141844-recurring_and_split_stripe_payments.org
Normal file
448
aweber/20210127141844-recurring_and_split_stripe_payments.org
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: a9835afc-e0be-4436-8274-c3898fdf119c
|
||||||
|
:END:
|
||||||
|
#+title: Recurring and split Stripe payments
|
||||||
|
#+options: prop:t
|
||||||
|
|
||||||
|
To be implemented in the [[id:9b3ed74d-41d4-4784-89e3-6a9183903b9e][Stripe Payments Service]].
|
||||||
|
|
||||||
|
Recurring and split payments are implementable in Stripe as [[https://stripe.com/docs/billing/subscriptions/overview#subscription-lifecycle][subscriptions]]. The
|
||||||
|
key difference is that split payments have a limited number of payment
|
||||||
|
iterations (e.g. 5 monthly installments) whereas recurring payments are ongoing.
|
||||||
|
* Stripe objects
|
||||||
|
** Products
|
||||||
|
Represents something being sold.
|
||||||
|
** Prices
|
||||||
|
Amount and frequency to be charged for a product. Many prices may be available
|
||||||
|
for a single product.
|
||||||
|
** Customers
|
||||||
|
Represents a buyer (subscriber?). Needs to be configured with a payment method.
|
||||||
|
** Subscriptions
|
||||||
|
Represents a [[*Products][Product]] being offered to a [[* Customers][Customer]] at a [[* Prices][Price]].
|
||||||
|
** Invoices
|
||||||
|
Generated at billing times.
|
||||||
|
** Payment Intents
|
||||||
|
Attempts to pay an [[* Invoices][Invoice]].
|
||||||
|
* Easy Commerce Flow
|
||||||
|
** Setup
|
||||||
|
[[*Products][Products]] and [[* Prices][Prices]] should be set up in the customer's stripe account and
|
||||||
|
referenced in their landing page.
|
||||||
|
|
||||||
|
#+begin_src plantuml :file "ecommerce-products.svg"
|
||||||
|
actor "AWeber Customer" as customer
|
||||||
|
participant "Landing Page Editor" as lp
|
||||||
|
participant "Stripe Payments (Authenticated)" as sp
|
||||||
|
participant "Stripe" as stripe
|
||||||
|
|
||||||
|
== Load product information ==
|
||||||
|
customer -> lp : Edit landing page
|
||||||
|
lp -> sp : GET /stripe-authenticated/products
|
||||||
|
sp -> stripe : Get products with prices
|
||||||
|
sp -> lp : Return list of products with pricing
|
||||||
|
|
||||||
|
== Save product information ==
|
||||||
|
customer -> lp : Save landing page
|
||||||
|
alt create a new product & price
|
||||||
|
lp -> sp : POST /stripe-authenticated/products
|
||||||
|
else update an existing product & price
|
||||||
|
lp -> sp : PATCH /stripe-authenticated/products/{UUID}
|
||||||
|
end
|
||||||
|
sp -> stripe : Store product and price
|
||||||
|
sp -> lp : 200 Return product and price IDs
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:ecommerce-products.svg]]
|
||||||
|
** Purchase
|
||||||
|
#+begin_src plantuml :file "ecommerce-subscribing.svg"
|
||||||
|
actor "Buyer" as buyer
|
||||||
|
participant "Landing Page" as lp
|
||||||
|
participant "Stripe Payments (Unauthenticated)" as sp
|
||||||
|
box "Internal"
|
||||||
|
participant "Core API" as capi
|
||||||
|
end box
|
||||||
|
participant "Stripe" as stripe
|
||||||
|
|
||||||
|
buyer -> lp : Place order
|
||||||
|
lp -> sp : POST /stripe/subscription with product and price IDs
|
||||||
|
sp -> stripe : Create customer
|
||||||
|
sp -> stripe : Create subscription
|
||||||
|
sp -> stripe : Create initial invoice
|
||||||
|
sp -> lp : Return payment intent client secret
|
||||||
|
|
||||||
|
lp -> stripe : Confirm payment
|
||||||
|
stripe -> lp : Return payment intent
|
||||||
|
|
||||||
|
lp -> sp : POST /stripe/fulfillment
|
||||||
|
sp -> stripe : Fetch payment intent and check status
|
||||||
|
sp -> capi : Add subscriber with tags
|
||||||
|
sp -> lp : 200 OK
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:ecommerce-subscribing.svg]]
|
||||||
|
|
||||||
|
*** Subscribing
|
||||||
|
Unlike ordering, which creates a single payment intent, we'll instead need to
|
||||||
|
take the payment information provided by the buyer and create a [[* Customers][Customer]] object
|
||||||
|
in Stripe, as well as a [[* Subscriptions][Subscription]] to a [[*Products][Product]] at a specific [[* Prices][Price]].
|
||||||
|
*** Fulfillment
|
||||||
|
After a successful payment, the [[* Subscriptions][Subscription]] is marked as =active=, and the
|
||||||
|
buyer should be subscribed to start receiving their content.
|
||||||
|
** Automatic payments
|
||||||
|
#+begin_src plantuml :file "ecommerce-payment-webhooks.svg"
|
||||||
|
participant "Stripe Payments (Unauthenticated)" as sp
|
||||||
|
box "Internal"
|
||||||
|
participant "RabbitMQ" as amqp
|
||||||
|
end box
|
||||||
|
participant "Stripe" as stripe
|
||||||
|
|
||||||
|
stripe -> sp : POST /stripe/webhooks (payment_intent.succeeded)
|
||||||
|
sp -> amqp : Sales tracking event (pageview.v4)
|
||||||
|
sp -> amqp : Payment succeeded event (stripe_payment_succeeded.v1)
|
||||||
|
sp -> stripe : 200 OK
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:ecommerce-payment-webhooks.svg]]
|
||||||
|
|
||||||
|
** Payment failure / Cancellation
|
||||||
|
#+begin_src plantuml :file "ecommerce-cancellation-webhooks.svg"
|
||||||
|
participant "Stripe Payments (Unauthenticated)" as sp
|
||||||
|
box "Internal"
|
||||||
|
participant "Core API" as capi
|
||||||
|
end box
|
||||||
|
participant "Stripe" as stripe
|
||||||
|
|
||||||
|
stripe -> sp : POST /stripe/webhooks (customer.subscription.updated)
|
||||||
|
sp -> stripe : Fetch product metadata
|
||||||
|
sp -> capi : Remove tags from subscriber or unsubscribe
|
||||||
|
sp -> stripe : 200 OK
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:ecommerce-cancellation-webhooks.svg]]
|
||||||
|
|
||||||
|
* Questions
|
||||||
|
- Should stripe-payments or the integrations service handle price and product management?
|
||||||
|
+ stripe-payments would need authenticated endpoints exposed
|
||||||
|
|
||||||
|
* Project :taskjuggler_project:
|
||||||
|
:PROPERTIES:
|
||||||
|
:COLUMNS: %50ITEM(Task) %Effort %allocate %task_id %blocker
|
||||||
|
:start: 2021-02-22
|
||||||
|
:END:
|
||||||
|
|
||||||
|
#+begin: columnview
|
||||||
|
| Task | Effort | allocate | task_id | blocker |
|
||||||
|
|-------------------------------------------------------------------+--------+-------------------+-------------------------+-------------------------------------------------------------------------------|
|
||||||
|
| Project | | | | |
|
||||||
|
| Tasks | | | | |
|
||||||
|
| Backend: Product Management | | | | |
|
||||||
|
| Define API for managing products and prices | 1d | backend | api | |
|
||||||
|
| Endpoint: Create products and prices | 3d | backend | create | api |
|
||||||
|
| Endpoint: Update products and prices | 5d | backend | update | api |
|
||||||
|
| Endpoint: Fetch products and prices | 2d | backend | fetch | api |
|
||||||
|
| Endpoint: Update /order | 2d | backend | order_both | api |
|
||||||
|
| Migrate products from existing landing pages into Stripe on save | | frontend | migrate | order_both |
|
||||||
|
| Backfill products from existing landing pages into Stripe | 10d | backend, frontend | backfill | order_both |
|
||||||
|
| Backend: Subscriptions | | | | |
|
||||||
|
| Endpoint: Update /order to create customers for product purchases | 10d | backend | order_products | milestone_management |
|
||||||
|
| Endpoint: Add subscription support to /order | 10d | backend | order_subscriptions | milestone_management |
|
||||||
|
| Endpoint: Update /fulfill & /webhooks (iteration 1) | 3d | backend | webhooks_1 | milestone_management |
|
||||||
|
| Endpoint: Update /webhooks to process subscriptions (iteration 2) | 8d | backend | webhooks_2 | webhooks_1, order_subscriptions |
|
||||||
|
| Database: Add database for event tracking | 3d | backend | database | |
|
||||||
|
| Endpoint: Add event tracking to /webhooks | 5d | backend | tracking | database, webhooks_2 |
|
||||||
|
| Backend: Resiliency | | | | |
|
||||||
|
| Poller: Create poller to check for unprocessed events | 10d | backend | poller | milestone_subscriptions |
|
||||||
|
| Backend: Cleanup | | | | |
|
||||||
|
| Backfill products from existing landing pages into Stripe | 10d | backend, frontend | backfill2 | milestone_subscriptions |
|
||||||
|
| Endpoint: Update /order remove /fulfill | 1d | backend | cleanup | backfill2 |
|
||||||
|
| Frontend | | | | |
|
||||||
|
| Frontend: Plugin | 2d | frontend | plugin | |
|
||||||
|
| Frontend: Templates | 1d | frontend | templates | plugin |
|
||||||
|
| Frontend: Builder (Product Management) | 12d | frontend | builder_products | plugin, templates |
|
||||||
|
| Frontend: Builder (Recurring Payments) | 17d | frontend | builder_recurring | milestone_management |
|
||||||
|
| Milestones | | | | |
|
||||||
|
| Milestone 1: Product Management | 5d | release | milestone_management | api, create, update, fetch, order_both, migrate, backfill, builder_products |
|
||||||
|
| Milestone 2: Support Subscriptions | 5d | release | milestone_subscriptions | order_products, webhooks_1, webhooks_2, database, tracking, builder_recurring |
|
||||||
|
| Milestone 3: Add Resiliency | | | milestone_resiliency | poller |
|
||||||
|
| Milestone 4: Deprecate Product Name/Price | | | milestone_cleanup | milestone_subscriptions, cleanup |
|
||||||
|
#+end:
|
||||||
|
|
||||||
|
#+begin_export html
|
||||||
|
<iframe src="reports/recurring-and-split-stripe-payments/Plan.html" width="100%" height="1000"></iframe>
|
||||||
|
#+end_export
|
||||||
|
** Tasks
|
||||||
|
|
||||||
|
*** Backend: Product Management
|
||||||
|
**** DONE Define API for managing products and prices
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 1d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:task_id: api
|
||||||
|
:END:
|
||||||
|
Probably makes sense to treat price + recurrence as product attributes, e.g.
|
||||||
|
|
||||||
|
- name :: String
|
||||||
|
- Price :: Object
|
||||||
|
+ amount :: Integer
|
||||||
|
+ currency :: Enum["usd"]
|
||||||
|
+ recurrence :: Optional[Object]
|
||||||
|
- interval :: Enum["weekly", "monthly", "yearly"]
|
||||||
|
- times :: Union["unlimited", Integer]
|
||||||
|
|
||||||
|
**** DONE Endpoint: Create products and prices
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 3d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: api
|
||||||
|
:TASK_ID: create
|
||||||
|
:END:
|
||||||
|
- Define metadata schema for purchase and cancellation actions (add tags, unsubscribe)
|
||||||
|
|
||||||
|
**** DONE Endpoint: Update products and prices
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 5d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: api
|
||||||
|
:TASK_ID: update
|
||||||
|
:END:
|
||||||
|
- Add new price object if the price or recurrence has changed, leaving the old one
|
||||||
|
|
||||||
|
**** DONE Endpoint: Fetch products and prices
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 2d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: api
|
||||||
|
:TASK_ID: fetch
|
||||||
|
:END:
|
||||||
|
|
||||||
|
**** DONE Endpoint: Update /order
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 2d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: api
|
||||||
|
:task_id: order_both
|
||||||
|
:END:
|
||||||
|
|
||||||
|
**** DONE Migrate products from existing landing pages into Stripe on save
|
||||||
|
:PROPERTIES:
|
||||||
|
:ALLOCATE: frontend
|
||||||
|
:blocker: order_both
|
||||||
|
:TASK_ID: migrate
|
||||||
|
:END:
|
||||||
|
**** CANCELLED Backfill products from existing landing pages into Stripe
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 10d
|
||||||
|
:ALLOCATE: backend, frontend
|
||||||
|
:blocker: order_both
|
||||||
|
:task_id: backfill
|
||||||
|
:END:
|
||||||
|
**** DONE Separate Products and Prices
|
||||||
|
*** Backend: Subscriptions
|
||||||
|
**** TODO Endpoint: Create /purchase to create customers for product purchases
|
||||||
|
:PROPERTIES:
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: milestone_management
|
||||||
|
:start: 2021-04-22
|
||||||
|
:Effort: 12d
|
||||||
|
:TASK_ID: order_products
|
||||||
|
:END:
|
||||||
|
If the product being ordered is specified by id:
|
||||||
|
- Create the customer w/ the provided payment method
|
||||||
|
- Create an invoice
|
||||||
|
- Pay the invoice
|
||||||
|
- Document the /order endpoint as deprecated
|
||||||
|
- Create acceptance tests
|
||||||
|
|
||||||
|
**** TODO Endpoint: Update /fulfill and /webhooks (iteration 1)
|
||||||
|
:PROPERTIES:
|
||||||
|
:Effort: 5d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:BLOCKER: milestone_management
|
||||||
|
:task_id: webhooks_1
|
||||||
|
:start: 2021-04-22
|
||||||
|
:END:
|
||||||
|
- move add subscriber and sales tracking event emission to the webhook endpoint
|
||||||
|
- no-op /fulfill
|
||||||
|
- Document /fulfill endpoint as deprecated
|
||||||
|
- Send =AWeber-Options: update-unconfirmed= header
|
||||||
|
- Update acceptance tests to account for out-of-band fulfillment
|
||||||
|
**** TODO Endpoint: Add subscription support to /purchase
|
||||||
|
:PROPERTIES:
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: milestone_management
|
||||||
|
:start: 2021-04-22
|
||||||
|
:Effort: 12d
|
||||||
|
:TASK_ID: order_subscriptions
|
||||||
|
:END:
|
||||||
|
If the product being ordered has a recurring price type:
|
||||||
|
- Create the customer w/ the provided payment method
|
||||||
|
- Create a subscription
|
||||||
|
- Create the initial invoice
|
||||||
|
- Pay the initial invoice
|
||||||
|
- Add acceptance tests for subscription purchases
|
||||||
|
**** TODO Endpoint: Update /webhooks to process subscriptions (iteration 2)
|
||||||
|
:PROPERTIES:
|
||||||
|
:Effort: 10d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: webhooks_1, order_subscriptions
|
||||||
|
:TASK_ID: webhooks_2
|
||||||
|
:END:
|
||||||
|
- Add logic to process a subscription
|
||||||
|
- Add the events corresponding to cancellation of a subscription
|
||||||
|
+ Process the remove tag/unsubscribe actions that are saved as metadata on the
|
||||||
|
subscription
|
||||||
|
+ Ensure that unconfirmed subscribers can be unsubscribed
|
||||||
|
- Need a way to differentiate fulfillment of a payment intent for a subscription vs a single payment
|
||||||
|
+ Pull the invoice for the payment intent with the expanded subscription details if they exist
|
||||||
|
**** TODO Database: Add database for event tracking
|
||||||
|
:PROPERTIES:
|
||||||
|
:Effort: 3d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:BLOCKER: milestone_management
|
||||||
|
:start: 2021-04-22
|
||||||
|
:task_id: database
|
||||||
|
:END:
|
||||||
|
- new postgres users ( one for the poller, one for the stripe-payments service )
|
||||||
|
- Needs the following information in an events table:
|
||||||
|
+ timestamp from the event
|
||||||
|
+ timestamp of action completion
|
||||||
|
+ webhook type
|
||||||
|
+ webhook id
|
||||||
|
+ stripe account id
|
||||||
|
+ aweber account id
|
||||||
|
+ and whether it was successfully processed (process state)
|
||||||
|
- dynamodb does not fit the use case because we need to retrieve all events in a time frame, and filter by account. We are also constantly deleting events.
|
||||||
|
+ we also will have two services updating the database and need transaction safety
|
||||||
|
- Scope assumes handing off the schema design to the DBA team
|
||||||
|
**** TODO Endpoint: Add event tracking to /webhooks
|
||||||
|
:PROPERTIES:
|
||||||
|
:Effort: 7d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:BLOCKER: database, webhooks_2
|
||||||
|
:TASK_ID: tracking
|
||||||
|
:END:
|
||||||
|
- Mark processed events in the database
|
||||||
|
+ Do we need transactions? Assume yes.
|
||||||
|
- Handling duplicate and out-of-order event cases
|
||||||
|
+ Fulfill only once (adding subscriber + tags)
|
||||||
|
+ Unsubscribe only once (removing subscriber / tags)
|
||||||
|
+ Don't fulfill if unsubscribed
|
||||||
|
*** Backend: Resiliency
|
||||||
|
We will need a job to periodically poll Stripe for unhandled webhooks for
|
||||||
|
processing.
|
||||||
|
|
||||||
|
- This job will need to share a data store with the stripe payments service to
|
||||||
|
track which webhooks have been processed.
|
||||||
|
+ Is this necessary, or does stripe expose the processed state of the
|
||||||
|
webhooks? Webhook attempts and responses are logged in the console.
|
||||||
|
- The job will need to maintain a lock to prevent concurrent runs.
|
||||||
|
- How will unhandled webhooks get processed?
|
||||||
|
+ Send them to the stripe payments service endpoint?
|
||||||
|
- Would have to store status or update it in Stripe.
|
||||||
|
+ Have Stripe send them?
|
||||||
|
- Is this possible?
|
||||||
|
**** TODO Poller: Create poller to check for unprocessed events
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 10d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:BLOCKER: milestone_subscriptions
|
||||||
|
:TASK_ID: poller
|
||||||
|
:END:
|
||||||
|
*** Backend: Cleanup
|
||||||
|
**** TODO Backfill products from existing landing pages into Stripe
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 10d
|
||||||
|
:ALLOCATE: backend, frontend
|
||||||
|
:blocker: milestone_subscriptions
|
||||||
|
:task_id: backfill2
|
||||||
|
:END:
|
||||||
|
**** TODO Endpoint: Remove /order and /fulfill
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 1d
|
||||||
|
:ALLOCATE: backend
|
||||||
|
:blocker: backfill2
|
||||||
|
:TASK_ID: cleanup
|
||||||
|
:END:
|
||||||
|
*** Frontend
|
||||||
|
Builder originally estimated at 25d combined.
|
||||||
|
**** TODO Frontend: Plugin
|
||||||
|
:PROPERTIES:
|
||||||
|
:Effort: 2d
|
||||||
|
:ALLOCATE: frontend
|
||||||
|
:TASK_ID: plugin
|
||||||
|
:BLOCKER:
|
||||||
|
:END:
|
||||||
|
**** TODO Frontend: Templates
|
||||||
|
:PROPERTIES:
|
||||||
|
:Effort: 1d
|
||||||
|
:ALLOCATE: frontend
|
||||||
|
:TASK_ID: templates
|
||||||
|
:BLOCKER: plugin
|
||||||
|
:END:
|
||||||
|
**** TODO Frontend: Builder (Product Management)
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 12d
|
||||||
|
:ALLOCATE: frontend
|
||||||
|
:TASK_ID: builder_products
|
||||||
|
:BLOCKER: plugin, templates
|
||||||
|
:END:
|
||||||
|
**** TODO Frontend: Builder (Recurring Payments)
|
||||||
|
:PROPERTIES:
|
||||||
|
:EFFORT: 28d
|
||||||
|
:ALLOCATE: frontend
|
||||||
|
:TASK_ID: builder_recurring
|
||||||
|
:BLOCKER: milestone_management
|
||||||
|
:start: 2021-04-22
|
||||||
|
:END:
|
||||||
|
** Milestones
|
||||||
|
*** Milestone 1: Product Management
|
||||||
|
:PROPERTIES:
|
||||||
|
:BLOCKER: api, create, update, fetch, order_both, migrate, backfill, builder_products
|
||||||
|
:TASK_ID: milestone_management
|
||||||
|
:Effort: 5d
|
||||||
|
:ALLOCATE: release
|
||||||
|
:END:
|
||||||
|
*** Milestone 2: Support Subscriptions
|
||||||
|
:PROPERTIES:
|
||||||
|
:BLOCKER: order_products, webhooks_1, webhooks_2, database, tracking, builder_recurring
|
||||||
|
:TASK_ID: milestone_subscriptions
|
||||||
|
:Effort: 10d
|
||||||
|
:ALLOCATE: release
|
||||||
|
:END:
|
||||||
|
*** Milestone 3: Add Resiliency
|
||||||
|
:PROPERTIES:
|
||||||
|
:BLOCKER: poller
|
||||||
|
:TASK_ID: milestone_resiliency
|
||||||
|
:END:
|
||||||
|
*** Milestone 4: Deprecate Product Name/Price
|
||||||
|
:PROPERTIES:
|
||||||
|
:BLOCKER: milestone_subscriptions, cleanup
|
||||||
|
:TASK_ID: milestone_cleanup
|
||||||
|
:END:
|
||||||
|
* Resources :taskjuggler_resource:
|
||||||
|
|
||||||
|
*** Backend
|
||||||
|
:PROPERTIES:
|
||||||
|
:resource_id: backend
|
||||||
|
:efficiency: 1.3
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Frontend
|
||||||
|
:PROPERTIES:
|
||||||
|
:resource_id: frontend
|
||||||
|
:efficiency: 0.8
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Release Validation
|
||||||
|
:PROPERTIES:
|
||||||
|
:resource_id: release
|
||||||
|
:efficiency: 1.0
|
||||||
|
:END:
|
||||||
|
|
||||||
|
* Variables :noexport:
|
||||||
|
Local Variables:
|
||||||
|
org-taskjuggler-reports-directory: "reports/recurring-and-split-stripe-payments"
|
||||||
|
End:
|
38
aweber/20210210113027-control_panel_http_requests.org
Normal file
38
aweber/20210210113027-control_panel_http_requests.org
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 33e47957-b3d0-41c9-8977-7243b42a76dd
|
||||||
|
:END:
|
||||||
|
#+title: Control Panel HTTP Requests
|
||||||
|
#+PROPERTY: header-args :exports both :eval no-export
|
||||||
|
#+PROPERTY: header-args:http :cookie .cookies :cookie-jar .cookies
|
||||||
|
|
||||||
|
* Cookies
|
||||||
|
| Name | Description |
|
||||||
|
|-------------+-------------|
|
||||||
|
| AUTORESPSID | Session ID |
|
||||||
|
|
||||||
|
Cookies for requests in this document are stored in cookie file by curl in
|
||||||
|
=~/.cookies= (https://curl.se/docs/http-cookies.html).
|
||||||
|
* AJAX Requests
|
||||||
|
Control Panel controller actions that expect to be called as AJAX endpoints
|
||||||
|
expect the =X-Requested-With= header to be present and set to =XMLHttpRequest=.
|
||||||
|
* Logging In
|
||||||
|
** Fetching a CSRF Token
|
||||||
|
#+name: login-csrf
|
||||||
|
#+begin_src http :pretty
|
||||||
|
GET localhost:8080/users/pub/csrf
|
||||||
|
X-Requested-With:XMLHttpRequest
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS: login-csrf
|
||||||
|
: 63116e764c5d31cdd3e4f230ee3740527f6eb1c76aea1cb04e30da5d68e24d78
|
||||||
|
|
||||||
|
** Sending credentials
|
||||||
|
#+begin_src http :pretty :var csrf=login-csrf
|
||||||
|
POST localhost:8080/users/account/loginAjax
|
||||||
|
X-Requested-With: XMLHttpRequest
|
||||||
|
|
||||||
|
username=lookatme@example.com&password=testing&_csrf=${csrf}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
: {"submitStatus":{"code":200,"message":"\/users\/","category":"status_success"},"validationErrors":[]}
|
1166
aweber/20210302135423-stripe_api.org
Normal file
1166
aweber/20210302135423-stripe_api.org
Normal file
File diff suppressed because it is too large
Load diff
383
aweber/20210316155320-stripe_payments_tracking_database.org
Normal file
383
aweber/20210316155320-stripe_payments_tracking_database.org
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 6af95849-8f78-4697-ab48-3712ff2f5ee1
|
||||||
|
:END:
|
||||||
|
#+title: Stripe payments tracking database
|
||||||
|
#+OPTIONS: ^:nil
|
||||||
|
|
||||||
|
Database for tracking payments and subscriptions managed by the [[id:9b3ed74d-41d4-4784-89e3-6a9183903b9e][Stripe Payments Service]].
|
||||||
|
* State Tracking
|
||||||
|
** Legacy purchase
|
||||||
|
- Client initiates purchase
|
||||||
|
- Backend coordinates with Stripe and returns a payment intent
|
||||||
|
+ Track new purchase and related incomplete payment
|
||||||
|
- Client completes purchase with Stripe
|
||||||
|
- Stripe notifies backend of payment intent status (update purchase)
|
||||||
|
+ Track event
|
||||||
|
+ Track payment as completed
|
||||||
|
+ Fulfill purchase
|
||||||
|
+ Track purchase as fulfilled
|
||||||
|
|
||||||
|
#+begin_src plantuml :file stripe-legacy-purchase-tracking.svg
|
||||||
|
participant Stripe
|
||||||
|
actor Client
|
||||||
|
participant "stripe-payments" as Backend
|
||||||
|
database Tracking
|
||||||
|
|
||||||
|
Client -> Backend: Initiate purchase
|
||||||
|
Backend -> Tracking : <font color="blue">Store incomplete purchase</font>
|
||||||
|
Backend -> Stripe : Create payment intent
|
||||||
|
Backend -> Tracking : <font color="blue">Store incomplete payment</font>
|
||||||
|
Backend -> Client : Return payment intent
|
||||||
|
Client -> Stripe : Complete purchase
|
||||||
|
...
|
||||||
|
alt Success
|
||||||
|
Stripe ---> Backend : payment_intent.succeeded
|
||||||
|
Backend -> Tracking : <font color="green">Store event</font>
|
||||||
|
alt Event not previously handled
|
||||||
|
Backend -> Tracking : <font color="blue">Mark payment as succeeded</font>
|
||||||
|
Backend -> Backend : Fulfill purchase
|
||||||
|
Backend -> Tracking : <font color="blue">Mark purchase as fulfilled</font>
|
||||||
|
end
|
||||||
|
else Failure
|
||||||
|
Stripe --> Backend : payment_intent.payment_failed
|
||||||
|
Backend -> Tracking : <font color="green">Store event</font>
|
||||||
|
end
|
||||||
|
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:stripe-legacy-purchase-tracking.svg]]
|
||||||
|
|
||||||
|
#+caption: Legacy purchase flow with events
|
||||||
|
#+RESULTS:
|
||||||
|
|
||||||
|
** Product purchase
|
||||||
|
- Client prepares payment method with Stripe
|
||||||
|
- Client initiates purchase
|
||||||
|
- Backend coordinates with Stripe to complete the purchase
|
||||||
|
+ Attaches the payment method to the customer
|
||||||
|
+ For single products
|
||||||
|
- Creates an invoice item
|
||||||
|
- Creates an invoice
|
||||||
|
- Pays the invoice
|
||||||
|
+ For subscriptions
|
||||||
|
- Creates the subscription
|
||||||
|
|
||||||
|
#+begin_src plantuml :file stripe-purchase-tracking.svg
|
||||||
|
participant Stripe
|
||||||
|
actor Client
|
||||||
|
participant "stripe-payments" as Backend
|
||||||
|
database Tracking
|
||||||
|
|
||||||
|
Client -> Stripe : Create payment method
|
||||||
|
Client -> Backend: Initiate purchase
|
||||||
|
Backend -> Tracking : <font color="blue">Store incomplete purchase</font>
|
||||||
|
alt Non-recurring
|
||||||
|
Backend -> Stripe : Create invoice item
|
||||||
|
Backend -> Stripe : Create invoice
|
||||||
|
Backend -> Stripe : Pay invoice
|
||||||
|
Backend -> Tracking : <font color="blue">Store completed payment</font>
|
||||||
|
else Recurring
|
||||||
|
Backend -> Stripe : Create subscription
|
||||||
|
Backend -> Tracking : <font color="blue">Store active subscription</font>
|
||||||
|
end
|
||||||
|
...
|
||||||
|
Stripe --> Backend : Subscription activated
|
||||||
|
...
|
||||||
|
alt
|
||||||
|
Stripe --> Backend : customer.subscription.updated (no longer active)
|
||||||
|
else
|
||||||
|
Stripe --> Backend : subscription_schedule.canceled
|
||||||
|
end
|
||||||
|
Backend -> Tracking : <font color="green">Store event</font>
|
||||||
|
Backend -> Backend : Trigger unsubscribe actions
|
||||||
|
Backend -> Tracking : <font color="blue">Mark subscription as terminated</font>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+caption: Product purchase flow
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:stripe-purchase-tracking.svg]]
|
||||||
|
|
||||||
|
#+begin_src plantuml :file stripe-purchase-tracking-payment-events.svg
|
||||||
|
participant Stripe
|
||||||
|
actor Client
|
||||||
|
participant "stripe-payments" as Backend
|
||||||
|
database Tracking
|
||||||
|
|
||||||
|
== Payment succeeded ==
|
||||||
|
Stripe --> Backend : payment_intent.succeeded
|
||||||
|
Backend -> Tracking : <font color="green">Store event</font>
|
||||||
|
alt Event not previously handled
|
||||||
|
Backend -> Tracking : <font color="blue">Mark payment as succeeded</font>
|
||||||
|
Backend -> Stripe : Look up invoice
|
||||||
|
|
||||||
|
note over Backend
|
||||||
|
Recurring if an invoice exists
|
||||||
|
and has an associated subscription
|
||||||
|
end note
|
||||||
|
alt Non-recurring
|
||||||
|
Backend -> Backend : Fulfill purchase
|
||||||
|
Backend -> Tracking : <font color="blue">Mark purchase as fulfilled</font>
|
||||||
|
else Recurring
|
||||||
|
end
|
||||||
|
end
|
||||||
|
== Payment failed ==
|
||||||
|
Stripe --> Backend : payment_intent.payment_failed
|
||||||
|
Backend -> Tracking : <font color="green">Store event</font>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+caption: Payment events
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:stripe-purchase-tracking-payment-events.svg]]
|
||||||
|
|
||||||
|
#+begin_src plantuml :file stripe-purchase-subscription-events.svg
|
||||||
|
participant Stripe
|
||||||
|
actor Client
|
||||||
|
participant "stripe-payments" as Backend
|
||||||
|
database Tracking
|
||||||
|
|
||||||
|
== Subscription activated ==
|
||||||
|
alt
|
||||||
|
Stripe --> Backend : customer.subscription.created (active)
|
||||||
|
else
|
||||||
|
Stripe --> Backend : customer.subscription.updated (active)
|
||||||
|
end
|
||||||
|
Backend -> Tracking : <font color="green">Store event</font>
|
||||||
|
alt New subscription
|
||||||
|
Backend -> Tracking : <font color="blue">Mark subscription as active</font>
|
||||||
|
Backend -> Backend : Fulfill purchase
|
||||||
|
Backend -> Tracking : <font color="blue">Mark purchase as fulfilled</font>
|
||||||
|
else Subscription already processed
|
||||||
|
note over Backend
|
||||||
|
One of the following is true:
|
||||||
|
|
||||||
|
<font color="green">- Already received an activation event for this subscription</font>
|
||||||
|
<font color="green">- Already receieved a termination event for this subscription</font>
|
||||||
|
<font color="blue">- Subscription already marked as active or terminated</font>
|
||||||
|
<font color="blue">- Purchase already marked as fulfilled</font>
|
||||||
|
end note
|
||||||
|
end
|
||||||
|
== Subscription terminated ==
|
||||||
|
alt
|
||||||
|
Stripe --> Backend : customer.subscription.updated (no longer active)
|
||||||
|
else
|
||||||
|
Stripe --> Backend : subscription_schedule.canceled
|
||||||
|
end
|
||||||
|
Backend -> Tracking : <font color="green">Store event</font>
|
||||||
|
alt Active subscription
|
||||||
|
Backend -> Backend : Trigger unsubscribe actions
|
||||||
|
Backend -> Tracking : <font color="blue">Mark subscription as terminated</font>
|
||||||
|
else Subscription not yet processed
|
||||||
|
note over Backend
|
||||||
|
One of the following is true:
|
||||||
|
|
||||||
|
<font color="green">- Did not receive an activation event for this subscription</font>
|
||||||
|
<font color="blue">- Subscription is not tracked</font>
|
||||||
|
end note
|
||||||
|
else Subscription already terminated
|
||||||
|
note over Backend
|
||||||
|
One of the following is true:
|
||||||
|
|
||||||
|
<font color="green">- Already received a termination event for this subscription</font>
|
||||||
|
<font color="blue">- Subscription already marked as terminated</font>
|
||||||
|
end note
|
||||||
|
end
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+caption: Subscription events
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:stripe-purchase-subscription-events.svg]]
|
||||||
|
|
||||||
|
* Tables
|
||||||
|
** Purchases
|
||||||
|
Purchases made via the [[id:9b3ed74d-41d4-4784-89e3-6a9183903b9e][Stripe Payments Service]].
|
||||||
|
|
||||||
|
#+begin_src plantuml :file stripe-purchase-states.svg
|
||||||
|
[*] -> New
|
||||||
|
New -> Fulfilled
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+caption: Purchase state diagram
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:stripe-purchase-states.svg]]
|
||||||
|
|
||||||
|
#+caption: Purchases
|
||||||
|
| Field | Type | Nullable | Description |
|
||||||
|
|----------------+-----------+----------+--------------------------------------------|
|
||||||
|
| id | UUID | N | Auto-generated purchase ID |
|
||||||
|
| created | timestamp | N | Time the purchase was initiated |
|
||||||
|
| last_updated | timestamp | N | Time the purchase was last updated |
|
||||||
|
| account | UUID | N | The AWeber customer account purchased from |
|
||||||
|
| stripe_account | text | N | The Stripe account of the AWeber customer |
|
||||||
|
| fulfilled | boolean | N | Was this purchase fulfilled |
|
||||||
|
|
||||||
|
- Store which automations were applied & when
|
||||||
|
** Subscriptions and Split Payments
|
||||||
|
Purchases made with recurring payments, managed using a Stripe Subscription.
|
||||||
|
|
||||||
|
#+begin_src plantuml :file stripe-subscription-states.svg
|
||||||
|
[*] -> New
|
||||||
|
New -> Active
|
||||||
|
Active --> Terminated : Payment Failed
|
||||||
|
Active --> Terminated : Unsubscribed
|
||||||
|
New --> Terminated : Payment Failed
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+caption: Subscription state diagram
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:stripe-subscription-states.svg]]
|
||||||
|
|
||||||
|
#+caption: Subscriptions
|
||||||
|
| Field | Type | Nullable | Description |
|
||||||
|
|----------------+---------------------+----------+---------------------------------------------------------|
|
||||||
|
| id | text | N | The Stripe subscription ID |
|
||||||
|
| created | timestamp | N | Time the purchase was initiated |
|
||||||
|
| last_updated | timestamp | N | Time the purchase was last updated |
|
||||||
|
| account | UUID | N | The AWeber customer account the subscription belongs to |
|
||||||
|
| stripe_account | text | N | The Stripe account of the AWeber customer |
|
||||||
|
| status | [[subscription_status][subscription_status]] | N | Status of the subscription |
|
||||||
|
|
||||||
|
<<subscription_status>>
|
||||||
|
#+caption: ENUM: Subscription Status
|
||||||
|
| new |
|
||||||
|
| active |
|
||||||
|
| terminated |
|
||||||
|
** Payments
|
||||||
|
Payments collected from buyers.
|
||||||
|
|
||||||
|
#+begin_src plantuml :file stripe-payment-states.svg
|
||||||
|
[*] -> New
|
||||||
|
New --> Paid : Payment succeeded
|
||||||
|
New --> Failed : Payment failed
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+caption: Payment state diagram
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:stripe-payment-states.svg]]
|
||||||
|
|
||||||
|
#+caption: Payments
|
||||||
|
| Field | Type | Nullable | Description |
|
||||||
|
|----------------+----------------+----------+-------------------------------------------|
|
||||||
|
| id | text | N | The Stripe payment intent ID |
|
||||||
|
| created | timestamp | N | Time the purchase was initiated |
|
||||||
|
| last_updated | timestamp | N | Time the purchase was last updated |
|
||||||
|
| account | UUID | N | The AWeber customer account that was paid |
|
||||||
|
| stripe_account | text | N | The Stripe account of the AWeber customer |
|
||||||
|
| purchase | UUID | N | |
|
||||||
|
| status | [[payment_status][payment_status]] | N | Status of the payment intent |
|
||||||
|
|
||||||
|
<<payment_status>>
|
||||||
|
#+caption: ENUM: Payment Status
|
||||||
|
| new |
|
||||||
|
| paid |
|
||||||
|
| failed |
|
||||||
|
** Events
|
||||||
|
#+caption: Events
|
||||||
|
| Field | Type | Nullable | Description |
|
||||||
|
|----------------+-----------+----------+---------------------------------------------------------|
|
||||||
|
| id | text | N | Stripe event id |
|
||||||
|
| type | text | N | Stripe event type |
|
||||||
|
| account | UUID | N | The AWeber customer account the subscription belongs to |
|
||||||
|
| stripe_account | text | N | The Stripe account of the AWeber customer |
|
||||||
|
| timestamp | timestamp | N | Time event was published |
|
||||||
|
| subscription | text | Y | Related Stripe subscription ID |
|
||||||
|
|
||||||
|
Events should expire out of the database over time. Stripe maintains events in
|
||||||
|
its database for up to 30 days.
|
||||||
|
*** Modeling in DynamoDB
|
||||||
|
**** Table
|
||||||
|
- Partition Key :: =id=
|
||||||
|
|
||||||
|
The table index provides for rapid lookup of individual events.
|
||||||
|
**** Attributes
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|----------------+-------------------------+----------+-----------------------------------------------------------|
|
||||||
|
| id | String | Y | Stripe event id |
|
||||||
|
| type | String | Y | Stripe event type |
|
||||||
|
| stripe_account | String | Y | The Stripe account of the AWeber customer |
|
||||||
|
| timestamp | String (timestamp) | Y | Time event was published (e.g. "2020-01-01 01:02:34.567") |
|
||||||
|
| date | String (date) | Y | Date portion of timestamp (e.g. "2020-01-01") |
|
||||||
|
| time | String (time) | Y | Time portion of timestamp (e.g. "01:02:34.567") |
|
||||||
|
| expiration | Number (Unix timestamp) | Y | TTL expiration time of the event record |
|
||||||
|
| subscription | String | N | Related Stripe subscription ID |
|
||||||
|
**** TTL
|
||||||
|
The events table will automatically expire and remove rows for which
|
||||||
|
=expiration= has passed. We may set =expiration= to be the event creation time
|
||||||
|
plus thirty days to match Stripe's own expiration. This will prevent old items
|
||||||
|
from piling up in the table.
|
||||||
|
**** Time index (GSI)
|
||||||
|
- Partition Key :: =date=
|
||||||
|
- Sort Key :: =time=
|
||||||
|
- Projection :: Keys Only
|
||||||
|
|
||||||
|
|
||||||
|
The date and time portions of the event timestamp will be separated to allow for
|
||||||
|
scanning all events within a day, filterable by time. This should support our
|
||||||
|
polling job, which should run multiple times per day. We'll have to be careful
|
||||||
|
around date boundaries when looking up events (e.g. by including a query for
|
||||||
|
events from the previous day during the first run of the current day).
|
||||||
|
**** Stripe Account index (GSI)
|
||||||
|
- Partition Key :: =stripe_account=
|
||||||
|
- Sort Key :: =timestamp=
|
||||||
|
- Projection :: Include type, subscription
|
||||||
|
|
||||||
|
The account index provides for rapid lookup of events for a particular stripe
|
||||||
|
account, filterable by timestamp.
|
||||||
|
**** Subscription index (GSI)
|
||||||
|
- Partition Key :: =subscription=
|
||||||
|
- Sort Key :: =timestamp=
|
||||||
|
- Projection :: Include type, stripe_account
|
||||||
|
|
||||||
|
The account index provides for rapid lookup of events for a particular
|
||||||
|
subscription, filterable by timestamp.
|
||||||
|
**** IAM Policy
|
||||||
|
Put together by referencing [[id:ab2d34bf-97b1-4e50-8e9a-597d0f8fcf01][DynamoDB IAM Policies]].
|
||||||
|
#+caption: Stripe Payments DynamoDB IAM Policy
|
||||||
|
#+begin_src json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "dynamodb:ListTables",
|
||||||
|
"Resource": "*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:BatchWriteItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:PutItem",
|
||||||
|
"dynamodb:DeleteItem",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:UpdateItem",
|
||||||
|
"dynamodb:DescribeTimeToLive",
|
||||||
|
"dynamodb:CreateTable",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:UpdateTable",
|
||||||
|
"dynamodb:UpdateTimeToLive"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:dynamodb:*:018154689201:table/*-stripe-payments-*/index/*",
|
||||||
|
"arn:aws:dynamodb:*:018154689201:table/*-stripe-payments-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
* Notes
|
||||||
|
- How does the [[id:83d61eef-0781-46e0-b959-1a739cff5ea3][poller]] use these stored events?
|
||||||
|
+ /To identify events retrieved from Stripe which do not need to be replayed/
|
||||||
|
- How do we replay events?
|
||||||
|
+ Send it to the webhook endpoint? An internal webhook endpoint that shares
|
||||||
|
code with the public one?
|
||||||
|
- /The same webhook endpoint would be preferable, requires signing the
|
||||||
|
payload/
|
||||||
|
- Do we expose the processed events as an internal endpoint in the
|
||||||
|
stripe-payments service, or give the polling app a database connection?
|
||||||
|
+ /API seems preferable/
|
||||||
|
|
||||||
|
Following some discussion, the [[Events][Events]] table should be sufficient for now.
|
|
@ -0,0 +1,12 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: fe6374ac-8fa6-4579-bd42-6d4de92de86a
|
||||||
|
:END:
|
||||||
|
#+title: Finding number of subscribers with a tag
|
||||||
|
|
||||||
|
* Retrieve number of subscribers per tag
|
||||||
|
- Tagging -> Kubernetes
|
||||||
|
- Add appdb connection to tagging
|
||||||
|
- Add endpoint to tagging
|
||||||
|
- Expose tagging endpoints via authenticated kong
|
||||||
|
* Hide tags
|
||||||
|
* Delete tags
|
23
aweber/20210318112158-tagging_roadmap.org
Normal file
23
aweber/20210318112158-tagging_roadmap.org
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 8435e743-c092-43e9-bcc6-a8098aa4110c
|
||||||
|
:END:
|
||||||
|
#+title: Tagging Roadmap
|
||||||
|
|
||||||
|
* Retrieve number of subscribers per tag
|
||||||
|
** Migrate the tagging service from AWS to Kubernetes
|
||||||
|
** Expose tagging endpoints via authenticated kong
|
||||||
|
** Add appdb connection to tagging
|
||||||
|
|
||||||
|
** Create an AppDB user for the tagging service
|
||||||
|
- Full access to the subscriber_tags table
|
||||||
|
- Read access to the list.subscribers table
|
||||||
|
|
||||||
|
** Add endpoint to tagging to fetch subscribers on a tag
|
||||||
|
e.g.:
|
||||||
|
#+begin_example
|
||||||
|
GET tagging.service.production.consul/{tag}/account/{account}/subscribers
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
** Update the tagging service to update the subscriber_tags table directly.
|
||||||
|
** Retire the subscriber-tag-sync consumer
|
||||||
|
* Hide tags
|
43
aweber/20210323161636-moving_pages_out_of_sites.org
Normal file
43
aweber/20210323161636-moving_pages_out_of_sites.org
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 193f7c04-0a03-4870-90c8-2b5e3c4c92ce
|
||||||
|
:END:
|
||||||
|
#+title: Moving pages out of Sites
|
||||||
|
#+filetags: :project:
|
||||||
|
|
||||||
|
A [[id:db322997-ff5e-416a-8dc8-f29e6a4928c8][Technical Initiative]] to rewrite pages in the [[id:57ee2f00-9bcd-4e0f-8a77-ae1f2d4cda89][Control Panel]] still built in PHP.
|
||||||
|
|
||||||
|
* Pages in Sites
|
||||||
|
** TODO Dashboard
|
||||||
|
*** Broadcasts
|
||||||
|
*** Lists
|
||||||
|
*** Subscribers
|
||||||
|
In progress.
|
||||||
|
** Content Creation
|
||||||
|
*** TODO Follow-ups
|
||||||
|
*** TODO Blog Broadcasts
|
||||||
|
*** TODO Email Template Manager
|
||||||
|
** Subscribers
|
||||||
|
*** TODO Manage Subscribers
|
||||||
|
*** TODO Add Subscribers
|
||||||
|
*** TODO Import History
|
||||||
|
** Reports
|
||||||
|
*** TODO Classic Reports
|
||||||
|
*** TODO Report API
|
||||||
|
*** TODO Tracking
|
||||||
|
** Lists
|
||||||
|
*** TODO Manage Lists
|
||||||
|
*** TODO List Settings
|
||||||
|
*** TODO Custom Fields
|
||||||
|
*** TODO List Automations
|
||||||
|
** Accounts
|
||||||
|
*** TODO My Account
|
||||||
|
*** Billing
|
||||||
|
*** Notifications
|
||||||
|
** Advocates
|
||||||
|
- Create an affiliate management API
|
||||||
|
- Create an affiliate frontend application
|
||||||
|
- Embed the affiliate app in the CP and link them with CP accounts.
|
||||||
|
** TODO Help
|
||||||
|
Should this be separated?
|
||||||
|
** Integrations
|
||||||
|
😱
|
8
aweber/20210323162325-technical_initiative.org
Normal file
8
aweber/20210323162325-technical_initiative.org
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: db322997-ff5e-416a-8dc8-f29e6a4928c8
|
||||||
|
:END:
|
||||||
|
#+title: Technical Initiative
|
||||||
|
|
||||||
|
* Active
|
||||||
|
- [[id:193f7c04-0a03-4870-90c8-2b5e3c4c92ce][Moving pages out of Sites]]
|
||||||
|
- [[id:b4f579f7-f848-4a7b-b7bc-f34fec36346a][Cleaning up public endpoints in proxy services]]
|
|
@ -0,0 +1,29 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: b4f579f7-f848-4a7b-b7bc-f34fec36346a
|
||||||
|
:END:
|
||||||
|
#+title: Cleaning up public endpoints in proxy services
|
||||||
|
|
||||||
|
A [[id:db322997-ff5e-416a-8dc8-f29e6a4928c8][Technical Initiative]] to move endpoints exposed in "proxy" services into more
|
||||||
|
relevant services and instead expose them via Kong.
|
||||||
|
|
||||||
|
* Proxy Services
|
||||||
|
|
||||||
|
** subscriber-proxy
|
||||||
|
|
||||||
|
*** TODO =/<subscriber_id>=
|
||||||
|
|
||||||
|
*** TODO =/batch_delete=
|
||||||
|
|
||||||
|
*** TODO =/batch_tag=
|
||||||
|
|
||||||
|
*** TODO =/batch_unsubscribe=
|
||||||
|
|
||||||
|
*** TODO =/bulk-tagging/jobs=
|
||||||
|
|
||||||
|
*** TODO =/bulk-tagging/jobs/<id>=
|
||||||
|
|
||||||
|
** search-proxy
|
||||||
|
|
||||||
|
** tagging-proxy
|
||||||
|
|
||||||
|
** search-recipients
|
4
aweber/20210324133247-imbi.org
Normal file
4
aweber/20210324133247-imbi.org
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 76933c22-fe7c-43e9-9ec9-62564377dd85
|
||||||
|
:END:
|
||||||
|
#+title: Imbi
|
|
@ -0,0 +1,13 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 9332ed8f-b669-4d3f-a25d-da751a8c2da1
|
||||||
|
:END:
|
||||||
|
#+title: Troubleshooting an unresolvable kubernetes service hostname
|
||||||
|
|
||||||
|
The service's selector wasn't matching any pods, therefore the service had no IP
|
||||||
|
to respond with. This can be troubleshooted by inspecting the endpoints for the
|
||||||
|
service. Due to the mismatch, none were available.
|
||||||
|
|
||||||
|
The ClusterIP service did not itself have an IP assigned even after fixing the
|
||||||
|
mismatch. This appears to be an optimization in that Kubernetes won't bother
|
||||||
|
assigning an IP to the service if the service has only a single endpoint, as it
|
||||||
|
is more expedient to return the sole endpoint's IP address.
|
15
aweber/20210412153138-purchase_tracking.org
Normal file
15
aweber/20210412153138-purchase_tracking.org
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: e2dab290-3e1b-4d5a-8628-61c2cb4896dc
|
||||||
|
:END:
|
||||||
|
#+title: Purchase tracking
|
||||||
|
|
||||||
|
* Related Confluence documents
|
||||||
|
- [[https://confluence.aweber.io/pages/viewpage.action?spaceKey=PD&title=2021-03-16+Record+purchase+details+for+AWeber+integrations][2021-03-16 Record purchase details for AWeber integrations]]
|
||||||
|
- [[https://confluence.aweber.io/pages/viewpage.action?spaceKey=API&title=Storing+PayPal+purchases+as+analytics+events][Storing PayPal purchases as analytics events]]
|
||||||
|
|
||||||
|
* Record purchases
|
||||||
|
- Track a separate event type for sales tracking.
|
||||||
|
- Changes to sales tracking URLs can break tracking currently.
|
||||||
|
- Separate URL profiles for Stripe and Paypal? (Stripe is currently using
|
||||||
|
ECommerce), and add an ECommerce event type for them.
|
||||||
|
* Segmenting on purchases
|
65
aweber/20210506110533-dynamodb_iam_policies.org
Normal file
65
aweber/20210506110533-dynamodb_iam_policies.org
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: ab2d34bf-97b1-4e50-8e9a-597d0f8fcf01
|
||||||
|
:END:
|
||||||
|
#+title: DynamoDB IAM Policies
|
||||||
|
|
||||||
|
#+caption: DynamoDB access for the k8s-labs-application role
|
||||||
|
#+begin_src json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "dynamodb:ListTables",
|
||||||
|
"Resource": "*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:BatchWriteItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:PutItem",
|
||||||
|
"dynamodb:DeleteItem",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:UpdateItem",
|
||||||
|
"dynamodb:DescribeTimeToLive",
|
||||||
|
"dynamodb:CreateTable",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:UpdateTable"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:dynamodb:*:018154689201:table/*-webhook-callbacks/index/*",
|
||||||
|
"arn:aws:dynamodb:*:018154689201:table/*-webhook-callbacks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:BatchWriteItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:PutItem",
|
||||||
|
"dynamodb:DeleteItem",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:UpdateItem",
|
||||||
|
"dynamodb:DescribeTimeToLive",
|
||||||
|
"dynamodb:CreateTable",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:UpdateTable"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:dynamodb:*:018154689201:table/*-webhooks",
|
||||||
|
"arn:aws:dynamodb:*:018154689201:table/*-webhooks/index/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
- [[https://docs.amazonaws.cn/en_us/amazondynamodb/latest/developerguide/access-control-overview.html][Overview of Managing Access Permissions to Your Amazon DynamoDB Resources]]
|
||||||
|
- [[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/api-permissions-reference.html][DynamoDB API Permissions: Actions, Resources, and Conditions Reference]]
|
|
@ -0,0 +1,41 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: bdc526d1-4f57-4210-93f2-12bb30d33ed9
|
||||||
|
:END:
|
||||||
|
#+title: Rebuild Unsubscribe Page as a React Application
|
||||||
|
#+filetags: :project:
|
||||||
|
|
||||||
|
* Flows
|
||||||
|
As described in [[https://confluence.aweber.io/display/~scottm/Unsubscribe+Flows][Unsubscribe Flows]]:
|
||||||
|
|
||||||
|
- View subscriptions
|
||||||
|
- Edit subscriptions
|
||||||
|
- Edit subscriber
|
||||||
|
+ Name
|
||||||
|
+ Email
|
||||||
|
+ Custom fields, if present
|
||||||
|
** Questions
|
||||||
|
- What do we do when the backend (database) is unavailable?
|
||||||
|
+ The backend could do some sort of queuing if the database is unavailable
|
||||||
|
(similar to the disk-persisted queue currently in use).
|
||||||
|
* Frontend
|
||||||
|
A new React application to support subscriber subscription management actions.
|
||||||
|
** Questions
|
||||||
|
- What will host the frontend application?
|
||||||
|
+ Deploy the application to S3 and have F5 route to it.
|
||||||
|
* Backend
|
||||||
|
A new python service to handle subscriber subscription management actions.
|
||||||
|
|
||||||
|
** Endpoints
|
||||||
|
*** Configuration
|
||||||
|
Returns account information and branding needed to display the page.
|
||||||
|
*** Get subscriptions
|
||||||
|
*** Edit subscriptions
|
||||||
|
*** Edit subscriber
|
||||||
|
Email verification needs to be processed if the subscriber updates their email
|
||||||
|
address in order for that change to take effect (addresses [[https://jira.aweber.io/browse/CCPANEL-11269][CCPANEL-11269]]).
|
||||||
|
Changes to other fields should be applied immediately.
|
||||||
|
**** Questions
|
||||||
|
- Do custom field changes need verification to change?
|
||||||
|
+ Probably not.
|
||||||
|
- Should we display a pending state until verification is complete?
|
||||||
|
+ We do not plan to store a pending state.
|
13
aweber/20210512133000-cobrowse_io.org
Normal file
13
aweber/20210512133000-cobrowse_io.org
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: d0a802dd-3258-4a86-b53f-287f7f6df6e6
|
||||||
|
:END:
|
||||||
|
#+title: Cobrowse.io
|
||||||
|
#+filetags: :project:
|
||||||
|
|
||||||
|
- Allows CS team to view a session of one of our customers.
|
||||||
|
- This will initially be available to all customers as a link on the help page.
|
||||||
|
- User-initiated, share the code with the AW agent.
|
||||||
|
- We will be using their cloud solution.
|
||||||
|
- Look into an API for adding admin notes that the session took place
|
||||||
|
- Look into information to be redacted in session recordings
|
||||||
|
+ Information displayed will have to be tagged with a CSS class to be identified
|
12
aweber/20210520114208-migrating_aws_services.org
Normal file
12
aweber/20210520114208-migrating_aws_services.org
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: af4ae6ee-5201-49ee-aa01-6cf6a0801908
|
||||||
|
:END:
|
||||||
|
#+title: Migrating AWS services
|
||||||
|
#+filetags: :project:
|
||||||
|
|
||||||
|
A [[id:db322997-ff5e-416a-8dc8-f29e6a4928c8][Technical Initiative]] to migrate services from ECS deployments to Kubernetes
|
||||||
|
deployments. The purpose is to homogenize our deployments to use Kubernetes,
|
||||||
|
which could theoretically be deployed to our local Kubernetes cluster or to EKS.
|
||||||
|
|
||||||
|
We currently maintain two separate sets of clusters in AWS. Services should be
|
||||||
|
migrated to the new cluster, or to kubernetes, ideally.
|
|
@ -0,0 +1,977 @@
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 022406d2-0480-4470-90d0-9533f6b9fa32
|
||||||
|
:END:
|
||||||
|
#+title: Supporting multiple currencies in Stripe
|
||||||
|
|
||||||
|