Stripe java.lang.IllegalArgumentException: Invalid Setup Intent client secret: - stripe-payments

E/App: Got error:
java.lang.IllegalArgumentException: Invalid Setup Intent client secret: pi_3LtYhHAZTS98jCaY3NVtpCAW_secret_yUvzN3ytPIexEVJGtOzvQCSOM
at com.stripe.android.model.SetupIntent$ClientSecret.
Here i added my all codes of java class and php api for the test but i got this above error on the same code so plz do my help
paymentSheet = new PaymentSheet(this, this::onPaymentSheetResult);
Fuel.INSTANCE.post("my api link",null).responseString(new Handler<String>() {
#Override
public void success(String s) {
try {
final JSONObject result = new JSONObject(s);
customerConfig = new PaymentSheet.CustomerConfiguration(
result.getString("customer"),
result.getString("ephemeralKey")
);
paymentIntentClientSecret = result.getString("paymentIntent");
PaymentConfiguration.init(getContext().getApplicationContext(), result.getString("publishableKey"));
} catch (JSONException e) { /* handle error */ }
}
#Override
public void failure(#NotNull FuelError fuelError) { /* handle error */ }
});
if(isset($action) && $action='pay' && isset($amount)){
\Stripe\Stripe::setApiKey('sk_test_qrdTKAz2kaeZEPZHK2OdrNpn');
// Use an existing Customer ID if this is a returning customer.
$userid = $_GET['u'];
$query_usr = "SELECT * FROM tbl_user WHERE user_id = '$userid'";
$r_usr = $conn->query($query_usr);
$user = $r_usr->fetch_assoc();
if($user['stripe_customer'] == NULL){
$customer = \Stripe\Customer::create([
'name' => $user['user_name'],
'email' => $user['user_email']
]);
$customer_id = $customer->id;
$conn->query("UPDATE tbl_user SET stripe_customer = '$customer_id' WHERE user_id = $userid");
}
else{
$customer_id = $user['stripe_customer'];
}
$ephemeralKey = \Stripe\EphemeralKey::create(
[
'customer' => $customer_id,
],
[
'stripe_version' => '2020-08-27',
]);
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => 1099,
'currency' => 'cad',
'customer' => $customer_id,
'automatic_payment_methods' => [
'enabled' => 'true',
],
]);
$si = \Stripe\SetupIntent::create([
'customer' => $customer_id
]);
$response['paymentIntent'] = $paymentIntent->client_secret;
$response['setupIntent'] = $si->client_secret;
$response['ephemeralKey']= $ephemeralKey->secret;
$response['customer']= $customer_id;
$response['publishableKey'] = 'pk_test_0aSL6prHXE16hsokTWYKk8Gz';
echo json_encode($response);
// return $response->withJson([
// 'paymentIntent' => $paymentIntent->client_secret,
// 'ephemeralKey' => $ephemeralKey->secret,
// 'customer' => $customer_id,
// 'publishableKey' => 'pk_live_8mBL0Iji2oVDpgmBAVwPiicC'
// ])->withStatus(200);
}
else
{
$response['error']=TRUE;
$response['error_msg']="Required Parameters are missing";
echo json_encode($response);
}
private void presentPaymentSheet() {
final PaymentSheet.Configuration configuration = new PaymentSheet.Configuration.Builder("!2min, Inc.")
.customer(customerConfig)
// Set `allowsDelayedPaymentMethods` to true if your business can handle payment methods
// that complete payment after a delay, like SEPA Debit and Sofort.
.allowsDelayedPaymentMethods(true).build();
paymentSheet.presentWithSetupIntent(
paymentIntentClientSecret,
configuration
);
}
private void onPaymentSheetResult(final PaymentSheetResult paymentSheetResult) {
if (paymentSheetResult instanceof PaymentSheetResult.Canceled) {
Log.d("cantag","Canceled");
} else if (paymentSheetResult instanceof PaymentSheetResult.Failed) {
Log.e("App", "Got error: ", ((PaymentSheetResult.Failed) paymentSheetResult).getError());
} else if (paymentSheetResult instanceof PaymentSheetResult.Completed) {
// Display for example, an order confirmation screen
Log.d("tcmp","Completed");
}
}

