71
loading...
This website collects cookies to deliver better user experience
staging
version of this app using your Stripe account in TEST mode is highly recommended since you're working with sensitive data).# Super Subscription Center
as the value to give your app a name:Email
and then to make it look nice select search
as the Left icon:select
customer.id as "cus_id",
customer.name,
customer.email,
subscription.id as "sub_id",
subscription.status,
subscription.current_period_end,
subscription.collection_method
from customer
left join subscription
on customer.id = subscription.customer_id;
JOIN
with the subscription
table.get_subscriptions
by clicking on the query in the list on the left.get_subscriptions
into the table in your app, open the right inspector, select your table in the canvas, and then in the data field enter {{get_subscriptions.data}}
. The double brackets (i.e. {{}}
) indicate that you are using JavaScript in Retool. Then, the get_subscriptions.data
is retrieving the data from your query.WHERE
clause to your get_subscriptions
query:select
customer.id as "cus_id",
customer.name,
customer.email,
subscription.id as "sub_id",
subscription.status,
subscription.current_period_end,
subscription.collection_method
from customer
left join subscription
on customer.id = subscription.customer_id
where subscription.status is not null and ({{ !textinput1.value }} or customer.email::text ilike {{ "%" + textinput1.value + "%" }});
WHERE
clause does two things:ilike
to search by the customer's email.get_subscriptions
query to add the following fields:select
customer.id as "cus_id",
customer.name,
customer.email,
subscription.id as "sub_id",
subscription.status,
subscription.current_period_end,
subscription.collection_method,
invoice.amount_due::numeric,
line_item.description
from customer
left join subscription
on customer.id = subscription.customer_id
left join invoice
on subscription.latest_invoice_id = invoice.id
left join invoice_line_item_map
on invoice.id = invoice_line_item_map.invoice_id
left join line_item
on invoice_line_item_map.line_item_id = line_item.id
where subscription.status is not null and ({{ !textinput1.value }} or customer.email::text ilike {{ "%" + textinput1.value + "%" }})
invoice.amount_due
and line_item.description
details you need by joining to the invoice
and line_item
table.cud_id
and sub_id
columns as well as the subscription end
column by clicking the eye icon.amount
column you can give the column a friendly name like Amount
and then for the data type select USD (cents)
:H3
by entering the value as ### Customer Name
.Customer Name
text component, select the component and in the inspector on the right enter the value as ### {{table1.selectedRow.data.name}}
:name
value from whatever row is selected in table1
. For your end user, this means the text box will immediately show the name of any customer they select in the table.### {{table1.selectedRow.data.status === "active" ? "Active" : table1.selectedRow.data.status === "canceled" ? "Canceled" : "Issue"}}
table
and utilize a ternary operator to show a user-friendly value for the status of the subscription.{{table1.selectedRow.data.email}} | {{table1.selectedRow.data.cus_id}}
{{table1.selectedRow.data.description}}
table1
as normal.Table1
. For the rest of the data in this card, you'll need to write new queries.get_customer
) as well as a helper function (calc_customer_stats
).get_customer
query, open the bottom drawer and click to create a new query against your Sequin database. Then enter the following SQL statement:SELECT
customer.id,
customer.created as "cus_created",
invoice.number,
invoice.amount_paid::int as "amount_paid",
invoice.period_start,
invoice.period_end,
invoice.created as "inv_created"
FROM customer
left join invoice
on customer.id = invoice.customer_id
where customer.id = {{table1.selectedRow.data.cus_id}}
order by invoice.number asc;
JOIN
with the invoice
table to pull in all their billing history. The WHERE
clause at the end filters the data for just the one customer selected in Table1
. Last, the ORDER BY
clause allows us to sort the results to make working with the data easier in the helper functions.get_customer
:get_customer
query to calculate some metrics:let index = get_customer.data.amount_paid.length;
let toDollars = (num) => {
return (num / 100).toLocaleString("en-US", {
style: "currency",
currency: "USD",
});
};
return {
mrr: toDollars(get_customer.data.amount_paid[index - 1]),
spend: toDollars(get_customer.data.amount_paid.reduce((i, o) => i + o)),
growth: toDollars(
get_customer.data.amount_paid[index - 1] - get_customer.data.amount_paid[0]
),
};
toDollars()
function.calc_customer_stats
.get_customer
query is run. So as a last step, open the get_customer
query and have the calc_customer_stats
query trigger on success:{{moment(get_customer.data.cus_created[0]).format("MMMM DD, YYYY")}}
moment.js
to format the datetime
value returned from your get_customer
query.{{calc_customer_stats.data.mrr}}
{{calc_customer_stats.data.spend}}
{{calc_customer_stats.data.growth}}
upcoming_invoice
details for the customer. Luckily, this data lives in your Sequin database.SELECT
upcoming_subscription_invoice.next_payment_attempt as "next_invoice_date",
(upcoming_subscription_invoice.amount_due/100.00)::money as "next_invoice_amount"
from upcoming_subscription_invoice
where upcoming_subscription_invoice.subscription_id = {{table1.selectedRow.data.sub_id}};
time period
and amount
of the next invoice associated to the subscription.get_next_invoice
:get_next_invoice
:Next Invoice on **{{moment(get_next_invoice.data.next_invoice_date[0]).format("MMMM Do YYYY")}}** for **{{get_next_invoice.data.next_invoice_amount[0]}}**
List View
component.List View
component allows you to show a list of items. It can dynamically show more or fewer items depending on how the underlying data changes.List View
component onto your app and then add a Container
component to the top of the list. As soon as you drop the Container
into the List View
component you'll see it's duplicated three times. This quickly gives you a sense of how the List View
component works by showing a new UI component for each item in an array of data.List View
component dynamic later, but for now you're just scaffolding the front-end. So to make things easier select the List View
component and in the inspector adjust the Number of rows to one for the time being.Container
you created:SELECT
invoice.id as "inv_id",
invoice.subscription_id as "sub_id",
invoice.number,
invoice.created,
invoice.status,
(invoice.amount_paid /100.00)::money as "amount",
invoice.hosted_invoice_url,
line_item.description as "line_item_description",
(price.unit_amount/100.00)::money as "unit amount",
price.recurring_interval,
product.name as "product",
charge.id as "charge_id",
charge.description as "charge_description",
(charge.amount/100.00)::money as "charge_amount",
charge.status as "charge_status",
charge.created as "charge_created"
from invoice
left join charge
on invoice.charge_id = charge.id
left join invoice_line_item_map
on invoice.id = invoice_line_item_map.invoice_id
left join line_item
on invoice_line_item_map.line_item_id = line_item.id
left join price
on line_item.price_id = price.id
left join product
on price.product_id = product.id
where invoice.subscription_id = {{table1.selectedRow.data.sub_id}}
group by invoice.id, charge.id, line_item.description, price.unit_amount, product.name, price.recurring_interval
order by invoice.number desc;
SELECT
across several tables that you JOIN
together in order to pull in invoices, line_items, prices, products, and charges. Then, you use the WHERE
statement to filter the data down to just the one subscription you have selected in Table1
.get_current_invoices
:get_current_invoices
query to your UI components.### 🧾 Invoice #: {{get_current_invoices.data.number[i]}}
[i]
at the end. So let's step through this:###
is markdown notation for an H3
text format.get_current_invoices.data.number
pulls in the invoice number from the get_current_invoices
query. Because we pull in all the invoices in the get_current_invoices
query, this value is actually an array.[i]
is extracting just one value from that array. The variable i
is used by the List View
component so that you request the same index from the array for every item in the same container
in the list. So for instance, the first container
in the list will use index 0 and then the second will use index 1. So on and so forth.get_current_invoices
query to the remaining values in the UI:UI Text Component | Value |
---|---|
Invoice Date and Time | {{moment(get_current_invoices.data.created[i]).format("MMM DD, YYYY - hh:mm A")}} |
Invoice Status | #### {{get_current_invoices.data.status[i] === "paid" ? "🟢 Paid" : "⚠️ Issue"}} |
Invoice Amount | {{get_current_invoices.data.amount[i]}} |
Invoice Plan | {{get_current_invoices.data.line_item_description[i]}} |
Payment | ##### Payment → {{get_current_invoices.data.charge_amount[i]}} |
Payment Status | ##### {{get_current_invoices.data.charge_status[i] === "succeeded" ? "✅ Success" : "⚠️ Issue"}} |
Payment Date and Time | {{moment(get_current_invoices.data.charge_created[i]).format("MMM DD, YYYY - hh:mm A")}} |
Payment Description | {{get_current_invoices.data.charge_description[i]}} |
List View
component and change the Number of rows in the inspector to {{get_current_invoices.data.inv_id.length}}
:View Invoice
.{{get_current_invoices.data.hosted_invoice_url[i]}}
get_current_invoices
query, you just need to associate this URL to the button.DELETE
request against the Stripe API./v1/subscriptions/{subscription_exposed_id}
as the operation.{{table1.selectedRow.data.sub_id}}
.get_subscriptions
query to trigger when your Stripe API call succeeds.1000
- this will ensure that your Sequin database is fully up-to-date before you refresh the data on the page to confirm the subscription has been deleted.cancel_subscription
.cancel_subscription
query:cancel_subscription
query{{table1.selectedRow.data.status === "canceled"}}