Subscription
Subscription is a recurring billing payment solution of Checkout API. It allows you to bill your customer based on a specific schedule automatically. Once a user makes his first payment via subscription, our system signs him up for recurring billing.
The user will be charged based on the subscription schedule. E.g. if you specify the product length as 1 month, we will charge the user every month. Next payment will happen the same day of the next month. If a payment happens on 30th of January, the next payment will be made in first days of March.
Integrating it only requires 3 steps:
Before starting, make sure you have product selection step finished in your application. The deduction period for each products needs to be clearly defined and the total period length of the subscription shall not exceed 6 months.
Payment methods for subscription
Subscirption supports certain payment methods:
Payment method | Payment system short code | Notes |
---|---|---|
Credit cards | cc | Requires credit card activation flow. |
FasterPay | fasterpay | |
PayPal | paypal | Requires spiderpipe for PayPal to be activated for your Paymentwall account. |
MercadoPago | mercadopago | |
Mobiamo | mobilegateway | Mobiamo has a special coverage of price points, see Mobiamo coverage. |
SEPA | sepadirectdebit | |
Alipay+ | alipayplus | |
Tosspay | tosspay | Confirm on billingKey |
Build payment page
Payment link can be built by widget
object. The configuration difference between single payment methods and all payment methods is the value of ps
parameter.
See parameter definition for the requirement and meaning of each parameters while using subscription.
<?php
require_once('lib/paymentwall.php');
Paymentwall_Config::getInstance()->set(array(
'api_type' => Paymentwall_Config::API_GOODS,
'public_key' => 'YOUR_PROJECT_KEY',
'private_key' => 'YOUR_SECRET_KEY'
));
$widget = new Paymentwall_Widget(
'user40012', // uid
'p1_1', // widget
array(
new Paymentwall_Product(
'product301', // ag_external_id
9.99, // amount
'USD', // currencyCode
'Gold Membership', // ag_name
Paymentwall_Product::TYPE_SUBSCRIPTION, // ag_type
1, // ag_period_length
Paymentwall_Product::PERIOD_TYPE_MONTH, // ag_period_type
true // ag_recurring
)
),
array(
'email' => 'user@hostname.com',
'history[registration_date]' => 'registered_date_of_user',
'ps' => 'all', // Replace it with specific payment system short code for single payment methods
'addtional_param_name' => 'addtional_param_value'
)
);
echo $widget->getUrl();
?>
var Paymentwall = require('paymentwall');
Paymentwall.Configure(
Paymentwall.Base.API_GOODS,
'YOUR_APPLICATION_KEY',
'YOUR_SECRET_KEY'
);
var widget = new Paymentwall.Widget(
'user40012', // uid
'p1_1', // widget
[
new Paymentwall.Product(
'product301', // ag_external_id
9.99, // amount
'USD', // currencyCode
'Gold Membership', // ag_name
Paymentwall.Product.TYPE_SUBSCRIPTION, // ag_type
1, // ag_period_length
Paymentwall.Product.PERIOD_TYPE_MONTH, // ag_period_type
true // ag_recurring
)
],
{
'email': 'user@hostname.com',
'history[registration_date]': 'registered_date_of_user',
'ps': 'all', // Replace it with specific payment system short code for single payment methods
'additional_param_name': 'additional_param_value'
}
);
widget.getUrl();
Config.getInstance().setLocalApiType(Config.API_GOODS);
Config.getInstance().setPublicKey("YOUR_APPLICATION_KEY");
Config.getInstance().setPrivateKey("YOUR_SECRET_KEY");
WidgetBuilder widgetBuilder = new WidgetBuilder("USER_ID", "p1_1");
widgetBuilder.setProduct(
new ProductBuilder("YOUR_PRODUCT_ID") {
{
setAmount(0.99);
setCurrencyCode("USD");
setName("YOUR_PRODUCT_NAME");
setProductType(Product.TYPE_SUBSCRIPTION);
setPeriodType("month");
setPeriodLength(3);
}
}.build());
widgetBuilder.setExtraParams(new LinkedHashMap<String, String>(){
{
put("email", "YOUR_CUSTOMER_EMAIL");
put("history[registration_date]","REGISTRATION_DATE");
put("ps","all"); // Replace it with specific payment system short code for single payment methods
}
});
Widget widget = widgetBuilder.build();
return widget.getUrl();
require 'paymentwall' # alternatively, require_relative '/path/to/paymentwall-ruby/lib/paymentwall.rb'
Paymentwall::Base::setApiType(Paymentwall::Base::API_GOODS)
Paymentwall::Base::setAppKey('YOUR_APPLICATION_KEY')
Paymentwall::Base::setSecretKey('YOUR_SECRET_KEY')
widget = Paymentwall::Widget.new(
'user40012',
'p1',
[
Paymentwall::Product.new(
'product301',
9.99,
'USD',
'Gold Membership',
Paymentwall::Product::TYPE_SUBSCRIPTION,
1,
Paymentwall::Product::PERIOD_TYPE_MONTH,
true
)
],
{
'email' => 'user@hostname.com',
'history[registration_date]' => 'registered_date_of_user',
'ps' => 'all', # Replace it with specific payment system short code for single payment methods
'additional_param_name' => 'additional_param_value'
}
)
puts widget.getUrl()
from paymentwall import *
Paymentwall.set_api_type(Paymentwall.API_GOODS)
Paymentwall.set_app_key('YOUR_PROJECT_KEY')
Paymentwall.set_secret_key('YOUR_SECRET_KEY')
product = Product(
'product301', # id of the product in your system
9.99, # price
'USD', # currency code
'Gold Membership', # product name
Product.TYPE_SUBSCRIPTION, # this is a time-based product
1, # duration is 1
Product.PERIOD_TYPE_WEEK, # week
True # recurring
)
widget = Widget(
'user40012', # uid
'p1_1', # widget
[product],
{
'email' : 'user@hostname.com',
'history[registration_date]' : 'registered_date_of_user',
'ps' : 'all', # Replace it with specific payment system short code for single payment methods
'additional_param_name' : 'additional_param_value'
}
)
print(widget.get_url())
using Paymentwall;
Paymentwall_Base.setApiType(Paymentwall_Base.API_GOODS);
Paymentwall_Base.setAppKey("YOUR_PROJECT_KEY");
Paymentwall_Base.setSecretKey("YOUR_SECRET_KEY");
List<Paymentwall_Product> productList = new List<Paymentwall_Product>();
Paymentwall_Product product = new Paymentwall_Product(
"product301", // ag_external_id
9.99, // amount
"USD", // currencyCode
"Gold Membership", // ag_name
Paymentwall_Product.TYPE_SUBSCRIPTION, // ag_type
1, // ag_period_length
Paymentwall_Product.PERIOD_TYPE_YEAR, // ag_period_type
true // ag_recurring
);
productList.Add(product);
Paymentwall_Widget widget = new Paymentwall_Widget(
"user40012",
"p1_1",
productList,
new Dictionary<string, string>() {
{
'email' => 'user@hostname.com',
'history[registration_date]' => 'registered_date_of_user',
'ps' => 'all', // Replace it with specific payment system short code for single payment methods
'additional_param_name' => 'additional_param_value'
}
}
);
Response.Write(widget.getUrl());
Handle pingback
Every time a user is billed, we will send a new pingback to inform you the customer has make a payment for his subscription. On your server side, put the following code as an online server interface to interact with our Pingback:
<?php
require_once('/path/to/paymentwall-php/lib/paymentwall.php');
Paymentwall_Config::getInstance()->set(array(
'api_type' => Paymentwall_Config::API_GOODS,
'public_key' => 'YOUR_APPLICATION_KEY', // available in your Paymentwall merchant area
'private_key' => 'YOUR_SECRET_KEY', // available in your Paymentwall merchant area
));
$pingback = new Paymentwall_Pingback($_GET, $_SERVER['REMOTE_ADDR']);
if ($pingback->validate(true)) {
$productId = $pingback->getProduct()->getId();
if ($pingback->isDeliverable()) {
// deliver the product
} else if ($pingback->isCancelable()) {
// withdraw the product
} else if ($pingback->isUnderReview()) {
// set "pending" as order status
}
echo 'OK'; // Paymentwall expects response to be OK, otherwise the pingback will be resent
} else {
echo $pingback->getErrorSummary();
}
?>
var Paymentwall = require('paymentwall');
Paymentwall.Configure(
Paymentwall.Base.API_GOODS,
'YOUR_PROJECT_KEY',
'YOUR_SECRET_KEY'
);
var pingback = new Paymentwall.Pingback("query data in pingback request", "ip address of pingback");
if (pingback.validate(true)) {
var productId = pingback.getProduct().getId();
if (pingback.isDeliverable()) {
// deliver the product
} else if (pingback.isCancelable()) {
// withdraw the product
}
console.log('OK'); // Paymentwall expects the string OK in response, otherwise the pingback will be resent
} else {
console.log(pingback.getErrorSummary());
}
import com.paymentwall.java.*;
Config.getInstance().setLocalApiType(Config.API_GOODS);
Config.getInstance().setPublicKey("YOUR_PROJECT_KEY");
Config.getInstance().setPrivateKey("YOUR_SECRET_KEY");
Pingback pingback = new Pingback(request.getParameterMap(), request.getRemoteAddr());
if (pingback.validate(true)) {
String goods = pingback.getProductId();
String userId = pingback.getUserId();
if (pingback.isDeliverable()) {
// deliver Product to user with userId
} else if (pingback.isCancelable()) {
// withdraw Product from user with userId
}
return "OK";
} else {
return pingback.getErrorSummary();
}
require 'paymentwall' # alternatively, require_relative '/path/to/paymentwall-ruby/lib/paymentwall.rb'
Paymentwall::Base::setApiType(Paymentwall::Base::API_GOODS)
Paymentwall::Base::setAppKey('YOUR_PROJECT_KEY') # available in your Paymentwall merchant area
Paymentwall::Base::setSecretKey('YOUR_SECRET_KEY') # available in your Paymentwall merchant area
pingback = Paymentwall::Pingback.new(request_get_params, request_ip_address)
if pingback.validate(true)
productId = pingback.getProduct().getId()
if pingback.isDeliverable()
# deliver the product
elsif pingback.isCancelable()
# withdraw the product
end
puts 'OK' # Paymentwall expects response to be OK, otherwise the pingback will be resent
else
puts pingback.getErrorSummary()
end
from paymentwall import *
Paymentwall.set_api_type(Paymentwall.API_GOODS)
Paymentwall.set_app_key('YOUR_PROJECT_KEY') # available in your merchant area
Paymentwall.set_secret_key('YOUR_SECRET_KEY') # available in your merchant area
pingback = Pingback({x:y for x, y in request.args.iteritems()}, request.remote_addr)
if pingback.validate(True):
product_id = pingback.get_product().get_id()
if pingback.is_deliverable():
# deliver the product
pass
elif pingback.is_cancelable():
# withdraw the product
pass
print('OK') # Paymentwall expects response to be OK, otherwise the pingback will be resent
else:
print(pingback.get_error_summary())
using Paymentwall;
Paymentwall_Base.setApiType(Paymentwall_Base.API_GOODS);
Paymentwall_Base.setAppKey("YOUR_PROJECT_KEY"); // available in your Paymentwall merchant area
Paymentwall_Base.setSecretKey("YOUR_SECRET_KEY"); // available in your Paymentwall merchant area
NameValueCollection parameters = Request.QueryString;
Paymentwall_Pingback pingback = new Paymentwall_Pingback(parameters, HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]);
if (pingback.validate(true))
{
string productId = pingback.getProduct().getId();
if (pingback.isDeliverable())
{
//deliver the product
}
else if (pingback.isCancelable())
{
//withdraw the product
}
Response.Write("OK"); // Paymentwall expects response to be OK, otherwise the pingback will be resent
}
else {
Response.Write(pingback.getErrorSummary());
}
A pingback request typically contains all the information for you to do the product delivery. As an addition,Paymentwall provides a series of reversed parameters as custom pingback parameters for specific needs, you can also add your own parameters as custom pingback parameter in order to implement parameter transmission.
Below is a sample with default format:
http://www.yourserver.com/pingback_path?uid=pwuser&goodsid=gold_membership&slength=3&speriod=day&type=0&ref=b1493048890&sign_version=2&sig=d94b23ba8585f29978706dd1b153ead9
After validating the pingback, your server is always expected to be able to proceed the delivery process and respond to it with only OK
in the body of response.
Other than successful subscription, there are other cases for pingback:
-
From the moment that the recurring was cancelled, Paymentwall will send a pingback with
type=12
. A subscription can be cancelled by either cancelled via submitting a ticket by using cancellation API or deactivating in Dashboard. -
A pingback with
type=13
is always generated when the recurring billing period is expired. -
If a user has insufficient funds or a payment fails for any other reason, Paymentwall will make 2 re-attempts (3 attempts in total) to charge the user. Payment Status API can report active status of the subscription with
date_next
for the date of next attempt. The subscription will be stopped if all of the attempts fail. And once it is stopped, it will no longer process an attempt to charge the user for the next scheduled payment. Paymentwall will send a pingback withtype=14
in this case , means the corresponding recurring billing payment failed.
Implement Delivery Confirmation API
Delivery Confirmation API allows you to notify Paymentwall about successful delivery of the purchased item(s) to the user. This information helps us resolve dispute cases and refund requests in the fastest and the most efficient manner.
This API expects secret key
to be sent as custom HTTPS header “X-ApiKey”.
<?php
require_once('path/to/lib/paymentwall.php');
Paymentwall_Config::getInstance()->set(array(
'private_key' => '[YOUR_SECRET_KEY]'
));
$delivery = new Paymentwall_GenerericApiObject('delivery');
$response = $delivery->post(array(
'payment_id' => 'b63400368',
'merchant_reference_id' => 'order_12345',
'type' => 'digital',
'status' => 'delivered',
'estimated_delivery_datetime' => '2015/01/15 15:00:00 +0300',
'estimated_update_datetime' => '2015/01/15 11:00:00 +0300',
'refundable' => true,
'details' => 'Item was delivered to the user account and via email',
'shipping_address[email]' => 'user@hostname.com',
'reason' => 'none',
'attachments[0]' => '@/usr/local/www/content/proof/b63400368/1.png',
'attachments[1]' => '@/usr/local/www/content/proof/b63400368/2.png',
));
if (isset($response['success'])) {
// delivery status is successfully saved
} elseif (isset($response['error'])) {
var_dump($response['error'], $response['notices']);
}
?>
'use strict';
var HttpAction = require('paymentwall/lib/HttpAction'),
util = require('util'),
querystring = require('querystring'),
ApiObject = require('paymentwall/lib/ApiObject'),
Paymentwall = require('paymentwall');
Paymentwall.Configure(
Paymentwall.Base.API_GOODS,
'YOUR_PROJECT_KEY ',
'YOUR_SECRET_KEY '
);
var api = new ApiObject();
api.createDeliveryRequest = function() {
var url = this.BRICK_BASE_URL;
var method = 'POST';
var post_options = this.createPostOptions(url, '/api/delivery', method);
return post_options;
};
var post_options = api.createDeliveryRequest();
var post_data = {
"payment_id" : "b63400368",
"merchant_reference_id" : "order_12345",
"type" : "digital",
"status" : "delivered",
"estimated_delivery_datetime" : "2018/08/23 15:00:00 +0300",
"estimated_update_datetime" : "2018/08/23 11:00:00 +0300",
"refundable" : true,
"details" : "Item was delivered to the user account and via email",
"shipping_address[email]" : "user@hostname.com",
"reason" : "none",
"attachments" : {}
};
post_data = querystring.stringify(post_data);
HttpAction.runAction(post_options, post_data, true, function(response) {
response = response.JSON_chunk;
if(response.success) {
// delivery status is successfully saved
} else if(response.error) {
console.log(response.error);
console.log(response.notices);
}
});
private void sendPost() {
HttpPost post = new HttpPost("https://api.paymentwall.com/api/delivery");
post.addHeader("X-ApiKey", “<PROJECT_SECRET_KEY>”);
// add request parameter, form parameters
List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
urlParameters.add(new BasicNameValuePair("payment_id", "b199488072"));
urlParameters.add(new BasicNameValuePair("merchant_reference_id", "w199488072"));
urlParameters.add(new BasicNameValuePair("type", "digital"));
urlParameters.add(new BasicNameValuePair("status", "order_placed"));
urlParameters.add(new BasicNameValuePair("estimated_delivery_datetime", "2015/01/15 15:00:00 +0300"));
urlParameters.add(new BasicNameValuePair("estimated_update_datetime", "2015/01/15 11:00:00 +0300"));
urlParameters.add(new BasicNameValuePair("refundable", "true"));
urlParameters.add(new BasicNameValuePair("details", "Item was delivered to the user account and via email"));
urlParameters.add(new BasicNameValuePair("shipping_address[email]", "test@paymentwall.com"));
urlParameters.add(new BasicNameValuePair("reason", "none"));
urlParameters.add(new BasicNameValuePair("attachments[0]", "@/usr/local/www/content/proof/b63400368/1.png"));
urlParameters.add(new BasicNameValuePair("attachments[1]", "@/usr/local/www/content/proof/b63400368/2.png"));
try {
post.setEntity(new UrlEncodedFormEntity(urlParameters));
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(post);
System.out.println(EntityUtils.toString(response.getEntity()));
}
catch(IOException ex) {
System.out.println(ex.getMessage());
}
}
curl https://api.paymentwall.com/api/delivery \
-H "X-ApiKey: [YOUR_SECRET_KEY]" \
-d "payment_id=b111260108" \
-d "merchant_reference_id=order_12345" \
-d "type=digital" \
-d "status=started" \
-d "is_test=1" \
-d "estimated_delivery_datetime=2017/01/15 15:04:55 +0500" \
-d "estimated_update_datetime=2017/01/16 15:04:55 +0500" \
-d "refundable=false" \
-d "details=Item was delivered to the user account and via email" \
-d "shipping_address[email]=test@paymentwall.com"
-d "reason=none"
Client-side action
In most cases, a complete payment experience is required wherein the end-users can be redirected back to the original application after making a payment.
There would be a link shown up in the payment successful page which is hosted by Paymentwall or our partners if you have success_url
presented as optional parameter while building the link of payment page. Your customers are then able to click it and go back to your applications.
Alternatively, client-side callback is suitable if you have additional requirements about client-side actions once a payment is finished.