Based off the error message, you should be passing in a SetupIntent's client_secret - a SetupIntent id starts with si_ . However, you're passing in a PaymentIntent's client_secret - a PaymentIntent id starts with pi_
If you intend to use a PaymentIntent, you should follow the guide here : https://stripe.com/docs/payments/accept-a-payment?platform=android
If you intend to use a SetupIntent, you should follow the guide here : https://stripe.com/docs/payments/save-and-reuse?platform=android&ui=payment-sheet

Related

How to pass additional params to Stripe Custom Flow?

Following Stripes official tutorial to create payment form with apple pay support: https://stripe.com/docs/payments/quickstart#collect-billing-address-details
But on thank you page, i need to know: user email and stripe customer id. How i can pass these parameters to thank you page?
My code for prepare.php:
$productID = $request['items'][0]['id'];
$customer = \Stripe\Customer::create();
$paymentIntent = \Stripe\PaymentIntent::create([
'customer' => $customer->id,
'setup_future_usage' => 'off_session',
'amount' => config('app.PRICE_TNT6WEEK') * 100, //6week program
'currency' => 'usd',
'automatic_payment_methods' => [
'enabled' => true,
],
'description' => $productID,
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
\Log::debug(['<pre>'.print_r($request->toArray(), true).'</pre>', $productID, $output]);
return response()->json($output);
My checkout.js:
// This is your test publishable API key.
const stripe = Stripe('{{config('app.STRIPE_KEY')}}');
// The items the customer wants to buy
const items = [{ id: "{{$productName}}"}];
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("{{route('prepare.product', [$slug, $slugVersion])}}", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items }),
}).then((r) => r.json());
elements = stripe.elements({ clientSecret });
const paymentElementOptions = {
layout: "tabs",
};
const paymentElement = elements.create("payment", paymentElementOptions);
paymentElement.mount("#payment-element");
// Create and mount the Address Element in billing mode
const addressElement = elements.create("address", {
mode: "billing",
defaultValues: {
name: 'Your Full Name...',
address: {
line1: 'Address...',
city: 'City...',
state: 'CA',
postal_code: '',
country: 'US',
},
},
fields: {
phone: 'always',
},
});
addressElement.mount("#address-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
//--- There we should make an additional ajax request with user data
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "{{route('upsell.show', [$slug, $slugVersion])}}",
payment_method_data: {
billing_details: {
name: document.getElementById("full_name").value,
email: document.getElementById("email").value,
phone: document.getElementById("phone").value,
}
},
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occurred.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
// ------- UI helpers -------
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
}
After collecting billing details with either the Address Element, or supplying the payment_method_data[billing_details], that information will be available on Payment Method billing_details (API ref). You can get this by either retrieving the Payment Method, or by retrieving the associated Payment Intent and using expansion to include the full Payment Method object with expand[]=payment_method

Lambda keeps giving the same errors

