XF 2.3 can someone explain to me how purchasables work

can someone help me understand the purchasable system I couldn't find any docs online anywhere, here is my code

my purchase form

Code:
<xf:js src="xf/payment.js" min="1" />

<div class="block">



    <div class="block-container">

        <div class="block-body">

            <xf:form action="{{link('purchase')}}" method="post">

                <xf:formrow rowtype="" label="Thread Title">

                    {$thread.title}

                </xf:formrow>

                <xf:formrow rowtype="" label="Details">

                    details.

                </xf:formrow>

                <xf:formrow rowtype="" label="Cost">

                    ${$order.cost_amount}

                </xf:formrow>

                <div class="inputGroup">

                    <xf:button type="submit" icon="purchase">{{ phrase('Purchase') }}</xf:button>

                </div>

            </xf:form>

        </div>

    </div>

</div>



the form above should ideally link to this purchasable when I click purchase? I'm just trying to understand the flow I keep getting invalid purchase request

Code:
<?php

namespace infdevspecialoffers\Purchasables;

use XF\Entity\PaymentProfile;
use XF\Entity\User;
use XF\Payment\CallbackState;
use XF\Purchasable\AbstractPurchasable;
use XF\Purchasable\Purchase;
use XF\Http\Request;


class ThreadBump extends AbstractPurchasable
{
    public function __construct(\XF\App $app)
    {
        parent::__construct($app); // pass $app to AbstractPurchasable
        // any additional setup here
    }
    public function getPurchasableTypeId()
    {
        return 'thread_bump';
    }

    public function getTitle()
    {
        return \XF::phrase('pay_to_post_thread');
    }

    public function getPurchaseFromRequest(Request $request, User $purchaser, &$error = null)
    {
        $profileId = $request->filter('payment_profile_id', 'uint');
        $threadId = $request->filter('thread_id', 'uint');

        $paymentProfile = \XF::app()->em()->find(PaymentProfile::class, $profileId);

        // Create the purchase request object dynamically

        $profileFinder = \XF::finder('XF:PaymentProfile');
        $paymentProfile = $profileFinder->where('provider_id', 'paypalrest')->fetchOne();

        return [
            'title' => \XF::phrase('thread_bump'),
            'description' => 'This is a thread bump',
            'purchasableTypeId' => 'thread_bump',
            'purchasable_id' => 117,
            'cost' => 10,
            'currency' => 'USD',
            'recurring' => false,
            'purchaser' => $purchaser,
            'paymentProfile' => $paymentProfile
        ];

        $threadFinder = \XF::finder('XF:Thread');
        $thread = $threadFinder->where('thread_id', $threadId);

        return $this->getPurchaseObject($paymentProfile, $thread, $purchaser);
    }

    public function completePurchase(CallbackState $state)
    {
        // ✅ this is called when PayPal confirms
        if ($state->paymentResult->isCompleted()) {
            // do your granting logic
            \XF::logError("Purchase completed for user");
            return true;
        }

        return false;
    }

    public function getPurchasableFromExtraData(array $extraData)
    {
        return null;
    }

    public function getPurchaseFromExtraData(array $extraData, PaymentProfile $paymentProfile, User $purchaser, &$error = null)
    {
        return null;
    }

    public function getPurchaseObject(PaymentProfile $paymentProfile, $purchasable, User $purchaser)
    {
        $purchase = new Purchase();

        $purchase->title = \XF::phrase('thread_bump');
        $purchase->description = 'fdgdfgdfgdsfgdfg';
        $purchase->cost = 6;
        $purchase->currency = 'USD';
        $purchase->recurring = false;
        $purchase->lengthAmount = $purchasable->length_amount;
        $purchase->lengthUnit = $purchasable->length_unit;
        $purchase->purchaser = $purchaser;
        $purchase->paymentProfile = $paymentProfile;
        $purchase->purchasableTypeId = $this->purchasableTypeId;
        $purchase->purchasableId = $purchasable->user_upgrade_id;
        $purchase->purchasableTitle = $purchasable->title;

        $router = \XF::app()->router('public');

        $purchase->returnUrl = $router->buildLink('canonical:account/upgrade-purchase');
        $purchase->updateUrl = $router->buildLink('canonical:account/upgrade-updated');
        $purchase->cancelUrl = $router->buildLink('canonical:account/upgrades');

        return $purchase;
    }

    public function reversePurchase(CallbackState $state)
    {
        return null;
    }

    public function getPurchasablesByProfileId($profileId)
    {
        return null;
    }
}
 
Back
Top Bottom