Test stores or developer stores do not support payment Billing. Please set it to test billing during the development and testing phases.
1.Pricing details Config
In the developer platform, you can edit the APP Listing to add packages and submit for review. Currently, it supports free, one-time, and recurring charging models. Each APP can only have one type of charging model, and once a package of a certain charging type is set, you cannot set other types of packages afterward.
One-time charge
Operation Path: Developer Platform --> apps --> App Display List [Management List] --> Edit listing.
Recurring charge
Operation Path: Developer Platform --> apps --> App Display List [Management List] --> Edit listing.
Recurring Charge Only
- free trial config
The term "trial period" means that there will be no charge during this time.
- Add plan
Recurring Charge & Usage Charge
- free trial config
The term "trial period" means that there will be no charge during this time.
- Add plan and Usage charge Config
Usage charges can only be applied under a recurring charging model.
Additional charges refer to the fees for adding usage.
2.App Call the Billing API
One-time Charge
Processing Timeline
Detail
-
The merchant selects a "one-time" subscription within the APP.
-
The APP invokes OpenAPI to create a one-time charge.
-
Create a webhook for tracking the status changes of the one-time invoice.
For each merchant that installs the APP, a webhook is created. Subsequently, the subscription status of the merchant can be determined by processing the events of this webhook.// 在商家完成 app 安装授权后调用该函数创建一次性收费 webhook CreateWebhook(form.ShopDomain, "app/purchases_one_time_update", "https://APP-Host/webhook/one_time", token) // CreateWebhook // @Description: // @param shopDomain 商家域名 eg:test.myshoplaza.com // @param event webhook 名称 // @param token access_token func CreateWebhook(shopDomain, event, address, token string) error { url := fmt.Sprintf("https://%s/openapi/2022-07/webhooks", shopDomain) method := "POST" param := map[string]interface{}{ "address": address, "topic": event, } payload, err := json.Marshal(param) if err != nil { return err } client := &http.Client{} req, err := http.NewRequest(method, url, bytes.NewReader(payload)) if err != nil { return err } req.Header.Add("Access-Token", token) req.Header.Add("Content-Type", "application/json") res, err := client.Do(req) if err != nil { return err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return err } fmt.Println(string(body)) return nil }
-
Create a one-time charge
-
The amount must be consistent with the amount set in the APP SetUp.
type CreateOneTimeChargeResp struct {
ID string `json:"id"`
AppID string `json:"application_id"`
Name string `json:"name"`
Price string `json:"price"`
ConfirmUrl string `json:"confirm_url"` //订阅确认链接
ReturnUrl string `json:"return_url"`
Status string `json:"status"`
Test bool `json:"test"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 在订阅套餐的接口中调用,创建一次性账单
func CreateOneTimeCharge(shopDomain, appName, appUrl, token string, price float64) (*CreateOneTimeChargeResp, error) {
url := fmt.Sprintf("https://%s/openapi/2022-01/application_charges", shopDomain)
body, err := json.Marshal(map[string]interface{}{
"application_charge": map[string]interface{}{
"name": appName, //app name
"price": price, //一次性收费的价格,需与套餐设置一致
"return_url": appUrl, //完成订阅后,重定向的页面
"test": false, //是否为测试链接
},
})
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
req.Header.Add("access-token", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
chargeResp := &CreateOneTimeChargeResp{}
err = json.Unmarshal(content, chargeResp)
if err != nil {
return nil, err
}
fmt.Println(chargeResp)
return chargeResp, nil
}
-
Shoplazza will reponse a confirmation_url
-
APP shows the confirmation_url page
-
The merchant confirms the subscription and initiates payment.
-
Shoplazza deducts the payment from the merchant's account and collects it into the Shoplazza account.
-
A webhook notifies the APP of the subscription information for this instance.
- Handling Webhook Notifications
When the status of a Billing invoice changes, the APP is notified via a webhook. The APP can determine the merchant's subscription status based on the received webhook event
type OneTimeChargeEvent struct { ID string `json:"id"` StoreID int `json:"store_id"` ApplicationID string `json:"application_id"` Name string `json:"name"` Price string `json:"price"` Status string `json:"status"` Test bool `json:"test"` UpdatedAt *time.Time `json:"updated_at"` } // gin 框架 handle 示例 func OneTimeChargeWebhook(c *gin.Context) { body, err := ioutil.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusInternalServerError, err.Error()) return } fmt.Printf("Header: %+v\n", c.Request.Header) headerHmac := c.GetHeader("X-Shoplazza-Hmac-Sha256") if !hmacCheck(headerHmac, clientSecret, body) { c.JSON(http.StatusUnauthorized, "hmac verification fails.") return } event := &OneTimeChargeEvent{} err = json.Unmarshal(body, event) if err != nil { fmt.Println(err.Error()) c.JSON(http.StatusInternalServerError, err.Error()) return } fmt.Printf("Body: %+v\n", event) // todo 根据账单状态进行处理 c.JSON(http.StatusOK, event) }
- Handling Webhook Notifications
-
Redirect to the return_url (the page to which the user is redirected after confirming on the confirmation_url).
Recurring Charge Only
Processing Timeline
Detail
-
The merchant selects a "recurring" subscription plan within the APP.
-
The APP invokes OpenAPI to create a recurring charge
-
Create a webhook for tracking the status changes of the recurring charge.
For each merchant that installs the APP, a webhook is created. Subsequently, the subscription status of the merchant can be determined by processing the events of this webhook.// 在商家完成 app 安装授权后调用该函数创建周期性账单更新 webhook CreateWebhook(form.ShopDomain, "app/subscriptions_update", "https://APP-Host/webhook/recurring", token) // CreateWebhook // @Description: // @param shopDomain 商家域名 eg:test.myshoplaza.com // @param event webhook 名称 // @param token access_token func CreateWebhook(shopDomain, event,address, token string) error { url := fmt.Sprintf("https://%s/openapi/2022-07/webhooks", shopDomain) method := "POST" param := map[string]interface{}{ "address": address, "topic": event, } payload, err := json.Marshal(param) if err != nil { return err } client := &http.Client{} req, err := http.NewRequest(method, url, bytes.NewReader(payload)) if err != nil { return err } req.Header.Add("Access-Token", token) req.Header.Add("Content-Type", "application/json") res, err := client.Do(req) if err != nil { return err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return err } fmt.Println(string(body)) return nil }
-
Create recurring charge
The package price must be consistent with the package price set in the APP listing.
-
type CreateRecurringChargeResp struct {
ID string `json:"id"`
ApplicationID string `json:"application_id"`
Name string `json:"name"`
Price string `json:"price"`
CappedAmount string `json:"capped_amount"`
Terms string `json:"terms"`
ReturnURL string `json:"return_url"`
ConfirmationURL string `json:"confirmation_url"` //确认链接
Status string `json:"status"`
TrialDays int `json:"trial_days"`
ActivatedOn string `json:"activated_on"`
TrialEndsOn string `json:"trial_ends_on"`
BillingOn string `json:"billing_on"`
CancelledOn string `json:"cancelled_on"`
CancelSubOn string `json:"cancel_sub_on"`
Test bool `json:"test"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 在订阅套餐的接口中调用,创建周期性账单
func createRecurringCharge(shopDomain, appName, appUrl, token string, price, cappedAmount float64) (*CreateRecurringChargeResp, error) {
url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges", shopDomain)
body, err := json.Marshal(map[string]interface{}{
"recurring_application_charge": map[string]interface{}{
"name": appName, //app name
"price": price, //套餐价格,需要与listing中设置的套餐价格一致
"return_url": appUrl, //商家完成订阅之后跳转的页面
"trial_days": 0, //试用期天数
"capped_amount": cappedAmount, //每个周期使用费上限
"terms": "terms", //使用费收费说明
"test": false, //是否为测试账单
},
})
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
req.Header.Add("access-token", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
chargeResp := &CreateRecurringChargeResp{}
err = json.Unmarshal(respBody, chargeResp)
if err != nil {
return nil, err
}
fmt.Println(chargeResp)
return chargeResp, nil
}
- Shoplazza will reponse a confirmation_url
- APP shows the confirmation_url page
- The merchant confirms the subscription and initiates payment.
- Shoplazza deducts the payment from the merchant's account and collects it into the Shoplazza account.
- A webhook notifies the APP of the subscription information for this instance.
- Handling Webhook Notifications , When the status of a Billing invoice changes, the APP is notified via a webhook. The APP can determine the merchant's subscription status based on the received webhook event.
type RecurringChargeEvent struct { ActivatedOn *time.Time `json:"activated_on"` ApplicationID string `json:"application_id"` BillingOn *time.Time `json:"billing_on"` CancelledOn *time.Time `json:"cancelled_on"` CappedAmount string `json:"capped_amount"` ID string `json:"id"` Name string `json:"name"` Price string `json:"price"` Status string `json:"status"` StoreID int `json:"store_id"` Test bool `json:"test"` TrialDays int `json:"trial_days"` TrialEndsOn *time.Time `json:"trial_ends_on"` UpdatedAt *time.Time `json:"updated_at"` } // gin 框架 handle 示例 func RecurringChargeWebhook(c *gin.Context) { body, err := ioutil.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusInternalServerError, err.Error()) return } fmt.Printf("Header: %+v\n", c.Request.Header) headerHmac := c.GetHeader("X-Shoplazza-Hmac-Sha256") if !hmacCheck(headerHmac, clientSecret, body) { c.JSON(http.StatusUnauthorized, "hmac verification fails.") return } event := &RecurringChargeEvent{} err = json.Unmarshal(body, event) if err != nil { fmt.Println(err.Error()) c.JSON(http.StatusInternalServerError, err.Error()) return } fmt.Printf("body: %+v\n", event) // todo 根据账单状态进行处理 c.JSON(http.StatusOK, event) }
- Redirect to the return_url (the page to which the user is redirected after confirming on the confirmation_url).
- Shoplazza automatically calculates and deducts the payment for the next cycle.
- The webhook pushes the information about the new changes in the subscription status to the APP.
Recurring charge & Usage Charge
Processing Timeline
Detail
-
The merchant selects a "recurring" subscription plan within the APP.
-
The APP invokes OpenAPI to create a recurring charge
-
Create a webhook for tracking the status changes of the recurring charge.
For each merchant that installs the APP, a webhook is created. Subsequently, the subscription status of the merchant can be determined by processing the events of this webhook.// 在商家完成 app 安装授权后调用该函数创建周期性账单更新 webhook CreateWebhook(form.ShopDomain, "app/subscriptions_update", "https://APP-Host/webhook/recurring", token) // CreateWebhook // @Description: // @param shopDomain 商家域名 eg:test.myshoplaza.com // @param event webhook 名称 // @param token access_token func CreateWebhook(shopDomain, event,address, token string) error { url := fmt.Sprintf("https://%s/openapi/2022-07/webhooks", shopDomain) method := "POST" param := map[string]interface{}{ "address": address, "topic": event, } payload, err := json.Marshal(param) if err != nil { return err } client := &http.Client{} req, err := http.NewRequest(method, url, bytes.NewReader(payload)) if err != nil { return err } req.Header.Add("Access-Token", token) req.Header.Add("Content-Type", "application/json") res, err := client.Do(req) if err != nil { return err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return err } fmt.Println(string(body)) return nil }
-
Create recurring charge
The package price must be consistent with the package price set in the APP listing.
-
type CreateRecurringChargeResp struct {
ID string `json:"id"`
ApplicationID string `json:"application_id"`
Name string `json:"name"`
Price string `json:"price"`
CappedAmount string `json:"capped_amount"`
Terms string `json:"terms"`
ReturnURL string `json:"return_url"`
ConfirmationURL string `json:"confirmation_url"` //确认链接
Status string `json:"status"`
TrialDays int `json:"trial_days"`
ActivatedOn string `json:"activated_on"`
TrialEndsOn string `json:"trial_ends_on"`
BillingOn string `json:"billing_on"`
CancelledOn string `json:"cancelled_on"`
CancelSubOn string `json:"cancel_sub_on"`
Test bool `json:"test"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 在订阅套餐的接口中调用,创建周期性账单
func createRecurringCharge(shopDomain, appName, appUrl, token string, price, cappedAmount float64) (*CreateRecurringChargeResp, error) {
url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges", shopDomain)
body, err := json.Marshal(map[string]interface{}{
"recurring_application_charge": map[string]interface{}{
"name": appName, //app name
"price": price, //套餐价格,需要与listing中设置的套餐价格一致
"return_url": appUrl, //商家完成订阅之后跳转的页面
"trial_days": 0, //试用期天数
"capped_amount": cappedAmount, //每个周期使用费上限
"terms": "terms", //使用费收费说明
"test": false, //是否为测试账单
},
})
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
req.Header.Add("access-token", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
chargeResp := &CreateRecurringChargeResp{}
err = json.Unmarshal(respBody, chargeResp)
if err != nil {
return nil, err
}
fmt.Println(chargeResp)
return chargeResp, nil
}
- Shoplazza will reponse a confirmation_url
- APP shows the confirmation_url page
-
The merchant confirms the subscription and initiates payment.
-
Shoplazza deducts the payment from the merchant's account and collects it into the Shoplazza account.
-
A webhook notifies the APP of the subscription information for this instance.
- Handling Webhook Notifications , When the status of a Billing invoice changes, the APP is notified via a webhook. The APP can determine the merchant's subscription status based on the received webhook event.
type RecurringChargeEvent struct { ActivatedOn *time.Time `json:"activated_on"` ApplicationID string `json:"application_id"` BillingOn *time.Time `json:"billing_on"` CancelledOn *time.Time `json:"cancelled_on"` CappedAmount string `json:"capped_amount"` ID string `json:"id"` Name string `json:"name"` Price string `json:"price"` Status string `json:"status"` StoreID int `json:"store_id"` Test bool `json:"test"` TrialDays int `json:"trial_days"` TrialEndsOn *time.Time `json:"trial_ends_on"` UpdatedAt *time.Time `json:"updated_at"` } // gin 框架 handle 示例 func RecurringChargeWebhook(c *gin.Context) { body, err := ioutil.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusInternalServerError, err.Error()) return } fmt.Printf("Header: %+v\n", c.Request.Header) headerHmac := c.GetHeader("X-Shoplazza-Hmac-Sha256") if !hmacCheck(headerHmac, clientSecret, body) { c.JSON(http.StatusUnauthorized, "hmac verification fails.") return } event := &RecurringChargeEvent{} err = json.Unmarshal(body, event) if err != nil { fmt.Println(err.Error()) c.JSON(http.StatusInternalServerError, err.Error()) return } fmt.Printf("body: %+v\n", event) // todo 根据账单状态进行处理 c.JSON(http.StatusOK, event) }
-
Redirect to the return_url (the page to which the user is redirected after confirming on the confirmation_url).
-
Within a subscription period, if there are usage fees, then create a Usage charge (which can be multiple charges).
-
Create usage charge
type CreateUsageChargeResp struct { ID string `json:"id"` Price string `json:"price"` BalanceRemaining string `json:"balance_remaining"` BalanceUsed string `json:"balance_used"` CreatedAt *time.Time `json:"created_at"` Description string `json:"description"` } func createUsageCharge(shopDomain, description, chargeID, token string, price float64) (*CreateUsageChargeResp, error) { url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges/%s/usage_charge", shopDomain, chargeID) body, err := json.Marshal(map[string]interface{}{ "usage_charge": map[string]interface{}{ "description": description, "price": price, }, }) req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) req.Header.Add("accept", "application/json") req.Header.Add("content-type", "application/json") req.Header.Add("access-token", token) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } respBody, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } chargeResp := &CreateUsageChargeResp{} err = json.Unmarshal(respBody, chargeResp) if err != nil { return nil, err } fmt.Printf("%+v\n", chargeResp) return chargeResp, err }
-
-
Shoplazza automatically consolidates the total usage fees for the current period and the subscription fee for the next period for the merchant and deducts the payment from the merchant.
-
The webhook pushes the information about the new changes in the subscription status to the APP.
Supplement:Trigger Webhook By BillingAPI Event
It will trigger a webhook to notice app at the following status change.
3.Developer Settlement Guide
Apps that have integrated with the Billing API can view income details on the developer platform, where developers can settle the app's earnings.
Income Review
When an app generates revenue through billing, you can view the app's income details on the page as shown in the figure.
After clicking "Details" to view the details of the settlement bill, you can see all transaction details for that bill's period:
Click "Details" on the transaction to view the breakdown of charges for that specific transaction.
Withdrawing Earnings
Select the withdrawable category, and after confirming that the bill details are correct, click on "Withdraw." This will send a review request to Shoplazza. Once the review is approved, the earnings will be received in the agreed-upon manner.
On the withdrawable page, you can view the withdrawal progress.
View Historical Earnings
In the "Withdrawn" category, you can see the settlement bills that have been previously withdrawn and completed. You can also view the specific proof of payment.