I've been trying to figure out an issue with an amazon lex chatbot I've been building all day. The node.js lambda keeps giving the same errors and for the life of me I can't figure out why. The chatbot is called BestiaryProject, the Intent is MonsterSearch, and the two slots are monsterType and monsterName.
The error I get is as follows:
"a9fba2d2-ec22-4092-b332-53ae16acb345 ERROR Invoke Error {"errorType":"Error","errorMessage":"Intent with name undefined not supported","stack":["Error: Intent with name undefined not supported"," at dispatch (/var/task/index.js:168:11)"," at Runtime.exports.handler (/var/task/index.js:189:9)"," at Runtime.handleOnce (file:///var/runtime/index.mjs:548:29)"]}"
here is the code:
function elicitSlot(sessionAttributes, intentName, slots, slotToElicit, message) {
return {
sessionAttributes,
dialogAction: {
type: 'ElicitSlot',
intentName,
slots,
slotToElicit,
message,
},
};
}
function close(sessionAttributes, fulfillmentState, message) {
return {
sessionAttributes,
dialogAction: {
type: 'Close',
fulfillmentState,
message,
},
};
}
function delegate(sessionAttributes, slots) {
return {
sessionAttributes,
dialogAction: {
type: 'Delegate',
slots,
},
};
}
// ---------------- Helper Functions --------------------------------------------------
function buildValidationResult(isValid, violatedSlot, messageContent) {
if (messageContent == null) {
return {
isValid,
violatedSlot,
};
}
return {
isValid,
violatedSlot,
message: { contentType: 'PlainText', content: messageContent },
};
}
let page = 0;
//function to validate user inputs and return page number
function validateMonsters(monsterType, monsterName, time) {
const monsterTypes = ['dragon', 'fiend', 'celestial', 'giant', 'magical beast', 'fey', 'undead', 'elemental'];
if (monsterType && monsterTypes.indexOf(monsterType) === -1) {
return buildValidationResult(false, 'monsterType', `I do not know what ${monsterType} is, would you like to try a different one?`);
}
const monsterNames = ['vampire', 'troll', 'fire giant', 'wyvern', 'true dragon', 'angel', 'azata', 'chimera', 'manticore', 'unicorn', 'dryad', 'ghoul', 'fire elemental', 'water elemental', 'balor', 'succubus'];
if (monsterName && monsterNames.indexOf(monsterName) === -1) {
return buildValidationResult(false, 'monsterName', `I've never heard of ${monsterName}, would you like to try a different one?`);
}
const pages = [9, 23, 44, 58, 68, 90, 116, 124, 125, 146, 148, 199, 268, 269, 270, 282];
if (monsterName == 'angel') {
page = pages[0];
}
if (monsterName == 'azata') {
page = pages[1];
}
if (monsterName == 'chimera') {
page = pages[2];
}
if (monsterName == 'balor') {
page = pages[3];
}
if (monsterName == 'succubus') {
page = pages[4];
}
if (monsterName == 'true dragon') {
page = pages[5];
}
if (monsterName == 'dryad') {
page = pages[6];
}
if (monsterName == 'fire elemental') {
page = pages[7];
}
if (monsterName == 'water elemental') {
page = pages[8];
}
if (monsterName == 'ghoul') {
page = pages[9];
}
if (monsterName == 'fire giant') {
page = pages[10];
}
if (monsterName == 'manticore') {
page = pages[11];
}
if (monsterName == 'troll') {
page = pages[12];
}
if (monsterName == 'unicorn') {
page = pages[13];
}
if (monsterName == 'vampire') {
page = pages[14];
}
if (monsterName == 'wyvern') {
page = pages[15];
}
return buildValidationResult(true, null, null);
}
// --------------- Functions that control the bot's behavior -----------------------
/**
* Performs dialog management and fulfillment for finding your monster.
*
* Beyond fulfillment, the implementation of this intent demonstrates the use of the elicitSlot dialog action
* in slot validation and re-prompting.
*
*/
function searchMonsters(intentRequest, callback) {
const monsterType = intentRequest.currentIntent.slots.monsterType;
const monsterName = intentRequest.currentIntent.slots.monsterName;
const source = intentRequest.invocationSource;
if (source === 'DialogCodeHook') {
// Perform basic validation on the supplied input slots. Use the elicitSlot dialog action to re-prompt for the first violation detected.
const slots = intentRequest.currentIntent.slots;
const validationResult = validateMonsters(monsterType, monsterName, page);
if (!validationResult.isValid) {
slots[`${validationResult.violatedSlot}`] = null;
callback(elicitSlot(intentRequest.sessionAttributes, intentRequest.currentIntent.name, slots, validationResult.violatedSlot, validationResult.message));
return;
}
}
// give user info on the monster, and rely on the goodbye message of the bot to define the message to the end user. In a real bot, this would likely involve a call to a backend service.
callback(close(intentRequest.sessionAttributes, 'Fulfilled',
{ contentType: 'PlainText', content: `Thanks, the monster ${monsterName} of type ${monsterType} can be found on page ${page}` }));
}
// --------------- Intents -----------------------
/**
* Called when the user specifies an intent for this skill.
*/
function dispatch(intentRequest, callback) {
console.log(`dispatch userId=${intentRequest.userId}, intentName=${intentRequest.currentIntent.name}`);
const intentName = intentRequest.currentIntent.name;
// Dispatch to your skill's intent handlers
if (intentName === 'MonsterSearch') {
return searchMonsters(intentRequest, callback);
}
throw new Error(`Intent with name ${intentName} not supported`);
}
// --------------- Main handler -----------------------
// Route the incoming request based on intent.
// The JSON body of the request is provided in the event slot.
exports.handler = (event, context, callback) => {
try {
console.log(`event.bot.name=${event.bot.name}`);
/**
* Uncomment this if statement and populate with your Lex bot name and / or version as
* a sanity check to prevent invoking this Lambda function from an undesired Lex bot or
* bot version.
*/
if (event.bot.name !== 'BestiaryProject') {
callback('Invalid Bot Name');
}
dispatch(event, (response) => callback(null, response));
} catch (err) {
callback(err);
}
};```
Quite simply what the error is telling you is that intentRequest.currentIntent.name is not resulting in a valid value; in fact you're getting back a null.
Try logging the entire intentRequest object at the start of your dispatch method to view the data that's passed through to your method and ensure you're looking in the right place for the intent name.

Saving and Reading UserState in Botframework v4

Hello I'm having a hard time dealing with UserStates in MSBF
Here's the setup of the dialogBot.ts
export class DialogBot extends ActivityHandler {
private conversationState: BotState;
private userState: BotState;
private dialog: Dialog;
private dialogState: StatePropertyAccessor<DialogState>;
/**
*
* #param {BotState} conversationState
* #param {BotState} userState
* #param {Dialog} dialog
*/
constructor(
conversationState: BotState,
userState: BotState,
dialog: Dialog
) {
super();
if (!conversationState) {
throw new Error(
'[DialogBot]: Missing parameter. conversationState is required'
);
}
if (!userState) {
throw new Error('[DialogBot]: Missing parameter. userState is required');
}
if (!dialog) {
throw new Error('[DialogBot]: Missing parameter. dialog is required');
}
this.conversationState = conversationState as ConversationState;
this.userState = userState as UserState;
this.dialog = dialog;
this.dialogState =
this.conversationState.createProperty<DialogState>('DialogState');
this.onMessage(async (context, next) => {
console.log('Running dialog with Message Activity.');
// Run the Dialog with the new message Activity.
await (this.dialog as MainDialog).run(context, this.dialogState);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onDialog(async (context, next) => {
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
In the MainDialog.ts I'm fetching a user from the database based on the userID passed on and if it fetches anything it should be saved in the UserState.
mainDialog.ts
export class MainDialog extends CancelAndHelpDialog {
private userProfileAccessor: StatePropertyAccessor<any>;
userState: UserState;
constructor(
bookingDialog: BookingDialog,
userState: UserState,
conversationState: ConversationState
) {
super('MainDialog');
// DECLARE DIALOGS HERE
const createJobOrderDialog = new CreateJobOrderDialog(
'createJobOrderDialog'
);
const checkJobOrderStatusDialog = new CheckJobOrderStatusDialog(
'checkJobOrderStatusDialog'
);
const accountSetupDialog = new AccountSetupDialog(
'accountSetupDialog',
userState
);
this.userProfileAccessor = userState.createProperty('userProfile');
this.userState = userState;
// Define the main dialog and its related components.
// This is a sample "book a flight" dialog.
this.addDialog(new TextPrompt('TextPrompt'));
this.addDialog(bookingDialog);
this.addDialog(createJobOrderDialog);
this.addDialog(checkJobOrderStatusDialog);
this.addDialog(accountSetupDialog);
this.addDialog(
new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.accountSetupStep.bind(this),
this.introStep.bind(this),
this.actStep.bind(this),
this.finalStep.bind(this)
])
);
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a DialogContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {TurnContext} context
*/
public async run(
context: TurnContext,
accessor: StatePropertyAccessor<DialogState>
) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(context);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
private async accountSetupStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
stepContext.context.activity.from.id = '*******************';
userProfile.isHandover = false;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
const result = await userService.getUser(
stepContext.context.activity.from.id
);
console.log(result);
if (Object.keys(result).length === 0) {
return await stepContext.beginDialog('accountSetupDialog');
} else {
userProfile.user = result;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
return await stepContext.next();
}
}
private async introStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
console.log('INTRO STEP USERPROFILE', userProfile);
await stepContext.context.sendActivities([
{
type: 'message',
text: `Hi ${userProfile.user.first_name}, welcome to Podmachine. Let us take care of the dirty stuff so you can sound like a Pro!`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: 'To start, you need to submit a job order.'
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `So what's a job order? It's basically sending a request to edit (1) one raw episode audio file to Podmachine team. We'll handle the rest. `
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `Since you're part of the early access users (Yay!), you're entitled to (1) one free job order / edit. Go ahead and click "Create New Job order."`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 }
]);
const messageText = (stepContext.options as any).restartMsg
? (stepContext.options as any).restartMsg
: `Please take note that once you submit your job order, Podmachine team will review it first. Make sure all the details you put in your job order are correct. It will be our basis when we do the edits. Thank you!`;
const promptMessage = MessageFactory.suggestedActions(
[
'Create New Job Order',
'Check Status',
'Chat with Team',
'Subscribe Now'
],
messageText
);
return await stepContext.prompt('TextPrompt', {
prompt: promptMessage
});
}
/**
* Second step in the waterall. This will use LUIS to attempt to extract the origin, destination and travel dates.
* Then, it hands off to the bookingDialog child dialog to collect any remaining details.
*/
private async actStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
// const bookingDetails = new BookingDetails();
const userProfile = await this.userProfileAccessor.get(stepContext.context, {});
console.log('USER PROFILE ACT STEP', userProfile);
switch (stepContext.result) {
case 'Create New Job Order':
return await stepContext.beginDialog('createJobOrderDialog');
break;
case 'Check Status':
return await stepContext.beginDialog('checkJobOrderStatusDialog');
break;
case 'Chat with Team':
userProfile.isHandover = true;
await stepContext.context.sendActivity(
`Hi ${userProfile.user.first_name}, we're glad to assist you. Please type your concern below. A Podmachine associate will getback to you within 3-5 minutes. Thank you for your patience.`
);
await this.userProfileAccessor.set(stepContext.context, userProfile);
return await stepContext.endDialog();
break;
case 'Upgrade Now':
await stepContext.context.sendActivity(
`Redirecting to Upgrade Now page...`
);
return await stepContext.endDialog();
break;
case 'Schedule a Checkpoint Meeting':
await stepContext.context.sendActivity(`Feature in progress...`);
return await stepContext.endDialog();
break;
default:
break;
}
return await stepContext.next();
// return await stepContext.beginDialog('bookingDialog', bookingDetails);
}
I can see the saved user details in the introStep but when it comes to the actStep I no longer see the value and it comes out undefined. Can you help me with implementing UserState because I'm not sure if I'm doing it correctly by loading it, the samples from github is not as clear.
USER PROFILE ACT STEP {}
[onTurnError] unhandled error: DialogContextError: Cannot read properties of undefined (reading 'first_name')
Looks like your bot aren't storing the state, so it can't recover it on the next turn.
Are you setting somewhere the storage your bot are using?
Check this doc on how to use storages:
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript

