Payment Customization
Payment is setup as a customization in Geekle using the features already provided by Nodeblocks. In this case we have setup payment based on an organization paying for the service.
The Organization Service Customizations
Custom Fields
First we need a place to store the information related to the payment. Nodeblocks allows for the addition of custom fields. We created a list of custom fields for the organization in src/custom-fields/organization.ts
.
export const customOrganizationFields: util.CustomField[] = [
// ... other custom fields
{
name: 'representative_person_name',
type: 'string',
},
{
name: 'payment_customer_id',
type: 'string',
},
{
name: 'needs_payment_method_setup',
type: 'boolean',
},
{
name: 'subscription_status',
type: 'string',
},
{
name: 'subscription_id',
type: 'string',
},
];
These fields will allow us to store the information we need to connect to the payment provider - in this case, Stripe.
Side Effects
This feature of nodeblocks allows you to trigger additional actions when an endpoint is request. Now that we have our custom fields setup on the Organization we can add a new side effect to store the data inside those fields.
Inside src/sideEffects/index.ts
we have added 2 payment side effects:
export function createStripeAPI() {
return new Stripe(getEnvString('STRIPE_API_KEY', ''));
}
export async function setSideEffectHandlers(
adapter: defaultAdapter.OrganizationDefaultAdapter
) {
for (const sideEffectHandler of [
//...other side effects
createPaymentCustomer,
setSubscriptionData,
]) {
adapter = await sideEffectHandler(adapter);
}
return adapter;
}
createPaymentCustomer
will create a customer in the payment platform (aka Stripe) and trigger after createOrganization
has been called.
setSubscriptionData
will create and connect a subscription to the payment customer is called after updateOrganization
has been called.
createPaymentCustomer
accesses the response
object which has the organization data. We include this organization id inside the create customer call to the stripe backend. Once the customer is created, we can update the
organization with the customer information as well, linking them between both platforms.
setSubscriptionData
accesses the response
and the context
because the updateOrganization
doesn't include the whole organization object. The organization is requested before the sideEffect is run and passed to the sideEffect using addHandlerSideEffect
. The function first validates that it is an approved organization request, and now it has all the information it needs to create the subscription using the customer id attached to the organization. It stores the subscription id on the org, and the org id on the subscription.
Custom Endpoints
Now that the data is stored on the org object, we want to make sure it is up to date with Stripe. We will create a webhook endpoint that will receive events from Stripe.
Inside the src/index.ts
we can add 2 new custom routes to the organization service.
/webhook/subscription
to receive webhook events from stripe/payment-dashboard
to generate a personal dashboard for customer to manage their subscriptions. The customer handler logic is stored undersrc/handler
.
const customRoutes = [
{
handler: createWebhookHandler(adapter),
method: 'post',
path: '/webhook/subscription',
validators: [
createIsWebHookAuthenticateValidator(
getEnvString('STRIPE_WEBHOOK_SECRET', '')
),
],
},
//... createPaymentDashboardLink
]
This list can then be passed to createNodeblocksOrganizationApp.startService
and the new endpoints are added!
So now when a user's trial ends or they miss a payment we can handle that logic in the createWebhookHandler
.
Orders, Catalog, Chat Service
We add custom validators to each service to check the needs_payment_method_setup
property, to ensure that the organization has paid for their usage of the platform.