KDPika Licensing Sign inStart free
API v1
Show all languages
Browse documentation

Choose a page in the navigation drawer.

KDPika Licensing · API v1 Advanced

Store service: shared decision contract

Store service: shared decision contract for the complete private reference application.

On this page
Updated 2026-10-18 UTCDownload Markdown

This complete shared PHP normalizer loads the eight provider traits shown in their respective guides. Download the whole application, not just this class.

What you will learn

Read and run the exact Store service: shared decision contract with the complete shared application, without exposing private credentials or claiming an unverified payment.

Complete source

normalize.phpphp
<?php
declare(strict_types=1);
// Verified current-provider state -> explicit store decisions; included by service.php.
// PHP 8.2+. A decision is not payment proof until authenticated provider API reads pass.

require_once __DIR__ . '/providers/stripe.php';
require_once __DIR__ . '/providers/paddle.php';
require_once __DIR__ . '/providers/lemon.php';
require_once __DIR__ . '/providers/gumroad.php';
require_once __DIR__ . '/providers/shopify.php';
require_once __DIR__ . '/providers/woocommerce.php';
require_once __DIR__ . '/providers/paypal.php';
require_once __DIR__ . '/providers/fastspring.php';

final class StoreNormalizer
{
    use StoreStripeNormalizer;
    use StorePaddleNormalizer;
    use StoreLemonNormalizer;
    use StoreGumroadNormalizer;
    use StoreShopifyNormalizer;
    use StoreWoocommerceNormalizer;
    use StorePaypalNormalizer;
    use StoreFastspringNormalizer;
    public function __construct(private array $config, private Closure $api, private Closure $lookup) {}

    private function get(string $path): array { return ($this->api)('GET', $path, null); }
    private function ref(mixed $id): string
    {
        storeRequire(is_string($id) || is_int($id), 'PROVIDER_REFERENCE_REQUIRED');
        $id = (string) $id;
        storeRequire($id !== '' && strlen($id) <= 190, 'PROVIDER_REFERENCE_REQUIRED');
        return rawurlencode($id);
    }
    private function mode(mixed $live): void
    {
        storeRequire(is_bool($live) && $live === ($this->config['environment'] === 'live'), 'PROVIDER_MODE_MISMATCH');
    }
    private function one(array $items): array
    {
        storeRequire(count($items) === 1 && is_array($items[0]) && (int) ($items[0]['quantity'] ?? 1) === 1, 'SINGLE_UNIT_LINE_REQUIRED');
        return $items[0];
    }
    private function paid(string $order, string $receipt, string $offer, string $email, string $name, bool $recurring, array $links = [], ?string $initialKey = null, bool $initial = true): array
    {
        storeRequire(isset($this->config['offer_map'][$offer]) && filter_var($email, FILTER_VALIDATE_EMAIL) !== false, 'UNMAPPED_OFFER_OR_RECIPIENT');
        return ['order' => $order, 'receipt' => $receipt, 'offer' => $this->config['offer_map'][$offer],
            'customer' => ['email' => $email, 'name' => $name], 'state' => 'paid', 'recurring' => $recurring,
            'links' => array_values(array_unique([$order, $receipt, ...$links])), 'initial_key' => $initialKey, 'initial' => $initial];
    }
    private function negative(string $reference, string $receipt, string $state): array
    {
        $order = ($this->lookup)($reference);
        return ['order' => is_string($order) ? $order : $reference, 'receipt' => $receipt, 'state' => $state,
            'links' => [], 'unmapped_reference' => is_string($order) ? null : $reference];
    }
    public function normalize(array $authenticated, array $headers): array
    {
        return match ($this->config['provider']) {
            'stripe' => $this->stripe($authenticated['native']),
            'paddle' => $this->paddle($authenticated['native']),
            'lemon-squeezy' => $this->lemon($authenticated['native']),
            'gumroad' => $this->gumroad($authenticated['native']),
            'shopify' => $this->shopify($authenticated['native'], $headers),
            'woocommerce' => $this->woocommerce($authenticated['native'], $headers),
            'paypal' => $this->paypal($authenticated['native']),
            'fastspring' => $this->fastspring($authenticated['native']),
            default => throw new StoreRefusal('UNSUPPORTED_PROVIDER')
        };
    }
}

Decision boundary

A paid decision carries an approved internal offer, authenticated recipient, stable order/payment receipt and initial-versus-renewal evidence. Negative decisions resolve the existing order through verified reference links. Unmatched adverse references are remembered for review. These normalized records are private application data, not a new KDPika public API, payment proof by themselves, or a universal provider schema.

Prerequisites

Complete the private service installation. Keep all files from the exact source archive together, use PHP 8.2+ with its documented extensions and start with an isolated Sandbox product/key. A source fragment alone is not the application.

Expected result

Only a confirmed provider purchase authorizes a licensing effect. A durable HTTP receipt, completed licensing operation and SMTP acceptance are separate outcomes. Inspect aggregate worker states and verify the resulting license with the same buyer email; no full key appears in public replies.

Common mistakes

Do not deploy one module without its archive dependencies, place configuration under the web root, skip provider API verification or clear a durable ledger to bypass review. Optional Node/Python ingress still requires the PHP worker.

Security notes

Use least-privilege private provider and product-scoped licensing credentials, verified HTTPS, owner-only persistent state and separate secrets per account/environment. Never trust browser payment claims. Preserve held operations after an unknown result and reconcile before another write; exactly-once behavior across systems is not guaranteed.

Troubleshooting

For a receipt refusal check raw-byte preservation, the matching endpoint secret/account/mode, server clock where applicable and the private bootstrap layout. For REVIEW/IN_FLIGHT inspect provider state, safe private error codes and the licensing ledger before retrying. Missing permissions, recipient data or an initial mapping are not successful payments. For mail verify the separate SMTP ledger and provider delivery evidence. Do not post raw events, keys or SQLite files publicly.

Next steps

Run the selected provider Sandbox procedure, then test private key email delivery. Review supported recipe limitations before enabling Live.

Was this page helpful?

Choose an answer

Do not include personal data, license keys or credentials. Retained for up to 30 days. This is not a support inbox.

Search documentation

Arrow keys to move, Enter to open, Escape to close. Search stays in your browser.

Cookie settings