Yii2 pagination doesn't change page

My pager doesn't change to chosen page. When I click to the next page button it reloads the page. I also tried without Pjax container but the result was the same. I red the other posts but still can't figure it out. What I am doing is :
$categoriesProvider = new ActiveDataProvider([
'query' => Page::find()->where(['enable' => 1, 'id_in' => $page_id])->orderBy('sort ASC'),
'pagination' => [
'pageSize' => 1,
'route' => "/".Yii::$app->getRequest()->getQueryParam('page')
]
]);
return $this->render('index', [
'categoriesProvider' => $categoriesProvider
]);
and in the view:
<?php Pjax::begin() ?>
<div class="content-area col-md-8 col-sm-6 col-xs-12 no-padding">
<?php if(!empty($categoriesProvider)){
echo ListView::widget([
'dataProvider' => $categoriesProvider,
'layout' => "{summary}\n{items}
\n<nav class='post-pagination col-md-12 col-sm-12 col-xs-12'>
{pager}
</nav>",
'itemView' => function($model, $key, $index, $widget){
return $this->render('_category', [
'model' => $model,
]);
},
'pager' => [
'nextPageLabel' => ">>",
'prevPageLabel' => "<<",
'maxButtonCount' => 5,
'options' => [
'tag' => 'ul',
'class' => 'pagination',
]
]
]);
}?>
</div><!-- Content Area /- -->
<?php Pjax::end(); ?>
URL configuration:
<?php
namespace frontend\controllers;
use backend\models\News;
use backend\models\Page;
use backend\models\Product;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class SplitterController extends Controller
{
public function actionManageRequest()
{
$param_page = \Yii::$app->getRequest()->getQueryParam('page');
$param_category = \Yii::$app->getRequest()->getQueryParam('category');
$param_product = \Yii::$app->getRequest()->getQueryParam('product');
$page = $this->getModel($param_page, new Page());
//If page is existing page model go forward
if(!empty($page)){
$controller = $this->getController($page->view);
if($this->checkId($param_page) === 'news'){
$model = new News();
}else{
$model = new Page();
}
$category = $this->getModel($param_category, $model);
if(!empty($category)){
$product = $this->getModel($param_product, new Product());
//If product is existing product model - go forward to single view
if(!empty($product)){
$this->registerTags($product);
try{
return \Yii::$app->runAction("$controller/view");
}
catch (\Throwable $e){
throw new NotFoundHttpException('Page not found',404);
}
}
//If product is not empty and product don't - throw exception
if(!empty($param_product) && $product === null){
throw new NotFoundHttpException('Page out found', 404);
}
$this->registerTags($category);
//If page model is news page - render news single view
if($this->checkId($param_page) === 'news'){
return \Yii::$app->runAction("$controller/multi-view");
}
try{
return \Yii::$app->runAction("$controller/multi-view");
}
catch (\Throwable $e){
throw new NotFoundHttpException('Page not found',404);
}
}
$this->registerTags($page);
//If category is not empty but no such page found - throw exception
if(!empty($param_category) && $category === null){
throw new NotFoundHttpException('Page not found', 404);
}
return \Yii::$app->runAction($page->view);
}
//If page is not empty but no such page found - throw exception
if(!empty($param_page) && $page === null){
throw new NotFoundHttpException('Page not found', 404);
}
$page = Page::findOne(13);
$this->registerTags($page);
return \Yii::$app->runAction($page->view);
}
private function getModel($param, $model)
{
$chunks = explode('-', $param);
$chunk_id = end($chunks);
return $model::findOne($chunk_id);
}
private function registerMetaTags($name, $content)
{
\Yii::$app->view->registerMetaTag([
'name' => $name,
'content' => $content
]);
}
private function registerTitle($title)
{
\Yii::$app->view->title = $title;
}
private function checkId($param)
{
$id = explode('-', $param);
$id = end($id);
switch ($id) {
case 14:
return 'news';
break;
default:
return 'page';
break;
}
}
private function getController($path)
{
$controller = explode('/', $path);
return $controller[0];
}
private function registerTags($model)
{
$this->registerMetaTags('description', $model->meta_title);
$this->registerTitle($model->meta_title);
}
}
Url manager rules :
'rules' => [
'<page>/<category>/<product>' => 'splitter/manage-request',
'<page>/<category>' => 'splitter/manage-request',
'<page>' => 'splitter/manage-request',
'' => 'splitter/manage-request'
],
It looks like Pagination param conflict with your URL rules. Pagination uses page GET param to store current page number in URL. But your rules also uses page param and overrides param used by Pagination. You should use different param in your pagination config, to avoid conflicts:
'pagination' => [
'pageSize' => 1,
'route' => "/".Yii::$app->getRequest()->getQueryParam('page'),
'pageParam' => 'paginationPage',
]
See https://www.yiiframework.com/doc/api/2.0/yii-data-pagination#$pageParam-detail
Or you can rename params in your rules to avoid such problem also in other places:
'rules' => [
'<r_page>/<r_category>/<r_product>' => 'splitter/manage-request',
'<r_page>/<r_category>' => 'splitter/manage-request',
'<r_page>' => 'splitter/manage-request',
'' => 'splitter/manage-request'
],

