<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های محمد عباسی</title><link>https://virgool.ir/feed/@mohammad.v184</link><description>Backend Developer at ArvanCloud</description><language>fa</language><pubDate>2026-11-10 15:07:20</pubDate><image><url>https://files.virgool.ir/upload/users/669215/avatar/r2Kxam.jpg</url><title>محمد عباسی</title><link>https://virgool.ir/@mohammad.v184</link></image><item><title>معماری تمیز(Clean) در گولنگ</title><link>https://virgool.ir/@mohammad.v184/clean-architecture-in-golang-zt43rkeya8gk</link><description>امروزه هر برنامه نویسی میدونه که اگر میخواد پروژه اش در آینده به بن بست نخوره، و بتونه به راحتی اون رو توسعه بده باید کدش منظم و قابل خواندن باشه و اگر هم بخواد در تیمی فعالیت بکنه همه ی اعضای تیم باید بر اساس یک قانون واحد کد نویسی کنن . اما بین این همه معماری و قانون و دستورالعمل های مختلف کدوم رو باید انتخاب کرد که هم درعین سادگی ، منظم و قدرتمند باشه ؟ پاسخ این سوال معماری تمیز هست.معماری تمیز توسط Robert C. Martin ( یا همون عمو باب معروف! ) توی کتاب :Clean Architecture: A Craftsman’s Guide to Software Structure and Designمعرفی شد.توی این مقاله میخوایم باهم مفاهیم این معماری توی یه اپلیکیشن گولنگی پیاده سازی کنیم.قوانین پایهقبل از شروع پیاده سازی معماری تمیز، ما یکسری قوانین کلی و بیسیک داریم که باید اپلیکیشنمون رو طبق این موارد طراحی کنیم :مستقل و بدون وابستگی به فریمورک، برنامه مون نباید زیاد به پکیج ها وفریمورک های خارجی وابسته باشه و صرفا باید به عنوان ابزار از این لایبرری ها استفاده کنیم.قابل تست، لاجیک برنامه باید به تنهایی و بدون UI ، دیتابیس و وب سرور قابل تست باشه.مستقل از UI، رابطه کاربری برنامه باید به راحتی و بدون وابستگی به خود اپلیکیشن قابل تغیر باشه.مستقل از دیتابیس، برنامه شما نباید به دیتابیس خاصی وابسته باشه و باید بتونید دیتابیس برنامه رو مثلا از mysql به مونگو تغیر بدین.مستقل از هر چیز خارجی، برنامه شما باید از هرچیز خارجی مستقل باشه درواقع برنامه شما هیچ چیزی بیرون از خودش رو نمیبینه.اگر دوست دارین بیشتر راجب موارد بالا بخونین میتونین این مقاله رو ببینین.نکته : تمام لایه های برنامه ما باید بدون وابستگی قابل تست باشن.Clean Architectureخب عمو باب اومده 4 تا لایه توی معماری خودش آورده:Entities ( موجودیت های برنامه )Usecase ( یوز کیس ها )Controller ( کنترولر ها )Framework &amp; Driver ( فریمورک و لایبرری ها )ما هم توی برنامه خودمون 4 تا لایه پیاده سازی میکنیم:Models Repository Usecase Delivery که در ادامه درمورد هرکدوم توضیح خواهم داد.مدل ها ( Models )مدل ها مرکزی ترین لایه داخل برنامه مون هستن و توی تمام لایه ها استفاده میشن، این لایه تمام موجودیت های برنامه رو شامل میشه مثل: Article, Student, Book.import &#34;time&#34;&#xA;&#xA;type Article struct {&#xA;&#x9;ID        int64     `json:&#34;id&#34;`&#xA;&#x9;Title     string    `json:&#34;title&#34;`&#xA;&#x9;Content   string    `json:&#34;content&#34;`&#xA;&#x9;UpdatedAt time.Time `json:&#34;updated_at&#34;`&#xA;&#x9;CreatedAt time.Time `json:&#34;created_at&#34;`&#xA;}&#xA;در واقع هر شیِ و آبجکتی اینجا ذخیره میشه.ریپازیتوری ( Repository )تمام عملیات های مربوط به دیتابیس CRUD( ساخت، مشاهده، ویرایش و حذف)، اینجا انجام میشه و هیچ منطقی از برنامه توی این لایه اتفاق نمیفته. صرفا این لایه برای اتصال به دیتابیس کاربرد داره.نکته : این لایه وابسته به دیتابیس هستش.یوزکیس  ( Usecase )تمام پردازش های بیزینسی توی این لایه انتفاق میفته، وظیفه این لایه محاسبه و پردازش داده ها برای ارائه به لایه Delivery هستش.و بعد این لایه با توجه به داده هایی که از لایه Delivery میگیره مشخص میکنه که باید داخل دیتابیس چیزی اضافه بشه و یا داده ای واکشی و برگدونده بشه.نکته :با توجه به نیاز این لایه به عملیات های دیتابیسی وابسته به لایه ریپازیتوری هستش.دلیوری ( Delivery )این لایه با توجه به اسمش وظیفه تحویل داده به کاربر رو داره که میتونه به صورت یک REST API یا یه صفحه HTM و یا هر نوع تایپی باشه.و همچنین این لایه میتونه داده هم از کاربر بگیره که برای پردازش به لایه Usecase بفرسته.نکته : این لایه به لایه Usecase وابسته هستش.ارتباط بین لایهبه غیر از لایه مدل که وابستگی خاصی نداره بقیه لایه ها نیاز به برقراری ارتباط بین هم دیگه دارن ما برای برقراری ارتباط بین لایه ها نیاز به یه قراردادی برای هر لایه داریم که اون لایه بر اساس قرارداد مشخص شده باید عمل کنه.برای مثال ما توی لایه Usecase نیاز داریم که عملیاتی سمت دیتابیس انجام بشه و باید با لایه Repository ارتباط بگیریم. خب ما توی لایه  Repository یه اینترفیس تعریف میکنیم و ازش بعنوان یک قرارداد برای برقراری ارتباط استفاده میکنیم:type ArticleRepository interface {&#xA;&#x9;Fetch(cursor string, num int64) ([]*models.Article, error)&#xA;&#x9;GetByID(id int64) (*models.Article, error)&#xA;&#x9;GetByTitle(title string) (*models.Article, error)&#xA;&#x9;Update(article *models.Article) (*models.Article, error)&#xA;&#x9;Store(a *models.Article) (int64, error)&#xA;&#x9;Delete(id int64) (bool, error)&#xA;}ما باید طبق این قرارداد این ریپازیتوری رو پیاده سازی کنیم.تست نویسی برای هرلایههمانطور که بالاتر هم گفتیم تمام لایه ها باید بدون وابستگی به هم دیگه قابل تست باشن.تست لایه مدل ها : این لایه فقط درصورتی نیاز به تست نویسی داره که فانکشن یا متود خاصی درش تعریف شده باشه.و در کل تست های این لایه خیلی ساده هستند.تست لایه ریپازیتوری :برای تست این لایه با توجه به نیاز این لایه به دیتابیس بهترین راه برای این کار استفاده از ماکینگ( Mocking ) هستش. برای ماک کردن فانکشنالیتی mysql توی گولنگ میتونین از این پکیج استفاده کنین.مثلا :func TestGetByID(t *testing.T) {&#xA; db, mock, err := sqlmock.New() &#xA; if err != nil { &#xA;    t.Fatalf(“an error ‘%s’ was not expected when opening a stub  &#xA;        database connection”, err) &#xA;  }  defer db.Close() &#xA; rows := sqlmock.NewRows([]string{&#xA;        “id”, “title”, “content”, “updated_at”, “created_at”}).   &#xA;        AddRow(1, “title 1”, “Content 1”, time.Now(), time.Now())  query := “SELECT id,title,content,updated_at, created_at FROM &#xA;          article WHERE ID = \\?”  mock.ExpectQuery(query).WillReturnRows(rows)  a := articleRepo.NewMysqlArticleRepository(db)  num := int64(1)  anArticle, err := a.GetByID(num)  assert.NoError(t, err) &#xA; assert.NotNil(t, anArticle)&#xA;}تست لایه یوز کیس:برای تست این لایه نیاز به یه ریپازیتوری داریم. ولی با توجه به اینکه نباید لایه ها توی تست وابسته باشن ما میتونیم طبق اینترفیسی که توی لایه ریپازیتوری ایجاد کردیم یه ماک از ریپازیتوری بسازیم و موقع تست بجای ریپازیتوری اصلی ریپازیتوری فیک رو تحویل لایه یوز کیس میدیم.مثلا :func TestFetch(t *testing.T) {&#xA;&#x9;mockArticleRepo := new(mocks.ArticleRepository)&#xA;&#x9;var mockArticle models.Article&#xA;&#x9;err := faker.FakeData(&amp;mockArticle)&#xA;&#x9;assert.NoError(t, err)&#xA;&#xA;&#x9;mockListArtilce := make([]*models.Article, 0)&#xA;&#x9;mockListArtilce = append(mockListArtilce, &amp;mockArticle)&#xA;&#x9;mockArticleRepo.On(&#34;Fetch&#34;, mock.AnythingOfType(&#34;string&#34;), mock.AnythingOfType(&#34;int64&#34;)).Return(mockListArtilce, nil)&#xA;&#x9;u := ucase.NewArticleUsecase(mockArticleRepo)&#xA;&#x9;num := int64(1)&#xA;&#x9;cursor := &#34;12&#34;&#xA;&#x9;list, nextCursor, err := u.Fetch(cursor, num)&#xA;&#x9;cursorExpected := strconv.Itoa(int(mockArticle.ID))&#xA;&#x9;assert.Equal(t, cursorExpected, nextCursor)&#xA;&#x9;assert.NotEmpty(t, nextCursor)&#xA;&#x9;assert.NoError(t, err)&#xA;&#x9;assert.Len(t, list, len(mockListArtilce))&#xA;&#xA;&#x9;mockArticleRepo.AssertCalled(t, &#34;Fetch&#34;, mock.AnythingOfType(&#34;string&#34;), mock.AnythingOfType(&#34;int64&#34;))&#xA;&#xA;}لایه تحویل:با توجه به نیاز این لایه به لایه مرکزی ترش یعنی یوز کیس برای تست این لایه باید لایه یوز کیس رو طبق اینترفیس تعریف شده ماک کنیم و به این لایه پاس یدیم.مثلا:func TestGetByID(t *testing.T) {&#xA; var mockArticle models.Article &#xA; err := faker.FakeData(&amp;mockArticle) &#xA; assert.NoError(t, err) &#xA; mockUCase := new(mocks.ArticleUsecase) &#xA; num := int(mockArticle.ID) &#xA; mockUCase.On(“GetByID”, int64(num)).Return(&amp;mockArticle, nil) &#xA; e := echo.New() &#xA; req, err := http.NewRequest(echo.GET, “/article/” +  &#xA;             strconv.Itoa(int(num)), strings.NewReader(“”))  assert.NoError(t, err)  rec := httptest.NewRecorder() &#xA; c := e.NewContext(req, rec) &#xA; c.SetPath(“article/:id”) &#xA; c.SetParamNames(“id”) &#xA; c.SetParamValues(strconv.Itoa(num))  handler:= articleHttp.ArticleHandler{&#xA;            AUsecase: mockUCase,&#xA;            Helper: httpHelper.HttpHelper{}&#xA; } &#xA; handler.GetByID(c)  assert.Equal(t, http.StatusOK, rec.Code) &#xA; mockUCase.AssertCalled(t, “GetByID”, int64(num))&#xA;}برای ماک کردن Struct ها در گولنگ میتونین از این پکیج استفاده کنین.و در نهایت:بعد از اینکه کار همه لایه ها تموم شد و تست هاشون هم پاس میشه، حالا ما باید این لایه هارو بیاریم داخل فایل main.go و بهم وصلشون کنیم تا یک اپلیکیشن کامل ساخته بشه.توی این فایل وابستگی ها و کانفیگ های مورد نیاز اپلیکیشن تعریف میشن و درواقع برنامه ما از این فایل شروع میشه.برای نوشتن این فایل مثل زیر میتونین عمل کنین:package main&#xA;&#xA;var config cfg.Config&#xA;&#xA;func init() {&#xA;&#x9;config = cfg.NewViperConfig()&#xA;&#xA;&#x9;if config.GetBool(`debug`) {&#xA;&#x9;&#x9;fmt.Println(&#34;Service RUN on DEBUG mode&#34;)&#xA;&#x9;}&#xA;&#xA;}&#xA;&#xA;func main() {&#xA;&#xA;&#x9;dbHost := config.GetString(`database.host`)&#xA;&#x9;dbPort := config.GetString(`database.port`)&#xA;&#x9;dbUser := config.GetString(`database.user`)&#xA;&#x9;dbPass := config.GetString(`database.pass`)&#xA;&#x9;dbName := config.GetString(`database.name`)&#xA;&#x9;connection := fmt.Sprintf(&#34;%s:%s@tcp(%s:%s)/%s&#34;, dbUser, dbPass, dbHost, dbPort, dbName)&#xA;&#x9;val := url.Values{}&#xA;&#x9;val.Add(&#34;parseTime&#34;, &#34;1&#34;)&#xA;&#x9;val.Add(&#34;loc&#34;, &#34;Asia/Tehran&#34;)&#xA;&#x9;dsn := fmt.Sprintf(&#34;%s?%s&#34;, connection, val.Encode())&#xA;&#x9;dbConn, err := sql.Open(`mysql`, dsn)&#xA;&#x9;if err != nil &amp;&amp; config.GetBool(&#34;debug&#34;) {&#xA;&#x9;&#x9;fmt.Println(err)&#xA;&#x9;}&#xA;&#x9;defer dbConn.Close()&#xA;&#x9;e := echo.New()&#xA;&#x9;middL := middleware.InitMiddleware()&#xA;&#x9;e.Use(middL.CORS)&#xA;&#xA;&#x9;ar := articleRepo.NewMysqlArticleRepository(dbConn)&#xA;&#x9;au := articleUcase.NewArticleUsecase(ar)&#xA;&#xA;&#x9;httpDeliver.NewArticleHttpHandler(e, au)&#xA;&#xA;&#x9;e.Start(config.GetString(&#34;server.address&#34;))&#xA;}&#xA;&#xA;امیدوارم این مقاله براتون مفید بوده باشه. اگه خوشتون اومد، لایک یادتون نره! و اگر دوست داشتین نظرتونو این زیر بهم بگین.</description><category>محمد عباسی</category><author>محمد عباسی</author><pubDate>Thu, 03 Feb 2022 19:46:09 +0330</pubDate></item><item><title>مدیریت خطاهای چندگانه در گولنگ</title><link>https://virgool.ir/@mohammad.v184/go-multiple-errors-management-d3qzjt6so7jo</link><description>mutierrمدیریت خطا همیشه یکی از تکراری و پربحث ترین موضوعات در گولنگ بوده و طبق این نظرسنجی توی سال 2019 مدیریت خطا جزو بزرگترین چالش برنامه نویسان گو هست.توی این مقاله ما میخوایم راجب حالت های مختلف بهبود مدیریت خطا داخل کانکارنسی یا همون همروندی صحبت کنیم و از پکیج هایی که توسعه داده شده برای این موضوع استفاده خواهیم کرد.حالت چندین خطا در یک گوروتینتوی این حالت ما یک گوروتین داریم که ممکنه چندبار خطا داخلش اتفاق بیفته, مثلا توی کد زیر ما نیاز داریم خطا هایی که اتفاق میفته رو لاگ بگیریم.این کد یه متن CSV میخونه و چک میکنه, اگر متن CSV معتبر نباشه , یک خطا اتفاق میفته و این خطا رو لاگ میگیره, ولی ساختاری که نمایش میده این خطاهارو شلخته اس و مناسب نیست.برای حل این موضوع دو تا پکیج خوب توسعه داده شده که میتونیم ازشون استفاده کنیم.پکیج go-multierror که توسط HashiCorp توسعه داده شده :این پکیج خطا ها رو میگیره, گروه بندی میکنه و با یک فرمت استاندارد نمایش میده,اگر بخوایم کد قبلی رو مجدد با همین پکیج پیاده سازی کنیم به این شکل میشه:خروجی کد بالا به این شکل میشه:پکیج multierr که توسط Uber توسعه داده شده:پیاده سازی این پکیج مشابه پکیج قبلی هست من فقط خروجی کد بالا رو با این پکیج این زیر میزارم:من یک بنچ مارک هم آماده کردم با همون کد بالا که بتونیم تفاوت عملکرد این دو پکیج رو ببینیم:name                    time/op         alloc/op        allocs/op&#xA;HashiCorpMultiErrors-4  6.01µs ± 1%     6.78kB ± 0%     77.0 ± 0%&#xA;UberMultiErrors-4       9.26µs ± 1%     10.3kB ± 0%      126 ± 0%همانطور که میبینیم پکیجی که Uber توسعه داده کمی کند تر هست و حافظه بیشتری هم مصرف میکنهاما طبق مستندات رسمی خود پکیج, این پکیج به گونه ای طراحی شده که خطا ها رو یکجا بگیره و گروه بندی کنه نه تیکه تیکه بگیره, من یک تغیر کوچیک روی کد بالا دادم که خطا ها رو جمع کنه یک جا بده به پکیج.حالا مجدد تست میکنیم:name                    time/op         alloc/op        allocs/op&#xA;HashiCorpMultiErrors-4  6.01µs ± 1%     6.78kB ± 0%     77.0 ± 0%&#xA;UberMultiErrors-4       6.02µs ± 1%     7.06kB ± 0%     77.0 ± 0%بله دیدیم که عملکردش بهبود پیدا کرده, به هر حال زیاد فرقی بین این دو پکیج نیست و هردو دارند اینترفیسی از پکیج error خود گولنگ پیاده سازی میکنند.حالت یک خطا در چندین گوروتینوقتی که برای انجام یک کاری باید چندین گوروتین داشته باشیم و احتمال وقوع خطا داخل این گوروتین ها وجود داشته باشه, لازمه که این خطا هارو به صورت صحیح مدیریت کنیم تا برنامه مون عملکرد بهتری داشته باشه.بیاین با هم یک برنامه ای بنویسیم که چندین گوروتین داشته باشه و پردازش هر گوروتین سه ثانیه طول بکشه که احتمال خطا داخل پردازش هم وجود داشته باشه:برای اینکه نشون بدیم خطا چطور اتفاق میفته توی گوروتین سوم پردازش اول خطا برمیگردونیم که به این شکل میشه:خب برنامه اجرا میکنیم و همانطور که انتظار داشتیم سه تا گوروتین هر سه مرحله طی کردند و برنامه تقریبا سه ثانیه طول کشید:go run .  0.30s user 0.19s system 14% cpu 3.274 totalخب تا اینجا همه چیز اکی بود ,  مشکل جایی بوجود میاد که ما بخوایم هر مرحله پردازش این گوروتین ها به هم وابسته باشن و در صورت موفق نبودن یکی از پردازش ها باقی پردازش ها در گوروتین های دیگه هم لغو بشن.برای حل این مشکل ما میتونیم از context ها در گولنگ استفاده کنیم که اگر یک گوروتین متوقف شد باقی گوروتین ها هم متوقف بشن.پکیج errgroup که به خوبی این موضوع پیاده سازی کرده و ما میتونیم ازش استفاده کنیم, خب کد بالا مجدد با این پکیج بازنویسی میکنیم:حالا برنامه اجرا میکنیم و میبینیم که با متوقف شدن یک گوروتین باقی گوروتین ها هم متوقف میشن که این باعث میشه برنامه مون سریع تر اجرا بشه:go run .  0.30s user 0.19s system 38% cpu 1.269 totalامیدوارم توضیحاتم مفید بوده باشه. اگه خوشتون اومد، لایک یادتون نره! انتقادات و پیشنهادات هم میتونید توی نظرات بهم بگین.</description><category>محمد عباسی</category><author>محمد عباسی</author><pubDate>Tue, 04 Jan 2022 15:47:48 +0330</pubDate></item><item><title>جنریک ها در گولنگ</title><link>https://virgool.ir/golangpub/generics-in-golang-nxjlnrgygzqf</link><description>What is Go generics?حتما میدونید که گولنگ یک زبان تایپ ثابت هست, یعنی اینکه هر متغیر داخل برنامه شما باشه حتما یک تایپ از قبل مشخص شده داره. مثلا وقتی که یک فانکشن تعریف میکنیم باید حتما نوع پارامتر هایی که فانکشن میگیره رو مشخص کنیم مثل کد زیر:func Print(s string) {اینجا ما اومدیم یه پارامتر رو تعریف کردیم با اسم s و تایپ string، ما میتونیم هر تعداد پارامتر دیگه‌ای که خواستیم با هر تایپی مثل float64، int، struct تعریف کنیم. ولی مشکل اینجا بوجود میاد که نمیتونیم یک عدد به این فانکشن پاس بدیم چون عدد‌ها از جنس int هستن و نمیشه یه int توی یک متغیر string بریزیم.قبلا ما میومدیم تایپ پارامتر s رو از نوع interface تعریف می‌کردیم، که این مشکل تا حدودی حل می‌شد، ولی باز هم محدودیت‌ها زیادی داشتیم.جنریک هااینجا بود که ما برای حل این مشکل اومدیم از چیزی به اسم جنریک‌ها استفاده کردیم.بریم با هم توی کد امتحان کنیم:func PrintAnything[T any](thing T) {  fmt.Println(thing) }بهتر نشد؟ ما جنریکی به اسم T تعریف کردیم که میتونه هر تایپی رو بگیره و یک پارامتر تعریف کردیم از نوع جنریک T اینطوری میتونیم هرچیزی به این فانکشن پاس بدیم تا پرینت کنه واسمون به همین سادگی:PrintAnything(&#34;Hello!&#34;)&#xA;PrintAnything(42)&#xA;PrintAnything(true)زمانی که این فانکشن فراخوانی میکنیم پارامتر thing همون لحظه با مقداری که بهش پاس میدیم تایپش مشخص میشه.جنریک تایپ ها تا اینجا که خیلی باحال بود, ما تونستیم یه فانکشن بنویسیم که میتونه یک آرگومان از هر تایپی بگیره.حالا اگر بخوایم یه تایپ بسازیم که بتونه هر نوعی داشته باشه چی؟ مثلا یه slice از هر چیزی.بله جنریک ها داخل تعریف تایپ هم کاربرد دارند ما میتونیم اینطوری این تایپ تعریف کنیم:type Bunch[E any] []Eاینجا ما میگیم که تایپ Bunch یک اسلایس از نوع مشخصی به اسم E هست, حالا خود همین E یه جنریک هست که از هر چیزی میتونه باشه برای مثال Bunch[int] یک slice از نوع int هست. مثال در کد:x := Bunch[int]{1, 2, 3}ما میتونیم از این نوع تایپ توی فانکشن ها هم استقاده کنیم:func PrintBunch[E any](b Bunch[E]) {حتی میتونه متود داشته باشه :func (b Bunch[E]) Print() {ما میتونیم یکسری محدودیت ها و شروط تعریف کنیم برای جنریک ها, مثلا میتونیم بگیم جنریک فقط string قبول کنه. مثل کد زیر:type StringableBunch[E Stringer] []Eاین قابلیت روی نسخه 1.18 به بعد گولنگ وجود داره. ( از این لینک میتونید نسخه ی 1.18-beta گولنگ دریافت کنید)اگر هم به هر دلیل نمیتونین این ورژن از گو نصب کنید میتونید از این لینک برین و داخل مرورگرتون تست کنین.امیدوارم توضیحاتم مفید بوده باشه. توضیحات تکمیلی رو می تونید توی این لینک ببینید. اگه براتون مفید بود و خوشتون اومد، لایک یادتون نره! انتقادات و پیشنهادات هم میتونید توی نظرات بهم بگین.</description><category>محمد عباسی</category><author>محمد عباسی</author><pubDate>Mon, 03 Jan 2022 21:51:25 +0330</pubDate></item><item><title>پکیج پرداخت آنلاین (درگاه بانکی) در گولنگ</title><link>https://virgool.ir/@mohammad.v184/gopayment-mkpg7hre4pzt</link><description>میخوام یه پکیج گولنگ معرفی کنم برای اتصال به درگاه های بانکی مختلف با تنظیمات متفاوت. چند وقتی میشه که کار نوشتن این پکیج رو شروع کردم و خودم روی پروژه هام دارم استفاده میکنم. این پکیج کاملا ماژولار درست شده که به راحتی میتونید تنظیماتتون رو انجام بدین و چندین درگاه (مثلا چند درگاه زرین پال با شناسه متفاوت) رو روی یک پروژه داشته باشید و با توجه به شرایطی که مدنظرتون هست به هرکدوم که خواستید ارجاع بدید.GoPaymentپکیج که روی گیت هاب موجوده،لینک مستندات رسمی پکیج این زیر گذاشتم :https://github.com/mohammadv184/gopaymentاین پکیج از درگاه های مختلفی استفاده میکنه که میتونید لیست کاملشو تو این لینک ببینیدبرای نصب پکیج کافیه از دستور زیر استفاده کنید:go get -u github.com/mohammadv184/gopaymentحالا پکیج نصب شده و میتونید استفاده کنید.برای گام اول باید درگاه پرداخت با تنظیمات خودتون ایجاد کنید و یه جا نگه دارین مثل زیر: // Configure the Gateway Driver&#xA;paypingDriver:=&amp;payping.Driver{&#xA;             Callback:    &#34;http://example.test/callback&#34;,&#xA;              Token:       &#34;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&#34;,&#xA;}&#xA;// OR&#xA;zarinpalDriver:=&amp;zarinpal.Driver{&#xA;               Callback:    &#34;http://example.test/callback&#34;, &#xA;               Merchent:        &#34;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&#34;,&#xA;}برای گام بعدی ما برای اینکه بتونیم کاربر به صفحه پرداخت منتقل کنیم نیاز به یک کدی داریم که قبلش باید از درگاه موردنظر بگیریم مثل زیر میتونین عمل کنین:&#xA;// Create new Payment.&#xA;payment := gopayment.NewPayment(paypingDriver)&#xA;// Set Invoice Amount.&#xA; payment.Amount(1000)&#xA;// Purchase the invoice.&#xA;err = payment.Purchase()&#xA;if err != nil {&#xA;    fmt.Println(err)&#xA;}&#xA;// Get Transaction ID&#xA;transactionID := payment.GetTransactionID()&#xA;اول یه payment میسازیم و درایوری که میخوایم پرداخت باهاش صورت بگیره مثلا درایور payping که قبلا ایجاد کردیم رو به عنوان آرگومان اول بهش پاس میدیم , بعد مبلغ فاکتور به تومان با متود Amount مشخص میکنیم و purchase میکنیم اگر عملیات موفق باشه و هیچ اروری بر نگردونه کد تراکنش میگیریم و یه جا نگه میداریم.  حالا میتونیم با متود payURL لینک صفحه پرداخت بگیریم و کاربر بفرستیم برای پرداخت:// Redirect the user to the bank payment page.&#xA; payUrl := payment.PayURL()&#xA;c.Redirect(http.StatusFound, payUrl)بعد از اینکه کاربر پرداخت رو انجام داد با یکسری پارامتر ها که برای هر درگاه متفاوته برمیگرده به همون آدرس callback که وقتی داشتیم درایور درگاه ایجاد میکردیم مشخص کردیم حالا باید نتیجه پرداخت چک کنیم و پرداخت تایید کنیم و به کاربر اطلاع بدیم پرداختش موفق بوده یا نه:&#xA;&#xA;refId := c.Query(&#34;refId&#34;)&#xA;VerifyRequest:=&amp;payping.VerifyRequest{&#xA;Amount: &#34;100&#34;,&#xA;RefID:  refID,&#xA;}&#xA;&#xA;if receipt, err := paypingDriver.Verify(VerifyRequest); err == nil {&#xA;cardNum, _ := receipt.GetDetail(&#34;cardNumber&#34;)&#xA;c.JSON(200, gin.H{&#xA;&#34;status&#34;: &#34;success&#34;,&#xA;&#34;data&#34;:   receipt.GetReferenceID(),&#xA;&#34;date&#34;:   receipt.GetDate().String(),&#xA;&#34;card&#34;:   cardNum,&#xA;&#34;driver&#34;: receipt.GetDriver(),&#xA;})&#xA;} else {&#xA;c.JSON(200, gin.H{&#xA;&#34;message&#34;: &#34;error &#34; + err.Error(),&#xA;})&#xA;}کل مراحل پرداخت به اینصورت بود که داخل این ریپو به صورت یک فروشگاه ساده پیاده سازی شده میتونید ببینید.امیدوارم توضیحاتم مفید بوده باشه. توضیحات تکمیلی رو می تونید توی گیت هاب ببینید. اگه پکیج رو نصب کردین و خوشتون اومد، ستاره یادتون نره! انتقادات و پیشنهادات هم میتونید توی Issue مطرح کنید یا Pull Request بزنید.</description><category>محمد عباسی</category><author>محمد عباسی</author><pubDate>Fri, 31 Dec 2021 22:21:46 +0330</pubDate></item></channel></rss>