Introduction video for reference
https://www.loom.com/share/f524dbd7858a47dea08f8a27c688ed46
- Have a HighLevel account to be able to create and launch marketplace Apps.
- Creating a marketplace App `in custom payment provider category`
- Create a service running in any cloud provider to handle all requests from GHL while payments are happening.
- Create a publicly hosted pages for payment, authentication and other features you want to offer to customers.
How to create a marketplace App for payment provider
To create a marketplace App, login to marketplace dashboard. Once logged in, create a new marketplace app with the following config.
Config for Settings page
Required scopes
payments/orders.readonly payments/orders.write payments/subscriptions.readonly payments/transactions.readonly payments/custom-provider.readonly payments/custom-provider.write products.readonly products/prices.readonly
Redirect Url
This url is used to complete the Oauth flow when your app is installed at any location. Once your app is installed at any location, the user is redirects to this given url with acode
in query parameter. This code can be used to exchange it for a Oauth Access token which would be used for any API calls to Highlevel.
Example redirect url: https://domain/path?code=0834cbd778dacf89c
Client Keys
Store these client keys on your backend server in a secure manner. These keys will be required while making calls for Oauth flow.
Webhook URL
This URL will receive webhook events whenever an App is installed or uninstalled from a location.
SSO Key
This SSO key should be securely stored. This will be used to decrypt the auth token received for Custom Pages (More on this later)
Payment Provider
Name
Name of the payment providerApp description
App description for Payment providerPayment provider type
Payment provider types specify to GHL about what kind of payments are supported by your payment provider.OneTime: This options indicates that one time payments are supported by payment provider where only single time fixed payment is collected without any kind of future payments possibility.
Recurring: This options indicates that the payment provider supports recurring payment where a fixed recurring charge can be created and started on payment provider. This type would mean you can support recurring products in payments, create and manage subscriptions on your end, as well as provide updates about all subscriptions to Highlevel via webhooks (more on this later). For ex. if subscription has a new payment, is canceled or unpaid etc. So that these updates can reflect in GHL as well.
Off Session: This option indicates that the payment provider supports off session payments. Which means a given customer can be charged any amount using an API not requiring any customer input/authorization. This typically works where you have the customer cards authorized on their profiles and can use those cards to charge the customer later in time
Logo
Logo shown on the payment provider details.
Profile
Once all the above settings are done. We can move to Profile section. Where the important bit is to set the category to Third Party Provider
. This will ensure your app shows up correctly in App Marketplace, as well as it's visible on the Payments > Integrations page for improved discoverability.
Custom Pages
In order to collect payment related credentials from the user after the app is installed in a location, we recommend using a custom page. A Custom page is a public website that is loaded in an iFrame inside App details page once the app is installed on this location. For any payments app, after installation this custom page will be opened directly, so it's easier to discover for users. Also when you go to Payments > Integrations, if your app is already installed, then from there we redirect users to this Custom page in App marketplace section if they click on Manage Integration
in Payments > Integration > Details details page.
This is all the config that is required for creating a marketplace App. Once this is done, let's move to authentication and app installation.
App Installation
- Whenever your app is installed in a location, immediately a new tab will open with oauth code on the redirect url provided earlier in config.
- Once the app is installed, the configured custom page is loaded.
- In parallel, Highlevel payments expects an API call with some basic config for payment integration. This creates a basic config in Highlevel payments for your payments app, as well as starts showing the payment app as a payment option in Integrations page. So the users can manage the integration from there as well.
{ name: String, // Name of the integration shown in GHL everywhere description: String, // A short description/tagline for payments app. Shown in Payments > Integrations page imageUrl: String, // Public image url for payment provider logo to be shown in GHL locationId: String, // Sub-account ID where the app is installed queryUrl: String, // A url which received different requests for all queries related to payments. Ex. Verify, Refund etc. paymentsUrl: String, // Public page url loaded in Iframe for making payments on frontend }
- Once the app is installed, the obvious next step for users should be to add relevant payment config (public keys, merchant Id etc.) required for the configuration of this payment gateway. Two kind of configs are needed for any payment provider, a test mode and a live mode config.
test mode config is used for testing payments where no real money is charged
live mode config is used by Customer for real payment where actual money is charged from valid cards/Banks.
- Once any user is updating the live or test config in the App Custom Page, Highlevel payments expects a test and live mode config update as well in following format. The two main parameters required for test and live mode config on Highlevel payments side are:
- apiKey: This key will be used for verification in backend calls made from Highelevel server to your server.
- publishableKey: Public api key used for frontend verification while initiating payment.
- Connect config API
- Once the liveMode or testMode keys are added, that particular mode of payments can be used on Highlevel payments. The last step is to set your app as a default payment provider for that Sub-account. That can be done in
Payments > Integrations > Your app > Set as Default
How the payment flow works
1. Once the paymentUrl is loaded in iframe, GHL expects a ready event, which should ideally be dispatched once the iframe is loaded completely and is ready to receive payment data and process payment. Once the ready event is dispatched by Iframe, GHL dispatches a data event sending all the data needed for the iframe to process the payment.
// Ready event dispatched by payment Iframe { type: 'custom_provider_ready', loaded: true } // Payment data event dispatched by GHL { type: 'payment_initiate_props', publishableKey: String, // Publishable key sent while connecting integration API amount: Number, // Amount in decimal currency with max 2 decimal places currency: String, // Standard 3 letter notation for currencies ex. USD, INR mode: String, // Payment mode: subscription/payment productDetails: {productId: string, priceId: string}, // productId and priceId for recurring products. More details can be fetched using the public api for Products/Prices contact?: { // Customer details for customer placing the order id: String, // Customer id in GHL name: String, // Full name of the customer email: String, contact: String, // Contact Number of customer with country code }, orderId: String, // GHL internal orderId for given order transactionId: String, // GHL internal transactionId for the given transaction subscriptionId: String, // GHL internal subscriptionId passed in case of a recurring product locationId: String, // Sub-account id for which the given order is created. }
2. Once the payment data event is dispatched, the Iframe should start the payment process. After the payment is done, GHL expects the following events for different outcomes from the payment
- Payment is successful
{ type: 'custom_element_success_response', chargeId: String, // Payment gateway chargeId for given transaction (Will be shown in order/transaction/subscription details page }
- Payment failed
{ type: 'custom_element_error_response', error: { description: String, // Error message to be shown to the user } }
- Payment canceled: emitted if user cancels the payment while going through the payment process
{ type: 'custom_element_close_response' }
3. If the payment is success, a backend API call is made to the queryUrl for verifying if the payment is success. If the payment is successful, it reflects on the frontend an appropriate action is taken like redirecting user to next page just like it happens with other payment gateways.
- Verify API call is sent with following payload
curl --location '${queryUrl}' \ --header 'Content-Type: application/json' \ --data '{ "type": "verify", "transactionId": "ghl_transaction_id", "apiKey": "661d4d5a2a0167fb235f99ae", "chargeId": "demo_charge_id", "subscriptionId":"ghl_subscription_id" }'
ResponseBody{ success: true //. This will mark the transaction and order both as success in GHL }
ResponseBody{ failed: true }
ResponseBody{ success: false }
Other events/actions
- Refund event
{ type: 'refund', amount: Number, transactionId: String, // Internal transaction ID against which refund is issued. }
Webhook events
- Subscriptions
- subscription.trialing
- subscription.active
- subscription.updated
- subscription.charged
- Payments
- payment.captured
https://backend.leadconnectorhq.com/payments/custom-provider/webhook Request type: POST Request body: as shown below.
{ event: enum, /* ['subscription.charged', 'subscription.trialing', 'subscription.active', 'subscription.updated', 'payment.captured'] */ chargeId: string, // payment charge id ghlSubscriptionId: string, subscriptionSnapshot: object, chargeSnapshot: object, ghlTransactionId: string, marketplaceAppId: string, locationId: string, // locationId apiKey: string, // payment provider api key }
1. payment.captured
{ event: enum, // 'payment.captured' chargeId: string, ghlTransactionId: string, chargeSnapshot: object, locationId: string, // locationId apiKey: string, // payment provider api key }
2. subscription.updated
{ event: enum, // 'subscription.updated' ghlSubscriptionId: string, subscriptionSnapshot: object, locationId: string, // locationId apiKey: string, // payment provider api key }
3. subscription.trialing, subscription.active
{ event: enum, // 'subscription.trialing', 'subscription.active' chargeId: string, ghlTransactionId: string, ghlSubscriptionId: string, marketplaceAppId: string, locationId: string, // locationId apiKey: string, // payment provider api key }
4. subscription.charged
{ event: enum, // 'subscription.charged' chargeId: string, ghlSubscriptionId: string, subscriptionSnapshot: object, chargeSnapshot: object, locationId: string, // locationId apiKey: string, // payment provider api key }
1. subscriptionSnapshot:-
subscriptionSnapshot: { id: string, // subscription id status: enum, // ['trialing', 'active', 'expired', 'canceled', 'unpaid', 'pending'] trialEnd: number, // trial end timestamp in unix / seconds createdAt: number, // createdAt timestamp in unix / seconds nextCharge: number // nextCharge timestamp in unix / seconds }
2. chargeSnapshot:-
chargeSnapshot: { status: enum, // ['succeeded', 'failed', 'pending'] amount: number, // in 100s i.e. actual amount upto 2 decimal place mutiplied by 100 chargeId: string, chargedAt: number // chargedAt timestamp in unix / seconds }
Public API docs: https://highlevel.stoplight.io/docs/integrations/d3e2affc0897a-create-new-integration
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article