Malum Checkout Form
This guide shows how to integrate a simple checkout form with the Malum Payment API. It now supports optional product presentation fields that render on the Malum checkout page, along with an additional product signature for integrity.
Prerequisites
Malum Business ID
Malum Private Key
Ability to edit your site's PHP backend
Core Flow
Collect the required parameters.
Create a signed message to authenticate the payment request.
Optionally include product presentation fields and create a signed product hash.
Post a form to Malum to start the checkout.
Handle the webhook notification and user redirects.
Required Parameters
amountnumeric string or integer representing the price in major currency unitscurrencyISO 4217 code such asUSD,EURwebhook_urlURL on your server to receive payment resultssuccess_urlwhere to redirect the buyer after a successful paymentcancel_urlwhere to redirect if the buyer cancels or the payment failsbusiness_idyour Malum business identifiercustomer_emailbuyer emailbuyer_pays_fees0or1metadataoptional opaque string you receive backsigned_messageauthentication signature described below
Optional Product Presentation Parameters
These fields allow Malum to display product details on the hosted checkout.
product_markdownfree form Markdown that describes the productproduct_titleshort plain titleproduct_imageabsolute URL to a product imageproduct_descriptionshort plain summaryproduct_linkabsolute URL to view the product on your sitesigned_product_hashintegrity signature for the five product fields
Signatures
Payment request signature
$signed_message = md5(
$amount
. $currency
. $webhook_url
. $success_url
. $cancel_url
. $customer_email
. $buyer_pays_fees
. $metadata
. $business_id
. $private_key
);Product presentation signature
$signed_product_hash = md5(
$product_markdown
. $product_title
. $product_image
. $product_description
. $product_link
. $private_key
);Quick Test Values
Use these while testing. Replace with real values for production.
<?php
// Test values
$amount = 1;
$currency = 'USD';
$webhook_url = 'https://example.com';
$success_url = 'https://example.com';
$cancel_url = 'https://example.com';
$business_id = 'TEST';
$private_key = 'sec_TEST';
$metadata = '';
$customer_email = '[email protected]';
$buyer_pays_fees = 0;
// Sign the payment request
$signed_message = md5(
$amount . $currency . $webhook_url . $success_url . $cancel_url .
$customer_email . $buyer_pays_fees . $metadata . $business_id . $private_key
);
// Optional product presentation
$product_markdown = 'This is a test product.';
$product_title = 'Test Product';
$product_image = 'https://via.placeholder.com/150';
$product_description = 'This is a description for the test product.';
$product_link = 'https://example.com/product';
// Sign the product block
$signed_product_hash = md5(
$product_markdown . $product_title . $product_image .
$product_description . $product_link . $private_key
);
?>Full PHP Example
This example posts the required fields and the optional product fields. Inputs that can contain special characters are HTML escaped.
<?php
// Values from the Quick Test Values section above
// ...
?>
<form method="POST" action="https://malum.co/api/v3/checkout/form">
<input type="hidden" name="amount" value="<?php echo $amount; ?>">
<input type="hidden" name="currency" value="<?php echo $currency; ?>">
<input type="hidden" name="webhook_url" value="<?php echo htmlspecialchars($webhook_url, ENT_QUOTES); ?>">
<input type="hidden" name="success_url" value="<?php echo htmlspecialchars($success_url, ENT_QUOTES); ?>">
<input type="hidden" name="cancel_url" value="<?php echo htmlspecialchars($cancel_url, ENT_QUOTES); ?>">
<input type="hidden" name="customer_email" value="<?php echo htmlspecialchars($customer_email, ENT_QUOTES); ?>">
<input type="hidden" name="buyer_pays_fees" value="<?php echo (int)$buyer_pays_fees; ?>">
<input type="hidden" name="metadata" value="<?php echo htmlspecialchars($metadata, ENT_QUOTES); ?>">
<input type="hidden" name="business_id" value="<?php echo htmlspecialchars($business_id, ENT_QUOTES); ?>">
<input type="hidden" name="signed_message" value="<?php echo $signed_message; ?>">
<!-- Optional product presentation fields -->
<input type="hidden" name="product_markdown" value="<?php echo htmlspecialchars($product_markdown, ENT_QUOTES); ?>">
<input type="hidden" name="product_title" value="<?php echo htmlspecialchars($product_title, ENT_QUOTES); ?>">
<input type="hidden" name="product_image" value="<?php echo htmlspecialchars($product_image, ENT_QUOTES); ?>">
<input type="hidden" name="product_description" value="<?php echo htmlspecialchars($product_description, ENT_QUOTES); ?>">
<input type="hidden" name="product_link" value="<?php echo htmlspecialchars($product_link, ENT_QUOTES); ?>">
<input type="hidden" name="signed_product_hash" value="<?php echo $signed_product_hash; ?>">
<button type="submit">Pay Now</button>
</form>Webhook Handling
Your webhook_url will receive a server to server POST from Malum with the payment status and identifiers. On your endpoint you should:
Read the payload and Malum signature header if provided.
Verify the payload authenticity according to Malum documentation, for example by reconstructing a signature or by fetching the payment status from Malum using your credentials.
Validate that the
amount,currency,metadata, and any product details match what you expect for the order.Mark the order as paid only after successful verification and idempotently process repeated notifications.
Redirect URLs
success_urlshown after a successful paymentcancel_urlshown if the buyer cancels or payment fails
Security Notes
Never expose your
private_keyto the client side. Keep it in server side configuration.Always rebuild
signed_messageand, if used,signed_product_hashon the server.Escape any user supplied values placed in hidden inputs using
htmlspecialcharsas shown above.Treat webhook as the source of truth for fulfillment. The browser redirect can be spoofed.
Troubleshooting
Signature mismatch. Ensure parameter order in your hashes matches the examples exactly and that you include
buyer_pays_feesin the payment signature.Wrong redirects. Confirm the exact values of
success_urlandcancel_urlthat you hashed are the same ones you post.Duplicate field issues. Include
signed_product_hashonly once in the form.
Production Checklist
Replace all test values with real ones
Use HTTPS everywhere
Log request IDs and verify signatures
Make webhook processing idempotent
Store and show your order number in
metadatafor easier reconciliation
Last updated