NodeJS: how to implement repository pattern

I would like to implement the Repository pattern in my NodeJS app, but I'm running into troubles with circular requires (I guess...).
How I'm trying to implement it:
PersonRepository class with methods: getAll, getById, create, update, delete
Person class with methods: init, createAccount, showRelations, addRelation,
First of all: Is my repository pattern design correct?
My classes:
personRepository.js
const PersonModel = require('./model');
const Person = require('./person');
class PersonRepository {
constructor() {
this._persons = new Set();
}
getAll( cb ) { // To Do: convert to promise
let results = new Set();
PersonModel.find({}, 'firstName lastName', (err, people) => {
if (err) {
console.error(err);
}
people.forEach((person, index) => {
let foundPerson = new Person(person._id.toString(), person.firstName, person.lastName, person.email, person.birthday);
results.add(foundPerson);
});
this._persons = results;
if (cb) cb(this._persons);
});
}
getById(id) {
return PersonModel.findOne({ _id: id });
}
getByEmail(email) {
throw new Error("Method not implemented");
}
create( person ) {
throw new Error("Method not implemented");
}
update ( person ) {
throw new Error("Method not implemented");
}
delete ( person ) {
throw new Error("Method not implemented");
}
}
module.exports = new PersonRepository();
person.js
const PersonModel = require('./model');
const personRepository = require('./personRepository');
class Person {
constructor(personId, first, last, email, birthday) {
this._id = personId ? personId : undefined;
this._firstName = first ? first : undefined;
this._lastName = last ? last : undefined;
this._email = email ? email : undefined;
this._birthday = birthday ? new Date(birthday) : undefined;
this._relations = new Map();
}
init() { // Get all data from database
personRepository.getById(this._id)
.then(console.log)
.catch(console.error);
}
}
module.exports = Person;
tests.js
console.log("--- GET ALL : results--- ");
personRepository.getAll( (persons) => {
for (let person of persons) {
person.loadAllData()
.then(() => {
console.log(person);
})
.catch((e) => {
console.log(e);
});
}
});
console.log("--- INIT : results--- ");
var personInit = new Person("59c18a9029ef510012312995");
console.log("before init");
console.log(personInit);
personInit.init();
console.log("after init");
console.log(personInit);
Problem:
When running the "Get all" test (without the INIT tests), it works.
When I add the INIT tests, I get the error:
personRepository.getById(this._id)
^
TypeError: personRepository.getById is not a function
at Person.init
How can I prevent this from happening?
- Change the way I require my modules?
- Change my design? (eg. don't require Person class in personRepository and just create a Set of ids in "getAll" instead of a Set of persons)
- Other ideas?
Thanks for helping me! I'm trying to solve this for hours now...
Solved it myself. The problem was a circular dependency between the 2 modules. Problem is fixed by moving the requires after the module.exports.
Reference: https://coderwall.com/p/myzvmg/circular-dependencies-in-node-js

Resources